diff --git a/.editorconfig b/.editorconfig index d51908caf..a3e5a0fc1 100644 --- a/.editorconfig +++ b/.editorconfig @@ -18,10 +18,8 @@ max_line_length = 80 [COMMIT_EDITMSG] max_line_length = 0 -<<<<<<< Updated upstream -======= [*.{js, vue, ts}] indent_size = 2 indent_style = space ->>>>>>> Stashed changes + diff --git a/.gitignore b/.gitignore index d0a16a46a..26802dca0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,11 @@ .composer/* +composer composer.phar composer.lock docs/build/ +node_modules/* .php_cs.cache +.cache/* ###> symfony/framework-bundle ### /.env.local @@ -23,3 +26,5 @@ docs/build/ /.php-cs-fixer.cache /.idea/ /.psalm/ + +node_modules/* diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ddf7fccbe..3f1d75ed5 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -9,7 +9,7 @@ cache: # Bring in any services we need http://docs.gitlab.com/ee/ci/docker/using_docker_images.html#what-is-a-service # See http://docs.gitlab.com/ee/ci/services/README.html for examples. services: - - name: postgis/postgis:12-3.1-alpine + - name: postgis/postgis:14-3.3-alpine alias: db - name: redis alias: redis @@ -21,7 +21,7 @@ variables: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres # configure database access - DATABASE_URL: postgresql://postgres:postgres@db:5432/postgres?serverVersion=12&charset=utf8 + DATABASE_URL: postgresql://postgres:postgres@db:5432/postgres?serverVersion=14&charset=utf8 # fetch the chill-app using git submodules GIT_SUBMODULE_STRATEGY: recursive REDIS_HOST: redis @@ -37,12 +37,11 @@ stages: build: stage: Composer install - image: registry.gitlab.com/chill-projet/chill-app/php-base-image:7.4 + image: gitea.champs-libres.be/chill-project/chill-skeleton-basic/base-image:php82 before_script: - - curl -sS https://getcomposer.org/installer | php - - php -d memory_limit=2G composer.phar config -g cache-dir "$(pwd)/.cache" + - composer config -g cache-dir "$(pwd)/.cache" script: - - php -d memory_limit=2G composer.phar install --optimize-autoloader --no-ansi --no-interaction --no-progress + - composer install --optimize-autoloader --no-ansi --no-interaction --no-progress cache: paths: - .cache/ @@ -54,9 +53,12 @@ build: code_style: stage: Tests - image: registry.gitlab.com/chill-projet/chill-app/php-base-image:7.4 + image: gitea.champs-libres.be/chill-project/chill-skeleton-basic/base-image:php82 script: - - bin/grumphp run --tasks=phpcsfixer + - php-cs-fixer fix --dry-run -v --show-progress=none + cache: + paths: + - .cache/ artifacts: expire_in: 30 min paths: @@ -65,30 +67,49 @@ code_style: phpstan_tests: stage: Tests - image: registry.gitlab.com/chill-projet/chill-app/php-base-image:7.4 + image: gitea.champs-libres.be/chill-project/chill-skeleton-basic/base-image:php82 script: - - bin/grumphp run --tasks=phpstan + - bin/phpstan analyze --memory-limit=2G + cache: + paths: + - .cache/ artifacts: expire_in: 30 min paths: - bin - tests/app/vendor/ -psalm_tests: +rector_tests: stage: Tests - image: registry.gitlab.com/chill-projet/chill-app/php-base-image:7.4 + image: gitea.champs-libres.be/chill-project/chill-skeleton-basic/base-image:php82 script: - - bin/grumphp run --tasks=psalm - allow_failure: true + - bin/rector --dry-run + cache: + paths: + - .cache/ artifacts: expire_in: 30 min paths: - bin - tests/app/vendor/ +# psalm_tests: +# stage: Tests +# image: gitea.champs-libres.be/chill-project/chill-skeleton-basic/base-image:php82 +# script: +# - bin/psalm +# allow_failure: true +# artifacts: +# expire_in: 30 min +# paths: +# - bin +# - tests/app/vendor/ + unit_tests: stage: Tests - image: registry.gitlab.com/chill-projet/chill-app/php-base-image:7.4 + 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 diff --git a/.php_cs.dist.php b/.php-cs-fixer.dist.php similarity index 63% rename from .php_cs.dist.php rename to .php-cs-fixer.dist.php index 4ad5af914..4b5bf98ee 100644 --- a/.php_cs.dist.php +++ b/.php-cs-fixer.dist.php @@ -1,22 +1,32 @@ getFinder() - ->ignoreDotFiles(false) +$finder + ->in(__DIR__.'/src') + ->append([__FILE__]) + ->exclude(['docs/', 'tests/app']) ->notPath('tests/app') - ->name(['.php_cs.dist.php']); + ->ignoreDotFiles(true) + ->name('**.php') +; + +$config = new PhpCsFixer\Config(); +$config + ->setFinder($finder) + ->setRiskyAllowed(true) + ->setCacheFile('.cache/php-cs-fixer.cache') + ->setUsingCache(true) +; $rules = $config->getRules(); @@ -69,9 +79,42 @@ $riskyRules = [ // 'psr_autoloading' => false, ]; +$untilFullSwitchToPhp8 = [ + 'blank_line_between_import_groups' => false, + 'declare_strict_types' => true, + 'multiline_whitespace_before_semicolons' => false, + 'phpdoc_no_empty_return' => false, +]; + $rules = array_merge( + [ + '@PhpCsFixer' => true, + '@PhpCsFixer:risky' => false, + '@Symfony' => false, + '@Symfony:risky' => false, + 'ordered_class_elements' => [ + 'order' => [ + 'use_trait', + 'constant_public', + 'constant_protected', + 'constant_private', + 'property_public', + 'property_protected', + 'property_private', + 'construct', + 'destruct', + 'magic', + 'phpunit', + 'method_public', + 'method_protected', + 'method_private', + ], + 'sort_algorithm' => 'alpha', + ] + ], $rules, - $riskyRules + $riskyRules, + $untilFullSwitchToPhp8, ); $rules['header_comment']['header'] = trim(file_get_contents(__DIR__ . '/resource/header.txt')); diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ed9cdebf..b2f951479 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,11 +11,33 @@ and this project adheres to ## Unreleased +* [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 + +## Test releases + +### 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 diff --git a/composer b/composer deleted file mode 100755 index 0e7ab8212..000000000 Binary files a/composer and /dev/null differ diff --git a/composer.json b/composer.json index aaeb8ebca..f6d3eb27a 100644 --- a/composer.json +++ b/composer.json @@ -8,47 +8,54 @@ "social worker" ], "require": { - "php": "^7.4", + "php": "^7.4|^8.2", + "ext-json": "*", + "ext-openssl": "*", + "ext-redis": "*", "champs-libres/async-uploader-bundle": "dev-sf4#d57134aee8e504a83c902ff0cf9f8d36ac418290", - "champs-libres/wopi-bundle": "dev-master#6dd8e0a14e00131eb4b889ecc30270ee4a0e5224", - "champs-libres/wopi-lib": "dev-master#8615f4a45a39fc2b6a98765ea835fcfd39618787", + "champs-libres/wopi-bundle": "dev-master@dev", + "champs-libres/wopi-lib": "dev-master@dev", "doctrine/doctrine-bundle": "^2.1", "doctrine/doctrine-migrations-bundle": "^3.0", - "doctrine/orm": "^2.7", + "doctrine/orm": "^2.13.0", "erusev/parsedown": "^1.7", "graylog2/gelf-php": "^1.5", "knplabs/knp-menu-bundle": "^3.0", "knplabs/knp-time-bundle": "^1.12", + "knpuniversity/oauth2-client-bundle": "^2.10", "league/csv": "^9.7.1", + "lexik/jwt-authentication-bundle": "^2.16", "nyholm/psr7": "^1.4", "ocramius/package-versions": "^1.10 || ^2", "odolbeau/phone-number-bundle": "^3.6", + "ovh/ovh": "^3.0", "phpoffice/phpspreadsheet": "^1.16", "ramsey/uuid-doctrine": "^1.7", "sensio/framework-extra-bundle": "^5.5", "spomky-labs/base64url": "^2.0", - "symfony/asset": "^4.4", "symfony/browser-kit": "^4.4", + "symfony/clock": "^6.2", "symfony/css-selector": "^4.4", "symfony/expression-language": "^4.4", "symfony/form": "^4.4", "symfony/framework-bundle": "^4.4", + "symfony/http-client": "^4.4 || ^5", "symfony/http-foundation": "^4.4", "symfony/intl": "^4.4", "symfony/mailer": "^5.4", + "symfony/messenger": "^5.4", "symfony/mime": "^5.4", "symfony/monolog-bundle": "^3.5", "symfony/security-bundle": "^4.4", "symfony/serializer": "^5.3", - "symfony/swiftmailer-bundle": "^3.5", "symfony/templating": "^4.4", "symfony/translation": "^4.4", "symfony/twig-bundle": "^4.4", "symfony/validator": "^4.4", - "symfony/web-link": "*", "symfony/webpack-encore-bundle": "^1.11", "symfony/workflow": "^4.4", "symfony/yaml": "^4.4", + "thenetworg/oauth2-azure": "^2.0", "twig/extra-bundle": "^3.0", "twig/intl-extra": "^3.0", "twig/markdown-extra": "^3.3", @@ -57,19 +64,25 @@ }, "require-dev": { "doctrine/doctrine-fixtures-bundle": "^3.3", - "drupol/php-conventions": "^5", "fakerphp/faker": "^1.13", + "jangregor/phpstan-prophecy": "^1.0", "nelmio/alice": "^3.8", "phpspec/prophecy-phpunit": "^2.0", + "phpstan/extension-installer": "^1.2", + "phpstan/phpstan": "^1.9", + "phpstan/phpstan-deprecation-rules": "^1.1", "phpstan/phpstan-strict-rules": "^1.0", "phpunit/phpunit": ">= 7.5", + "psalm/plugin-phpunit": "^0.18.4", + "psalm/plugin-symfony": "^4.0.2", + "rector/rector": "^0.15.23", "symfony/debug-bundle": "^5.1", "symfony/dotenv": "^4.4", "symfony/maker-bundle": "^1.20", "symfony/phpunit-bridge": "^4.4", "symfony/stopwatch": "^4.4", "symfony/var-dumper": "^4.4", - "symfony/web-profiler-bundle": "^4.4" + "vimeo/psalm": "^4.30.0" }, "conflict": { "symfony/symfony": "*" diff --git a/docs/source/_static/code/exports/BirthdateFilter.php b/docs/source/_static/code/exports/BirthdateFilter.php index 2c3cd765f..64c1d53a9 100644 --- a/docs/source/_static/code/exports/BirthdateFilter.php +++ b/docs/source/_static/code/exports/BirthdateFilter.php @@ -1,14 +1,14 @@ getQuery()->getResult(Query::HYDRATE_SCALAR); + return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } public function getTitle() @@ -91,9 +90,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(); @@ -106,9 +103,9 @@ class CountPerson implements ExportInterface return $qb; } - public function requiredRole() + public function requiredRole(): string { - return new Role(PersonVoter::STATS); + return PersonVoter::STATS; } public function supportsModifiers() diff --git a/docs/source/development/cronjob.rst b/docs/source/development/cronjob.rst new file mode 100644 index 000000000..df72fa922 --- /dev/null +++ b/docs/source/development/cronjob.rst @@ -0,0 +1,93 @@ + +.. Copyright (C) 2014-2023 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". + +.. _cronjob: + +Cron jobs +********* + +Some tasks must be executed regularly: refresh some materialized views, remove old data, ... + +For this purpose, one can programmatically implements a "cron job", which will be scheduled by a specific command. + +The command :code:`chill:cron-job:execute` +========================================== + +The command :code:`chill:cron-job:execute` will schedule a task, one by one. In a classical implementation, it should +be executed every 15 minutes (more or less), to ensure that every task can be executed. + +.. warning:: + + This command should not be executed in parallel. The installer should ensure that two job are executed concurrently. + +How to implements a cron job ? +============================== + +Implements a :code:`Chill\MainBundle\Cron\CronJobInterface`. Here is an example: + +.. code-block:: php + + namespace Chill\MainBundle\Service\Something; + + use Chill\MainBundle\Cron\CronJobInterface; + use Chill\MainBundle\Entity\CronJobExecution; + use DateInterval; + use DateTimeImmutable; + + class MyCronJob implements CronJobInterface + { + public function canRun(?CronJobExecution $cronJobExecution): bool + { + // the parameter $cronJobExecution contains data about the last execution of the cronjob + // if it is null, it should be executed immediatly + if (null === $cronJobExecution) { + return true; + } + + if ($cronJobExecution->getKey() !== $this->getKey()) { + throw new UnexpectedValueException(); + } + + // this cron job should be executed if the last execution is greater than one day, but only during the night + + $now = new DateTimeImmutable('now'); + + return $cronJobExecution->getLastStart() < $now->sub(new DateInterval('P1D')) + && in_array($now->format('H'), self::ACCEPTED_HOURS, true) + // introduce a random component to ensure a roll of task execution when multiple instances are hosted on same machines + && mt_rand(0, 5) === 0; + } + + public function getKey(): string + { + return 'arbitrary-and-unique-key'; + } + + public function run(): void + { + // here, we execute the command + } + } + +How are cron job scheduled ? +============================ + +If the command :code:`chill:cron-job:execute` is run with one or more :code:`job` argument, those jobs are run, **without checking that the job can run** (the method :code:`canRun` is not executed). + +If any :code:`job` argument is given, the :code:`CronManager` schedule job with those steps: + +* the tasks are ordered, with: + * a priority is given for tasks that weren't never executed; + * then, the tasks are ordered, the last executed are the first in the list +* then, for each tasks, and in the given order, the first task where :code:`canRun` return :code:`TRUE` will be executed. + +The command :code:`chill:cron-job:execute` execute **only one** task. + + + 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/exports.rst b/docs/source/development/exports.rst index 2d3323ff7..3b01f9e0f 100644 --- a/docs/source/development/exports.rst +++ b/docs/source/development/exports.rst @@ -10,17 +10,17 @@ Exports ******* -Export is an important issue for the Chill software : users should be able to : +Export is an important issue within the Chill software : users should be able to : - compute statistics about their activity ; -- list "things" which make part of their activities. +- list "things" which are a part of their activities. The `main bundle`_ provides a powerful framework to build custom queries with re-usable parts across differents bundles. .. contents:: Table of content :local: -.. seealso:: +.. seealso:: `The issue where this framework was discussed `_ Provides some information about the pursued features and architecture. @@ -32,37 +32,37 @@ Concepts Some vocabulary: 3 "Export elements" ------------------------------------ -Four terms are used for this framework : +Four terms are used for this framework : -exports - provides some basic operation on the date. Two kind of exports are available : +Exports + provide some basic operation on the data. Two kinds of exports are available : - computed data : it may be "the number of people", "the number of activities", "the duration of activities", ... - - list data : it may be "the list of people", "the list of activity", ... + - list data : it may be "the list of people", "the list of activities", ... -filters - The filters make a filter on the date: it removes some information the user doesn't want to introduce in the computation done by export. In other word, filters make a filter... +Filters + The filters create a filter on the data: it removes some information the user doesn't want to introduce in the computation done by the export. - Example of filter: "people under 18 years olds", "activities between the 1st of June and the 31st December", ... + Example of a filter: "people under 18 years olds", "activities between the 1st of June and the 31st December", ... -aggregators - The aggregator aggregates the data into some group (some software use the term 'bucket'). +Aggregators + The aggregator aggregates the data into some group (some software use the term 'bucket'). - Example of aggregator : "group people by gender", "group people by nationality", "group activity by type", ... + Example of an aggregator : "group people by gender", "group people by nationality", "group activity by type", ... -formatters - The formatters format the data into a :class:`Symfony\Component\HttpFoundation\Response`, which will be returned "as is" by the controller to the web client. +Formatters + The formatters format the data into a :class:`Symfony\Component\HttpFoundation\Response`, which will be returned "as is" by the controller to the web client. - Example of formatter: "format data as CSV", "format data as ods spreadsheet", ... + Example of a formatter: "format data as CSV", "format data as an ods spreadsheet", ... Anatomy of an export --------------------- -An export may be explained as a sentence, where each part of this sentence refers to one or multiple exports element. Examples : +An export can be thought of as a sentence where each part of this sentence refers to one or multiple export elements. Examples : **Example 1**: Count the number of people having at least one activity in the last 12 month, and group them by nationality and gender, and format them in a CSV spreadsheet. -Here : +Here : - *count the number of people* is the export part - *having at least one activity* is the filter part @@ -72,10 +72,10 @@ Here : Note that : -- aggregators, filters, exports and aggregators are cross-bundle. Here the bundle *activity* provides a filter which apply on an export provided by the person bundle ; -- there may exists multiple aggregator or filter for one export. Currently, only one export is allowed. +- Aggregators, filters, exports and formatters are cross-bundle. Here the bundle *activity* provides a filter which is applied on an export provided by the person bundle ; +- Multiple aggregator or filter for one export may exist. Currently, only one export is allowed. -The result might be : +The result might be : +-----------------------+----------------+---------------------------+ | Nationality | Gender | Number of people | @@ -89,9 +89,9 @@ The result might be : | France | Female | 150 | +-----------------------+----------------+---------------------------+ -**Example 2**: Count the average duration of an activity with type "meeting", which occurs between the 1st of June and the 31st of December, group them by week, and format the data in a OpenDocument spreadsheet. +**Example 2**: Count the average duration of an activity with type "meeting", which occurs between the 1st of June and the 31st of December, group them by week, and format the data in an OpenDocument spreadsheet. -Here : +Here : - *count the average duration of an activity* is the export part - *activity with type meeting* is a filter part @@ -102,7 +102,7 @@ Here : The result might be : +-----------------------+----------------------+ -| Week | Number of activities | +| Week | Number of activities | +=======================+======================+ | 2015-10 | 10 | +-----------------------+----------------------+ @@ -116,77 +116,77 @@ The result might be : Authorization and exports ------------------------- -Exports, filters and aggregators should not make see data the user is not allowed to see. +Exports, filters and aggregators should not show data the user is not allowed to see within the application. In other words, developers are required to take care of user authorization for each export. -It should exists a special role that should be granted to users which are allowed to build exports. For more simplicity, this role should apply on center, and should not requires special circles. +There should be a specific role that grants permission to users who are allowed to build exports. For more simplicity, this role should apply on a center, and should not require special circles. -How does the magic works ? +How does the magic work ? =========================== To build an export, we rely on the capacity of the database to execute queries with aggregate (i.e. GROUP BY) and filter (i.e. WHERE) instructions. An export is an SQL query which is initiated by an export, and modified by aggregators and filters. -.. note:: +.. note:: **Example**: Count the number of people having at least one activity in the last 12 month, and group them by nationality and gender - 1. The report initiate the query + 1. The report initiates the query .. code-block:: SQL SELECT count(people.*) FROM people - 2. The filter add a where and join clause : + 2. The filter adds a where and join clause : .. code-block:: SQL - SELECT count(people.*) FROM people - RIGHT JOIN activity + SELECT count(people.*) FROM people + RIGHT JOIN activity WHERE activity.date IS BETWEEN now AND 6 month ago - 3. The aggregator "nationality" add a GROUP BY clause and a column in the SELECT statement: + 3. The aggregator "nationality" adds a GROUP BY clause and a column in the SELECT statement: .. code-block:: sql - SELECT people.nationality, count(people.*) FROM people - RIGHT JOIN activity - WHERE activity.date IS BETWEEN now AND 6 month ago + SELECT people.nationality, count(people.*) FROM people + RIGHT JOIN activity + WHERE activity.date IS BETWEEN now AND 6 month ago GROUP BY nationality - 4. The aggregator "gender" do the same job as the nationality aggregator : it adds a GROUP BY clause and a column in the SELECT statement : + 4. The aggregator "gender" does the same job as the nationality aggregator : it adds a GROUP BY clause and a column in the SELECT statement : .. code-block:: sql - SELECT people.nationality, people.gender, count(people.*) - FROM people RIGHT JOIN activity - WHERE activity.date IS BETWEEN now AND 6 month ago + SELECT people.nationality, people.gender, count(people.*) + FROM people RIGHT JOIN activity + WHERE activity.date IS BETWEEN now AND 6 month ago GROUP BY nationality, gender -Each filter, aggregator and filter may collect parameters from the user by providing a form. This form is appended to the export form. Here is an example. +Each filter, aggregator and filter may collect parameters from the user through a form. This form is appended to the export form. Here is an example. .. figure:: /_static/screenshots/development/export_form-fullpage.png - The screenshot show the export form for ``CountPeople`` (Nombre de personnes). The filter by date of birth is checked (*Filtrer par date de naissance de la personne*), which allow to show a subform, which is provided by the :class:`Chill\PersonBundle\Export\Filter\BirthdateFilter`. The other filter, which are unchecked, does not show the subform. + The screenshot shows the export form for ``CountPeople`` (Nombre de personnes). The filter by date of birth is checked (*Filtrer par date de naissance de la personne*), which triggers a subform, which is provided by the :class:`Chill\PersonBundle\Export\Filter\BirthdateFilter`. The other unchecked filter does not show the subform. - Two aggregators are also checked : by Country of birth (*Aggréger les personnes par pays de naissance*, corresponding class is :class:`Chill\PersonBundle\Export\Aggregator\CountryOfBirthAggregator`, which also open a subform. The aggregator by gender (*Aggréger les personnes par genre*) is also checked, but there is no corresponding subform. + Two aggregators are also checked : by Country of birth (*Aggréger les personnes par pays de naissance*, the corresponding class is :class:`Chill\PersonBundle\Export\Aggregator\CountryOfBirthAggregator`, which also triggers a subform. The aggregator by gender (*Aggréger les personnes par genre*) is also checked, but there is no corresponding subform. The Export Manager ------------------ -The Export manager (:class:`Chill\MainBundle\Export\ExportManager` is the central class which register all exports, aggregators, filters and formatters. +The Export manager (:class:`Chill\MainBundle\Export\ExportManager` is the central class which registers all exports, aggregators, filters and formatters. -The export manager is also responsible for orchestrating the whole export process, producing a :class:`Symfony\FrameworkBundle\HttpFoundation\Request` to each export request. +The export manager is also responsible for orchestrating the whole export process, producing a :class:`Symfony\FrameworkBundle\HttpFoundation\Request` for each export request. The export form step -------------------- -The form step allow to build a form, aggregating different parts of the module. +The form step allows you to build a form, combining different parts of the module. -The building of forms is separated between different subform, which are responsible for rendering their part of the form (aggregators, filters, and export). +The building of forms is split into different subforms, where each one is responsible for rendering their part of the form (aggregators, filters, and export). .. figure:: /_static/puml/exports/form_steps.png :scale: 40% @@ -194,12 +194,12 @@ The building of forms is separated between different subform, which are responsi The formatter form step ----------------------- -The formatter form is processed *after* the user filled the export form. It is built the same way, but receive in parameters the data entered by the user on the previous step (i.e. export form). It may then adapt it accordingly (example: show a list of columns selected in aggregators). +The formatter form is processed *after* the user filled the export form. It is built the same way, but receives the data entered by the user on the previous step as parameters (i.e. export form). It may then adapt it accordingly (example: show a list of columns selected in aggregators). Processing the export --------------------- -The export process may be explained by this schema : +The export process can be explained by this schema : .. figure:: /_static/puml/exports/processing_export.png :scale: 40% @@ -219,20 +219,20 @@ This is an example of the ``CountPerson`` export : :language: php :linenos: -* **Line 36**: the ``getType`` function return a string. This string will be used to find the aggregtors and filters which will apply to this export. -* **Line 41**: a simple description to help user to understand what your export does. +* **Line 36**: the ``getType`` function returns a string. This string will be used to find the aggregtors and filters which will apply to this export. +* **Line 41**: a simple description to help users understand what your export does. * **Line 46**: The title of the export. A summary of what your export does. -* **Line 51**: The list of roles requires to execute this export. +* **Line 51**: The list of roles required to execute this export. * **Line 56**: We initiate the query here... -* **Line 59**: We have to filter the query with centers the users checked in the form. We process the $acl variable to get all ``Center`` object in one array -* **Line 63**: We create the query, with a query builder. -* **Line 74**: We simply returns the result, but take care of hydrating the results as an array. -* **Line 103**: return the list of formatters types which are allowed to apply on this filter +* **Line 59**: We have to filter the query with centers the users checked in the form. We process the $acl variable to get all ``Center`` objects in one array +* **Line 63**: We create the query with a query builder. +* **Line 74**: We return the result, but make sure to hydrate the results as an array. +* **Line 103**: return the list of formatter types which are allowed to be applied on this filter Filters ------- -This is an example of the *filter by birthdate*. This filter ask some information in a form (`buildForm` is not empty), and this form must be validated. To performs this validations, we implement a new Interface: :class:`Chill\MainBundle\Export\ExportElementValidatedInterface`: +This is an example of the *filter by birthdate*. This filter asks some information through a form (`buildForm` is not empty), and this form must be validated. To perform this validation, we implement a new Interface: :class:`Chill\MainBundle\Export\ExportElementValidatedInterface`: .. literalinclude:: /_static/code/exports/BirthdateFilter.php :language: php diff --git a/docs/source/development/index.rst b/docs/source/development/index.rst index f35bc12db..fd9ae43ba 100644 --- a/docs/source/development/index.rst +++ b/docs/source/development/index.rst @@ -34,6 +34,8 @@ As Chill rely on the `symfony `_ framework, reading the fram Useful snippets manual/index.rst Assets + Cron Jobs + Info about entities Layout and UI ************** diff --git a/docs/source/development/pagination/example.php b/docs/source/development/pagination/example.php index ed91d16a7..0d79395a7 100644 --- a/docs/source/development/pagination/example.php +++ b/docs/source/development/pagination/example.php @@ -1,14 +1,14 @@ `_ @@ -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 325b5da90..7c8ec1931 100644 --- a/docs/source/development/useful-snippets/controller-secured-for-person.php +++ b/docs/source/development/useful-snippets/controller-secured-for-person.php @@ -1,14 +1,14 @@ `_ to be run. As a developer, use `docker-compose `_ to bootstrap a dev environment in a glance. You do not need any other dependencies ; - Make is used to automate scripts. -Installation in development mode -******************************** +Installation +************ + +If you plan to run chill in production: + +1. install it locally first, and check if everything is ok on your local machine; +2. once ready, build the image from your local machine, and deploy them. + +If you want to develop some bundles, the first step is sufficient (until you deploy on production). + 1. Get the code =============== -Clone or download the chill-app project and `cd` into the main directory. +Clone or download the chill-skeleton project and `cd` into the main directory. .. code-block:: bash - git clone https://gitlab.com/Chill-Projet/chill-app.git + git clone https://gitlab.com/Chill-Projet/chill-skeleton-basic.git cd chill-app As a developer, the code will stay on your computer and will be executed in docker container. To avoid permission problem, the code should be run with the same uid/gid from your current user. This is why we get your current user id with the command ``id -u`` in each following scripts. -2. Prepare your variables -========================= +2. Prepare composer to download the sources +=========================================== -Have a look at the variable in ``.env.dist`` and in ``app/config/parameters.yml.dist`` and check if you need to adapt them. If they do not adapt with your need, or if some are missing: +As you are running in dev, you must configure an auth token for getting the source code. -1. copy the file as ``.env``: ``cp .env.dist .env`` +.. warning + + If you skip this part, the code will be downloaded from dist instead of source (with git repository). You will probably replace the source manually, but the next time you will run ```composer update```, your repository will be replaced and you might loose something. + +1. Create a personal access token from https://gitlab.com/-/profile/personal_access_tokens, with the `read_api` scope. +2. add a file called ```.composer/auth.json``` with this content: + + .. code-block:: json + + { + "gitlab-token": { + "gitlab.com": "glXXX-XXXXXXXXXXXXXXXXXXXX" + } + } + +2. Prepare your variables and environment +========================================= + +Copy ```docker-compose.override.dev.yml``` into ```docker-compose.override.yml``` + +.. code-block:: bash + + cp docker-compose.override.dev.template.yml docker-compose.override.yml + +2. Prepare your variables and docker-compose +============================================ + +Have a look at the variable in ``.env`` and check if you need to adapt them. If they do not adapt with your need, or if some are missing: + +1. copy the file as ``.env.local``: ``cp .env .env.local`` 2. you may replace some variables inside ``.env`` +Prepare also you docker-compose installation, and adapt it to your needs: + +1. If you plan to deploy on dev, copy the file ``docker-compose.override.dev.template.yml`` to ``docker-compose.override.yml``. +2. adapt to your needs. + + **Note**: If you intend to use the bundle ``Chill-Doc-Store``, you will need to configure and install an openstack object storage container with temporary url middleware. You will have to configure `secret keys `_. 3. Run the bootstrap script @@ -60,19 +111,30 @@ This script can be run using `make` This script will : 1. force docker-compose to, eventually, pull the base images and build the image used by this project ; -2. run an install script to download `composer `_ ; +2. run an install script to download `composer `_ ; 3. install the php dependencies 4. build assets +.. warning:: + + The script will work only if the binary ``docker-compose`` is located into your ``PATH``. If you use ``compose`` as a docker plugin, + you can simulate this binary by creating this file at (for instance), ``/usr/local/bin/docker-compose`` (and run ``chmod +x /usr/local/bin/docker-compose``): + + .. code-block:: bash + + #!/bin/bash + + /usr/bin/docker compose "$@" + .. note:: - In some cases it can happen that an old image (chill_base_php or chill_php) stored in the docker cache will make the script fail. To solve this problem you have to delete the image and the container, before the make init : + In some cases it can happen that an old image (chill_base_php82 or chill_php82) stored in the docker cache will make the script fail. To solve this problem you have to delete the image and the container, before the make init : .. code-block:: bash docker-compose images php - docker rmi -f chill_php:prod + docker rmi -f chill_php82:prod docker-compose rm php @@ -87,13 +149,21 @@ This script will : .. code-block:: bash - make migrate + # mount into to container + ./docker-php.sh + bin/console chill:db:sync-views + # and load fixtures + bin/console doctrine:migrations:migrate + Chill will be available at ``http://localhost:8001.`` Currently, there isn't any user or data. To add fixtures, run .. code-block:: bash - docker-compose exec --user $(id -u) php bin/console doctrine:fixtures:load --purge-with-truncate + # mount into to container + ./docker-php.sh + # and load fixtures (do not this for production) + bin/console doctrine:fixtures:load --purge-with-truncate There are several users available: @@ -102,7 +172,8 @@ There are several users available: The password is always ``password``. -Now, read `Operations` below. +Now, read `Operations` below. For running in production, read `prod_`. + Operations ********** @@ -110,7 +181,7 @@ Operations Build assets ============ -run those commands: +run those commands: .. code-block:: bash @@ -122,8 +193,8 @@ How to execute the console ? .. code-block:: bash # if a container is running - docker-compose exec --user $(id -u) php bin/console - # if not + ./docker-php.sh + # if not docker-compose run --user $(id -u) php bin/console How to create the database schema (= run migrations) ? @@ -132,9 +203,12 @@ How to create the database schema (= run migrations) ? .. code-block:: bash # if a container is running - docker-compose exec --user $(id -u) php bin/console doctrine:migrations:migrate - # if not + ./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 ? @@ -150,8 +224,9 @@ How to load fixtures ? (development mode only) .. code-block:: bash # if a container is running - docker-compose exec --user $(id -u) php bin/console doctrine:fixtures:load - # if not + ./docker-php.sh + bin/console doctrine:fixtures:load + # if not docker-compose run --user $(id -u) php bin/console doctrine:fixtures:load How to open a terminal in the project @@ -160,31 +235,49 @@ How to open a terminal in the project .. code-block:: bash # if a container is running - docker-compose exec --user $(id -u) php /bin/bash - # if not + ./docker-php.sh + # 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 ? ===================== .. code-block:: bash # if a container is running - docker-compose exec --user $(id -u) php ./composer.phar - # if not - docker-compose run --user $(id -u) php ./composer.phar + ./docker-php.sh + composer + # if not + docker-compose run --user $(id -u) php composer How to access to PGADMIN ? ========================== -Pgadmin is installed with docker-compose. +Pgadmin is installed with docker-compose, and is available **only if you uncomment the appropriate lines into ``docker-compose.override.yml``. You can access it at ``http://localhost:8002``. Credentials: -- login: admin@chill.social -- password: password +- login: from the variable you set into ``docker-composer.override.yml`` +- password: same :-) How to run tests ? ================== @@ -193,30 +286,72 @@ Tests reside inside the installed bundles. You must `cd` into that directory, do **Note**: some bundle require the fixture to be executed. See the dedicated _how-tos_. -Exemple, for running test inside `main` bundle: +Exemple, for running unit test inside `main` bundle: .. code-block:: bash # mount into the php image - docker-compose run --user $(id -u) php /bin/bash + ./docker-php.sh # cd into main directory - cd vendor/chill-project/main + cd vendor/chill-project/chill-bundles # download deps - php ../../../composer.phar install + git submodule init + git submodule update + composer install # run tests - /vendor/bin/phpunit + bin/phpunit src/Bundle/path/to/your/test + +Or for running tests to check code style and php conventions with csfixer and phpstan: + +.. code-block:: bash + + # run code style fixer + bin/grumphp run --tasks=phpcsfixer + # run phpstan + bin/grumphp run --tasks=phpstan + + +.. note:: + + To avoid phpstan block your commits: + + .. code-block:: bash + + git commit -n ... + + To avoid phpstan block your commits permanently: + + .. code-block:: bash + + ./bin/grumphp git:deinit How to run webpack interactively ================================ Executing :code:`bash docker-node.sh` will open a terminal in a node container, with volumes mounted. -Build the documentation API -=========================== +How to switch the branch for chill-bundles, and get new dependencies +==================================================================== -A basic configuration of `sami `_ is embedded within the project. +During development, you will switch to new branches for chill-bundles. As long as the dependencies are equals, this does not cause any problem. But sometimes, a new branch introduces a new dependency, and you must download it. -A configuration file for `phpDocumentor `_ is present. +.. warning:: + + Ensure that you have gitlab-token ready before updating your branches. See above. + +In order to do that without pain, use those steps: + +0. Ensuire you have a token, set +1. at the app's root, update the ``composer.json`` to your current branch: + +.. code-block:: json + + { + "require": { + "chill-bundles": "dev-@dev" + } + +2. mount into the php container (``./docker-php.sh``), and run ``composer update`` Error `An exception has been thrown during the rendering of a template ("Asset manifest file "/var/www/app/web/build/manifest.json" does not exist.").` on first run ==================================================================================================================================================================== @@ -230,8 +365,9 @@ Currently, to run this software in production, the *state of the art* is the fol 1. Run the software locally and tweak the configuration to your needs ; 2. Build the image and store them into a private container registry. This can be done using :code:`make build-and-push-image`. - - To be sure to target the correct container registry, you have to adapt the values ``IMAGE_NGINX`` and ``IMAGE_PHP`` date in the ``.env`` file. + + To be sure to target the correct container registry, you have to adapt the image names into your ``docker-compose.override.yml`` file. +3. Push the image on your registry, or upload them to the destination machine using ``docker image save`` and ``docker image load``. 3. Run the image on your production server, using docker-compose or eventually docker stack. You have to customize the variable set in docker-compose. See also the :ref:`running-production-tips-and-tricks` below. @@ -263,7 +399,7 @@ It is worth having an eye on the configuration of logstash container. Design principles ***************** -Why the DB URL is set in environment, and not in parameters.yml ? +Why the DB URL is also set in environment, and not in .env file ? ================================================================= Because, at startup, a script does check the db is up and, if not, wait for a couple of seconds before running ``entrypoint.sh``. For avoiding double configuration, the configuration of the PHP app takes his configuration from environment also (and it will be standard in future releases, with symfony 4.0). diff --git a/docs/source/installation/load-addresses.rst b/docs/source/installation/load-addresses.rst new file mode 100644 index 000000000..779032fd0 --- /dev/null +++ b/docs/source/installation/load-addresses.rst @@ -0,0 +1,50 @@ + +.. _addresses: + +Addresses +********* + +Chill can store a list of geolocated address references, which are used to suggest address and ensure that the data is correctly stored. + +Those addresses may be load from a dedicated source. + +In France +========= + +The address are loaded from the `BANO `_. The postal codes are loaded from `the official list of +postal codes `_ + +.. code-block:: bash + + # first, load postal codes + bin/console chill:main:postal-code:load:FR + # then, load all addresses, by departement (multiple departement can be loaded by repeating the departement code + bin/console chill:main:address-ref-from-bano 57 54 51 + +In Belgium +========== + +Addresses are prepared from the `BeST Address data `_. + +Postal code are loaded from this database. There is no need to load postal codes from another source (actually, this is strongly discouraged). + +The data are prepared for Chill (`See this repository `_). +One can select postal code by his first number (:code:`1xxx` for postal codes from 1000 to 1999), or a limited list for development purpose. + +.. code-block:: bash + + # load postal code from 1000 to 3999: + bin/console chill:main:address-ref-from-best-addresse 1xxx 2xxx 3xxx + + # load only an extract (for dev purposes) + bin/console chill:main:address-ref-from-best-addresse extract + + # load full addresses (discouraged) + bin/console chill:main:address-ref-from-best-addresse full + +.. note:: + + There is a possibility to load the full list of addresses is discouraged: the loading is optimized with smaller extracts. + + Once you load the full list, it is not possible to load smaller extract: each extract loaded **after** will not + delete the addresses loaded with the full extract (and some addresses will be present twice). diff --git a/docs/source/installation/msgraph-configure.rst b/docs/source/installation/msgraph-configure.rst new file mode 100644 index 000000000..2a0a17882 --- /dev/null +++ b/docs/source/installation/msgraph-configure.rst @@ -0,0 +1,320 @@ + +Configure Chill for calendar sync and SSO with Microsoft Graph (Outlook) +======================================================================== + +Chill offers the possibility to: + +* authenticate users using Microsoft Graph, with relatively small adaptations; +* synchronize calendar in both ways (`see the user manual for a large description of the feature `_). + +Both can be configured separately (synchronising calendars without SSO, or SSO without calendar). When calendar sync is configured without SSL, the user's email address is the key to associate Chill's users with Microsoft's ones. + +Configure SSO +------------- + +On Azure side +************* + +Configure an app with the Azure interface, and give it the name of your choice. + +Grab the tenant's ID for your app, which is visible on the main tab "Vue d'ensemble": + +.. figure:: ./saml_login_id_general.png + +This the variable which will be named :code:`SAML_IDP_APP_UUID`. + +Go to the "Single sign-on" ("Authentication unique") section. Choose "SAML" as protocol, and fill those values: + +.. figure:: ./saml_login_1.png + +1. The :code:`entityId` seems to be arbitrary. This will be your variable :code:`SAML_ENTITY_ID`; +2. The url response must be your Chill's URL appended by :code:`/saml/acs` +3. The only used attributes is :code:`emailaddress`, which must match the user's email one. + +.. figure:: ./saml_login_2.png + +You must download the certificate, as base64. The format for the download is :code:`cer`: you will remove the first and last line (the ones with :code:`-----BEGIN CERTIFICATE-----` and :code:`-----END CERTIFICATE-----`), and remove all the return line. The final result should be something as :code:`MIIAbcdef...XyZA=`. + +This certificat will be your :code:`SAML_IDP_X509_CERT` variable. + +The url login will be filled automatically with your tenant id. + +Do not forget to provider user's accesses to your app, using the "Utilisateurs et groupes" tab: + +.. figure:: ./saml_login_appro.png + + +You must know have gathered all the required variables for SSO: + +.. code-block:: + + SAML_BASE_URL=https://test.chill.be # must be + SAML_ENTITY_ID=https://test.chill.be # must match the one entered + SAML_IDP_APP_UUID=42XXXXXX-xxxx-xxxx-xxxx-xxxxxxxxxxxx + SAML_IDP_X509_CERT: MIIC...E8u3bk # truncated + +Configure chill app +******************* + +* add the bundle :code:`hslavich/oneloginsaml-bundle` +* add the configuration file (see example above) +* configure the security part (see example above) +* add a user SAML factory into your src, and register it + + +.. code-block:: yaml + + # config/packages/hslavich_onelogin.yaml + + parameters: + saml_base_url: '%env(resolve:SAML_BASE_URL)%' + saml_entity_id: '%env(resolve:SAML_ENTITY_ID)%' + saml_idp_x509cert: '%env(resolve:SAML_IDP_X509_CERT)%' + saml_idp_app_uuid: '%env(resolve:SAML_IDP_APP_UUID)%' + + + hslavich_onelogin_saml: + # Basic settings + idp: + entityId: 'https://sts.windows.net/%saml_idp_app_uuid%/' + singleSignOnService: + url: 'https://login.microsoftonline.com/%saml_idp_app_uuid%/saml2' + binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect' + singleLogoutService: + url: 'https://login.microsoftonline.com/%saml_idp_app_uuid%/saml2' + binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect' + x509cert: '%saml_idp_x509cert%' + sp: + entityId: '%saml_entity_id%' + assertionConsumerService: + url: '%saml_base_url%/saml/acs' + binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST' + singleLogoutService: + url: '%saml_base_url%/saml/' + binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect' + privateKey: '' + # Optional settings. + baseurl: '%saml_base_url%/saml' + strict: true + debug: true + security: + nameIdEncrypted: false + authnRequestsSigned: false + logoutRequestSigned: false + logoutResponseSigned: false + wantMessagesSigned: false + wantAssertionsSigned: false + wantNameIdEncrypted: false + requestedAuthnContext: true + signMetadata: false + wantXMLValidation: true + signatureAlgorithm: 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256' + digestAlgorithm: 'http://www.w3.org/2001/04/xmlenc#sha256' + contactPerson: + technical: + givenName: 'Tech User' + emailAddress: 'techuser@example.com' + support: + givenName: 'Support User' + emailAddress: 'supportuser@example.com' + organization: + en: + name: 'Example' + displayname: 'Example' + url: 'http://example.com' + + +.. code-block:: yaml + + # config/security.yaml + # merge this with other existing configurations + + security: + + + providers: + saml_provider: + # Loads user from user repository + entity: + class: Chill\MainBundle\Entity\User + property: username + + firewalls: + + + default: + # saml part: + saml: + username_attribute: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress + # weird behaviour in dev environment... configuration seems different + # username_attribute: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name + # Use the attribute's friendlyName instead of the name + use_attribute_friendly_name: false + user_factory: user_from_saml_factory + persist_user: true + check_path: saml_acs + login_path: saml_login + logout: + path: /saml/logout + + +.. code-block:: php + + // src/Security/SamlFactory.php + + namespace App\Security; + + use Chill\MainBundle\Entity\User; + use Hslavich\OneloginSamlBundle\Security\Authentication\Token\SamlTokenInterface; + use Hslavich\OneloginSamlBundle\Security\User\SamlUserFactoryInterface; + + class UserSamlFactory implements SamlUserFactoryInterface + { + public function createUser(SamlTokenInterface $token) + { + $attributes = $token->getAttributes(); + $user = new User(); + $user->setUsername($attributes['http://schemas.microsoft.com/identity/claims/displayname'][0]); + $user->setLabel($attributes['http://schemas.microsoft.com/identity/claims/displayname'][0]); + $user->setPassword(''); + $user->setEmail($attributes['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress'][0]); + $user->setAttributes($attributes); + + return $user; + } + } + + + +Configure sync +-------------- + +The sync processe might be configured in the same app, or into a different app. + +The synchronization processes use Oauth2.0 for authentication and authorization. + +.. note:: + + Two flows are in use: + + * we authenticate "on behalf of a user", to allow users to see their own calendar or other user's calendar into the web interface. + + Typically, when the page is loaded, Chill first check that an authorization token exists. If not, the user is redirected to Microsoft Azure for authentification and a new token is grabbed (most of the times, this is transparent for users). + + * Chill also acts "as a machine", to synchronize calendars with a daemon background. + +One can access the configuration using this screen (it is quite well hidden into the multiple of tabs): + +.. figure:: ./oauth_app_registration.png + + You can find the oauth configuration on the "Securité > Autorisations" tab, and click on "application registration" (not translated). + +Add a redirection URI for you authentification: + +.. figure:: ./oauth_api_authentification.png + + The URI must be "your chill public url" with :code:`/connect/azure/check` at the end. + +Allow some authorizations for your app: + +.. figure:: ./oauth_api_autorisees.png + +Take care of the separation between autorization "on behalf of a user" (déléguée), or "for a machine" (application). + +Some explanation: + +* Users must be allowed to read their user profile (:code:`User.Read`), and the profile of other users (:code:`User.ReadBasicAll`); +* They must be allowed to read their calendar (:code:`Calendars.Read`), and the calendars shared with them (:code:`Calendars.Read.Shared`); + +The sync daemon must have write access: + +* the daemon must be allowed to read all users and their profile, to establish a link between them and the Chill's users: (:code:`Users.Read.All`); +* it must also be allowed to read and write into the calendars (:code:`Calendars.ReadWrite.All`) +* for sending invitation to other users, the permission (:code:`Mail.Send`) must be granted. + +At this step, you might choose to accept those permissions for all users, or let them do it by yourself. + +Grab your client id: + +.. figure:: ./oauth_api_client_id.png + +This will be your :code:`OAUTH_AZURE_CLIENT_ID` variable. + + +Generate a secret: + +.. figure:: ./oauth_api_secret.png + +This will be your :code:`OAUTH_AZURE_CLIENT_SECRET` variable. + +And get you azure's tenant id, which is the same as the :code:`SAML_IDP_APP_UUID` (see above). + +Your variables will be: + +.. code-block:: + + OAUTH_AZURE_CLIENT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + OAUTH_AZURE_CLIENT_TENANT=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + OAUTH_AZURE_CLIENT_SECRET: 3-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + +Then, configure chill: + +Enable the calendar sync with microsoft azure: + +.. code-block:: yaml + + # config/packages/chill_calendar.yaml + + chill_calendar: + remote_calendars_sync: + microsoft_graph: + enabled: true + +and configure the oauth client: + +.. code-block:: yaml + + # config/packages/knp_oauth2_client.yaml + knpu_oauth2_client: + clients: + azure: + type: azure + client_id: '%env(OAUTH_AZURE_CLIENT_ID)%' + client_secret: '%env(OAUTH_AZURE_CLIENT_SECRET)%' + redirect_route: chill_calendar_remote_connect_azure_check + redirect_params: { } + tenant: '%env(OAUTH_AZURE_CLIENT_TENANT)%' + url_api: 'https://graph.microsoft.com/' + default_end_point_version: '2.0' + + +You can now process for the first api authorization on the application side, (unless you did it in the Azure interface), and get a first token, by using : + +:code:`bin/console chill:calendar:msgraph-grant-admin-consent` + +This will generate a url that you can use to grant your app for your tenant. The redirection may fails in the browser, but this is not relevant: if you get an authorization token in the CLI, the authentication works. + +Run the processes to synchronize +-------------------------------- + +The calendar synchronization is processed using symfony messenger. It seems to be intersting to configure a queue (in the postgresql database it is the most simple way), and to run a worker for synchronization, at least in production. + +The association between chill's users and Microsoft's users is done by this cli command: + +.. code-block:: + + bin/console chill:calendar:msgraph-user-map-subscribe + +This command: + +* will associate the Microsoft's user metadata in our database; +* and, most important, create a subscription to get notification when the user alter his calendar, to sync chill's event and ranges in sync. + +The subscription least at most 3 days. This command should be runned: + +* at least each time a user is added; +* and, at least, every three days. + +In production, we advise to run it at least every day to get the sync working. + + diff --git a/docs/source/installation/oauth_api_authentification.png b/docs/source/installation/oauth_api_authentification.png new file mode 100644 index 000000000..f6ecad32b Binary files /dev/null and b/docs/source/installation/oauth_api_authentification.png differ diff --git a/docs/source/installation/oauth_api_autorisees.png b/docs/source/installation/oauth_api_autorisees.png new file mode 100644 index 000000000..41222f971 Binary files /dev/null and b/docs/source/installation/oauth_api_autorisees.png differ diff --git a/docs/source/installation/oauth_api_client_id.png b/docs/source/installation/oauth_api_client_id.png new file mode 100644 index 000000000..7aa620ba7 Binary files /dev/null and b/docs/source/installation/oauth_api_client_id.png differ diff --git a/docs/source/installation/oauth_api_secret.png b/docs/source/installation/oauth_api_secret.png new file mode 100644 index 000000000..7e2fc1c28 Binary files /dev/null and b/docs/source/installation/oauth_api_secret.png differ diff --git a/docs/source/installation/oauth_app_registration.png b/docs/source/installation/oauth_app_registration.png new file mode 100644 index 000000000..757d34f00 Binary files /dev/null and b/docs/source/installation/oauth_app_registration.png differ diff --git a/docs/source/installation/prod-calendar-sms-sending.rst b/docs/source/installation/prod-calendar-sms-sending.rst new file mode 100644 index 000000000..6529b95d9 --- /dev/null +++ b/docs/source/installation/prod-calendar-sms-sending.rst @@ -0,0 +1,27 @@ + +Send short messages (SMS) with calendar bundle +============================================== + +To activate the sending of messages, you should run this command on a regularly basis (using, for instance, a cronjob): + +.. code-block:: bash + + bin/console chill:calendar:send-short-messages + +A transporter must be configured for the message to be effectively sent. + +Configure OVH Transporter +------------------------- + +Currently, this is the only one transporter available. + +For configuring this, simply add this config variable in your environment: + +```env +SHORT_MESSAGE_DSN=ovh://applicationKey:applicationSecret@endpoint?consumerKey=xxxx&sender=yyyy&service_name=zzzz +``` + +In order to generate the application key, secret, and consumerKey, refers to their `documentation `_. + +Before to be able to send your first sms, you must enable your account, grab some credits, and configure a sender. The service_name is an internal configuration generated by OVH. + diff --git a/docs/source/installation/prod.rst b/docs/source/installation/prod.rst new file mode 100644 index 000000000..21da15267 --- /dev/null +++ b/docs/source/installation/prod.rst @@ -0,0 +1,71 @@ +.. Copyright (C) 2014-2019 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". + +.. _prod: + +Installation for production +########################### + +An installation use these services, which are deployed using docker containers: + +* a php-fpm image, which run the Php and Symfony code for Chill; +* a nginx image, which serves the assets, and usually proxy the php requests to the fpm image; +* a redis server, which stores the cache, sessions (this is currently hardcoded in the php image), and some useful keys (like wopi locks); +* a postgresql database. The use of postgresql is mandatory; +* a relatorio service, which transform odt templates to full documents (replacing the placeholders); + +Some external services: + +* (required) an openstack object store, configured with `temporary url ` configured (no openstack users is required). This is currently the only way to store documents from chill; +* a mailer service (SMTP) +* (optional) a service for verifying phone number. Currently, only Twilio is possible; +* (optional) a service for sending Short Messages (SMS). Currently, only Ovh is possible; + +The `docker-compose.yaml` file of chill app is a basis for a production install. The environment variable in the ```.env``` and ```.env.prod``` should be overriden by environment variables, or ```.env.local``` files. + +This should be adapted to your needs: + +* The image for php and nginx apps are pre-compiled images, with the default configuration and bundle. If they do not fullfill your needs, you should compile your own images. + + .. TODO: + + As the time of writing (2022-07-03) those images are not published yet. + +* 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 +========= + +The command :code:`chill:cron-job:execute` should be executed every 15 minutes (more or less). + +This command should never be executed concurrently. It should be not have more than one process for a single instance. + +Post-install tasks +================== + +- import addresses. See :ref:`addresses`. + + +Tweak symfony messenger +======================= + +Calendar sync is processed using symfony messenger. + +You can tweak the configuration + +Going further: + +* Configure the saml login and synchronisation with Outlook api diff --git a/docs/source/installation/saml_login_1.png b/docs/source/installation/saml_login_1.png new file mode 100644 index 000000000..ec62a7acb Binary files /dev/null and b/docs/source/installation/saml_login_1.png differ diff --git a/docs/source/installation/saml_login_2.png b/docs/source/installation/saml_login_2.png new file mode 100644 index 000000000..736b74579 Binary files /dev/null and b/docs/source/installation/saml_login_2.png differ diff --git a/docs/source/installation/saml_login_appro.png b/docs/source/installation/saml_login_appro.png new file mode 100644 index 000000000..526eff63d Binary files /dev/null and b/docs/source/installation/saml_login_appro.png differ diff --git a/docs/source/installation/saml_login_id_general.png b/docs/source/installation/saml_login_id_general.png new file mode 100644 index 000000000..3062e6515 Binary files /dev/null and b/docs/source/installation/saml_login_id_general.png differ diff --git a/exports_alias_conventions.csv b/exports_alias_conventions.csv new file mode 100644 index 000000000..ab32cda8e --- /dev/null +++ b/exports_alias_conventions.csv @@ -0,0 +1,63 @@ +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 new file mode 100644 index 000000000..fd7844691 --- /dev/null +++ b/exports_alias_conventions.md @@ -0,0 +1,74 @@ +# Export conventions + + +Add condition with distinct alias on each export join clauses (Indicators + Filters + Aggregators) + +These are alias conventions : + +| Entity | Join | Attribute | Alias | +|:----------------------------------------|:----------------------------------------|:-------------------------------------------|:---------------------------------------| +| AccompanyingPeriod::class | | | acp | +| | AccompanyingPeriodWork::class | acp.works | acpw | +| | AccompanyingPeriodParticipation::class | acp.participations | acppart | +| | Location::class | acp.administrativeLocation | acploc | +| | ClosingMotive::class | acp.closingMotive | acpmotive | +| | UserJob::class | acp.job | acpjob | +| | Origin::class | acp.origin | acporigin | +| | Scope::class | acp.scopes | acpscope | +| | SocialIssue::class | acp.socialIssues | acpsocialissue | +| | User::class | acp.user | acpuser | +| | AccompanyingPeriopStepHistory::class | acp.stepHistories | acpstephistories | +| 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 | +| | CurrentPersonAddress::class | person.currentPersonAddress | currentPersonAddress (on a given date) | +| ResidentialAddress::class | | | resaddr | +| | ThirdParty::class | resaddr.hostThirdParty | tparty | +| ThirdParty::class | | | tparty | +| | ThirdPartyCategory::class | tparty.categories | tpartycat | +| HouseholdMember::class | | | householdmember | +| | Household::class | householdmember.household | household | +| | Person::class | householdmember.person | memberperson | +| | | memberperson.center | membercenter | +| Household::class | | | household | +| | HouseholdComposition::class | household.compositions | composition | +| Activity::class | | | activity | +| | Person::class | activity.person | actperson | +| | AccompanyingPeriod::class | activity.accompanyingPeriod | acp | +| | Person::class | activity\_person\_having\_activity.person | person\_person\_having\_activity | +| | ActivityReason::class | activity\_person\_having\_activity.reasons | reasons\_person\_having\_activity | +| | ActivityType::class | activity.activityType | acttype | +| | Location::class | activity.location | actloc | +| | SocialAction::class | activity.socialActions | actsocialaction | +| | SocialIssue::class | activity.socialIssues | actsocialssue | +| | ThirdParty::class | activity.thirdParties | acttparty | +| | User::class | activity.user | actuser | +| | User::class | activity.users | actusers | +| | ActivityReason::class | activity.reasons | actreasons | +| | Center::class | actperson.center | actcenter | +| | Person::class | activity.createdBy | actcreator | +| ActivityReason::class | | | actreasons | +| | ActivityReasonCategory::class | actreason.category | actreasoncat | +| Calendar::class | | | cal | +| | CancelReason::class | cal.cancelReason | calcancel | +| | Location::class | cal.location | calloc | +| | User::class | cal.user | caluser | +| VendeePerson::class | | | vp | +| | SituationProfessionelle::class | vp.situationProfessionelle | vpprof | +| | StatutLogement::class | vp.statutLogement | vplog | +| | TempsDeTravail::class | vp.tempsDeTravail | vptt | diff --git a/grumphp.yml b/grumphp.yml deleted file mode 100644 index 8efce6109..000000000 --- a/grumphp.yml +++ /dev/null @@ -1,39 +0,0 @@ -imports: - - { - resource: tests/app/vendor/drupol/php-conventions/config/php73/grumphp.yml, - } - -parameters: - tasks.phpcsfixer.config: .php_cs.dist.php - tasks.license.name: AGPL-3.0 - tasks.license.holder: Champs-Libres - tasks.license.date_from: 2001 - - tasks.phpcsfixer.allow_risky: true - tasks.phpcsfixer.diff: true - tasks.phpstan.level: 1 - tasks.phpstan.blocking: true - tasks.phpstan.ignore_patterns: - - "/.github/" - - "/.idea/" - - "/build/" - - "/benchmarks/" - - "/docs/" - - "/node_modules/" - - "/resource/" - - "/spec/" - - "/var/" - - "/vendor/" - - # Psalm - tasks.psalm.blocking: true - tasks.psalm.ignore_patterns: - - "/.github/" - - "/.idea/" - - "/build/" - - "/benchmarks/" - - "/node_modules/" - - "/resource/" - - "/spec/" - - "/var/" - - "/vendor/" diff --git a/package.json b/package.json new file mode 100644 index 000000000..590976cfa --- /dev/null +++ b/package.json @@ -0,0 +1,68 @@ +{ + "name": "chill", + "version": "2.0.0", + "devDependencies": { + "@alexlafroscia/yaml-merge": "^4.0.0", + "@apidevtools/swagger-cli": "^4.0.4", + "@babel/core": "^7.20.5", + "@babel/preset-env": "^7.20.2", + "@ckeditor/ckeditor5-build-classic": "^35.3.2", + "@ckeditor/ckeditor5-dev-utils": "^31.1.13", + "@ckeditor/ckeditor5-dev-webpack-plugin": "^31.1.13", + "@ckeditor/ckeditor5-markdown-gfm": "^35.3.2", + "@ckeditor/ckeditor5-theme-lark": "^35.3.2", + "@ckeditor/ckeditor5-vue": "^4.0.1", + "@symfony/webpack-encore": "^4.1.0", + "@tsconfig/node14": "^1.0.1", + "bindings": "^1.5.0", + "bootstrap": "^5.0.1", + "chokidar": "^3.5.1", + "fork-awesome": "^1.1.7", + "jquery": "^3.6.0", + "node-sass": "^8.0.0", + "popper.js": "^1.16.1", + "postcss-loader": "^7.0.2", + "raw-loader": "^4.0.2", + "sass-loader": "^13.0.0", + "select2": "^4.0.13", + "select2-bootstrap-theme": "0.1.0-beta.10", + "style-loader": "^3.3.1", + "ts-loader": "^9.3.1", + "typescript": "^4.7.2", + "vue-loader": "^17.0.0", + "webpack": "^5.75.0", + "webpack-cli": "^5.0.1" + }, + "dependencies": { + "@fullcalendar/core": "^6.1.4", + "@fullcalendar/daygrid": "^6.1.4", + "@fullcalendar/interaction": "^6.1.4", + "@fullcalendar/list": "^6.1.4", + "@fullcalendar/timegrid": "^6.1.4", + "@fullcalendar/vue3": "^6.1.4", + "@popperjs/core": "^2.9.2", + "@types/leaflet": "^1.9.3", + "dropzone": "^5.7.6", + "es6-promise": "^4.2.8", + "leaflet": "^1.7.1", + "masonry-layout": "^4.2.2", + "mime": "^3.0.0", + "swagger-ui": "^4.15.5", + "vis-network": "^9.1.0", + "vue": "^3.2.37", + "vue-i18n": "^9.1.6", + "vue-multiselect": "3.0.0-alpha.2", + "vue-toast-notification": "^2.0", + "vuex": "^4.0.0" + }, + "browserslist": [ + "Firefox ESR" + ], + "scripts": { + "dev-server": "encore dev-server", + "dev": "encore dev", + "watch": "encore dev --watch", + "build": "encore production --progress" + }, + "private": true +} diff --git a/phpstan-baseline-level-2.neon b/phpstan-baseline-level-2.neon new file mode 100644 index 000000000..6f7cec495 --- /dev/null +++ b/phpstan-baseline-level-2.neon @@ -0,0 +1,301 @@ +parameters: + ignoreErrors: + - + message: "#^Only booleans are allowed in a negated boolean, Chill\\\\ActivityBundle\\\\Entity\\\\Activity\\|null given\\.$#" + count: 1 + path: src/Bundle/ChillActivityBundle/Controller/ActivityController.php + + - + message: "#^Only booleans are allowed in a ternary operator condition, Chill\\\\MainBundle\\\\Entity\\\\Scope\\|null given\\.$#" + count: 1 + path: src/Bundle/ChillActivityBundle/Controller/ActivityController.php + + - + message: "#^Only booleans are allowed in a ternary operator condition, Chill\\\\PersonBundle\\\\Entity\\\\Person\\|null given\\.$#" + count: 1 + path: src/Bundle/ChillActivityBundle/Controller/ActivityController.php + + - + message: "#^Only booleans are allowed in a ternary operator condition, DateTime\\|null given\\.$#" + count: 1 + path: src/Bundle/ChillActivityBundle/Controller/ActivityController.php + + - + message: "#^Only booleans are allowed in a negated boolean, Chill\\\\ActivityBundle\\\\Entity\\\\ActivityReasonCategory\\|null given\\.$#" + count: 3 + path: src/Bundle/ChillActivityBundle/Controller/ActivityReasonCategoryController.php + + - + message: "#^Only booleans are allowed in a negated boolean, Chill\\\\ActivityBundle\\\\Entity\\\\ActivityReason\\|null given\\.$#" + count: 2 + path: src/Bundle/ChillActivityBundle/Controller/ActivityReasonController.php + + - + message: "#^Call to method DateTime\\:\\:setTimezone\\(\\) with incorrect case\\: setTimeZone$#" + count: 1 + path: src/Bundle/ChillActivityBundle/Form/ActivityType.php + + - + message: "#^Only booleans are allowed in &&, Chill\\\\PersonBundle\\\\Entity\\\\AccompanyingPeriod\\|null given on the right side\\.$#" + count: 2 + path: src/Bundle/ChillActivityBundle/Form/ActivityType.php + + - + message: "#^Only booleans are allowed in an if condition, Chill\\\\AsideActivityBundle\\\\Entity\\\\AsideActivityCategory\\|null given\\.$#" + count: 1 + path: src/Bundle/ChillAsideActivityBundle/src/Entity/AsideActivityCategory.php + + - + message: "#^Call to method DateTimeImmutable\\:\\:setTimezone\\(\\) with incorrect case\\: setTimeZone$#" + count: 1 + path: src/Bundle/ChillAsideActivityBundle/src/Form/AsideActivityFormType.php + + - + message: "#^Only booleans are allowed in a ternary operator condition, Chill\\\\MainBundle\\\\Entity\\\\Location\\|null given\\.$#" + count: 1 + path: src/Bundle/ChillCalendarBundle/Controller/CalendarController.php + + - + message: "#^Only booleans are allowed in an if condition, Symfony\\\\Component\\\\Validator\\\\ConstraintViolationListInterface given\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Command/CreateFieldsOnGroupCommand.php + + - + message: "#^Only booleans are allowed in a negated boolean, Chill\\\\CustomFieldsBundle\\\\Entity\\\\CustomFieldsGroup\\|null given\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Controller/CustomFieldController.php + + - + message: "#^Only booleans are allowed in a negated boolean, Chill\\\\CustomFieldsBundle\\\\Entity\\\\CustomField\\|null given\\.$#" + count: 3 + path: src/Bundle/ChillCustomFieldsBundle/Controller/CustomFieldController.php + + - + message: "#^Only booleans are allowed in a ternary operator condition, Chill\\\\CustomFieldsBundle\\\\Entity\\\\CustomFieldsGroup given\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Controller/CustomFieldController.php + + - + message: "#^Only booleans are allowed in a negated boolean, Chill\\\\CustomFieldsBundle\\\\Entity\\\\CustomFieldsGroup\\|null given\\.$#" + count: 4 + path: src/Bundle/ChillCustomFieldsBundle/Controller/CustomFieldsGroupController.php + + - + message: "#^Only booleans are allowed in a ternary operator condition, string given\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldLongChoice.php + + - + message: "#^Only booleans are allowed in a negated boolean, string given\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Form/DataTransformer/CustomFieldsGroupToIdTransformer.php + + - + message: "#^Call to method Chill\\\\PersonBundle\\\\Entity\\\\Person\\:\\:getFirstName\\(\\) with incorrect case\\: getFirstname$#" + count: 1 + path: src/Bundle/ChillEventBundle/Controller/EventController.php + + - + message: "#^Call to method Chill\\\\PersonBundle\\\\Entity\\\\Person\\:\\:getLastName\\(\\) with incorrect case\\: getLastname$#" + count: 1 + path: src/Bundle/ChillEventBundle/Controller/EventController.php + + - + message: "#^Call to method Chill\\\\PersonBundle\\\\Entity\\\\Person\\:\\:getMobilenumber\\(\\) with incorrect case\\: getMobileNumber$#" + count: 1 + path: src/Bundle/ChillEventBundle/Controller/EventController.php + + - + message: "#^Call to method Chill\\\\PersonBundle\\\\Entity\\\\Person\\:\\:getPhonenumber\\(\\) with incorrect case\\: getPhoneNumber$#" + count: 1 + path: src/Bundle/ChillEventBundle/Controller/EventController.php + + - + message: "#^Only booleans are allowed in a negated boolean, Chill\\\\EventBundle\\\\Entity\\\\Event given\\.$#" + count: 1 + path: src/Bundle/ChillEventBundle/Controller/EventController.php + + - + message: "#^Only booleans are allowed in a negated boolean, Chill\\\\EventBundle\\\\Entity\\\\Event\\|null given\\.$#" + count: 3 + path: src/Bundle/ChillEventBundle/Controller/EventController.php + + - + message: "#^Only booleans are allowed in a ternary operator condition, int given\\.$#" + count: 1 + path: src/Bundle/ChillEventBundle/Controller/EventController.php + + - + message: "#^Only numeric types are allowed in pre\\-increment, string given\\.$#" + count: 2 + path: src/Bundle/ChillEventBundle/Controller/EventController.php + + - + message: "#^Only booleans are allowed in a negated boolean, Chill\\\\EventBundle\\\\Entity\\\\EventType\\|null given\\.$#" + count: 4 + path: src/Bundle/ChillEventBundle/Controller/EventTypeController.php + + - + message: "#^Only booleans are allowed in a negated boolean, Chill\\\\EventBundle\\\\Entity\\\\Participation\\|null given\\.$#" + count: 1 + path: src/Bundle/ChillEventBundle/Controller/ParticipationController.php + + - + message: "#^Only booleans are allowed in a negated boolean, Chill\\\\EventBundle\\\\Entity\\\\Role\\|null given\\.$#" + count: 4 + path: src/Bundle/ChillEventBundle/Controller/RoleController.php + + - + message: "#^Only booleans are allowed in a negated boolean, Chill\\\\EventBundle\\\\Entity\\\\Status\\|null given\\.$#" + count: 4 + path: src/Bundle/ChillEventBundle/Controller/StatusController.php + + - + message: "#^Only booleans are allowed in a negated boolean, Chill\\\\MainBundle\\\\Entity\\\\Language\\|null given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Command/LoadAndUpdateLanguagesCommand.php + + - + message: "#^Only booleans are allowed in an if condition, Chill\\\\MainBundle\\\\Entity\\\\Language\\|null given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Command/LoadAndUpdateLanguagesCommand.php + + - + message: "#^Only booleans are allowed in an if condition, int given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Command/LoadPostalCodesCommand.php + + - + message: "#^Only booleans are allowed in a negated boolean, Chill\\\\MainBundle\\\\Entity\\\\Center\\|null given\\.$#" + count: 3 + path: src/Bundle/ChillMainBundle/Controller/CenterController.php + + - + message: "#^Call to method Redis\\:\\:setex\\(\\) with incorrect case\\: setEx$#" + count: 2 + path: src/Bundle/ChillMainBundle/Controller/ExportController.php + + - + message: "#^Only booleans are allowed in a negated boolean, Chill\\\\MainBundle\\\\Entity\\\\PermissionsGroup\\|null given\\.$#" + count: 5 + path: src/Bundle/ChillMainBundle/Controller/PermissionsGroupController.php + + - + message: "#^Only booleans are allowed in a negated boolean, Chill\\\\MainBundle\\\\Entity\\\\RoleScope\\|null given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Controller/PermissionsGroupController.php + + - + message: "#^Only booleans are allowed in a negated boolean, Chill\\\\MainBundle\\\\Entity\\\\Scope\\|null given\\.$#" + count: 3 + path: src/Bundle/ChillMainBundle/Controller/ScopeController.php + + - + message: "#^Only booleans are allowed in a negated boolean, Chill\\\\MainBundle\\\\Entity\\\\GroupCenter\\|null given\\.$#" + count: 2 + path: src/Bundle/ChillMainBundle/Controller/UserController.php + + - + message: "#^Only booleans are allowed in a negated boolean, Chill\\\\MainBundle\\\\Entity\\\\User\\|null given\\.$#" + count: 2 + path: src/Bundle/ChillMainBundle/Controller/UserController.php + + - + message: "#^Only booleans are allowed in an if condition, int given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadPostalCodes.php + + - + message: "#^Only numeric types are allowed in pre\\-increment, string given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Export/Formatter/SpreadsheetListFormatter.php + + - + message: "#^Only booleans are allowed in an if condition, int given\\.$#" + count: 2 + path: src/Bundle/ChillMainBundle/Form/DataTransformer/IdToEntityDataTransformer.php + + - + message: "#^Only booleans are allowed in a ternary operator condition, string\\|null given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Form/Type/PickUserLocationType.php + + - + message: "#^Only booleans are allowed in a ternary operator condition, int\\<0, max\\> given\\.$#" + count: 4 + path: src/Bundle/ChillMainBundle/Search/SearchApiQuery.php + + - + message: "#^Call to method Chill\\\\MainBundle\\\\Entity\\\\Address\\:\\:getPostcode\\(\\) with incorrect case\\: getPostCode$#" + count: 6 + path: src/Bundle/ChillMainBundle/Serializer/Normalizer/AddressNormalizer.php + + - + message: "#^Only booleans are allowed in an if condition, Symfony\\\\Component\\\\Serializer\\\\Mapping\\\\ClassDiscriminatorMapping\\|null given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Serializer/Normalizer/DoctrineExistingEntityNormalizer.php + + - + message: "#^Only booleans are allowed in a negated boolean, null given\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/Person.php + + - + message: "#^Only booleans are allowed in a ternary operator condition, Chill\\\\PersonBundle\\\\Entity\\\\AccompanyingPeriodParticipation\\|null given\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/Person.php + + - + message: "#^Call to method Chill\\\\PersonBundle\\\\Entity\\\\Person\\:\\:getFirstName\\(\\) with incorrect case\\: getFirstname$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Form/Type/PickPersonType.php + + - + message: "#^Call to method Chill\\\\PersonBundle\\\\Entity\\\\Person\\:\\:getLastName\\(\\) with incorrect case\\: getLastname$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Form/Type/PickPersonType.php + + - + message: "#^Only booleans are allowed in an if condition, int\\<0, max\\> given\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Search/SimilarPersonMatcher.php + + - + message: "#^Only booleans are allowed in &&, null given on the left side\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/MembersEditorNormalizer.php + + - + message: "#^Only booleans are allowed in a ternary operator condition, Chill\\\\PersonBundle\\\\Entity\\\\Household\\\\Household\\|null given\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonJsonNormalizer.php + + - + message: "#^Only booleans are allowed in a ternary operator condition, array\\ given\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonJsonNormalizer.php + + - + message: "#^Call to method Chill\\\\PersonBundle\\\\Entity\\\\Person\\:\\:getDeathdate\\(\\) with incorrect case\\: getDeathDate$#" + count: 2 + path: src/Bundle/ChillPersonBundle/Templating/Entity/PersonRender.php + + - + message: "#^Only booleans are allowed in a negated boolean, Chill\\\\PersonBundle\\\\Entity\\\\Person\\|null given\\.$#" + count: 1 + path: src/Bundle/ChillReportBundle/Controller/ReportController.php + + - + message: "#^Only booleans are allowed in a negated boolean, Chill\\\\ReportBundle\\\\Entity\\\\Report given\\.$#" + count: 1 + path: src/Bundle/ChillReportBundle/Controller/ReportController.php + + - + message: "#^Call to method Chill\\\\ThirdPartyBundle\\\\Entity\\\\ThirdParty\\:\\:setFirstname\\(\\) with incorrect case\\: setFirstName$#" + count: 1 + path: src/Bundle/ChillThirdPartyBundle/EventListener/ThirdPartyEventListener.php + + - + message: "#^Dynamic call to static method Chill\\\\ThirdPartyBundle\\\\ThirdPartyType\\\\ThirdPartyTypeProviderInterface\\:\\:getKey\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillThirdPartyBundle/ThirdPartyType/ThirdPartyTypeManager.php diff --git a/phpstan-baseline-level-3.neon b/phpstan-baseline-level-3.neon new file mode 100644 index 000000000..b7091f4ad --- /dev/null +++ b/phpstan-baseline-level-3.neon @@ -0,0 +1,804 @@ +parameters: + ignoreErrors: + - + message: "#^Return type \\(array\\\\) of method Chill\\\\ActivityBundle\\\\Repository\\\\ActivityACLAwareRepository\\:\\:findByPerson\\(\\) should be covariant with return type \\(array\\\\) of method Chill\\\\ActivityBundle\\\\Repository\\\\ActivityACLAwareRepositoryInterface\\:\\:findByPerson\\(\\)$#" + count: 1 + path: src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php + + - + message: "#^Return type \\(array\\\\) of method Chill\\\\ActivityBundle\\\\Repository\\\\ActivityReasonRepository\\:\\:findAll\\(\\) should be covariant with return type \\(array\\\\) of method Doctrine\\\\ORM\\\\EntityRepository\\\\:\\:findAll\\(\\)$#" + count: 1 + path: src/Bundle/ChillActivityBundle/Repository/ActivityReasonRepository.php + + - + message: "#^Return type \\(array\\\\>\\) of method Chill\\\\AsideActivityBundle\\\\Security\\\\AsideActivityVoter\\:\\:getRolesWithHierarchy\\(\\) should be covariant with return type \\(array\\\\>\\) of method Chill\\\\MainBundle\\\\Security\\\\ProvideRoleHierarchyInterface\\:\\:getRolesWithHierarchy\\(\\)$#" + count: 1 + path: src/Bundle/ChillAsideActivityBundle/src/Security/AsideActivityVoter.php + + - + message: "#^Parameter \\#1 \\$criteria \\(array\\\\) of method Chill\\\\CalendarBundle\\\\Repository\\\\CalendarDocRepository\\:\\:findBy\\(\\) should be contravariant with parameter \\$criteria \\(array\\) of method Chill\\\\CalendarBundle\\\\Repository\\\\CalendarDocRepositoryInterface\\:\\:findBy\\(\\)$#" + count: 1 + path: src/Bundle/ChillCalendarBundle/Repository/CalendarDocRepository.php + + - + message: "#^Parameter \\#1 \\$criteria \\(array\\\\) of method Chill\\\\CalendarBundle\\\\Repository\\\\CalendarDocRepository\\:\\:findOneBy\\(\\) should be contravariant with parameter \\$criteria \\(array\\) of method Chill\\\\CalendarBundle\\\\Repository\\\\CalendarDocRepositoryInterface\\:\\:findOneBy\\(\\)$#" + count: 1 + path: src/Bundle/ChillCalendarBundle/Repository/CalendarDocRepository.php + + - + message: "#^Parameter \\#2 \\$orderBy \\(array\\\\|null\\) of method Chill\\\\CalendarBundle\\\\Repository\\\\CalendarDocRepository\\:\\:findBy\\(\\) should be contravariant with parameter \\$orderBy \\(array\\|null\\) of method Chill\\\\CalendarBundle\\\\Repository\\\\CalendarDocRepositoryInterface\\:\\:findBy\\(\\)$#" + count: 1 + path: src/Bundle/ChillCalendarBundle/Repository/CalendarDocRepository.php + + - + message: "#^Return type \\(array\\\\) of method Chill\\\\CalendarBundle\\\\Repository\\\\CalendarDocRepository\\:\\:findAll\\(\\) should be covariant with return type \\(array\\\\) of method Chill\\\\CalendarBundle\\\\Repository\\\\CalendarDocRepositoryInterface\\:\\:findAll\\(\\)$#" + count: 1 + path: src/Bundle/ChillCalendarBundle/Repository/CalendarDocRepository.php + + - + message: "#^Return type \\(array\\\\) of method Chill\\\\CalendarBundle\\\\Repository\\\\CalendarDocRepository\\:\\:findBy\\(\\) should be covariant with return type \\(array\\\\) of method Chill\\\\CalendarBundle\\\\Repository\\\\CalendarDocRepositoryInterface\\:\\:findBy\\(\\)$#" + count: 1 + path: src/Bundle/ChillCalendarBundle/Repository/CalendarDocRepository.php + + - + message: "#^Parameter \\#2 \\$subject \\(Chill\\\\CalendarBundle\\\\Entity\\\\CalendarDoc\\) of method Chill\\\\CalendarBundle\\\\Security\\\\Voter\\\\CalendarDocVoter\\:\\:voteOnAttribute\\(\\) should be contravariant with parameter \\$subject \\(mixed\\) of method Symfony\\\\Component\\\\Security\\\\Core\\\\Authorization\\\\Voter\\\\Voter\\:\\:voteOnAttribute\\(\\)$#" + count: 1 + path: src/Bundle/ChillCalendarBundle/Security/Voter/CalendarDocVoter.php + + - + message: "#^Parameter \\#2 \\$subject \\(Chill\\\\CalendarBundle\\\\Entity\\\\Invite\\) of method Chill\\\\CalendarBundle\\\\Security\\\\Voter\\\\InviteVoter\\:\\:voteOnAttribute\\(\\) should be contravariant with parameter \\$subject \\(mixed\\) of method Symfony\\\\Component\\\\Security\\\\Core\\\\Authorization\\\\Voter\\\\Voter\\:\\:voteOnAttribute\\(\\)$#" + count: 1 + path: src/Bundle/ChillCalendarBundle/Security/Voter/InviteVoter.php + + + - + message: "#^Return type \\(int\\|void\\|null\\) of method Chill\\\\CustomFieldsBundle\\\\Command\\\\CreateFieldsOnGroupCommand\\:\\:execute\\(\\) should be covariant with return type \\(int\\) of method Symfony\\\\Component\\\\Console\\\\Command\\\\Command\\:\\:execute\\(\\)$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Command/CreateFieldsOnGroupCommand.php + + - + message: "#^Parameter \\#1 \\$customFieldsGroup \\(Chill\\\\CustomFieldsBundle\\\\Entity\\\\CustomFieldsGroup\\|null\\) of method Chill\\\\CustomFieldsBundle\\\\Form\\\\DataTransformer\\\\CustomFieldsGroupToIdTransformer\\:\\:transform\\(\\) should be contravariant with parameter \\$value \\(mixed\\) of method Symfony\\\\Component\\\\Form\\\\DataTransformerInterface\\:\\:transform\\(\\)$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Form/DataTransformer/CustomFieldsGroupToIdTransformer.php + + - + message: "#^Parameter \\#1 \\$id \\(string\\) of method Chill\\\\CustomFieldsBundle\\\\Form\\\\DataTransformer\\\\CustomFieldsGroupToIdTransformer\\:\\:reverseTransform\\(\\) should be contravariant with parameter \\$value \\(mixed\\) of method Symfony\\\\Component\\\\Form\\\\DataTransformerInterface\\:\\:reverseTransform\\(\\)$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Form/DataTransformer/CustomFieldsGroupToIdTransformer.php + + - + message: "#^Parameter \\#2 \\$query \\(Doctrine\\\\ORM\\\\QueryBuilder\\) of method Chill\\\\DocGeneratorBundle\\\\Controller\\\\AdminDocGeneratorTemplateController\\:\\:orderQuery\\(\\) should be contravariant with parameter \\$query \\(mixed\\) of method Chill\\\\MainBundle\\\\CRUD\\\\Controller\\\\CRUDController\\:\\:orderQuery\\(\\)$#" + count: 1 + path: src/Bundle/ChillDocGeneratorBundle/Controller/AdminDocGeneratorTemplateController.php + + - + message: "#^Parameter \\#1 \\$object \\(Doctrine\\\\Common\\\\Collections\\\\Collection\\) of method Chill\\\\DocGeneratorBundle\\\\Serializer\\\\Normalizer\\\\CollectionDocGenNormalizer\\:\\:normalize\\(\\) should be contravariant with parameter \\$object \\(mixed\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\NormalizerInterface\\:\\:normalize\\(\\)$#" + count: 2 + path: src/Bundle/ChillDocGeneratorBundle/Serializer/Normalizer/CollectionDocGenNormalizer.php + + - + message: "#^Parameter \\#2 \\$format \\(string\\) of method Chill\\\\DocGeneratorBundle\\\\Serializer\\\\Normalizer\\\\CollectionDocGenNormalizer\\:\\:supportsNormalization\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\ContextAwareNormalizerInterface\\:\\:supportsNormalization\\(\\)$#" + count: 1 + path: src/Bundle/ChillDocGeneratorBundle/Serializer/Normalizer/CollectionDocGenNormalizer.php + + - + message: "#^Parameter \\#2 \\$format \\(string\\) of method Chill\\\\DocGeneratorBundle\\\\Serializer\\\\Normalizer\\\\CollectionDocGenNormalizer\\:\\:supportsNormalization\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\NormalizerInterface\\:\\:supportsNormalization\\(\\)$#" + count: 1 + path: src/Bundle/ChillDocGeneratorBundle/Serializer/Normalizer/CollectionDocGenNormalizer.php + + - + message: "#^Return type \\(array\\|ArrayObject\\|bool\\|float\\|int\\|string\\|void\\|null\\) of method Chill\\\\DocGeneratorBundle\\\\Serializer\\\\Normalizer\\\\CollectionDocGenNormalizer\\:\\:normalize\\(\\) should be covariant with return type \\(array\\|ArrayObject\\|bool\\|float\\|int\\|string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\NormalizerInterface\\:\\:normalize\\(\\)$#" + count: 2 + path: src/Bundle/ChillDocGeneratorBundle/Serializer/Normalizer/CollectionDocGenNormalizer.php + + - + message: "#^Parameter \\#2 \\$format \\(string\\) of method Chill\\\\DocGeneratorBundle\\\\Serializer\\\\Normalizer\\\\DocGenObjectNormalizer\\:\\:normalize\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\NormalizerInterface\\:\\:normalize\\(\\)$#" + count: 1 + path: src/Bundle/ChillDocGeneratorBundle/Serializer/Normalizer/DocGenObjectNormalizer.php + + - + message: "#^Parameter \\#2 \\$format \\(string\\) of method Chill\\\\DocGeneratorBundle\\\\Serializer\\\\Normalizer\\\\DocGenObjectNormalizer\\:\\:supportsNormalization\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\NormalizerInterface\\:\\:supportsNormalization\\(\\)$#" + count: 1 + path: src/Bundle/ChillDocGeneratorBundle/Serializer/Normalizer/DocGenObjectNormalizer.php + + - + message: "#^Return type \\(Chill\\\\MainBundle\\\\Entity\\\\Scope\\|null\\) of method Chill\\\\DocStoreBundle\\\\Entity\\\\PersonDocument\\:\\:getScope\\(\\) should be covariant with return type \\(Chill\\\\MainBundle\\\\Entity\\\\Scope\\) of method Chill\\\\MainBundle\\\\Entity\\\\HasScopeInterface\\:\\:getScope\\(\\)$#" + count: 1 + path: src/Bundle/ChillDocStoreBundle/Entity/PersonDocument.php + + - + message: "#^Parameter \\#2 \\$subject \\(Chill\\\\DocStoreBundle\\\\Entity\\\\PersonDocument\\) of method Chill\\\\DocStoreBundle\\\\Security\\\\Authorization\\\\PersonDocumentVoter\\:\\:voteOnAttribute\\(\\) should be contravariant with parameter \\$subject \\(mixed\\) of method Symfony\\\\Component\\\\Security\\\\Core\\\\Authorization\\\\Voter\\\\Voter\\:\\:voteOnAttribute\\(\\)$#" + count: 1 + path: src/Bundle/ChillDocStoreBundle/Security/Authorization/PersonDocumentVoter.php + + - + message: "#^Parameter \\#3 \\$format \\(string\\) of method Chill\\\\DocStoreBundle\\\\Serializer\\\\Normalizer\\\\StoredObjectDenormalizer\\:\\:denormalize\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\DenormalizerInterface\\:\\:denormalize\\(\\)$#" + count: 1 + path: src/Bundle/ChillDocStoreBundle/Serializer/Normalizer/StoredObjectDenormalizer.php + + - + message: "#^Parameter \\#3 \\$format \\(string\\) of method Chill\\\\DocStoreBundle\\\\Serializer\\\\Normalizer\\\\StoredObjectDenormalizer\\:\\:supportsDenormalization\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\DenormalizerInterface\\:\\:supportsDenormalization\\(\\)$#" + count: 1 + path: src/Bundle/ChillDocStoreBundle/Serializer/Normalizer/StoredObjectDenormalizer.php + + - + message: "#^Parameter \\#1 \\$object \\(Chill\\\\DocStoreBundle\\\\Entity\\\\AccompanyingCourseDocument\\) of method Chill\\\\DocStoreBundle\\\\Workflow\\\\AccompanyingCourseDocumentWorkflowHandler\\:\\:getRelatedObjects\\(\\) should be contravariant with parameter \\$object \\(object\\) of method Chill\\\\MainBundle\\\\Workflow\\\\EntityWorkflowHandlerInterface\\:\\:getRelatedObjects\\(\\)$#" + count: 1 + path: src/Bundle/ChillDocStoreBundle/Workflow/AccompanyingCourseDocumentWorkflowHandler.php + + - + message: "#^Parameter \\#1 \\$value \\(null\\) of method Chill\\\\EventBundle\\\\Form\\\\ChoiceLoader\\\\EventChoiceLoader\\:\\:loadChoiceList\\(\\) should be contravariant with parameter \\$value \\(\\(callable\\(\\)\\: mixed\\)\\|null\\) of method Symfony\\\\Component\\\\Form\\\\ChoiceList\\\\Loader\\\\ChoiceLoaderInterface\\:\\:loadChoiceList\\(\\)$#" + count: 1 + path: src/Bundle/ChillEventBundle/Form/ChoiceLoader/EventChoiceLoader.php + + - + message: "#^Parameter \\#2 \\$value \\(null\\) of method Chill\\\\EventBundle\\\\Form\\\\ChoiceLoader\\\\EventChoiceLoader\\:\\:loadChoicesForValues\\(\\) should be contravariant with parameter \\$value \\(\\(callable\\(\\)\\: mixed\\)\\|null\\) of method Symfony\\\\Component\\\\Form\\\\ChoiceList\\\\Loader\\\\ChoiceLoaderInterface\\:\\:loadChoicesForValues\\(\\)$#" + count: 1 + path: src/Bundle/ChillEventBundle/Form/ChoiceLoader/EventChoiceLoader.php + + - + message: "#^Parameter \\#2 \\$value \\(null\\) of method Chill\\\\EventBundle\\\\Form\\\\ChoiceLoader\\\\EventChoiceLoader\\:\\:loadValuesForChoices\\(\\) should be contravariant with parameter \\$value \\(\\(callable\\(\\)\\: mixed\\)\\|null\\) of method Symfony\\\\Component\\\\Form\\\\ChoiceList\\\\Loader\\\\ChoiceLoaderInterface\\:\\:loadValuesForChoices\\(\\)$#" + count: 1 + path: src/Bundle/ChillEventBundle/Form/ChoiceLoader/EventChoiceLoader.php + + - + message: "#^Parameter \\#2 \\$subject \\(Chill\\\\EventBundle\\\\Entity\\\\Event\\) of method Chill\\\\EventBundle\\\\Security\\\\Authorization\\\\EventVoter\\:\\:voteOnAttribute\\(\\) should be contravariant with parameter \\$subject \\(mixed\\) of method Symfony\\\\Component\\\\Security\\\\Core\\\\Authorization\\\\Voter\\\\Voter\\:\\:voteOnAttribute\\(\\)$#" + count: 1 + path: src/Bundle/ChillEventBundle/Security/Authorization/EventVoter.php + + - + message: "#^Parameter \\#2 \\$subject \\(Chill\\\\EventBundle\\\\Entity\\\\Participation\\) of method Chill\\\\EventBundle\\\\Security\\\\Authorization\\\\ParticipationVoter\\:\\:voteOnAttribute\\(\\) should be contravariant with parameter \\$subject \\(mixed\\) of method Symfony\\\\Component\\\\Security\\\\Core\\\\Authorization\\\\Voter\\\\Voter\\:\\:voteOnAttribute\\(\\)$#" + count: 1 + path: src/Bundle/ChillEventBundle/Security/Authorization/ParticipationVoter.php + + - + message: "#^Parameter \\#1 \\$entity \\(Chill\\\\EventBundle\\\\Entity\\\\Event\\) of method Chill\\\\EventBundle\\\\Timeline\\\\TimelineEventProvider\\:\\:getEntityTemplate\\(\\) should be contravariant with parameter \\$entity \\(Chill\\\\MainBundle\\\\Timeline\\\\type\\) of method Chill\\\\MainBundle\\\\Timeline\\\\TimelineProviderInterface\\:\\:getEntityTemplate\\(\\)$#" + count: 1 + path: src/Bundle/ChillEventBundle/Timeline/TimelineEventProvider.php + + - + message: "#^Parameter \\#2 \\$type \\(null\\) of method Chill\\\\MainBundle\\\\CRUD\\\\Routing\\\\CRUDRoutesLoader\\:\\:supports\\(\\) should be contravariant with parameter \\$type \\(string\\|null\\) of method Symfony\\\\Component\\\\Config\\\\Loader\\\\LoaderInterface\\:\\:supports\\(\\)$#" + count: 2 + path: src/Bundle/ChillMainBundle/CRUD/Routing/CRUDRoutesLoader.php + + - + message: "#^Parameter \\#2 \\$query \\(Doctrine\\\\ORM\\\\QueryBuilder\\) of method Chill\\\\MainBundle\\\\Controller\\\\LocationApiController\\:\\:orderQuery\\(\\) should be contravariant with parameter \\$query \\(mixed\\) of method Chill\\\\MainBundle\\\\CRUD\\\\Controller\\\\AbstractCRUDController\\:\\:orderQuery\\(\\)$#" + count: 1 + path: src/Bundle/ChillMainBundle/Controller/LocationApiController.php + + - + message: "#^Parameter \\#3 \\$query \\(Doctrine\\\\ORM\\\\QueryBuilder\\) of method Chill\\\\MainBundle\\\\Controller\\\\UserApiController\\:\\:customizeQuery\\(\\) should be contravariant with parameter \\$query \\(mixed\\) of method Chill\\\\MainBundle\\\\CRUD\\\\Controller\\\\AbstractCRUDController\\:\\:customizeQuery\\(\\)$#" + count: 1 + path: src/Bundle/ChillMainBundle/Controller/UserApiController.php + + - + message: "#^Return type \\(object\\|null\\) of method Chill\\\\MainBundle\\\\DependencyInjection\\\\ChillMainExtension\\:\\:getConfiguration\\(\\) should be covariant with return type \\(Symfony\\\\Component\\\\Config\\\\Definition\\\\ConfigurationInterface\\|null\\) of method Symfony\\\\Component\\\\DependencyInjection\\\\Extension\\\\ConfigurationExtensionInterface\\:\\:getConfiguration\\(\\)$#" + count: 1 + path: src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php + + - + message: "#^Return type \\(object\\|null\\) of method Chill\\\\MainBundle\\\\DependencyInjection\\\\ChillMainExtension\\:\\:getConfiguration\\(\\) should be covariant with return type \\(Symfony\\\\Component\\\\Config\\\\Definition\\\\ConfigurationInterface\\|null\\) of method Symfony\\\\Component\\\\DependencyInjection\\\\Extension\\\\Extension\\:\\:getConfiguration\\(\\)$#" + count: 1 + path: src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php + + - + message: "#^Parameter \\#1 \\$value \\(null\\) of method Chill\\\\MainBundle\\\\Form\\\\ChoiceLoader\\\\PostalCodeChoiceLoader\\:\\:loadChoiceList\\(\\) should be contravariant with parameter \\$value \\(\\(callable\\(\\)\\: mixed\\)\\|null\\) of method Symfony\\\\Component\\\\Form\\\\ChoiceList\\\\Loader\\\\ChoiceLoaderInterface\\:\\:loadChoiceList\\(\\)$#" + count: 1 + path: src/Bundle/ChillMainBundle/Form/ChoiceLoader/PostalCodeChoiceLoader.php + + - + message: "#^Parameter \\#2 \\$value \\(null\\) of method Chill\\\\MainBundle\\\\Form\\\\ChoiceLoader\\\\PostalCodeChoiceLoader\\:\\:loadChoicesForValues\\(\\) should be contravariant with parameter \\$value \\(\\(callable\\(\\)\\: mixed\\)\\|null\\) of method Symfony\\\\Component\\\\Form\\\\ChoiceList\\\\Loader\\\\ChoiceLoaderInterface\\:\\:loadChoicesForValues\\(\\)$#" + count: 1 + path: src/Bundle/ChillMainBundle/Form/ChoiceLoader/PostalCodeChoiceLoader.php + + - + message: "#^Parameter \\#2 \\$value \\(null\\) of method Chill\\\\MainBundle\\\\Form\\\\ChoiceLoader\\\\PostalCodeChoiceLoader\\:\\:loadValuesForChoices\\(\\) should be contravariant with parameter \\$value \\(\\(callable\\(\\)\\: mixed\\)\\|null\\) of method Symfony\\\\Component\\\\Form\\\\ChoiceList\\\\Loader\\\\ChoiceLoaderInterface\\:\\:loadValuesForChoices\\(\\)$#" + count: 1 + path: src/Bundle/ChillMainBundle/Form/ChoiceLoader/PostalCodeChoiceLoader.php + + - + message: "#^Parameter \\#1 \\$address \\(Chill\\\\MainBundle\\\\Entity\\\\Address\\) of method Chill\\\\MainBundle\\\\Form\\\\DataMapper\\\\AddressDataMapper\\:\\:mapDataToForms\\(\\) should be contravariant with parameter \\$viewData \\(mixed\\) of method Symfony\\\\Component\\\\Form\\\\DataMapperInterface\\:\\:mapDataToForms\\(\\)$#" + count: 1 + path: src/Bundle/ChillMainBundle/Form/DataMapper/AddressDataMapper.php + + - + message: "#^Parameter \\#1 \\$forms \\(Iterator\\) of method Chill\\\\MainBundle\\\\Form\\\\DataMapper\\\\AddressDataMapper\\:\\:mapFormsToData\\(\\) should be contravariant with parameter \\$forms \\(iterable\\&Traversable\\) of method Symfony\\\\Component\\\\Form\\\\DataMapperInterface\\:\\:mapFormsToData\\(\\)$#" + count: 1 + path: src/Bundle/ChillMainBundle/Form/DataMapper/AddressDataMapper.php + + - + message: "#^Parameter \\#2 \\$address \\(Chill\\\\MainBundle\\\\Entity\\\\Address\\) of method Chill\\\\MainBundle\\\\Form\\\\DataMapper\\\\AddressDataMapper\\:\\:mapFormsToData\\(\\) should be contravariant with parameter \\$viewData \\(mixed\\) of method Symfony\\\\Component\\\\Form\\\\DataMapperInterface\\:\\:mapFormsToData\\(\\)$#" + count: 1 + path: src/Bundle/ChillMainBundle/Form/DataMapper/AddressDataMapper.php + + - + message: "#^Parameter \\#2 \\$forms \\(Iterator\\) of method Chill\\\\MainBundle\\\\Form\\\\DataMapper\\\\AddressDataMapper\\:\\:mapDataToForms\\(\\) should be contravariant with parameter \\$forms \\(iterable\\&Traversable\\) of method Symfony\\\\Component\\\\Form\\\\DataMapperInterface\\:\\:mapDataToForms\\(\\)$#" + count: 1 + path: src/Bundle/ChillMainBundle/Form/DataMapper/AddressDataMapper.php + + - + message: "#^Parameter \\#1 \\$value \\(string\\) of method Chill\\\\MainBundle\\\\Form\\\\DataTransformer\\\\IdToEntityDataTransformer\\:\\:reverseTransform\\(\\) should be contravariant with parameter \\$value \\(mixed\\) of method Symfony\\\\Component\\\\Form\\\\DataTransformerInterface\\:\\:reverseTransform\\(\\)$#" + count: 1 + path: src/Bundle/ChillMainBundle/Form/DataTransformer/IdToEntityDataTransformer.php + + - + message: "#^Parameter \\#1 \\$value \\(array\\\\|Chill\\\\MainBundle\\\\Entity\\\\User\\) of method Chill\\\\MainBundle\\\\Form\\\\Type\\\\DataTransformer\\\\EntityToJsonTransformer\\:\\:transform\\(\\) should be contravariant with parameter \\$value \\(mixed\\) of method Symfony\\\\Component\\\\Form\\\\DataTransformerInterface\\:\\:transform\\(\\)$#" + count: 1 + path: src/Bundle/ChillMainBundle/Form/Type/DataTransformer/EntityToJsonTransformer.php + + - + message: "#^Parameter \\#1 \\$array \\(array\\) of method Chill\\\\MainBundle\\\\Form\\\\Type\\\\DataTransformer\\\\MultipleObjectsToIdTransformer\\:\\:transform\\(\\) should be contravariant with parameter \\$value \\(mixed\\) of method Symfony\\\\Component\\\\Form\\\\DataTransformerInterface\\:\\:transform\\(\\)$#" + count: 1 + path: src/Bundle/ChillMainBundle/Form/Type/DataTransformer/MultipleObjectsToIdTransformer.php + + - + message: "#^Parameter \\#1 \\$id \\(string\\) of method Chill\\\\MainBundle\\\\Form\\\\Type\\\\DataTransformer\\\\ObjectToIdTransformer\\:\\:reverseTransform\\(\\) should be contravariant with parameter \\$value \\(mixed\\) of method Symfony\\\\Component\\\\Form\\\\DataTransformerInterface\\:\\:reverseTransform\\(\\)$#" + count: 1 + path: src/Bundle/ChillMainBundle/Form/Type/DataTransformer/ObjectToIdTransformer.php + + - + message: "#^Parameter \\#2 \\$subject \\(Chill\\\\MainBundle\\\\Entity\\\\Workflow\\\\EntityWorkflow\\) of method Chill\\\\MainBundle\\\\Security\\\\Authorization\\\\EntityWorkflowVoter\\:\\:voteOnAttribute\\(\\) should be contravariant with parameter \\$subject \\(mixed\\) of method Symfony\\\\Component\\\\Security\\\\Core\\\\Authorization\\\\Voter\\\\Voter\\:\\:voteOnAttribute\\(\\)$#" + count: 1 + path: src/Bundle/ChillMainBundle/Security/Authorization/EntityWorkflowVoter.php + + - + message: "#^Parameter \\#1 \\$entity \\(Chill\\\\MainBundle\\\\Entity\\\\HasCenterInterface\\) of method Chill\\\\MainBundle\\\\Security\\\\Resolver\\\\DefaultCenterResolver\\:\\:resolveCenter\\(\\) should be contravariant with parameter \\$entity \\(object\\) of method Chill\\\\MainBundle\\\\Security\\\\Resolver\\\\CenterResolverInterface\\:\\:resolveCenter\\(\\)$#" + count: 1 + path: src/Bundle/ChillMainBundle/Security/Resolver/DefaultCenterResolver.php + + - + message: "#^Parameter \\#1 \\$entity \\(Chill\\\\MainBundle\\\\Entity\\\\HasScopeInterface\\|Chill\\\\MainBundle\\\\Entity\\\\HasScopesInterface\\) of method Chill\\\\MainBundle\\\\Security\\\\Resolver\\\\DefaultScopeResolver\\:\\:resolveScope\\(\\) should be contravariant with parameter \\$entity \\(mixed\\) of method Chill\\\\MainBundle\\\\Security\\\\Resolver\\\\ScopeResolverInterface\\:\\:resolveScope\\(\\)$#" + count: 1 + path: src/Bundle/ChillMainBundle/Security/Resolver/DefaultScopeResolver.php + + - + message: "#^Parameter \\#1 \\$address \\(Chill\\\\MainBundle\\\\Entity\\\\Address\\) of method Chill\\\\MainBundle\\\\Serializer\\\\Normalizer\\\\AddressNormalizer\\:\\:normalize\\(\\) should be contravariant with parameter \\$object \\(mixed\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\NormalizerInterface\\:\\:normalize\\(\\)$#" + count: 2 + path: src/Bundle/ChillMainBundle/Serializer/Normalizer/AddressNormalizer.php + + - + message: "#^Parameter \\#2 \\$format \\(string\\) of method Chill\\\\MainBundle\\\\Serializer\\\\Normalizer\\\\AddressNormalizer\\:\\:supportsNormalization\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\ContextAwareNormalizerInterface\\:\\:supportsNormalization\\(\\)$#" + count: 1 + path: src/Bundle/ChillMainBundle/Serializer/Normalizer/AddressNormalizer.php + + - + message: "#^Parameter \\#2 \\$format \\(string\\) of method Chill\\\\MainBundle\\\\Serializer\\\\Normalizer\\\\AddressNormalizer\\:\\:supportsNormalization\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\NormalizerInterface\\:\\:supportsNormalization\\(\\)$#" + count: 1 + path: src/Bundle/ChillMainBundle/Serializer/Normalizer/AddressNormalizer.php + + - + message: "#^Parameter \\#2 \\$format \\(string\\) of method Chill\\\\MainBundle\\\\Serializer\\\\Normalizer\\\\CenterNormalizer\\:\\:normalize\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\NormalizerInterface\\:\\:normalize\\(\\)$#" + count: 1 + path: src/Bundle/ChillMainBundle/Serializer/Normalizer/CenterNormalizer.php + + - + message: "#^Parameter \\#2 \\$format \\(string\\) of method Chill\\\\MainBundle\\\\Serializer\\\\Normalizer\\\\CenterNormalizer\\:\\:supportsNormalization\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\NormalizerInterface\\:\\:supportsNormalization\\(\\)$#" + count: 1 + path: src/Bundle/ChillMainBundle/Serializer/Normalizer/CenterNormalizer.php + + - + message: "#^Parameter \\#3 \\$format \\(string\\) of method Chill\\\\MainBundle\\\\Serializer\\\\Normalizer\\\\CenterNormalizer\\:\\:denormalize\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\DenormalizerInterface\\:\\:denormalize\\(\\)$#" + count: 1 + path: src/Bundle/ChillMainBundle/Serializer/Normalizer/CenterNormalizer.php + + - + message: "#^Parameter \\#3 \\$format \\(string\\) of method Chill\\\\MainBundle\\\\Serializer\\\\Normalizer\\\\CenterNormalizer\\:\\:supportsDenormalization\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\DenormalizerInterface\\:\\:supportsDenormalization\\(\\)$#" + count: 1 + path: src/Bundle/ChillMainBundle/Serializer/Normalizer/CenterNormalizer.php + + - + message: "#^Parameter \\#1 \\$collection \\(Chill\\\\MainBundle\\\\Serializer\\\\Model\\\\Collection\\) of method Chill\\\\MainBundle\\\\Serializer\\\\Normalizer\\\\CollectionNormalizer\\:\\:normalize\\(\\) should be contravariant with parameter \\$object \\(mixed\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\NormalizerInterface\\:\\:normalize\\(\\)$#" + count: 1 + path: src/Bundle/ChillMainBundle/Serializer/Normalizer/CollectionNormalizer.php + + - + message: "#^Parameter \\#2 \\$format \\(string\\) of method Chill\\\\MainBundle\\\\Serializer\\\\Normalizer\\\\CollectionNormalizer\\:\\:supportsNormalization\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\NormalizerInterface\\:\\:supportsNormalization\\(\\)$#" + count: 1 + path: src/Bundle/ChillMainBundle/Serializer/Normalizer/CollectionNormalizer.php + + - + message: "#^Parameter \\#1 \\$object \\(Chill\\\\MainBundle\\\\Entity\\\\Embeddable\\\\CommentEmbeddable\\) of method Chill\\\\MainBundle\\\\Serializer\\\\Normalizer\\\\CommentEmbeddableDocGenNormalizer\\:\\:normalize\\(\\) should be contravariant with parameter \\$object \\(mixed\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\NormalizerInterface\\:\\:normalize\\(\\)$#" + count: 2 + path: src/Bundle/ChillMainBundle/Serializer/Normalizer/CommentEmbeddableDocGenNormalizer.php + + - + message: "#^Parameter \\#2 \\$format \\(string\\) of method Chill\\\\MainBundle\\\\Serializer\\\\Normalizer\\\\DateNormalizer\\:\\:normalize\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\NormalizerInterface\\:\\:normalize\\(\\)$#" + count: 2 + path: src/Bundle/ChillMainBundle/Serializer/Normalizer/DateNormalizer.php + + - + message: "#^Parameter \\#2 \\$format \\(string\\) of method Chill\\\\MainBundle\\\\Serializer\\\\Normalizer\\\\DateNormalizer\\:\\:supportsNormalization\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\ContextAwareNormalizerInterface\\:\\:supportsNormalization\\(\\)$#" + count: 1 + path: src/Bundle/ChillMainBundle/Serializer/Normalizer/DateNormalizer.php + + - + message: "#^Parameter \\#2 \\$format \\(string\\) of method Chill\\\\MainBundle\\\\Serializer\\\\Normalizer\\\\DateNormalizer\\:\\:supportsNormalization\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\NormalizerInterface\\:\\:supportsNormalization\\(\\)$#" + count: 1 + path: src/Bundle/ChillMainBundle/Serializer/Normalizer/DateNormalizer.php + + - + message: "#^Parameter \\#3 \\$format \\(string\\) of method Chill\\\\MainBundle\\\\Serializer\\\\Normalizer\\\\DateNormalizer\\:\\:denormalize\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\DenormalizerInterface\\:\\:denormalize\\(\\)$#" + count: 1 + path: src/Bundle/ChillMainBundle/Serializer/Normalizer/DateNormalizer.php + + - + message: "#^Parameter \\#3 \\$format \\(string\\) of method Chill\\\\MainBundle\\\\Serializer\\\\Normalizer\\\\DateNormalizer\\:\\:supportsDenormalization\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\DenormalizerInterface\\:\\:supportsDenormalization\\(\\)$#" + count: 1 + path: src/Bundle/ChillMainBundle/Serializer/Normalizer/DateNormalizer.php + + - + message: "#^Parameter \\#3 \\$format \\(string\\) of method Chill\\\\MainBundle\\\\Serializer\\\\Normalizer\\\\DiscriminatedObjectDenormalizer\\:\\:denormalize\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\DenormalizerInterface\\:\\:denormalize\\(\\)$#" + count: 2 + path: src/Bundle/ChillMainBundle/Serializer/Normalizer/DiscriminatedObjectDenormalizer.php + + - + message: "#^Parameter \\#3 \\$format \\(string\\) of method Chill\\\\MainBundle\\\\Serializer\\\\Normalizer\\\\DiscriminatedObjectDenormalizer\\:\\:supportsDenormalization\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\ContextAwareDenormalizerInterface\\:\\:supportsDenormalization\\(\\)$#" + count: 1 + path: src/Bundle/ChillMainBundle/Serializer/Normalizer/DiscriminatedObjectDenormalizer.php + + - + message: "#^Parameter \\#3 \\$format \\(string\\) of method Chill\\\\MainBundle\\\\Serializer\\\\Normalizer\\\\DiscriminatedObjectDenormalizer\\:\\:supportsDenormalization\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\DenormalizerInterface\\:\\:supportsDenormalization\\(\\)$#" + count: 1 + path: src/Bundle/ChillMainBundle/Serializer/Normalizer/DiscriminatedObjectDenormalizer.php + + - + message: "#^Parameter \\#3 \\$format \\(string\\) of method Chill\\\\MainBundle\\\\Serializer\\\\Normalizer\\\\DoctrineExistingEntityNormalizer\\:\\:denormalize\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\DenormalizerInterface\\:\\:denormalize\\(\\)$#" + count: 1 + path: src/Bundle/ChillMainBundle/Serializer/Normalizer/DoctrineExistingEntityNormalizer.php + + - + message: "#^Parameter \\#3 \\$format \\(string\\) of method Chill\\\\MainBundle\\\\Serializer\\\\Normalizer\\\\DoctrineExistingEntityNormalizer\\:\\:supportsDenormalization\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\DenormalizerInterface\\:\\:supportsDenormalization\\(\\)$#" + count: 1 + path: src/Bundle/ChillMainBundle/Serializer/Normalizer/DoctrineExistingEntityNormalizer.php + + - + message: "#^Parameter \\#1 \\$object \\(Chill\\\\MainBundle\\\\Entity\\\\Workflow\\\\EntityWorkflow\\) of method Chill\\\\MainBundle\\\\Serializer\\\\Normalizer\\\\EntityWorkflowNormalizer\\:\\:normalize\\(\\) should be contravariant with parameter \\$object \\(mixed\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\NormalizerInterface\\:\\:normalize\\(\\)$#" + count: 1 + path: src/Bundle/ChillMainBundle/Serializer/Normalizer/EntityWorkflowNormalizer.php + + - + message: "#^Parameter \\#1 \\$object \\(Chill\\\\MainBundle\\\\Entity\\\\Workflow\\\\EntityWorkflowStep\\) of method Chill\\\\MainBundle\\\\Serializer\\\\Normalizer\\\\EntityWorkflowStepNormalizer\\:\\:normalize\\(\\) should be contravariant with parameter \\$object \\(mixed\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\NormalizerInterface\\:\\:normalize\\(\\)$#" + count: 1 + path: src/Bundle/ChillMainBundle/Serializer/Normalizer/EntityWorkflowStepNormalizer.php + + - + message: "#^Parameter \\#1 \\$object \\(Chill\\\\MainBundle\\\\Entity\\\\Notification\\) of method Chill\\\\MainBundle\\\\Serializer\\\\Normalizer\\\\NotificationNormalizer\\:\\:normalize\\(\\) should be contravariant with parameter \\$object \\(mixed\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\NormalizerInterface\\:\\:normalize\\(\\)$#" + count: 1 + path: src/Bundle/ChillMainBundle/Serializer/Normalizer/NotificationNormalizer.php + + - + message: "#^Return type \\(array\\|ArrayObject\\|bool\\|float\\|int\\|string\\|void\\|null\\) of method Chill\\\\MainBundle\\\\Serializer\\\\Normalizer\\\\NotificationNormalizer\\:\\:normalize\\(\\) should be covariant with return type \\(array\\|ArrayObject\\|bool\\|float\\|int\\|string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\NormalizerInterface\\:\\:normalize\\(\\)$#" + count: 1 + path: src/Bundle/ChillMainBundle/Serializer/Normalizer/NotificationNormalizer.php + + - + message: "#^Parameter \\#1 \\$data \\(string\\|null\\) of method Chill\\\\MainBundle\\\\Serializer\\\\Normalizer\\\\PhonenumberNormalizer\\:\\:denormalize\\(\\) should be contravariant with parameter \\$data \\(mixed\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\DenormalizerInterface\\:\\:denormalize\\(\\)$#" + count: 1 + path: src/Bundle/ChillMainBundle/Serializer/Normalizer/PhonenumberNormalizer.php + + - + message: "#^Parameter \\#3 \\$format \\(string\\) of method Chill\\\\MainBundle\\\\Serializer\\\\Normalizer\\\\PhonenumberNormalizer\\:\\:supportsDenormalization\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\DenormalizerInterface\\:\\:supportsDenormalization\\(\\)$#" + count: 1 + path: src/Bundle/ChillMainBundle/Serializer/Normalizer/PhonenumberNormalizer.php + + - + message: "#^Parameter \\#3 \\$format \\(string\\) of method Chill\\\\MainBundle\\\\Serializer\\\\Normalizer\\\\PointNormalizer\\:\\:denormalize\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\DenormalizerInterface\\:\\:denormalize\\(\\)$#" + count: 1 + path: src/Bundle/ChillMainBundle/Serializer/Normalizer/PointNormalizer.php + + - + message: "#^Parameter \\#3 \\$format \\(string\\) of method Chill\\\\MainBundle\\\\Serializer\\\\Normalizer\\\\PointNormalizer\\:\\:supportsDenormalization\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\DenormalizerInterface\\:\\:supportsDenormalization\\(\\)$#" + count: 1 + path: src/Bundle/ChillMainBundle/Serializer/Normalizer/PointNormalizer.php + + - + message: "#^Parameter \\#2 \\$format \\(string\\) of method Chill\\\\MainBundle\\\\Serializer\\\\Normalizer\\\\PrivateCommentEmbeddableNormalizer\\:\\:normalize\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\NormalizerInterface\\:\\:normalize\\(\\)$#" + count: 1 + path: src/Bundle/ChillMainBundle/Serializer/Normalizer/PrivateCommentEmbeddableNormalizer.php + + - + message: "#^Parameter \\#2 \\$format \\(string\\) of method Chill\\\\MainBundle\\\\Serializer\\\\Normalizer\\\\UserNormalizer\\:\\:normalize\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\NormalizerInterface\\:\\:normalize\\(\\)$#" + count: 2 + path: src/Bundle/ChillMainBundle/Serializer/Normalizer/UserNormalizer.php + + - + message: "#^Parameter \\#2 \\$format \\(string\\) of method Chill\\\\MainBundle\\\\Serializer\\\\Normalizer\\\\UserNormalizer\\:\\:supportsNormalization\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\ContextAwareNormalizerInterface\\:\\:supportsNormalization\\(\\)$#" + count: 1 + path: src/Bundle/ChillMainBundle/Serializer/Normalizer/UserNormalizer.php + + - + message: "#^Parameter \\#2 \\$format \\(string\\) of method Chill\\\\MainBundle\\\\Serializer\\\\Normalizer\\\\UserNormalizer\\:\\:supportsNormalization\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\NormalizerInterface\\:\\:supportsNormalization\\(\\)$#" + count: 1 + path: src/Bundle/ChillMainBundle/Serializer/Normalizer/UserNormalizer.php + + + - + message: "#^Parameter \\#1 \\$value \\(string\\) of method Chill\\\\MainBundle\\\\Validation\\\\Validator\\\\ValidPhonenumber\\:\\:validate\\(\\) should be contravariant with parameter \\$value \\(mixed\\) of method Symfony\\\\Component\\\\Validator\\\\ConstraintValidatorInterface\\:\\:validate\\(\\)$#" + count: 2 + path: src/Bundle/ChillMainBundle/Validation/Validator/ValidPhonenumber.php + + - + message: "#^Parameter \\#2 \\$constraint \\(Chill\\\\MainBundle\\\\Validation\\\\Constraint\\\\PhonenumberConstraint\\) of method Chill\\\\MainBundle\\\\Validation\\\\Validator\\\\ValidPhonenumber\\:\\:validate\\(\\) should be contravariant with parameter \\$constraint \\(Symfony\\\\Component\\\\Validator\\\\Constraint\\) of method Symfony\\\\Component\\\\Validator\\\\ConstraintValidatorInterface\\:\\:validate\\(\\)$#" + count: 2 + path: src/Bundle/ChillMainBundle/Validation/Validator/ValidPhonenumber.php + + - + message: "#^Parameter \\#1 \\$value \\(object\\) of method Chill\\\\MainBundle\\\\Validator\\\\Constraints\\\\Entity\\\\UserCircleConsistencyValidator\\:\\:validate\\(\\) should be contravariant with parameter \\$value \\(mixed\\) of method Symfony\\\\Component\\\\Validator\\\\ConstraintValidatorInterface\\:\\:validate\\(\\)$#" + count: 2 + path: src/Bundle/ChillMainBundle/Validator/Constraints/Entity/UserCircleConsistencyValidator.php + + - + message: "#^Parameter \\#2 \\$constraint \\(Chill\\\\MainBundle\\\\Validator\\\\Constraints\\\\Entity\\\\UserCircleConsistency\\) of method Chill\\\\MainBundle\\\\Validator\\\\Constraints\\\\Entity\\\\UserCircleConsistencyValidator\\:\\:validate\\(\\) should be contravariant with parameter \\$constraint \\(Symfony\\\\Component\\\\Validator\\\\Constraint\\) of method Symfony\\\\Component\\\\Validator\\\\ConstraintValidatorInterface\\:\\:validate\\(\\)$#" + count: 2 + path: src/Bundle/ChillMainBundle/Validator/Constraints/Entity/UserCircleConsistencyValidator.php + + - + message: "#^Parameter \\#1 \\$value \\(Chill\\\\MainBundle\\\\Entity\\\\Workflow\\\\EntityWorkflow\\) of method Chill\\\\MainBundle\\\\Workflow\\\\Validator\\\\EntityWorkflowCreationValidator\\:\\:validate\\(\\) should be contravariant with parameter \\$value \\(mixed\\) of method Symfony\\\\Component\\\\Validator\\\\ConstraintValidatorInterface\\:\\:validate\\(\\)$#" + count: 2 + path: src/Bundle/ChillMainBundle/Workflow/Validator/EntityWorkflowCreationValidator.php + + - + message: "#^Parameter \\#1 \\$value \\(Chill\\\\MainBundle\\\\Entity\\\\Workflow\\\\EntityWorkflowStep\\) of method Chill\\\\MainBundle\\\\Workflow\\\\Validator\\\\StepDestValidValidator\\:\\:validate\\(\\) should be contravariant with parameter \\$value \\(mixed\\) of method Symfony\\\\Component\\\\Validator\\\\ConstraintValidatorInterface\\:\\:validate\\(\\)$#" + count: 2 + path: src/Bundle/ChillMainBundle/Workflow/Validator/StepDestValidValidator.php + + - + message: "#^Parameter \\#3 \\$query \\(Doctrine\\\\ORM\\\\QueryBuilder\\) of method Chill\\\\PersonBundle\\\\Controller\\\\HouseholdCompositionTypeApiController\\:\\:customizeQuery\\(\\) should be contravariant with parameter \\$query \\(mixed\\) of method Chill\\\\MainBundle\\\\CRUD\\\\Controller\\\\AbstractCRUDController\\:\\:customizeQuery\\(\\)$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Controller/HouseholdCompositionTypeApiController.php + + - + message: "#^Return type \\(Doctrine\\\\Common\\\\Collections\\\\Collection\\) of method Chill\\\\PersonBundle\\\\Entity\\\\AccompanyingPeriod\\:\\:getScopes\\(\\) should be covariant with return type \\(iterable\\\\) of method Chill\\\\MainBundle\\\\Entity\\\\HasScopesInterface\\:\\:getScopes\\(\\)$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php + + - + message: "#^Return type \\(Chill\\\\MainBundle\\\\Entity\\\\Center\\|null\\) of method Chill\\\\PersonBundle\\\\Entity\\\\Person\\:\\:getCenter\\(\\) should be covariant with return type \\(Chill\\\\MainBundle\\\\Entity\\\\Center\\) of method Chill\\\\MainBundle\\\\Entity\\\\HasCenterInterface\\:\\:getCenter\\(\\)$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/Person.php + + - + message: "#^Parameter \\#1 \\$value \\(null\\) of method Chill\\\\PersonBundle\\\\Form\\\\ChoiceLoader\\\\PersonChoiceLoader\\:\\:loadChoiceList\\(\\) should be contravariant with parameter \\$value \\(\\(callable\\(\\)\\: mixed\\)\\|null\\) of method Symfony\\\\Component\\\\Form\\\\ChoiceList\\\\Loader\\\\ChoiceLoaderInterface\\:\\:loadChoiceList\\(\\)$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Form/ChoiceLoader/PersonChoiceLoader.php + + - + message: "#^Parameter \\#2 \\$value \\(null\\) of method Chill\\\\PersonBundle\\\\Form\\\\ChoiceLoader\\\\PersonChoiceLoader\\:\\:loadChoicesForValues\\(\\) should be contravariant with parameter \\$value \\(\\(callable\\(\\)\\: mixed\\)\\|null\\) of method Symfony\\\\Component\\\\Form\\\\ChoiceList\\\\Loader\\\\ChoiceLoaderInterface\\:\\:loadChoicesForValues\\(\\)$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Form/ChoiceLoader/PersonChoiceLoader.php + + - + message: "#^Parameter \\#2 \\$value \\(null\\) of method Chill\\\\PersonBundle\\\\Form\\\\ChoiceLoader\\\\PersonChoiceLoader\\:\\:loadValuesForChoices\\(\\) should be contravariant with parameter \\$value \\(\\(callable\\(\\)\\: mixed\\)\\|null\\) of method Symfony\\\\Component\\\\Form\\\\ChoiceList\\\\Loader\\\\ChoiceLoaderInterface\\:\\:loadValuesForChoices\\(\\)$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Form/ChoiceLoader/PersonChoiceLoader.php + + - + message: "#^Parameter \\#2 \\$viewData \\(Doctrine\\\\Common\\\\Collections\\\\Collection\\) of method Chill\\\\PersonBundle\\\\Form\\\\DataMapper\\\\PersonAltNameDataMapper\\:\\:mapFormsToData\\(\\) should be contravariant with parameter \\$viewData \\(mixed\\) of method Symfony\\\\Component\\\\Form\\\\DataMapperInterface\\:\\:mapFormsToData\\(\\)$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Form/DataMapper/PersonAltNameDataMapper.php + + - + message: "#^Parameter \\#3 \\$limit \\(int\\) of method Chill\\\\PersonBundle\\\\Repository\\\\AccompanyingPeriod\\\\AccompanyingPeriodWorkEvaluationRepository\\:\\:findBy\\(\\) should be contravariant with parameter \\$limit \\(int\\|null\\) of method Doctrine\\\\Persistence\\\\ObjectRepository\\\\:\\:findBy\\(\\)$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkEvaluationRepository.php + + - + message: "#^Parameter \\#4 \\$offset \\(int\\) of method Chill\\\\PersonBundle\\\\Repository\\\\AccompanyingPeriod\\\\AccompanyingPeriodWorkEvaluationRepository\\:\\:findBy\\(\\) should be contravariant with parameter \\$offset \\(int\\|null\\) of method Doctrine\\\\Persistence\\\\ObjectRepository\\\\:\\:findBy\\(\\)$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkEvaluationRepository.php + + - + message: "#^Parameter \\#3 \\$limit \\(int\\) of method Chill\\\\PersonBundle\\\\Repository\\\\Household\\\\HouseholdCompositionRepository\\:\\:findBy\\(\\) should be contravariant with parameter \\$limit \\(int\\|null\\) of method Doctrine\\\\Persistence\\\\ObjectRepository\\\\:\\:findBy\\(\\)$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Repository/Household/HouseholdCompositionRepository.php + + - + message: "#^Parameter \\#4 \\$offset \\(int\\) of method Chill\\\\PersonBundle\\\\Repository\\\\Household\\\\HouseholdCompositionRepository\\:\\:findBy\\(\\) should be contravariant with parameter \\$offset \\(int\\|null\\) of method Doctrine\\\\Persistence\\\\ObjectRepository\\\\:\\:findBy\\(\\)$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Repository/Household/HouseholdCompositionRepository.php + + - + message: "#^Parameter \\#3 \\$limit \\(int\\) of method Chill\\\\PersonBundle\\\\Repository\\\\Household\\\\HouseholdCompositionRepositoryInterface\\:\\:findBy\\(\\) should be contravariant with parameter \\$limit \\(int\\|null\\) of method Doctrine\\\\Persistence\\\\ObjectRepository\\\\:\\:findBy\\(\\)$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Repository/Household/HouseholdCompositionRepositoryInterface.php + + - + message: "#^Parameter \\#4 \\$offset \\(int\\) of method Chill\\\\PersonBundle\\\\Repository\\\\Household\\\\HouseholdCompositionRepositoryInterface\\:\\:findBy\\(\\) should be contravariant with parameter \\$offset \\(int\\|null\\) of method Doctrine\\\\Persistence\\\\ObjectRepository\\\\:\\:findBy\\(\\)$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Repository/Household/HouseholdCompositionRepositoryInterface.php + + - + message: "#^Return type \\(class\\-string\\) of method Chill\\\\PersonBundle\\\\Repository\\\\SocialWork\\\\EvaluationRepository\\:\\:getClassName\\(\\) should be covariant with return type \\(class\\-string\\\\) of method Doctrine\\\\Persistence\\\\ObjectRepository\\\\:\\:getClassName\\(\\)$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Repository/SocialWork/EvaluationRepository.php + + - + message: "#^Return type \\(class\\-string\\) of method Chill\\\\PersonBundle\\\\Repository\\\\SocialWork\\\\EvaluationRepositoryInterface\\:\\:getClassName\\(\\) should be covariant with return type \\(class\\-string\\\\) of method Doctrine\\\\Persistence\\\\ObjectRepository\\\\:\\:getClassName\\(\\)$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Repository/SocialWork/EvaluationRepositoryInterface.php + + - + message: "#^Return type \\(class\\-string\\) of method Chill\\\\PersonBundle\\\\Repository\\\\SocialWork\\\\GoalRepository\\:\\:getClassName\\(\\) should be covariant with return type \\(class\\-string\\\\) of method Doctrine\\\\Persistence\\\\ObjectRepository\\\\:\\:getClassName\\(\\)$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Repository/SocialWork/GoalRepository.php + + - + message: "#^Return type \\(class\\-string\\) of method Chill\\\\PersonBundle\\\\Repository\\\\SocialWork\\\\ResultRepository\\:\\:getClassName\\(\\) should be covariant with return type \\(class\\-string\\\\) of method Doctrine\\\\Persistence\\\\ObjectRepository\\\\:\\:getClassName\\(\\)$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Repository/SocialWork/ResultRepository.php + + - + message: "#^Return type \\(class\\-string\\) of method Chill\\\\PersonBundle\\\\Repository\\\\SocialWork\\\\SocialActionRepository\\:\\:getClassName\\(\\) should be covariant with return type \\(class\\-string\\\\) of method Doctrine\\\\Persistence\\\\ObjectRepository\\\\:\\:getClassName\\(\\)$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Repository/SocialWork/SocialActionRepository.php + + - + message: "#^Return type \\(class\\-string\\) of method Chill\\\\PersonBundle\\\\Repository\\\\SocialWork\\\\SocialIssueRepository\\:\\:getClassName\\(\\) should be covariant with return type \\(class\\-string\\\\) of method Doctrine\\\\Persistence\\\\ObjectRepository\\\\:\\:getClassName\\(\\)$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Repository/SocialWork/SocialIssueRepository.php + + - + message: "#^Parameter \\#2 \\$subject \\(Chill\\\\PersonBundle\\\\Entity\\\\AccompanyingPeriod\\\\AccompanyingPeriodWorkEvaluationDocument\\) of method Chill\\\\PersonBundle\\\\Security\\\\Authorization\\\\AccompanyingPeriodWorkEvaluationDocumentVoter\\:\\:voteOnAttribute\\(\\) should be contravariant with parameter \\$subject \\(mixed\\) of method Symfony\\\\Component\\\\Security\\\\Core\\\\Authorization\\\\Voter\\\\Voter\\:\\:voteOnAttribute\\(\\)$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Security/Authorization/AccompanyingPeriodWorkEvaluationDocumentVoter.php + + - + message: "#^Parameter \\#2 \\$subject \\(Chill\\\\PersonBundle\\\\Entity\\\\AccompanyingPeriod\\\\AccompanyingPeriodWorkEvaluation\\) of method Chill\\\\PersonBundle\\\\Security\\\\Authorization\\\\AccompanyingPeriodWorkEvaluationVoter\\:\\:voteOnAttribute\\(\\) should be contravariant with parameter \\$subject \\(mixed\\) of method Symfony\\\\Component\\\\Security\\\\Core\\\\Authorization\\\\Voter\\\\Voter\\:\\:voteOnAttribute\\(\\)$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Security/Authorization/AccompanyingPeriodWorkEvaluationVoter.php + + - + message: "#^Parameter \\#2 \\$subject \\(Chill\\\\PersonBundle\\\\Entity\\\\AccompanyingPeriod\\\\AccompanyingPeriodWork\\) of method Chill\\\\PersonBundle\\\\Security\\\\Authorization\\\\AccompanyingPeriodWorkVoter\\:\\:voteOnAttribute\\(\\) should be contravariant with parameter \\$subject \\(mixed\\) of method Symfony\\\\Component\\\\Security\\\\Core\\\\Authorization\\\\Voter\\\\Voter\\:\\:voteOnAttribute\\(\\)$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Security/Authorization/AccompanyingPeriodWorkVoter.php + + - + message: "#^Parameter \\#1 \\$period \\(Chill\\\\PersonBundle\\\\Entity\\\\AccompanyingPeriod\\|null\\) of method Chill\\\\PersonBundle\\\\Serializer\\\\Normalizer\\\\AccompanyingPeriodDocGenNormalizer\\:\\:normalize\\(\\) should be contravariant with parameter \\$object \\(mixed\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\NormalizerInterface\\:\\:normalize\\(\\)$#" + count: 2 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/AccompanyingPeriodDocGenNormalizer.php + + - + message: "#^Parameter \\#2 \\$format \\(string\\) of method Chill\\\\PersonBundle\\\\Serializer\\\\Normalizer\\\\AccompanyingPeriodDocGenNormalizer\\:\\:supportsNormalization\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\ContextAwareNormalizerInterface\\:\\:supportsNormalization\\(\\)$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/AccompanyingPeriodDocGenNormalizer.php + + - + message: "#^Parameter \\#2 \\$format \\(string\\) of method Chill\\\\PersonBundle\\\\Serializer\\\\Normalizer\\\\AccompanyingPeriodDocGenNormalizer\\:\\:supportsNormalization\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\NormalizerInterface\\:\\:supportsNormalization\\(\\)$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/AccompanyingPeriodDocGenNormalizer.php + + - + message: "#^Parameter \\#1 \\$origin \\(Chill\\\\PersonBundle\\\\Entity\\\\AccompanyingPeriod\\\\Origin\\) of method Chill\\\\PersonBundle\\\\Serializer\\\\Normalizer\\\\AccompanyingPeriodOriginNormalizer\\:\\:normalize\\(\\) should be contravariant with parameter \\$object \\(mixed\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\NormalizerInterface\\:\\:normalize\\(\\)$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/AccompanyingPeriodOriginNormalizer.php + + - + message: "#^Parameter \\#2 \\$format \\(string\\) of method Chill\\\\PersonBundle\\\\Serializer\\\\Normalizer\\\\AccompanyingPeriodOriginNormalizer\\:\\:supportsNormalization\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\NormalizerInterface\\:\\:supportsNormalization\\(\\)$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/AccompanyingPeriodOriginNormalizer.php + + - + message: "#^Parameter \\#1 \\$participation \\(Chill\\\\PersonBundle\\\\Entity\\\\AccompanyingPeriodParticipation\\) of method Chill\\\\PersonBundle\\\\Serializer\\\\Normalizer\\\\AccompanyingPeriodParticipationNormalizer\\:\\:normalize\\(\\) should be contravariant with parameter \\$object \\(mixed\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\NormalizerInterface\\:\\:normalize\\(\\)$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/AccompanyingPeriodParticipationNormalizer.php + + - + message: "#^Parameter \\#2 \\$format \\(string\\) of method Chill\\\\PersonBundle\\\\Serializer\\\\Normalizer\\\\AccompanyingPeriodParticipationNormalizer\\:\\:supportsNormalization\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\NormalizerInterface\\:\\:supportsNormalization\\(\\)$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/AccompanyingPeriodParticipationNormalizer.php + + - + message: "#^Parameter \\#3 \\$format \\(string\\) of method Chill\\\\PersonBundle\\\\Serializer\\\\Normalizer\\\\AccompanyingPeriodResourceNormalizer\\:\\:denormalize\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\DenormalizerInterface\\:\\:denormalize\\(\\)$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/AccompanyingPeriodResourceNormalizer.php + + - + message: "#^Parameter \\#3 \\$format \\(string\\) of method Chill\\\\PersonBundle\\\\Serializer\\\\Normalizer\\\\AccompanyingPeriodResourceNormalizer\\:\\:supportsDenormalization\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\DenormalizerInterface\\:\\:supportsDenormalization\\(\\)$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/AccompanyingPeriodResourceNormalizer.php + + - + message: "#^Parameter \\#3 \\$format \\(string\\) of method Chill\\\\PersonBundle\\\\Serializer\\\\Normalizer\\\\AccompanyingPeriodWorkDenormalizer\\:\\:denormalize\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\DenormalizerInterface\\:\\:denormalize\\(\\)$#" + count: 2 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/AccompanyingPeriodWorkDenormalizer.php + + - + message: "#^Parameter \\#3 \\$format \\(string\\) of method Chill\\\\PersonBundle\\\\Serializer\\\\Normalizer\\\\AccompanyingPeriodWorkDenormalizer\\:\\:supportsDenormalization\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\ContextAwareDenormalizerInterface\\:\\:supportsDenormalization\\(\\)$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/AccompanyingPeriodWorkDenormalizer.php + + - + message: "#^Parameter \\#3 \\$format \\(string\\) of method Chill\\\\PersonBundle\\\\Serializer\\\\Normalizer\\\\AccompanyingPeriodWorkDenormalizer\\:\\:supportsDenormalization\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\DenormalizerInterface\\:\\:supportsDenormalization\\(\\)$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/AccompanyingPeriodWorkDenormalizer.php + + - + message: "#^Parameter \\#3 \\$format \\(string\\) of method Chill\\\\PersonBundle\\\\Serializer\\\\Normalizer\\\\AccompanyingPeriodWorkEvaluationDenormalizer\\:\\:denormalize\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\DenormalizerInterface\\:\\:denormalize\\(\\)$#" + count: 2 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/AccompanyingPeriodWorkEvaluationDenormalizer.php + + - + message: "#^Parameter \\#3 \\$format \\(string\\) of method Chill\\\\PersonBundle\\\\Serializer\\\\Normalizer\\\\AccompanyingPeriodWorkEvaluationDenormalizer\\:\\:supportsDenormalization\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\ContextAwareDenormalizerInterface\\:\\:supportsDenormalization\\(\\)$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/AccompanyingPeriodWorkEvaluationDenormalizer.php + + - + message: "#^Parameter \\#3 \\$format \\(string\\) of method Chill\\\\PersonBundle\\\\Serializer\\\\Normalizer\\\\AccompanyingPeriodWorkEvaluationDenormalizer\\:\\:supportsDenormalization\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\DenormalizerInterface\\:\\:supportsDenormalization\\(\\)$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/AccompanyingPeriodWorkEvaluationDenormalizer.php + + - + message: "#^Parameter \\#1 \\$object \\(Chill\\\\PersonBundle\\\\Entity\\\\AccompanyingPeriod\\\\AccompanyingPeriodWorkEvaluation\\) of method Chill\\\\PersonBundle\\\\Serializer\\\\Normalizer\\\\AccompanyingPeriodWorkEvaluationNormalizer\\:\\:normalize\\(\\) should be contravariant with parameter \\$object \\(mixed\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\NormalizerInterface\\:\\:normalize\\(\\)$#" + count: 2 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/AccompanyingPeriodWorkEvaluationNormalizer.php + + - + message: "#^Parameter \\#1 \\$object \\(Chill\\\\PersonBundle\\\\Entity\\\\AccompanyingPeriod\\\\AccompanyingPeriodWork\\) of method Chill\\\\PersonBundle\\\\Serializer\\\\Normalizer\\\\AccompanyingPeriodWorkNormalizer\\:\\:normalize\\(\\) should be contravariant with parameter \\$object \\(mixed\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\NormalizerInterface\\:\\:normalize\\(\\)$#" + count: 2 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/AccompanyingPeriodWorkNormalizer.php + + - + message: "#^Parameter \\#3 \\$format \\(string\\) of method Chill\\\\PersonBundle\\\\Serializer\\\\Normalizer\\\\MembersEditorNormalizer\\:\\:denormalize\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\DenormalizerInterface\\:\\:denormalize\\(\\)$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/MembersEditorNormalizer.php + + - + message: "#^Parameter \\#3 \\$format \\(string\\) of method Chill\\\\PersonBundle\\\\Serializer\\\\Normalizer\\\\MembersEditorNormalizer\\:\\:supportsDenormalization\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\DenormalizerInterface\\:\\:supportsDenormalization\\(\\)$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/MembersEditorNormalizer.php + + - + message: "#^Parameter \\#2 \\$format \\(string\\) of method Chill\\\\PersonBundle\\\\Serializer\\\\Normalizer\\\\PersonDocGenNormalizer\\:\\:normalize\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\NormalizerInterface\\:\\:normalize\\(\\)$#" + count: 2 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonDocGenNormalizer.php + + - + message: "#^Parameter \\#2 \\$format \\(string\\) of method Chill\\\\PersonBundle\\\\Serializer\\\\Normalizer\\\\PersonDocGenNormalizer\\:\\:supportsNormalization\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\ContextAwareNormalizerInterface\\:\\:supportsNormalization\\(\\)$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonDocGenNormalizer.php + + - + message: "#^Parameter \\#2 \\$format \\(string\\) of method Chill\\\\PersonBundle\\\\Serializer\\\\Normalizer\\\\PersonDocGenNormalizer\\:\\:supportsNormalization\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\NormalizerInterface\\:\\:supportsNormalization\\(\\)$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonDocGenNormalizer.php + + - + message: "#^Parameter \\#1 \\$person \\(Chill\\\\PersonBundle\\\\Entity\\\\Person\\) of method Chill\\\\PersonBundle\\\\Serializer\\\\Normalizer\\\\PersonJsonNormalizer\\:\\:normalize\\(\\) should be contravariant with parameter \\$object \\(mixed\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\NormalizerInterface\\:\\:normalize\\(\\)$#" + count: 2 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonJsonNormalizer.php + + - + message: "#^Parameter \\#2 \\$format \\(string\\) of method Chill\\\\PersonBundle\\\\Serializer\\\\Normalizer\\\\PersonJsonNormalizer\\:\\:supportsNormalization\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\NormalizerInterface\\:\\:supportsNormalization\\(\\)$#" + count: 2 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonJsonNormalizer.php + + - + message: "#^Parameter \\#3 \\$format \\(string\\) of method Chill\\\\PersonBundle\\\\Serializer\\\\Normalizer\\\\PersonJsonNormalizer\\:\\:denormalize\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\DenormalizerInterface\\:\\:denormalize\\(\\)$#" + count: 2 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonJsonNormalizer.php + + - + message: "#^Parameter \\#3 \\$format \\(string\\) of method Chill\\\\PersonBundle\\\\Serializer\\\\Normalizer\\\\PersonJsonNormalizer\\:\\:supportsDenormalization\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\DenormalizerInterface\\:\\:supportsDenormalization\\(\\)$#" + count: 2 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonJsonNormalizer.php + + - + message: "#^Parameter \\#1 \\$relation \\(Chill\\\\PersonBundle\\\\Entity\\\\Relationships\\\\Relationship\\) of method Chill\\\\PersonBundle\\\\Serializer\\\\Normalizer\\\\RelationshipDocGenNormalizer\\:\\:normalize\\(\\) should be contravariant with parameter \\$object \\(mixed\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\NormalizerInterface\\:\\:normalize\\(\\)$#" + count: 2 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/RelationshipDocGenNormalizer.php + + - + message: "#^Parameter \\#2 \\$format \\(string\\) of method Chill\\\\PersonBundle\\\\Serializer\\\\Normalizer\\\\RelationshipDocGenNormalizer\\:\\:supportsNormalization\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\ContextAwareNormalizerInterface\\:\\:supportsNormalization\\(\\)$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/RelationshipDocGenNormalizer.php + + - + message: "#^Parameter \\#2 \\$format \\(string\\) of method Chill\\\\PersonBundle\\\\Serializer\\\\Normalizer\\\\RelationshipDocGenNormalizer\\:\\:supportsNormalization\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\NormalizerInterface\\:\\:supportsNormalization\\(\\)$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/RelationshipDocGenNormalizer.php + + - + message: "#^Parameter \\#2 \\$format \\(string\\) of method Chill\\\\PersonBundle\\\\Serializer\\\\Normalizer\\\\SocialActionNormalizer\\:\\:normalize\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\NormalizerInterface\\:\\:normalize\\(\\)$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/SocialActionNormalizer.php + + - + message: "#^Parameter \\#2 \\$format \\(string\\) of method Chill\\\\PersonBundle\\\\Serializer\\\\Normalizer\\\\SocialActionNormalizer\\:\\:supportsNormalization\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\NormalizerInterface\\:\\:supportsNormalization\\(\\)$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/SocialActionNormalizer.php + + - + message: "#^Parameter \\#2 \\$format \\(string\\) of method Chill\\\\PersonBundle\\\\Serializer\\\\Normalizer\\\\SocialIssueNormalizer\\:\\:normalize\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\NormalizerInterface\\:\\:normalize\\(\\)$#" + count: 2 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/SocialIssueNormalizer.php + + - + message: "#^Parameter \\#2 \\$format \\(string\\) of method Chill\\\\PersonBundle\\\\Serializer\\\\Normalizer\\\\SocialIssueNormalizer\\:\\:supportsNormalization\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\ContextAwareNormalizerInterface\\:\\:supportsNormalization\\(\\)$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/SocialIssueNormalizer.php + + - + message: "#^Parameter \\#2 \\$format \\(string\\) of method Chill\\\\PersonBundle\\\\Serializer\\\\Normalizer\\\\SocialIssueNormalizer\\:\\:supportsNormalization\\(\\) should be contravariant with parameter \\$format \\(string\\|null\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\NormalizerInterface\\:\\:supportsNormalization\\(\\)$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/SocialIssueNormalizer.php + + - + message: "#^Parameter \\#1 \\$object \\(Chill\\\\MainBundle\\\\Entity\\\\Workflow\\\\EntityWorkflow\\) of method Chill\\\\PersonBundle\\\\Serializer\\\\Normalizer\\\\WorkflowNormalizer\\:\\:normalize\\(\\) should be contravariant with parameter \\$object \\(mixed\\) of method Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\NormalizerInterface\\:\\:normalize\\(\\)$#" + count: 2 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/WorkflowNormalizer.php + + + - + message: "#^Parameter \\#1 \\$object \\(Chill\\\\PersonBundle\\\\Entity\\\\AccompanyingPeriod\\\\AccompanyingPeriodWorkEvaluationDocument\\) of method Chill\\\\PersonBundle\\\\Workflow\\\\AccompanyingPeriodWorkEvaluationDocumentWorkflowHandler\\:\\:getRelatedObjects\\(\\) should be contravariant with parameter \\$object \\(object\\) of method Chill\\\\MainBundle\\\\Workflow\\\\EntityWorkflowHandlerInterface\\:\\:getRelatedObjects\\(\\)$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Workflow/AccompanyingPeriodWorkEvaluationDocumentWorkflowHandler.php + + - + message: "#^Parameter \\#1 \\$object \\(Chill\\\\PersonBundle\\\\Entity\\\\AccompanyingPeriod\\\\AccompanyingPeriodWorkEvaluation\\) of method Chill\\\\PersonBundle\\\\Workflow\\\\AccompanyingPeriodWorkEvaluationWorkflowHandler\\:\\:getRelatedObjects\\(\\) should be contravariant with parameter \\$object \\(object\\) of method Chill\\\\MainBundle\\\\Workflow\\\\EntityWorkflowHandlerInterface\\:\\:getRelatedObjects\\(\\)$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Workflow/AccompanyingPeriodWorkEvaluationWorkflowHandler.php + + - + message: "#^Parameter \\#1 \\$object \\(Chill\\\\PersonBundle\\\\Entity\\\\AccompanyingPeriod\\\\AccompanyingPeriodWork\\) of method Chill\\\\PersonBundle\\\\Workflow\\\\AccompanyingPeriodWorkWorkflowHandler\\:\\:getRelatedObjects\\(\\) should be contravariant with parameter \\$object \\(object\\) of method Chill\\\\MainBundle\\\\Workflow\\\\EntityWorkflowHandlerInterface\\:\\:getRelatedObjects\\(\\)$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Workflow/AccompanyingPeriodWorkWorkflowHandler.php + + - + message: "#^Return type \\(Chill\\\\MainBundle\\\\Entity\\\\Center\\|null\\) of method Chill\\\\TaskBundle\\\\Entity\\\\AbstractTask\\:\\:getCenter\\(\\) should be covariant with return type \\(Chill\\\\MainBundle\\\\Entity\\\\Center\\) of method Chill\\\\MainBundle\\\\Entity\\\\HasCenterInterface\\:\\:getCenter\\(\\)$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Entity/AbstractTask.php + + - + message: "#^Return type \\(Chill\\\\MainBundle\\\\Entity\\\\Scope\\|null\\) of method Chill\\\\TaskBundle\\\\Entity\\\\AbstractTask\\:\\:getScope\\(\\) should be covariant with return type \\(Chill\\\\MainBundle\\\\Entity\\\\Scope\\) of method Chill\\\\MainBundle\\\\Entity\\\\HasScopeInterface\\:\\:getScope\\(\\)$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Entity/AbstractTask.php + + - + message: "#^Parameter \\#3 \\$entity \\(Chill\\\\ThirdPartyBundle\\\\Entity\\\\ThirdParty\\) of method Chill\\\\ThirdPartyBundle\\\\Controller\\\\ThirdPartyController\\:\\:onPostFetchEntity\\(\\) should be contravariant with parameter \\$entity \\(mixed\\) of method Chill\\\\MainBundle\\\\CRUD\\\\Controller\\\\CRUDController\\:\\:onPostFetchEntity\\(\\)$#" + count: 1 + path: src/Bundle/ChillThirdPartyBundle/Controller/ThirdPartyController.php + + - + message: "#^Parameter \\#1 \\$createdAt \\(DateTimeImmutable\\) of method Chill\\\\ThirdPartyBundle\\\\Entity\\\\ThirdParty\\:\\:setCreatedAt\\(\\) should be contravariant with parameter \\$datetime \\(DateTimeInterface\\) of method Chill\\\\MainBundle\\\\Doctrine\\\\Model\\\\TrackCreationInterface\\:\\:setCreatedAt\\(\\)$#" + count: 1 + path: src/Bundle/ChillThirdPartyBundle/Entity/ThirdParty.php + + - + message: "#^Parameter \\#1 \\$updatedAt \\(DateTimeImmutable\\) of method Chill\\\\ThirdPartyBundle\\\\Entity\\\\ThirdParty\\:\\:setUpdatedAt\\(\\) should be contravariant with parameter \\$datetime \\(DateTimeInterface\\) of method Chill\\\\MainBundle\\\\Doctrine\\\\Model\\\\TrackUpdateInterface\\:\\:setUpdatedAt\\(\\)$#" + count: 1 + path: src/Bundle/ChillThirdPartyBundle/Entity/ThirdParty.php + + - + message: "#^Parameter \\#3 \\$limit \\(null\\) of method Chill\\\\ThirdPartyBundle\\\\Repository\\\\ThirdPartyRepository\\:\\:findBy\\(\\) should be contravariant with parameter \\$limit \\(int\\|null\\) of method Doctrine\\\\Persistence\\\\ObjectRepository\\\\:\\:findBy\\(\\)$#" + count: 1 + path: src/Bundle/ChillThirdPartyBundle/Repository/ThirdPartyRepository.php + + - + message: "#^Parameter \\#4 \\$offset \\(null\\) of method Chill\\\\ThirdPartyBundle\\\\Repository\\\\ThirdPartyRepository\\:\\:findBy\\(\\) should be contravariant with parameter \\$offset \\(int\\|null\\) of method Doctrine\\\\Persistence\\\\ObjectRepository\\\\:\\:findBy\\(\\)$#" + count: 1 + path: src/Bundle/ChillThirdPartyBundle/Repository/ThirdPartyRepository.php + + - + message: "#^Parameter \\#2 \\$subject \\(Chill\\\\ThirdPartyBundle\\\\Entity\\\\ThirdParty\\|null\\) of method Chill\\\\ThirdPartyBundle\\\\Security\\\\Voter\\\\ThirdPartyVoter\\:\\:voteOnAttribute\\(\\) should be contravariant with parameter \\$subject \\(mixed\\) of method Symfony\\\\Component\\\\Security\\\\Core\\\\Authorization\\\\Voter\\\\Voter\\:\\:voteOnAttribute\\(\\)$#" + count: 1 + path: src/Bundle/ChillThirdPartyBundle/Security/Voter/ThirdPartyVoter.php + + - + message: "#^Parameter \\#1 \\$document \\(Chill\\\\DocStoreBundle\\\\Entity\\\\StoredObject\\) of method Chill\\\\WopiBundle\\\\Service\\\\Wopi\\\\ChillDocumentManager\\:\\:getBasename\\(\\) should be contravariant with parameter \\$document \\(ChampsLibres\\\\WopiLib\\\\Contract\\\\Entity\\\\Document\\) of method ChampsLibres\\\\WopiLib\\\\Contract\\\\Service\\\\DocumentManagerInterface\\:\\:getBasename\\(\\)$#" + count: 1 + path: src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentManager.php + + - + message: "#^Parameter \\#1 \\$document \\(Chill\\\\DocStoreBundle\\\\Entity\\\\StoredObject\\) of method Chill\\\\WopiBundle\\\\Service\\\\Wopi\\\\ChillDocumentManager\\:\\:getCreationDate\\(\\) should be contravariant with parameter \\$document \\(ChampsLibres\\\\WopiLib\\\\Contract\\\\Entity\\\\Document\\) of method ChampsLibres\\\\WopiLib\\\\Contract\\\\Service\\\\DocumentManagerInterface\\:\\:getCreationDate\\(\\)$#" + count: 1 + path: src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentManager.php + + - + message: "#^Parameter \\#1 \\$document \\(Chill\\\\DocStoreBundle\\\\Entity\\\\StoredObject\\) of method Chill\\\\WopiBundle\\\\Service\\\\Wopi\\\\ChillDocumentManager\\:\\:getDocumentId\\(\\) should be contravariant with parameter \\$document \\(ChampsLibres\\\\WopiLib\\\\Contract\\\\Entity\\\\Document\\) of method ChampsLibres\\\\WopiLib\\\\Contract\\\\Service\\\\DocumentManagerInterface\\:\\:getDocumentId\\(\\)$#" + count: 1 + path: src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentManager.php + + - + message: "#^Parameter \\#1 \\$document \\(Chill\\\\DocStoreBundle\\\\Entity\\\\StoredObject\\) of method Chill\\\\WopiBundle\\\\Service\\\\Wopi\\\\ChillDocumentManager\\:\\:getLastModifiedDate\\(\\) should be contravariant with parameter \\$document \\(ChampsLibres\\\\WopiLib\\\\Contract\\\\Entity\\\\Document\\) of method ChampsLibres\\\\WopiLib\\\\Contract\\\\Service\\\\DocumentManagerInterface\\:\\:getLastModifiedDate\\(\\)$#" + count: 1 + path: src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentManager.php diff --git a/phpstan-baseline-level-4.neon b/phpstan-baseline-level-4.neon new file mode 100644 index 000000000..833a55afb --- /dev/null +++ b/phpstan-baseline-level-4.neon @@ -0,0 +1,4793 @@ +parameters: + ignoreErrors: + - + message: "#^Access to an undefined property Chill\\\\ActivityBundle\\\\Entity\\\\Activity\\:\\:\\$personsAssociated\\.$#" + count: 1 + path: src/Bundle/ChillActivityBundle/Controller/ActivityController.php + + - + message: "#^Access to an undefined property Chill\\\\ActivityBundle\\\\Entity\\\\Activity\\:\\:\\$personsNotAssociated\\.$#" + count: 1 + path: src/Bundle/ChillActivityBundle/Controller/ActivityController.php + + - + message: "#^Call to an undefined method Symfony\\\\Component\\\\Serializer\\\\SerializerInterface\\:\\:normalize\\(\\)\\.$#" + count: 2 + path: src/Bundle/ChillActivityBundle/Controller/ActivityController.php + + - + message: "#^Method Symfony\\\\Contracts\\\\EventDispatcher\\\\EventDispatcherInterface\\:\\:dispatch\\(\\) invoked with 2 parameters, 1 required\\.$#" + count: 1 + path: src/Bundle/ChillActivityBundle/Controller/ActivityController.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and 'ChillActivityBundle…'\\|'ChillActivityBundle…' will always evaluate to false\\.$#" + count: 3 + path: src/Bundle/ChillActivityBundle/Controller/ActivityController.php + + - + message: "#^Method Chill\\\\ActivityBundle\\\\Controller\\\\ActivityReasonCategoryController\\:\\:createCreateForm\\(\\) should return Symfony\\\\Component\\\\Form\\\\Form but returns Symfony\\\\Component\\\\Form\\\\FormInterface\\.$#" + count: 1 + path: src/Bundle/ChillActivityBundle/Controller/ActivityReasonCategoryController.php + + - + message: "#^Method Chill\\\\ActivityBundle\\\\Controller\\\\ActivityReasonCategoryController\\:\\:createEditForm\\(\\) should return Symfony\\\\Component\\\\Form\\\\Form but returns Symfony\\\\Component\\\\Form\\\\FormInterface\\.$#" + count: 1 + path: src/Bundle/ChillActivityBundle/Controller/ActivityReasonCategoryController.php + + - + message: "#^Method Chill\\\\ActivityBundle\\\\Controller\\\\ActivityReasonController\\:\\:createCreateForm\\(\\) should return Symfony\\\\Component\\\\Form\\\\Form but returns Symfony\\\\Component\\\\Form\\\\FormInterface\\.$#" + count: 1 + path: src/Bundle/ChillActivityBundle/Controller/ActivityReasonController.php + + - + message: "#^Method Chill\\\\ActivityBundle\\\\Controller\\\\ActivityReasonController\\:\\:createEditForm\\(\\) should return Symfony\\\\Component\\\\Form\\\\Form but returns Symfony\\\\Component\\\\Form\\\\FormInterface\\.$#" + count: 1 + path: src/Bundle/ChillActivityBundle/Controller/ActivityReasonController.php + + - + message: "#^Else branch is unreachable because previous condition is always true\\.$#" + count: 1 + path: src/Bundle/ChillActivityBundle/DataFixtures/ORM/LoadActivity.php + + - + message: "#^Strict comparison using \\!\\=\\= between null and Chill\\\\ActivityBundle\\\\Entity\\\\ActivityReason will always evaluate to true\\.$#" + count: 1 + path: src/Bundle/ChillActivityBundle/DataFixtures/ORM/LoadActivity.php + + - + message: "#^Unreachable statement \\- code above always terminates\\.$#" + count: 1 + path: src/Bundle/ChillActivityBundle/DataFixtures/ORM/LoadActivityNotifications.php + + - + message: "#^Call to an undefined method Symfony\\\\Component\\\\Config\\\\Definition\\\\Builder\\\\NodeDefinition\\:\\:children\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillActivityBundle/DependencyInjection/Configuration.php + + - + message: "#^Method Symfony\\\\Component\\\\Config\\\\Definition\\\\Builder\\\\TreeBuilder\\:\\:getRootNode\\(\\) invoked with 1 parameter, 0 required\\.$#" + count: 1 + path: src/Bundle/ChillActivityBundle/DependencyInjection/Configuration.php + + - + message: "#^Property Chill\\\\ActivityBundle\\\\Entity\\\\Activity\\:\\:\\$user \\(Chill\\\\MainBundle\\\\Entity\\\\User\\) does not accept Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface\\.$#" + count: 1 + path: src/Bundle/ChillActivityBundle/Entity/Activity.php + + - + message: "#^Property Chill\\\\ActivityBundle\\\\Entity\\\\ActivityPresence\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: src/Bundle/ChillActivityBundle/Entity/ActivityPresence.php + + - + message: "#^Property Chill\\\\ActivityBundle\\\\Entity\\\\ActivityReason\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: src/Bundle/ChillActivityBundle/Entity/ActivityReason.php + + - + message: "#^Argument of an invalid type string supplied for foreach, only iterables are supported\\.$#" + count: 1 + path: src/Bundle/ChillActivityBundle/Entity/ActivityReasonCategory.php + + - + message: "#^Binary operation \"\\.\" between 'ActivityReasonCateg…' and array results in an error\\.$#" + count: 1 + path: src/Bundle/ChillActivityBundle/Entity/ActivityReasonCategory.php + + - + message: "#^Method Chill\\\\ActivityBundle\\\\Entity\\\\ActivityReasonCategory\\:\\:getName\\(\\) should return array but returns string\\.$#" + count: 3 + path: src/Bundle/ChillActivityBundle/Entity/ActivityReasonCategory.php + + - + message: "#^Property Chill\\\\ActivityBundle\\\\Entity\\\\ActivityReasonCategory\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: src/Bundle/ChillActivityBundle/Entity/ActivityReasonCategory.php + + - + message: "#^Property Chill\\\\ActivityBundle\\\\Entity\\\\ActivityReasonCategory\\:\\:\\$name \\(string\\) does not accept array\\.$#" + count: 1 + path: src/Bundle/ChillActivityBundle/Entity/ActivityReasonCategory.php + + - + message: "#^Expression on left side of \\?\\? is not nullable\\.$#" + count: 1 + path: src/Bundle/ChillActivityBundle/EntityListener/ActivityEntityListener.php + + - + message: "#^Property Chill\\\\ActivityBundle\\\\Export\\\\Aggregator\\\\ACPAggregators\\\\DateAggregator\\:\\:\\$translator is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/DateAggregator.php + + - + message: "#^PHPDoc tag @param for parameter \\$resolver with type Chill\\\\ActivityBundle\\\\Form\\\\OptionsResolverInterface is not subtype of native type Symfony\\\\Component\\\\OptionsResolver\\\\OptionsResolver\\.$#" + count: 1 + path: src/Bundle/ChillActivityBundle/Form/ActivityReasonCategoryType.php + + - + message: "#^Parameter \\$resolver of method Chill\\\\ActivityBundle\\\\Form\\\\ActivityReasonCategoryType\\:\\:configureOptions\\(\\) has invalid type Chill\\\\ActivityBundle\\\\Form\\\\OptionsResolverInterface\\.$#" + count: 1 + path: src/Bundle/ChillActivityBundle/Form/ActivityReasonCategoryType.php + + - + message: "#^Call to an undefined method Doctrine\\\\ORM\\\\EntityRepository\\\\:\\:findByAccompanyingPeriod\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php + + - + message: "#^Call to an undefined method Doctrine\\\\ORM\\\\EntityRepository\\\\:\\:findByPersonImplied\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php + + - + message: "#^Method Chill\\\\ActivityBundle\\\\Repository\\\\ActivityACLAwareRepository\\:\\:findByAccompanyingPeriod\\(\\) has invalid return type Chill\\\\ActivityBundle\\\\Repository\\\\Activity\\.$#" + count: 1 + path: src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php + + - + message: "#^Method Chill\\\\ActivityBundle\\\\Repository\\\\ActivityACLAwareRepository\\:\\:getWhereClause\\(\\) should return array but returns string\\.$#" + count: 1 + path: src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php + + - + message: "#^Property Chill\\\\ActivityBundle\\\\Repository\\\\ActivityACLAwareRepository\\:\\:\\$repository is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php + + - + message: "#^Method Chill\\\\ActivityBundle\\\\Repository\\\\ActivityACLAwareRepositoryInterface\\:\\:findByAccompanyingPeriod\\(\\) has invalid return type Chill\\\\ActivityBundle\\\\Repository\\\\Activity\\.$#" + count: 1 + path: src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepositoryInterface.php + + - + message: "#^Method Chill\\\\ActivityBundle\\\\Repository\\\\ActivityACLAwareRepositoryInterface\\:\\:findByPerson\\(\\) has invalid return type Chill\\\\ActivityBundle\\\\Repository\\\\Activity\\.$#" + count: 1 + path: src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepositoryInterface.php + + - + message: "#^Expression on left side of \\?\\? is not nullable\\.$#" + count: 1 + path: src/Bundle/ChillActivityBundle/Repository/ActivityReasonRepository.php + + - + message: "#^Property Chill\\\\ActivityBundle\\\\Service\\\\DocGenerator\\\\ActivityContext\\:\\:\\$documentCategoryRepository is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillActivityBundle/Service/DocGenerator/ActivityContext.php + + - + message: "#^Parameter \\$context of method Chill\\\\ActivityBundle\\\\Timeline\\\\TimelineActivityProvider\\:\\:getEntityTemplate\\(\\) has invalid type Chill\\\\MainBundle\\\\Timeline\\\\type\\.$#" + count: 1 + path: src/Bundle/ChillActivityBundle/Timeline/TimelineActivityProvider.php + + - + message: "#^Parameter \\$entity of method Chill\\\\ActivityBundle\\\\Timeline\\\\TimelineActivityProvider\\:\\:getEntityTemplate\\(\\) has invalid type Chill\\\\MainBundle\\\\Timeline\\\\type\\.$#" + count: 1 + path: src/Bundle/ChillActivityBundle/Timeline/TimelineActivityProvider.php + + - + message: "#^Result of && is always false\\.$#" + count: 7 + path: src/Bundle/ChillActivityBundle/Validator/Constraints/ActivityValidityValidator.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and Chill\\\\MainBundle\\\\Entity\\\\Embeddable\\\\CommentEmbeddable will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillActivityBundle/Validator/Constraints/ActivityValidityValidator.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and Chill\\\\MainBundle\\\\Entity\\\\User will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillActivityBundle/Validator/Constraints/ActivityValidityValidator.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and DateTime will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillActivityBundle/Validator/Constraints/ActivityValidityValidator.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and Doctrine\\\\Common\\\\Collections\\\\Collection will always evaluate to false\\.$#" + count: 2 + path: src/Bundle/ChillActivityBundle/Validator/Constraints/ActivityValidityValidator.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and bool will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillActivityBundle/Validator/Constraints/ActivityValidityValidator.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and string will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillActivityBundle/Validator/Constraints/ActivityValidityValidator.php + + - + message: "#^Call to an undefined method Symfony\\\\Component\\\\Config\\\\Definition\\\\Builder\\\\NodeDefinition\\:\\:children\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillAsideActivityBundle/src/DependencyInjection/Configuration.php + + - + message: "#^Method Symfony\\\\Component\\\\Config\\\\Definition\\\\Builder\\\\TreeBuilder\\:\\:getRootNode\\(\\) invoked with 1 parameter, 0 required\\.$#" + count: 1 + path: src/Bundle/ChillAsideActivityBundle/src/DependencyInjection/Configuration.php + + - + message: "#^Property Chill\\\\AsideActivityBundle\\\\Entity\\\\AsideActivity\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: src/Bundle/ChillAsideActivityBundle/src/Entity/AsideActivity.php + + - + message: "#^Property Chill\\\\AsideActivityBundle\\\\Entity\\\\AsideActivityCategory\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: src/Bundle/ChillAsideActivityBundle/src/Entity/AsideActivityCategory.php + + - + message: "#^Call to method DateTimeImmutable\\:\\:add\\(\\) on a separate line has no effect\\.$#" + count: 1 + path: src/Bundle/ChillAsideActivityBundle/src/Form/AsideActivityFormType.php + + - + message: "#^Call to method DateTimeImmutable\\:\\:setTimezone\\(\\) on a separate line has no effect\\.$#" + count: 1 + path: src/Bundle/ChillAsideActivityBundle/src/Form/AsideActivityFormType.php + + - + message: "#^Property Chill\\\\AsideActivityBundle\\\\Form\\\\AsideActivityFormType\\:\\:\\$storage is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillAsideActivityBundle/src/Form/AsideActivityFormType.php + + - + message: "#^Static call to instance method Chill\\\\BudgetBundle\\\\Calculator\\\\CalculatorInterface\\:\\:getAlias\\(\\)\\.$#" + count: 3 + path: src/Bundle/ChillBudgetBundle/Calculator/CalculatorManager.php + + - + message: "#^Call to an undefined method Chill\\\\BudgetBundle\\\\Entity\\\\AbstractElement\\:\\:getId\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillBudgetBundle/Controller/AbstractElementController.php + + - + message: "#^Method Chill\\\\BudgetBundle\\\\Controller\\\\AbstractElementController\\:\\:createDeleteForm\\(\\) should return Symfony\\\\Component\\\\Form\\\\Form but returns Symfony\\\\Component\\\\Form\\\\FormInterface\\.$#" + count: 1 + path: src/Bundle/ChillBudgetBundle/Controller/AbstractElementController.php + + - + message: "#^Call to an undefined method Doctrine\\\\ORM\\\\EntityRepository\\\\:\\:findByEntityAndDate\\(\\)\\.$#" + count: 3 + path: src/Bundle/ChillBudgetBundle/Controller/ElementController.php + + - + message: "#^Call to an undefined method Doctrine\\\\ORM\\\\EntityRepository\\\\:\\:findByHousehold\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillBudgetBundle/Controller/ElementController.php + + - + message: "#^Call to an undefined method Doctrine\\\\ORM\\\\EntityRepository\\\\:\\:findByPerson\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillBudgetBundle/Controller/ElementController.php + + - + message: "#^Call to an undefined method Doctrine\\\\ORM\\\\EntityRepository\\\\:\\:findByEntityAndDate\\(\\)\\.$#" + count: 3 + path: src/Bundle/ChillBudgetBundle/Controller/ElementController.php + + - + message: "#^Call to an undefined method Doctrine\\\\ORM\\\\EntityRepository\\\\:\\:findByHousehold\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillBudgetBundle/Controller/ElementController.php + + - + message: "#^Call to an undefined method Doctrine\\\\ORM\\\\EntityRepository\\\\:\\:findByPerson\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillBudgetBundle/Controller/ElementController.php + + - + message: "#^Call to an undefined method Symfony\\\\Component\\\\Config\\\\Definition\\\\Builder\\\\NodeDefinition\\:\\:children\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillBudgetBundle/DependencyInjection/Configuration.php + + - + message: "#^Method Symfony\\\\Component\\\\Config\\\\Definition\\\\Builder\\\\TreeBuilder\\:\\:getRootNode\\(\\) invoked with 1 parameter, 0 required\\.$#" + count: 1 + path: src/Bundle/ChillBudgetBundle/DependencyInjection/Configuration.php + + - + message: "#^Property Chill\\\\BudgetBundle\\\\Entity\\\\AbstractElement\\:\\:\\$startDate \\(DateTimeImmutable\\) does not accept null\\.$#" + count: 1 + path: src/Bundle/ChillBudgetBundle/Entity/AbstractElement.php + + - + message: "#^Strict comparison using \\=\\=\\= between 0 and string will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillBudgetBundle/Entity/AbstractElement.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and DateTimeImmutable will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillBudgetBundle/Entity/AbstractElement.php + + - + message: "#^Property Chill\\\\BudgetBundle\\\\Entity\\\\ChargeKind\\:\\:\\$tags is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillBudgetBundle/Entity/ChargeKind.php + + - + message: "#^Property Chill\\\\BudgetBundle\\\\Entity\\\\ResourceKind\\:\\:\\$tags is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillBudgetBundle/Entity/ResourceKind.php + + - + message: "#^Method Chill\\\\BudgetBundle\\\\Repository\\\\ChargeKindRepository\\:\\:findAll\\(\\) has invalid return type Chill\\\\BudgetBundle\\\\Repository\\\\ChargeType\\.$#" + count: 1 + path: src/Bundle/ChillBudgetBundle/Repository/ChargeKindRepository.php + + - + message: "#^Method Chill\\\\BudgetBundle\\\\Repository\\\\ChargeKindRepository\\:\\:findAllActive\\(\\) has invalid return type Chill\\\\BudgetBundle\\\\Repository\\\\ChargeType\\.$#" + count: 1 + path: src/Bundle/ChillBudgetBundle/Repository/ChargeKindRepository.php + + - + message: "#^Method Chill\\\\BudgetBundle\\\\Repository\\\\ChargeKindRepository\\:\\:findAllByType\\(\\) has invalid return type Chill\\\\BudgetBundle\\\\Repository\\\\ChargeType\\.$#" + count: 1 + path: src/Bundle/ChillBudgetBundle/Repository/ChargeKindRepository.php + + - + message: "#^Method Chill\\\\BudgetBundle\\\\Repository\\\\ChargeKindRepository\\:\\:findBy\\(\\) has invalid return type Chill\\\\BudgetBundle\\\\Repository\\\\ChargeType\\.$#" + count: 1 + path: src/Bundle/ChillBudgetBundle/Repository/ChargeKindRepository.php + + - + message: "#^PHPDoc tag @param for parameter \\$limit with type mixed is not subtype of native type int\\|null\\.$#" + count: 1 + path: src/Bundle/ChillBudgetBundle/Repository/ChargeKindRepository.php + + - + message: "#^PHPDoc tag @param for parameter \\$offset with type mixed is not subtype of native type int\\|null\\.$#" + count: 1 + path: src/Bundle/ChillBudgetBundle/Repository/ChargeKindRepository.php + + - + message: "#^Method Chill\\\\BudgetBundle\\\\Repository\\\\ResourceKindRepository\\:\\:findAll\\(\\) has invalid return type Chill\\\\BudgetBundle\\\\Repository\\\\ResourceType\\.$#" + count: 1 + path: src/Bundle/ChillBudgetBundle/Repository/ResourceKindRepository.php + + - + message: "#^Method Chill\\\\BudgetBundle\\\\Repository\\\\ResourceKindRepository\\:\\:findAllActive\\(\\) has invalid return type Chill\\\\BudgetBundle\\\\Repository\\\\ResourceType\\.$#" + count: 1 + path: src/Bundle/ChillBudgetBundle/Repository/ResourceKindRepository.php + + - + message: "#^Method Chill\\\\BudgetBundle\\\\Repository\\\\ResourceKindRepository\\:\\:findAllByType\\(\\) has invalid return type Chill\\\\BudgetBundle\\\\Repository\\\\ResourceType\\.$#" + count: 1 + path: src/Bundle/ChillBudgetBundle/Repository/ResourceKindRepository.php + + - + message: "#^Method Chill\\\\BudgetBundle\\\\Repository\\\\ResourceKindRepository\\:\\:findBy\\(\\) has invalid return type Chill\\\\BudgetBundle\\\\Repository\\\\ResourceType\\.$#" + count: 1 + path: src/Bundle/ChillBudgetBundle/Repository/ResourceKindRepository.php + + - + message: "#^PHPDoc tag @param for parameter \\$limit with type mixed is not subtype of native type int\\|null\\.$#" + count: 1 + path: src/Bundle/ChillBudgetBundle/Repository/ResourceKindRepository.php + + - + message: "#^PHPDoc tag @param for parameter \\$offset with type mixed is not subtype of native type int\\|null\\.$#" + count: 1 + path: src/Bundle/ChillBudgetBundle/Repository/ResourceKindRepository.php + + - + message: "#^Property Chill\\\\BudgetBundle\\\\Service\\\\Summary\\\\SummaryBudget\\:\\:\\$chargeLabels is unused\\.$#" + count: 1 + path: src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudget.php + + - + message: "#^Property Chill\\\\BudgetBundle\\\\Service\\\\Summary\\\\SummaryBudget\\:\\:\\$resourcesLabels is unused\\.$#" + count: 1 + path: src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudget.php + + - + message: "#^Property Chill\\\\CalendarBundle\\\\Command\\\\AzureGrantAdminConsentAndAcquireToken\\:\\:\\$clientRegistry is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillCalendarBundle/Command/AzureGrantAdminConsentAndAcquireToken.php + + - + message: "#^Property Chill\\\\CalendarBundle\\\\Command\\\\SendTestShortMessageOnCalendarCommand\\:\\:\\$phoneNumberHelper is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillCalendarBundle/Command/SendTestShortMessageOnCalendarCommand.php + + - + message: "#^Strict comparison using \\=\\=\\= between false and DateInterval will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillCalendarBundle/Command/SendTestShortMessageOnCalendarCommand.php + + - + message: "#^Strict comparison using \\=\\=\\= between false and DateTimeImmutable will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillCalendarBundle/Command/SendTestShortMessageOnCalendarCommand.php + + - + message: "#^Call to an undefined method Symfony\\\\Component\\\\Form\\\\FormInterface\\:\\:isClicked\\(\\)\\.$#" + count: 4 + path: src/Bundle/ChillCalendarBundle/Controller/CalendarController.php + + - + message: "#^Call to an undefined method Symfony\\\\Component\\\\Serializer\\\\SerializerInterface\\:\\:normalize\\(\\)\\.$#" + count: 2 + path: src/Bundle/ChillCalendarBundle/Controller/CalendarController.php + + - + message: "#^Unreachable statement \\- code above always terminates\\.$#" + count: 1 + path: src/Bundle/ChillCalendarBundle/Controller/CalendarController.php + + - + message: "#^Property Chill\\\\CalendarBundle\\\\Controller\\\\CalendarDocController\\:\\:\\$docGeneratorTemplateRepository is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillCalendarBundle/Controller/CalendarDocController.php + + - + message: "#^Property Chill\\\\CalendarBundle\\\\Controller\\\\CalendarDocController\\:\\:\\$serializer is unused\\.$#" + count: 1 + path: src/Bundle/ChillCalendarBundle/Controller/CalendarDocController.php + + - + message: "#^Method Symfony\\\\Component\\\\HttpFoundation\\\\Session\\\\SessionInterface\\:\\:remove\\(\\) invoked with 2 parameters, 1 required\\.$#" + count: 1 + path: src/Bundle/ChillCalendarBundle/Controller/RemoteCalendarConnectAzureController.php + + - + message: "#^Call to an undefined method Symfony\\\\Component\\\\Config\\\\Definition\\\\Builder\\\\NodeDefinition\\:\\:children\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillCalendarBundle/DependencyInjection/Configuration.php + + - + message: "#^Method Symfony\\\\Component\\\\Config\\\\Definition\\\\Builder\\\\TreeBuilder\\:\\:getRootNode\\(\\) invoked with 1 parameter, 0 required\\.$#" + count: 1 + path: src/Bundle/ChillCalendarBundle/DependencyInjection/Configuration.php + + - + message: "#^Call to an undefined method Doctrine\\\\Common\\\\Collections\\\\Collection\\:\\:matching\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillCalendarBundle/Entity/Calendar.php + + - + message: "#^Property Chill\\\\CalendarBundle\\\\Entity\\\\CalendarRange\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: src/Bundle/ChillCalendarBundle/Entity/CalendarRange.php + + - + message: "#^Property Chill\\\\CalendarBundle\\\\Entity\\\\CancelReason\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: src/Bundle/ChillCalendarBundle/Entity/CancelReason.php + + - + message: "#^Property Chill\\\\CalendarBundle\\\\Messenger\\\\Message\\\\CalendarMessage\\:\\:\\$oldInvites \\(array\\\\) does not accept array\\\\.$#" + count: 1 + path: src/Bundle/ChillCalendarBundle/Messenger/Message/CalendarMessage.php + + - + message: "#^PHPDoc tag @return has invalid value \\(array\\\\)\\: Unexpected token \"\\:\", expected '\\>' at offset 408$#" + count: 2 + path: src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/EventsOnUserSubscriptionCreator.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and string will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/EventsOnUserSubscriptionCreator.php + + - + message: "#^Method Chill\\\\CalendarBundle\\\\RemoteCalendar\\\\Connector\\\\MSGraph\\\\OnBehalfOfUserTokenStorage\\:\\:getToken\\(\\) should return TheNetworg\\\\OAuth2\\\\Client\\\\Token\\\\AccessToken but returns League\\\\OAuth2\\\\Client\\\\Token\\\\AccessTokenInterface\\.$#" + count: 1 + path: src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/OnBehalfOfUserTokenStorage.php + + - + message: "#^Call to method DateTimeImmutable\\:\\:setTimezone\\(\\) on a separate line has no effect\\.$#" + count: 1 + path: src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/RemoteEventConverter.php + + - + message: "#^Unreachable statement \\- code above always terminates\\.$#" + count: 1 + path: src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/RemoteToLocalSync/CalendarSyncer.php + + - + message: "#^Expression on left side of \\?\\? is not nullable\\.$#" + count: 2 + path: src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraphRemoteCalendarConnector.php + + - + message: "#^PHPDoc tag @return has invalid value \\(array\\{\\?id\\: string, \\?lastModifiedDateTime\\: int, \\?changeKey\\: string\\}\\)\\: Unexpected token \"\\:\", expected '\\}' at offset 129$#" + count: 2 + path: src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraphRemoteCalendarConnector.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and string will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraphRemoteCalendarConnector.php + + - + message: "#^Property Chill\\\\CalendarBundle\\\\Security\\\\Voter\\\\CalendarVoter\\:\\:\\$authorizationHelper is unused\\.$#" + count: 1 + path: src/Bundle/ChillCalendarBundle/Security/Voter/CalendarVoter.php + + - + message: "#^Property Chill\\\\CalendarBundle\\\\Security\\\\Voter\\\\CalendarVoter\\:\\:\\$centerResolverManager is unused\\.$#" + count: 1 + path: src/Bundle/ChillCalendarBundle/Security/Voter/CalendarVoter.php + + - + message: "#^Call to an undefined method Symfony\\\\Component\\\\Console\\\\Helper\\\\HelperInterface\\:\\:ask\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Command/CreateFieldsOnGroupCommand.php + + - + message: "#^If condition is always true\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Command/CreateFieldsOnGroupCommand.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and Chill\\\\CustomFieldsBundle\\\\Service\\\\CustomFieldInterface will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Command/CreateFieldsOnGroupCommand.php + + - + message: "#^Method Chill\\\\CustomFieldsBundle\\\\Controller\\\\CustomFieldController\\:\\:createCreateForm\\(\\) should return Symfony\\\\Component\\\\Form\\\\Form but returns Symfony\\\\Component\\\\Form\\\\FormInterface\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Controller/CustomFieldController.php + + - + message: "#^Method Chill\\\\CustomFieldsBundle\\\\Controller\\\\CustomFieldController\\:\\:createEditForm\\(\\) should return Symfony\\\\Component\\\\Form\\\\Form but returns Symfony\\\\Component\\\\Form\\\\FormInterface\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Controller/CustomFieldController.php + + - + message: "#^PHPDoc tag @param has invalid value \\(string\\)\\: Unexpected token \"\\\\n \\* \", expected variable at offset 130$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Controller/CustomFieldController.php + + - + message: "#^Ternary operator condition is always true\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Controller/CustomFieldController.php + + - + message: "#^Call to an undefined method Doctrine\\\\Persistence\\\\ObjectManager\\:\\:createQuery\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Controller/CustomFieldsGroupController.php + + - + message: "#^Call to an undefined method Doctrine\\\\Persistence\\\\ObjectRepository\\\\:\\:findOneByEntity\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Controller/CustomFieldsGroupController.php + + - + message: "#^Call to an undefined method Doctrine\\\\Persistence\\\\ObjectRepository\\\\:\\:findOneById\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Controller/CustomFieldsGroupController.php + + - + message: "#^Call to an undefined method Symfony\\\\Component\\\\Form\\\\FormInterface\\:\\:isClicked\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Controller/CustomFieldsGroupController.php + + - + message: "#^Method Chill\\\\CustomFieldsBundle\\\\Controller\\\\CustomFieldsGroupController\\:\\:createCreateForm\\(\\) should return Symfony\\\\Component\\\\Form\\\\Form but returns Symfony\\\\Component\\\\Form\\\\FormInterface\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Controller/CustomFieldsGroupController.php + + - + message: "#^Method Chill\\\\CustomFieldsBundle\\\\Controller\\\\CustomFieldsGroupController\\:\\:createEditForm\\(\\) should return Symfony\\\\Component\\\\Form\\\\Form but returns Symfony\\\\Component\\\\Form\\\\FormInterface\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Controller/CustomFieldsGroupController.php + + - + message: "#^Method Chill\\\\CustomFieldsBundle\\\\Controller\\\\CustomFieldsGroupController\\:\\:createMakeDefaultForm\\(\\) should return Symfony\\\\Component\\\\Form\\\\Form but returns Symfony\\\\Component\\\\Form\\\\FormInterface\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Controller/CustomFieldsGroupController.php + + - + message: "#^Call to an undefined method Symfony\\\\Contracts\\\\Translation\\\\TranslatorInterface\\:\\:getFallbackLocales\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldChoice.php + + - + message: "#^Method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldChoice\\:\\:buildOptionsForm\\(\\) should return Symfony\\\\Component\\\\Form\\\\FormTypeInterface\\|null but returns Symfony\\\\Component\\\\Form\\\\FormBuilderInterface\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldChoice.php + + - + message: "#^Method Symfony\\\\Component\\\\Form\\\\FormBuilderInterface\\:\\:create\\(\\) invoked with 4 parameters, 1\\-3 required\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldChoice.php + + - + message: "#^Parameter \\$builder of method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldChoice\\:\\:buildForm\\(\\) has invalid type Chill\\\\CustomFieldsBundle\\\\CustomField\\\\FormBuilderInterface\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldChoice.php + + - + message: "#^Parameter \\$builder of method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldChoice\\:\\:buildOptionsForm\\(\\) has invalid type Chill\\\\CustomFieldsBundle\\\\CustomField\\\\FormBuilderInterface\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldChoice.php + + - + message: "#^Parameter \\$customField of method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldChoice\\:\\:buildForm\\(\\) has invalid type Chill\\\\CustomFieldsBundle\\\\CustomField\\\\CustomField\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldChoice.php + + - + message: "#^Parameter \\$customField of method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldChoice\\:\\:deserialize\\(\\) has invalid type Chill\\\\CustomFieldsBundle\\\\CustomField\\\\CustomField\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldChoice.php + + - + message: "#^Parameter \\$customField of method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldChoice\\:\\:render\\(\\) has invalid type Chill\\\\CustomFieldsBundle\\\\CustomField\\\\CustomField\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldChoice.php + + - + message: "#^Parameter \\$customField of method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldChoice\\:\\:serialize\\(\\) has invalid type Chill\\\\CustomFieldsBundle\\\\CustomField\\\\CustomField\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldChoice.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and array\\|string will always evaluate to false\\.$#" + count: 2 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldChoice.php + + - + message: "#^Unreachable statement \\- code above always terminates\\.$#" + count: 3 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldChoice.php + + - + message: "#^Method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldDate\\:\\:buildOptionsForm\\(\\) should return Symfony\\\\Component\\\\Form\\\\FormTypeInterface\\|null but returns Symfony\\\\Component\\\\Form\\\\FormBuilderInterface\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldDate.php + + - + message: "#^Method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldDate\\:\\:render\\(\\) should return string but returns null\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldDate.php + + - + message: "#^Parameter \\$builder of method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldDate\\:\\:buildForm\\(\\) has invalid type Chill\\\\CustomFieldsBundle\\\\CustomField\\\\FormBuilderInterface\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldDate.php + + - + message: "#^Parameter \\$builder of method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldDate\\:\\:buildOptionsForm\\(\\) has invalid type Chill\\\\CustomFieldsBundle\\\\CustomField\\\\FormBuilderInterface\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldDate.php + + - + message: "#^Parameter \\$customField of method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldDate\\:\\:buildForm\\(\\) has invalid type Chill\\\\CustomFieldsBundle\\\\CustomField\\\\CustomField\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldDate.php + + - + message: "#^Parameter \\$customField of method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldDate\\:\\:deserialize\\(\\) has invalid type Chill\\\\CustomFieldsBundle\\\\CustomField\\\\CustomField\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldDate.php + + - + message: "#^Parameter \\$customField of method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldDate\\:\\:render\\(\\) has invalid type Chill\\\\CustomFieldsBundle\\\\CustomField\\\\CustomField\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldDate.php + + - + message: "#^Parameter \\$customField of method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldDate\\:\\:serialize\\(\\) has invalid type Chill\\\\CustomFieldsBundle\\\\CustomField\\\\CustomField\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldDate.php + + - + message: "#^PHPDoc tag @param for parameter \\$builder with type Chill\\\\CustomFieldsBundle\\\\CustomField\\\\FormBuilderInterface is not subtype of native type Symfony\\\\Component\\\\Form\\\\FormBuilderInterface\\.$#" + count: 2 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldInterface.php + + - + message: "#^PHPDoc tag @param for parameter \\$customField with type Chill\\\\CustomFieldsBundle\\\\CustomField\\\\CustomField is not subtype of native type Chill\\\\CustomFieldsBundle\\\\Entity\\\\CustomField\\.$#" + count: 4 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldInterface.php + + - + message: "#^Parameter \\$builder of method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldInterface\\:\\:buildForm\\(\\) has invalid type Chill\\\\CustomFieldsBundle\\\\CustomField\\\\FormBuilderInterface\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldInterface.php + + - + message: "#^Parameter \\$builder of method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldInterface\\:\\:buildOptionsForm\\(\\) has invalid type Chill\\\\CustomFieldsBundle\\\\CustomField\\\\FormBuilderInterface\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldInterface.php + + - + message: "#^Parameter \\$customField of method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldInterface\\:\\:buildForm\\(\\) has invalid type Chill\\\\CustomFieldsBundle\\\\CustomField\\\\CustomField\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldInterface.php + + - + message: "#^Parameter \\$customField of method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldInterface\\:\\:deserialize\\(\\) has invalid type Chill\\\\CustomFieldsBundle\\\\CustomField\\\\CustomField\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldInterface.php + + - + message: "#^Parameter \\$customField of method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldInterface\\:\\:render\\(\\) has invalid type Chill\\\\CustomFieldsBundle\\\\CustomField\\\\CustomField\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldInterface.php + + - + message: "#^Parameter \\$customField of method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldInterface\\:\\:serialize\\(\\) has invalid type Chill\\\\CustomFieldsBundle\\\\CustomField\\\\CustomField\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldInterface.php + + - + message: "#^Anonymous function never returns null so it can be removed from the return type\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldLongChoice.php + + - + message: "#^Method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldLongChoice\\:\\:buildOptionsForm\\(\\) should return Symfony\\\\Component\\\\Form\\\\FormTypeInterface\\|null but returns Symfony\\\\Component\\\\Form\\\\FormBuilderInterface\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldLongChoice.php + + - + message: "#^Parameter \\$builder of method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldLongChoice\\:\\:buildForm\\(\\) has invalid type Chill\\\\CustomFieldsBundle\\\\CustomField\\\\FormBuilderInterface\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldLongChoice.php + + - + message: "#^Parameter \\$builder of method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldLongChoice\\:\\:buildOptionsForm\\(\\) has invalid type Chill\\\\CustomFieldsBundle\\\\CustomField\\\\FormBuilderInterface\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldLongChoice.php + + - + message: "#^Parameter \\$customField of method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldLongChoice\\:\\:buildForm\\(\\) has invalid type Chill\\\\CustomFieldsBundle\\\\CustomField\\\\CustomField\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldLongChoice.php + + - + message: "#^Parameter \\$customField of method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldLongChoice\\:\\:deserialize\\(\\) has invalid type Chill\\\\CustomFieldsBundle\\\\CustomField\\\\CustomField\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldLongChoice.php + + - + message: "#^Parameter \\$customField of method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldLongChoice\\:\\:render\\(\\) has invalid type Chill\\\\CustomFieldsBundle\\\\CustomField\\\\CustomField\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldLongChoice.php + + - + message: "#^Parameter \\$customField of method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldLongChoice\\:\\:serialize\\(\\) has invalid type Chill\\\\CustomFieldsBundle\\\\CustomField\\\\CustomField\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldLongChoice.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and Chill\\\\CustomFieldsBundle\\\\Entity\\\\CustomFieldLongChoice\\\\Option will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldLongChoice.php + + - + message: "#^Ternary operator condition is always true\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldLongChoice.php + + - + message: "#^Method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldNumber\\:\\:buildOptionsForm\\(\\) should return Symfony\\\\Component\\\\Form\\\\FormTypeInterface\\|null but returns Symfony\\\\Component\\\\Form\\\\FormBuilderInterface\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldNumber.php + + - + message: "#^Parameter \\$builder of method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldNumber\\:\\:buildForm\\(\\) has invalid type Chill\\\\CustomFieldsBundle\\\\CustomField\\\\FormBuilderInterface\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldNumber.php + + - + message: "#^Parameter \\$builder of method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldNumber\\:\\:buildOptionsForm\\(\\) has invalid type Chill\\\\CustomFieldsBundle\\\\CustomField\\\\FormBuilderInterface\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldNumber.php + + - + message: "#^Parameter \\$customField of method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldNumber\\:\\:buildForm\\(\\) has invalid type Chill\\\\CustomFieldsBundle\\\\CustomField\\\\CustomField\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldNumber.php + + - + message: "#^Parameter \\$customField of method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldNumber\\:\\:deserialize\\(\\) has invalid type Chill\\\\CustomFieldsBundle\\\\CustomField\\\\CustomField\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldNumber.php + + - + message: "#^Parameter \\$customField of method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldNumber\\:\\:render\\(\\) has invalid type Chill\\\\CustomFieldsBundle\\\\CustomField\\\\CustomField\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldNumber.php + + - + message: "#^Parameter \\$customField of method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldNumber\\:\\:serialize\\(\\) has invalid type Chill\\\\CustomFieldsBundle\\\\CustomField\\\\CustomField\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldNumber.php + + - + message: "#^Method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldText\\:\\:buildOptionsForm\\(\\) should return Symfony\\\\Component\\\\Form\\\\FormTypeInterface\\|null but returns Symfony\\\\Component\\\\Form\\\\FormBuilderInterface\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldText.php + + - + message: "#^Parameter \\$builder of method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldText\\:\\:buildForm\\(\\) has invalid type Chill\\\\CustomFieldsBundle\\\\CustomField\\\\FormBuilderInterface\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldText.php + + - + message: "#^Parameter \\$builder of method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldText\\:\\:buildOptionsForm\\(\\) has invalid type Chill\\\\CustomFieldsBundle\\\\CustomField\\\\FormBuilderInterface\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldText.php + + - + message: "#^Parameter \\$customField of method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldText\\:\\:buildForm\\(\\) has invalid type Chill\\\\CustomFieldsBundle\\\\CustomField\\\\CustomField\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldText.php + + - + message: "#^Parameter \\$customField of method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldText\\:\\:deserialize\\(\\) has invalid type Chill\\\\CustomFieldsBundle\\\\CustomField\\\\CustomField\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldText.php + + - + message: "#^Parameter \\$customField of method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldText\\:\\:render\\(\\) has invalid type Chill\\\\CustomFieldsBundle\\\\CustomField\\\\CustomField\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldText.php + + - + message: "#^Parameter \\$customField of method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldText\\:\\:serialize\\(\\) has invalid type Chill\\\\CustomFieldsBundle\\\\CustomField\\\\CustomField\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldText.php + + - + message: "#^Property Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldText\\:\\:\\$requestStack is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldText.php + + - + message: "#^Method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldTitle\\:\\:buildOptionsForm\\(\\) should return Symfony\\\\Component\\\\Form\\\\FormTypeInterface\\|null but returns Symfony\\\\Component\\\\Form\\\\FormBuilderInterface\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldTitle.php + + - + message: "#^Parameter \\$builder of method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldTitle\\:\\:buildForm\\(\\) has invalid type Chill\\\\CustomFieldsBundle\\\\CustomField\\\\FormBuilderInterface\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldTitle.php + + - + message: "#^Parameter \\$builder of method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldTitle\\:\\:buildOptionsForm\\(\\) has invalid type Chill\\\\CustomFieldsBundle\\\\CustomField\\\\FormBuilderInterface\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldTitle.php + + - + message: "#^Parameter \\$customField of method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldTitle\\:\\:buildForm\\(\\) has invalid type Chill\\\\CustomFieldsBundle\\\\CustomField\\\\CustomField\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldTitle.php + + - + message: "#^Parameter \\$customField of method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldTitle\\:\\:deserialize\\(\\) has invalid type Chill\\\\CustomFieldsBundle\\\\CustomField\\\\CustomField\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldTitle.php + + - + message: "#^Parameter \\$customField of method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldTitle\\:\\:render\\(\\) has invalid type Chill\\\\CustomFieldsBundle\\\\CustomField\\\\CustomField\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldTitle.php + + - + message: "#^Parameter \\$customField of method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldTitle\\:\\:serialize\\(\\) has invalid type Chill\\\\CustomFieldsBundle\\\\CustomField\\\\CustomField\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldTitle.php + + - + message: "#^Property Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldTitle\\:\\:\\$requestStack is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldTitle.php + + - + message: "#^Call to an undefined method Symfony\\\\Component\\\\Config\\\\Definition\\\\Builder\\\\NodeDefinition\\:\\:children\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/DependencyInjection/Configuration.php + + - + message: "#^Method Symfony\\\\Component\\\\Config\\\\Definition\\\\Builder\\\\TreeBuilder\\:\\:getRootNode\\(\\) invoked with 1 parameter, 0 required\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/DependencyInjection/Configuration.php + + - + message: "#^Method Chill\\\\CustomFieldsBundle\\\\Entity\\\\CustomField\\:\\:getName\\(\\) should return array but returns string\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Entity/CustomField.php + + - + message: "#^Property Chill\\\\CustomFieldsBundle\\\\Entity\\\\CustomField\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Entity/CustomField.php + + - + message: "#^Property Chill\\\\CustomFieldsBundle\\\\Entity\\\\CustomFieldLongChoice\\\\Option\\:\\:\\$children is never written, only read\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Entity/CustomFieldLongChoice/Option.php + + - + message: "#^Property Chill\\\\CustomFieldsBundle\\\\Entity\\\\CustomFieldLongChoice\\\\Option\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Entity/CustomFieldLongChoice/Option.php + + - + message: "#^Property Chill\\\\CustomFieldsBundle\\\\Entity\\\\CustomFieldsDefaultGroup\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Entity/CustomFieldsDefaultGroup.php + + - + message: "#^Method Chill\\\\CustomFieldsBundle\\\\Entity\\\\CustomFieldsGroup\\:\\:getActiveCustomFields\\(\\) should return Doctrine\\\\Common\\\\Collections\\\\Collection but returns array\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Entity/CustomFieldsGroup.php + + - + message: "#^Method Chill\\\\CustomFieldsBundle\\\\Entity\\\\CustomFieldsGroup\\:\\:getName\\(\\) should return array but returns string\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Entity/CustomFieldsGroup.php + + - + message: "#^Property Chill\\\\CustomFieldsBundle\\\\Entity\\\\CustomFieldsGroup\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Entity/CustomFieldsGroup.php + + - + message: "#^Call to method buildOptionsForm\\(\\) on an unknown class Chill\\\\CustomFieldsBundle\\\\Service\\\\CustomFieldInterface\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Form/CustomFieldType.php + + - + message: "#^PHPDoc tag @param for parameter \\$resolver with type Chill\\\\CustomFieldsBundle\\\\Form\\\\OptionsResolverInterface is not subtype of native type Symfony\\\\Component\\\\OptionsResolver\\\\OptionsResolver\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Form/CustomFieldType.php + + - + message: "#^Parameter \\$resolver of method Chill\\\\CustomFieldsBundle\\\\Form\\\\CustomFieldType\\:\\:configureOptions\\(\\) has invalid type Chill\\\\CustomFieldsBundle\\\\Form\\\\OptionsResolverInterface\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Form/CustomFieldType.php + + - + message: "#^Property Chill\\\\CustomFieldsBundle\\\\Form\\\\CustomFieldsGroupType\\:\\:\\$translator \\(Symfony\\\\Component\\\\Translation\\\\TranslatorInterface\\) does not accept Symfony\\\\Contracts\\\\Translation\\\\TranslatorInterface\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Form/CustomFieldsGroupType.php + + - + message: "#^Instanceof between Chill\\\\CustomFieldsBundle\\\\Entity\\\\CustomFieldsGroup and Chill\\\\CustomFieldsBundle\\\\Entity\\\\CustomFieldsGroup will always evaluate to true\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Form/DataTransformer/CustomFieldsGroupToIdTransformer.php + + - + message: "#^Instanceof between string and Chill\\\\CustomFieldsBundle\\\\Entity\\\\CustomFieldsGroup will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Form/DataTransformer/CustomFieldsGroupToIdTransformer.php + + - + message: "#^Method Chill\\\\CustomFieldsBundle\\\\Form\\\\DataTransformer\\\\CustomFieldsGroupToIdTransformer\\:\\:transform\\(\\) should return string but returns int\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Form/DataTransformer/CustomFieldsGroupToIdTransformer.php + + - + message: "#^Call to an undefined method Chill\\\\CustomFieldsBundle\\\\Entity\\\\CustomField\\:\\:getLabel\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Form/DataTransformer/JsonCustomFieldToArrayTransformer.php + + - + message: "#^Call to an undefined method Doctrine\\\\Persistence\\\\ObjectRepository\\\\:\\:findOneById\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Form/DataTransformer/JsonCustomFieldToArrayTransformer.php + + - + message: "#^Call to method getCustomFieldByType\\(\\) on an unknown class Chill\\\\CustomFieldsBundle\\\\Form\\\\Type\\\\CustomFieldCompiler\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Form/Type/CustomFieldType.php + + - + message: "#^Property Chill\\\\CustomFieldsBundle\\\\Form\\\\Type\\\\CustomFieldType\\:\\:\\$customFieldCompiler \\(Chill\\\\CustomFieldsBundle\\\\Form\\\\Type\\\\CustomFieldCompiler\\) does not accept Chill\\\\CustomFieldsBundle\\\\Service\\\\CustomFieldProvider\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Form/Type/CustomFieldType.php + + - + message: "#^Property Chill\\\\CustomFieldsBundle\\\\Form\\\\Type\\\\CustomFieldType\\:\\:\\$customFieldCompiler has unknown class Chill\\\\CustomFieldsBundle\\\\Form\\\\Type\\\\CustomFieldCompiler as its type\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Form/Type/CustomFieldType.php + + - + message: "#^Property Chill\\\\CustomFieldsBundle\\\\Form\\\\Type\\\\CustomFieldType\\:\\:\\$om is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Form/Type/CustomFieldType.php + + - + message: "#^Invalid array key type Chill\\\\CustomFieldsBundle\\\\Service\\\\type\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Service/CustomFieldProvider.php + + - + message: "#^Method Chill\\\\CustomFieldsBundle\\\\Service\\\\CustomFieldProvider\\:\\:getCustomFieldByType\\(\\) has invalid return type Chill\\\\CustomFieldsBundle\\\\Service\\\\CustomFieldInterface\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Service/CustomFieldProvider.php + + - + message: "#^Parameter \\$serviceName of method Chill\\\\CustomFieldsBundle\\\\Service\\\\CustomFieldProvider\\:\\:addCustomField\\(\\) has invalid type Chill\\\\CustomFieldsBundle\\\\Service\\\\type\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Service/CustomFieldProvider.php + + - + message: "#^Parameter \\$type of method Chill\\\\CustomFieldsBundle\\\\Service\\\\CustomFieldProvider\\:\\:addCustomField\\(\\) has invalid type Chill\\\\CustomFieldsBundle\\\\Service\\\\type\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Service/CustomFieldProvider.php + + - + message: "#^Property Chill\\\\CustomFieldsBundle\\\\Service\\\\CustomFieldProvider\\:\\:\\$container \\(Chill\\\\CustomFieldsBundle\\\\Service\\\\Container\\) does not accept Symfony\\\\Component\\\\DependencyInjection\\\\ContainerInterface\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Service/CustomFieldProvider.php + + - + message: "#^Property Chill\\\\CustomFieldsBundle\\\\Service\\\\CustomFieldProvider\\:\\:\\$container has unknown class Chill\\\\CustomFieldsBundle\\\\Service\\\\Container as its type\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Service/CustomFieldProvider.php + + - + message: "#^Property Chill\\\\CustomFieldsBundle\\\\Service\\\\CustomFieldProvider\\:\\:\\$container is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Service/CustomFieldProvider.php + + - + message: "#^Call to method deserialize\\(\\) on an unknown class Chill\\\\CustomFieldsBundle\\\\Service\\\\CustomFieldInterface\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Service/CustomFieldsHelper.php + + - + message: "#^Call to method isEmptyValue\\(\\) on an unknown class Chill\\\\CustomFieldsBundle\\\\Service\\\\CustomFieldInterface\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Service/CustomFieldsHelper.php + + - + message: "#^Call to method render\\(\\) on an unknown class Chill\\\\CustomFieldsBundle\\\\Service\\\\CustomFieldInterface\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Service/CustomFieldsHelper.php + + - + message: "#^Method Chill\\\\CustomFieldsBundle\\\\Service\\\\CustomFieldsHelper\\:\\:renderCustomField\\(\\) has invalid return type Chill\\\\CustomFieldsBundle\\\\Service\\\\The\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Service/CustomFieldsHelper.php + + - + message: "#^Property Chill\\\\CustomFieldsBundle\\\\Service\\\\CustomFieldsHelper\\:\\:\\$em is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Service/CustomFieldsHelper.php + + - + message: "#^Method Chill\\\\CustomFieldsBundle\\\\Templating\\\\Twig\\\\CustomFieldRenderingTwig\\:\\:renderWidget\\(\\) should return string but returns Chill\\\\CustomFieldsBundle\\\\Service\\\\The\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Templating/Twig/CustomFieldRenderingTwig.php + + - + message: "#^Property Chill\\\\CustomFieldsBundle\\\\Templating\\\\Twig\\\\CustomFieldRenderingTwig\\:\\:\\$container \\(Symfony\\\\Component\\\\DependencyInjection\\\\Container\\) does not accept Symfony\\\\Component\\\\DependencyInjection\\\\ContainerInterface\\|null\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Templating/Twig/CustomFieldRenderingTwig.php + + - + message: "#^Parameter \\$customFielsGroup of method Chill\\\\CustomFieldsBundle\\\\Templating\\\\Twig\\\\CustomFieldsGroupRenderingTwig\\:\\:renderWidget\\(\\) has invalid type Chill\\\\CustomFieldsBundle\\\\Templating\\\\Twig\\\\CustomFieldsGroud\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Templating/Twig/CustomFieldsGroupRenderingTwig.php + + - + message: "#^Property Chill\\\\CustomFieldsBundle\\\\Templating\\\\Twig\\\\CustomFieldsGroupRenderingTwig\\:\\:\\$container \\(Symfony\\\\Component\\\\DependencyInjection\\\\Container\\) does not accept Symfony\\\\Component\\\\DependencyInjection\\\\ContainerInterface\\|null\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Templating/Twig/CustomFieldsGroupRenderingTwig.php + + - + message: "#^Left side of && is always true\\.$#" + count: 1 + path: src/Bundle/ChillDocGeneratorBundle/Controller/DocGeneratorTemplateController.php + + - + message: "#^PHPDoc tag @return with type void is incompatible with native type Symfony\\\\Component\\\\HttpFoundation\\\\RedirectResponse\\.$#" + count: 1 + path: src/Bundle/ChillDocGeneratorBundle/Controller/DocGeneratorTemplateController.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and int will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillDocGeneratorBundle/Controller/DocGeneratorTemplateController.php + + - + message: "#^Binary operation \"\\.\" between 'Adding doc…' and array\\{filename\\: 'pKNlhCrQDCRsAuC8vYH…', key\\: '\\{\"alg\"\\:\"A256CBC\",…', iv\\: '\\[86,231,83,148,117…', type\\: 'application/vnd…'\\} results in an error\\.$#" + count: 1 + path: src/Bundle/ChillDocGeneratorBundle/DataFixtures/ORM/LoadDocGeneratorTemplate.php + + - + message: "#^Call to an undefined method Symfony\\\\Component\\\\Config\\\\Definition\\\\Builder\\\\NodeDefinition\\:\\:children\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillDocGeneratorBundle/DependencyInjection/Configuration.php + + - + message: "#^Property Chill\\\\DocGeneratorBundle\\\\Entity\\\\DocGeneratorTemplate\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: src/Bundle/ChillDocGeneratorBundle/Entity/DocGeneratorTemplate.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and Doctrine\\\\Common\\\\Collections\\\\Collection will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillDocGeneratorBundle/Serializer/Normalizer/CollectionDocGenNormalizer.php + + - + message: "#^Call to an undefined method ReflectionType\\:\\:getName\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillDocGeneratorBundle/Serializer/Normalizer/DocGenObjectNormalizer.php + + - + message: "#^Expression on left side of \\?\\? is not nullable\\.$#" + count: 1 + path: src/Bundle/ChillDocGeneratorBundle/Serializer/Normalizer/DocGenObjectNormalizer.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and Chill\\\\PersonBundle\\\\Entity\\\\AccompanyingPeriod will always evaluate to false\\.$#" + count: 2 + path: src/Bundle/ChillDocStoreBundle/Controller/DocumentAccompanyingCourseController.php + + - + message: "#^Call to an undefined method Doctrine\\\\Persistence\\\\ObjectManager\\:\\:createQuery\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillDocStoreBundle/Controller/DocumentCategoryController.php + + - + message: "#^Method Symfony\\\\Contracts\\\\EventDispatcher\\\\EventDispatcherInterface\\:\\:dispatch\\(\\) invoked with 2 parameters, 1 required\\.$#" + count: 4 + path: src/Bundle/ChillDocStoreBundle/Controller/DocumentPersonController.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and Chill\\\\PersonBundle\\\\Entity\\\\Person will always evaluate to false\\.$#" + count: 2 + path: src/Bundle/ChillDocStoreBundle/Controller/DocumentPersonController.php + + - + message: "#^Method Symfony\\\\Component\\\\Config\\\\Definition\\\\Builder\\\\TreeBuilder\\:\\:getRootNode\\(\\) invoked with 1 parameter, 0 required\\.$#" + count: 1 + path: src/Bundle/ChillDocStoreBundle/DependencyInjection/Configuration.php + + - + message: "#^Property Chill\\\\DocStoreBundle\\\\Entity\\\\Document\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: src/Bundle/ChillDocStoreBundle/Entity/Document.php + + - + message: "#^Property Chill\\\\DocStoreBundle\\\\Entity\\\\Document\\:\\:\\$user has unknown class Chill\\\\PersonBundle\\\\Entity\\\\user as its type\\.$#" + count: 1 + path: src/Bundle/ChillDocStoreBundle/Entity/Document.php + + - + message: "#^Property Chill\\\\DocStoreBundle\\\\Entity\\\\StoredObject\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: src/Bundle/ChillDocStoreBundle/Entity/StoredObject.php + + - + message: "#^Call to an undefined method Doctrine\\\\ORM\\\\EntityRepository\\\\:\\:findByFilename\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillDocStoreBundle/Object/ObjectToAsyncFileTransformer.php + + - + message: "#^Property Chill\\\\DocStoreBundle\\\\Repository\\\\AccompanyingCourseDocumentRepository\\:\\:\\$em is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillDocStoreBundle/Repository/AccompanyingCourseDocumentRepository.php + + - + message: "#^Property Chill\\\\DocStoreBundle\\\\Repository\\\\DocumentCategoryRepository\\:\\:\\$em is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillDocStoreBundle/Repository/DocumentCategoryRepository.php + + - + message: "#^Instanceof between Chill\\\\DocStoreBundle\\\\Entity\\\\PersonDocument and Chill\\\\DocStoreBundle\\\\Entity\\\\PersonDocument will always evaluate to true\\.$#" + count: 1 + path: src/Bundle/ChillDocStoreBundle/Security/Authorization/PersonDocumentVoter.php + + - + message: "#^Default value of the parameter \\#5 \\$options \\(array\\{\\}\\) of method Chill\\\\DocStoreBundle\\\\Templating\\\\WopiEditTwigExtensionRuntime\\:\\:renderButtonGroup\\(\\) is incompatible with type array\\{small\\: bool\\}\\.$#" + count: 1 + path: src/Bundle/ChillDocStoreBundle/Templating/WopiEditTwigExtensionRuntime.php + + - + message: "#^Property Chill\\\\DocStoreBundle\\\\Templating\\\\WopiEditTwigExtensionRuntime\\:\\:\\$discovery is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillDocStoreBundle/Templating/WopiEditTwigExtensionRuntime.php + + - + message: "#^Call to an undefined method Doctrine\\\\Persistence\\\\ObjectRepository\\\\:\\:countByPerson\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillEventBundle/Controller/EventController.php + + - + message: "#^Call to an undefined method Doctrine\\\\Persistence\\\\ObjectRepository\\\\:\\:findByPersonInCircle\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillEventBundle/Controller/EventController.php + + - + message: "#^Cannot call method getUsernameCanonical\\(\\) on int\\\\|int\\<1, max\\>\\.$#" + count: 1 + path: src/Bundle/ChillEventBundle/Controller/EventController.php + + - + message: "#^Method Symfony\\\\Contracts\\\\EventDispatcher\\\\EventDispatcherInterface\\:\\:dispatch\\(\\) invoked with 2 parameters, 1 required\\.$#" + count: 1 + path: src/Bundle/ChillEventBundle/Controller/EventController.php + + - + message: "#^Negated boolean expression is always false\\.$#" + count: 1 + path: src/Bundle/ChillEventBundle/Controller/EventController.php + + - + message: "#^Method Chill\\\\EventBundle\\\\Controller\\\\EventTypeController\\:\\:createCreateForm\\(\\) should return Symfony\\\\Component\\\\Form\\\\Form but returns Symfony\\\\Component\\\\Form\\\\FormInterface\\.$#" + count: 1 + path: src/Bundle/ChillEventBundle/Controller/EventTypeController.php + + - + message: "#^Method Chill\\\\EventBundle\\\\Controller\\\\EventTypeController\\:\\:createDeleteForm\\(\\) should return Symfony\\\\Component\\\\Form\\\\Form but returns Symfony\\\\Component\\\\Form\\\\FormInterface\\.$#" + count: 1 + path: src/Bundle/ChillEventBundle/Controller/EventTypeController.php + + - + message: "#^Method Chill\\\\EventBundle\\\\Controller\\\\EventTypeController\\:\\:createEditForm\\(\\) should return Symfony\\\\Component\\\\Form\\\\Form but returns Symfony\\\\Component\\\\Form\\\\FormInterface\\.$#" + count: 1 + path: src/Bundle/ChillEventBundle/Controller/EventTypeController.php + + - + message: "#^Call to an undefined method Traversable\\:\\:count\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillEventBundle/Controller/ParticipationController.php + + - + message: "#^Call to an undefined method Traversable\\:\\:current\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillEventBundle/Controller/ParticipationController.php + + - + message: "#^Method Chill\\\\EventBundle\\\\Controller\\\\ParticipationController\\:\\:handleRequest\\(\\) has invalid return type Chill\\\\EventBundle\\\\Controller\\\\Participations\\.$#" + count: 1 + path: src/Bundle/ChillEventBundle/Controller/ParticipationController.php + + - + message: "#^Strict comparison using \\!\\=\\= between null and int will always evaluate to true\\.$#" + count: 1 + path: src/Bundle/ChillEventBundle/Controller/ParticipationController.php + + - + message: "#^Strict comparison using \\!\\=\\= between null and int\\|string will always evaluate to true\\.$#" + count: 1 + path: src/Bundle/ChillEventBundle/Controller/ParticipationController.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and Chill\\\\EventBundle\\\\Entity\\\\Event will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillEventBundle/Controller/ParticipationController.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and Chill\\\\EventBundle\\\\Entity\\\\Participation will always evaluate to false\\.$#" + count: 2 + path: src/Bundle/ChillEventBundle/Controller/ParticipationController.php + + - + message: "#^Method Chill\\\\EventBundle\\\\Controller\\\\RoleController\\:\\:createCreateForm\\(\\) should return Symfony\\\\Component\\\\Form\\\\Form but returns Symfony\\\\Component\\\\Form\\\\FormInterface\\.$#" + count: 1 + path: src/Bundle/ChillEventBundle/Controller/RoleController.php + + - + message: "#^Method Chill\\\\EventBundle\\\\Controller\\\\RoleController\\:\\:createDeleteForm\\(\\) should return Symfony\\\\Component\\\\Form\\\\Form but returns Symfony\\\\Component\\\\Form\\\\FormInterface\\.$#" + count: 1 + path: src/Bundle/ChillEventBundle/Controller/RoleController.php + + - + message: "#^Method Chill\\\\EventBundle\\\\Controller\\\\RoleController\\:\\:createEditForm\\(\\) should return Symfony\\\\Component\\\\Form\\\\Form but returns Symfony\\\\Component\\\\Form\\\\FormInterface\\.$#" + count: 1 + path: src/Bundle/ChillEventBundle/Controller/RoleController.php + + - + message: "#^Method Chill\\\\EventBundle\\\\Controller\\\\StatusController\\:\\:createCreateForm\\(\\) should return Symfony\\\\Component\\\\Form\\\\Form but returns Symfony\\\\Component\\\\Form\\\\FormInterface\\.$#" + count: 1 + path: src/Bundle/ChillEventBundle/Controller/StatusController.php + + - + message: "#^Method Chill\\\\EventBundle\\\\Controller\\\\StatusController\\:\\:createDeleteForm\\(\\) should return Symfony\\\\Component\\\\Form\\\\Form but returns Symfony\\\\Component\\\\Form\\\\FormInterface\\.$#" + count: 1 + path: src/Bundle/ChillEventBundle/Controller/StatusController.php + + - + message: "#^Method Chill\\\\EventBundle\\\\Controller\\\\StatusController\\:\\:createEditForm\\(\\) should return Symfony\\\\Component\\\\Form\\\\Form but returns Symfony\\\\Component\\\\Form\\\\FormInterface\\.$#" + count: 1 + path: src/Bundle/ChillEventBundle/Controller/StatusController.php + + - + message: "#^Method Symfony\\\\Component\\\\Config\\\\Definition\\\\Builder\\\\TreeBuilder\\:\\:getRootNode\\(\\) invoked with 1 parameter, 0 required\\.$#" + count: 1 + path: src/Bundle/ChillEventBundle/DependencyInjection/Configuration.php + + - + message: "#^Call to an undefined method Chill\\\\EventBundle\\\\Entity\\\\Participation\\:\\:getIterator\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillEventBundle/Entity/Event.php + + - + message: "#^Call to an undefined method Chill\\\\EventBundle\\\\Entity\\\\Participation\\:\\:removeElement\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillEventBundle/Entity/Event.php + + - + message: "#^Method Chill\\\\EventBundle\\\\Entity\\\\Event\\:\\:getModerator\\(\\) should return int but returns Chill\\\\MainBundle\\\\Entity\\\\User\\.$#" + count: 1 + path: src/Bundle/ChillEventBundle/Entity/Event.php + + - + message: "#^Property Chill\\\\EventBundle\\\\Entity\\\\Event\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: src/Bundle/ChillEventBundle/Entity/Event.php + + - + message: "#^Property Chill\\\\EventBundle\\\\Entity\\\\Event\\:\\:\\$moderator \\(Chill\\\\MainBundle\\\\Entity\\\\User\\) does not accept int\\.$#" + count: 1 + path: src/Bundle/ChillEventBundle/Entity/Event.php + + - + message: "#^Property Chill\\\\EventBundle\\\\Entity\\\\Event\\:\\:\\$participations \\(Chill\\\\EventBundle\\\\Entity\\\\Participation\\) does not accept Doctrine\\\\Common\\\\Collections\\\\ArrayCollection\\<\\*NEVER\\*, \\*NEVER\\*\\>\\.$#" + count: 1 + path: src/Bundle/ChillEventBundle/Entity/Event.php + + - + message: "#^Property Chill\\\\EventBundle\\\\Entity\\\\EventType\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: src/Bundle/ChillEventBundle/Entity/EventType.php + + - + message: "#^Property Chill\\\\EventBundle\\\\Entity\\\\Participation\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: src/Bundle/ChillEventBundle/Entity/Participation.php + + - + message: "#^Result of \\|\\| is always false\\.$#" + count: 2 + path: src/Bundle/ChillEventBundle/Entity/Participation.php + + - + message: "#^Strict comparison using \\=\\=\\= between Chill\\\\EventBundle\\\\Entity\\\\Event and null will always evaluate to false\\.$#" + count: 3 + path: src/Bundle/ChillEventBundle/Entity/Participation.php + + - + message: "#^Strict comparison using \\=\\=\\= between Chill\\\\EventBundle\\\\Entity\\\\Role and null will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillEventBundle/Entity/Participation.php + + - + message: "#^Strict comparison using \\=\\=\\= between Chill\\\\EventBundle\\\\Entity\\\\Status and null will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillEventBundle/Entity/Participation.php + + - + message: "#^Property Chill\\\\EventBundle\\\\Entity\\\\Role\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: src/Bundle/ChillEventBundle/Entity/Role.php + + - + message: "#^Property Chill\\\\EventBundle\\\\Entity\\\\Status\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: src/Bundle/ChillEventBundle/Entity/Status.php + + - + message: "#^Call to method setDefaults\\(\\) on an unknown class Symfony\\\\Component\\\\OptionsResolver\\\\OptionsResolverInterface\\.$#" + count: 1 + path: src/Bundle/ChillEventBundle/Form/EventTypeType.php + + - + message: "#^Call to method setDefaults\\(\\) on an unknown class Symfony\\\\Component\\\\OptionsResolver\\\\OptionsResolverInterface\\.$#" + count: 1 + path: src/Bundle/ChillEventBundle/Form/RoleType.php + + - + message: "#^Call to method setDefaults\\(\\) on an unknown class Symfony\\\\Component\\\\OptionsResolver\\\\OptionsResolverInterface\\.$#" + count: 1 + path: src/Bundle/ChillEventBundle/Form/StatusType.php + + - + message: "#^Property Chill\\\\EventBundle\\\\Form\\\\Type\\\\PickEventType\\:\\:\\$user \\(Chill\\\\MainBundle\\\\Entity\\\\User\\) does not accept string\\|Stringable\\|Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface\\.$#" + count: 1 + path: src/Bundle/ChillEventBundle/Form/Type/PickEventType.php + + - + message: "#^Call to an undefined method Symfony\\\\Component\\\\Form\\\\ResolvedFormTypeInterface\\:\\:getName\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillEventBundle/Form/Type/PickRoleType.php + + - + message: "#^Call to an undefined method Symfony\\\\Component\\\\Form\\\\ResolvedFormTypeInterface\\:\\:getName\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillEventBundle/Form/Type/PickStatusType.php + + - + message: "#^Method Chill\\\\EventBundle\\\\Search\\\\EventSearch\\:\\:renderResult\\(\\) should return string but returns array\\\\|bool\\>\\>\\.$#" + count: 1 + path: src/Bundle/ChillEventBundle/Search/EventSearch.php + + - + message: "#^Property Chill\\\\EventBundle\\\\Search\\\\EventSearch\\:\\:\\$user \\(Chill\\\\MainBundle\\\\Entity\\\\User\\) does not accept string\\|Stringable\\|Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface\\.$#" + count: 1 + path: src/Bundle/ChillEventBundle/Search/EventSearch.php + + - + message: "#^Instanceof between Chill\\\\EventBundle\\\\Entity\\\\Event and Chill\\\\EventBundle\\\\Entity\\\\Event will always evaluate to true\\.$#" + count: 1 + path: src/Bundle/ChillEventBundle/Security/Authorization/EventVoter.php + + - + message: "#^Unreachable statement \\- code above always terminates\\.$#" + count: 1 + path: src/Bundle/ChillEventBundle/Security/Authorization/EventVoter.php + + - + message: "#^Instanceof between Chill\\\\EventBundle\\\\Entity\\\\Participation and Chill\\\\EventBundle\\\\Entity\\\\Participation will always evaluate to true\\.$#" + count: 1 + path: src/Bundle/ChillEventBundle/Security/Authorization/ParticipationVoter.php + + - + message: "#^Unreachable statement \\- code above always terminates\\.$#" + count: 1 + path: src/Bundle/ChillEventBundle/Security/Authorization/ParticipationVoter.php + + - + message: "#^Parameter \\#2 \\$context \\(string\\) of method Chill\\\\EventBundle\\\\Timeline\\\\TimelineEventProvider\\:\\:getEntityTemplate\\(\\) should be compatible with parameter \\$context \\(Chill\\\\MainBundle\\\\Timeline\\\\type\\) of method Chill\\\\MainBundle\\\\Timeline\\\\TimelineProviderInterface\\:\\:getEntityTemplate\\(\\)$#" + count: 1 + path: src/Bundle/ChillEventBundle/Timeline/TimelineEventProvider.php + + - + message: "#^Call to an undefined method Symfony\\\\Component\\\\Config\\\\Definition\\\\Builder\\\\NodeDefinition\\:\\:children\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillFamilyMembersBundle/DependencyInjection/Configuration.php + + - + message: "#^Method Symfony\\\\Component\\\\Config\\\\Definition\\\\Builder\\\\TreeBuilder\\:\\:getRootNode\\(\\) invoked with 1 parameter, 0 required\\.$#" + count: 1 + path: src/Bundle/ChillFamilyMembersBundle/DependencyInjection/Configuration.php + + - + message: "#^Instanceof between DateTimeImmutable and DateTime will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillFamilyMembersBundle/Entity/AbstractFamilyMember.php + + - + message: "#^Instanceof between DateTimeImmutable and DateTimeImmutable will always evaluate to true\\.$#" + count: 2 + path: src/Bundle/ChillFamilyMembersBundle/Entity/AbstractFamilyMember.php + + - + message: "#^Method Chill\\\\FamilyMembersBundle\\\\Entity\\\\AbstractFamilyMember\\:\\:getBirthdate\\(\\) has invalid return type Chill\\\\FamilyMembersBundle\\\\Entity\\\\date_immutable\\.$#" + count: 1 + path: src/Bundle/ChillFamilyMembersBundle/Entity/AbstractFamilyMember.php + + - + message: "#^Method Chill\\\\FamilyMembersBundle\\\\Entity\\\\AbstractFamilyMember\\:\\:setBirthdate\\(\\) should return Chill\\\\FamilyMembersBundle\\\\Entity\\\\FamilyMember but returns \\$this\\(Chill\\\\FamilyMembersBundle\\\\Entity\\\\AbstractFamilyMember\\)\\.$#" + count: 1 + path: src/Bundle/ChillFamilyMembersBundle/Entity/AbstractFamilyMember.php + + - + message: "#^Method Chill\\\\FamilyMembersBundle\\\\Entity\\\\AbstractFamilyMember\\:\\:setEndDate\\(\\) should return Chill\\\\FamilyMembersBundle\\\\Entity\\\\FamilyMember but returns \\$this\\(Chill\\\\FamilyMembersBundle\\\\Entity\\\\AbstractFamilyMember\\)\\.$#" + count: 1 + path: src/Bundle/ChillFamilyMembersBundle/Entity/AbstractFamilyMember.php + + - + message: "#^Method Chill\\\\FamilyMembersBundle\\\\Entity\\\\AbstractFamilyMember\\:\\:setFirstname\\(\\) should return Chill\\\\FamilyMembersBundle\\\\Entity\\\\FamilyMember but returns \\$this\\(Chill\\\\FamilyMembersBundle\\\\Entity\\\\AbstractFamilyMember\\)\\.$#" + count: 1 + path: src/Bundle/ChillFamilyMembersBundle/Entity/AbstractFamilyMember.php + + - + message: "#^Method Chill\\\\FamilyMembersBundle\\\\Entity\\\\AbstractFamilyMember\\:\\:setGender\\(\\) should return Chill\\\\FamilyMembersBundle\\\\Entity\\\\FamilyMember but returns \\$this\\(Chill\\\\FamilyMembersBundle\\\\Entity\\\\AbstractFamilyMember\\)\\.$#" + count: 1 + path: src/Bundle/ChillFamilyMembersBundle/Entity/AbstractFamilyMember.php + + - + message: "#^Method Chill\\\\FamilyMembersBundle\\\\Entity\\\\AbstractFamilyMember\\:\\:setLastname\\(\\) should return Chill\\\\FamilyMembersBundle\\\\Entity\\\\FamilyMember but returns \\$this\\(Chill\\\\FamilyMembersBundle\\\\Entity\\\\AbstractFamilyMember\\)\\.$#" + count: 1 + path: src/Bundle/ChillFamilyMembersBundle/Entity/AbstractFamilyMember.php + + - + message: "#^Method Chill\\\\FamilyMembersBundle\\\\Entity\\\\AbstractFamilyMember\\:\\:setLink\\(\\) should return Chill\\\\FamilyMembersBundle\\\\Entity\\\\FamilyMember but returns \\$this\\(Chill\\\\FamilyMembersBundle\\\\Entity\\\\AbstractFamilyMember\\)\\.$#" + count: 1 + path: src/Bundle/ChillFamilyMembersBundle/Entity/AbstractFamilyMember.php + + - + message: "#^Method Chill\\\\FamilyMembersBundle\\\\Entity\\\\AbstractFamilyMember\\:\\:setProfessionnalSituation\\(\\) should return Chill\\\\FamilyMembersBundle\\\\Entity\\\\FamilyMember but returns \\$this\\(Chill\\\\FamilyMembersBundle\\\\Entity\\\\AbstractFamilyMember\\)\\.$#" + count: 1 + path: src/Bundle/ChillFamilyMembersBundle/Entity/AbstractFamilyMember.php + + - + message: "#^Method Chill\\\\FamilyMembersBundle\\\\Entity\\\\AbstractFamilyMember\\:\\:setStartDate\\(\\) should return Chill\\\\FamilyMembersBundle\\\\Entity\\\\FamilyMember but returns \\$this\\(Chill\\\\FamilyMembersBundle\\\\Entity\\\\AbstractFamilyMember\\)\\.$#" + count: 1 + path: src/Bundle/ChillFamilyMembersBundle/Entity/AbstractFamilyMember.php + + - + message: "#^Property Chill\\\\FamilyMembersBundle\\\\Entity\\\\AbstractFamilyMember\\:\\:\\$birthdate \\(Chill\\\\FamilyMembersBundle\\\\Entity\\\\date_immutable\\|null\\) does not accept DateTimeImmutable\\.$#" + count: 1 + path: src/Bundle/ChillFamilyMembersBundle/Entity/AbstractFamilyMember.php + + - + message: "#^Property Chill\\\\FamilyMembersBundle\\\\Entity\\\\AbstractFamilyMember\\:\\:\\$birthdate \\(Chill\\\\FamilyMembersBundle\\\\Entity\\\\date_immutable\\|null\\) does not accept DateTimeImmutable\\|null\\.$#" + count: 1 + path: src/Bundle/ChillFamilyMembersBundle/Entity/AbstractFamilyMember.php + + - + message: "#^Property Chill\\\\FamilyMembersBundle\\\\Entity\\\\AbstractFamilyMember\\:\\:\\$birthdate has unknown class Chill\\\\FamilyMembersBundle\\\\Entity\\\\date_immutable as its type\\.$#" + count: 1 + path: src/Bundle/ChillFamilyMembersBundle/Entity/AbstractFamilyMember.php + + - + message: "#^Result of \\|\\| is always true\\.$#" + count: 3 + path: src/Bundle/ChillFamilyMembersBundle/Entity/AbstractFamilyMember.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and DateTimeImmutable will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillFamilyMembersBundle/Entity/AbstractFamilyMember.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and null will always evaluate to true\\.$#" + count: 1 + path: src/Bundle/ChillFamilyMembersBundle/Entity/AbstractFamilyMember.php + + - + message: "#^Property Chill\\\\FamilyMembersBundle\\\\Entity\\\\FamilyMember\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: src/Bundle/ChillFamilyMembersBundle/Entity/FamilyMember.php + + - + message: "#^Call to an undefined method Doctrine\\\\Persistence\\\\ObjectManager\\:\\:createQueryBuilder\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/CRUD/Controller/AbstractCRUDController.php + + - + message: "#^Call to method select\\(\\) on an unknown class Chill\\\\MainBundle\\\\CRUD\\\\Controller\\\\QueryBuilder\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/CRUD/Controller/AbstractCRUDController.php + + - + message: "#^Call to method setFirstResult\\(\\) on an unknown class Chill\\\\MainBundle\\\\CRUD\\\\Controller\\\\QueryBuilder\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/CRUD/Controller/AbstractCRUDController.php + + - + message: "#^Method Chill\\\\MainBundle\\\\CRUD\\\\Controller\\\\AbstractCRUDController\\:\\:buildQueryEntities\\(\\) has invalid return type Chill\\\\MainBundle\\\\CRUD\\\\Controller\\\\QueryBuilder\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/CRUD/Controller/AbstractCRUDController.php + + - + message: "#^PHPDoc tag @throws with type Symfony\\\\Component\\\\Security\\\\Core\\\\Exception\\\\AccessDeniedHttpException is not subtype of Throwable$#" + count: 1 + path: src/Bundle/ChillMainBundle/CRUD/Controller/AbstractCRUDController.php + + - + message: "#^PHPDoc tag @param for parameter \\$postedDataContext with type string is incompatible with native type array\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/CRUD/Controller/ApiController.php + + - + message: "#^PHPDoc tag @param has invalid value \\(mixed id\\)\\: Unexpected token \"id\", expected variable at offset 956$#" + count: 1 + path: src/Bundle/ChillMainBundle/CRUD/Controller/ApiController.php + + - + message: "#^PHPDoc tag @param has invalid value \\(string action\\)\\: Unexpected token \"action\", expected variable at offset 929$#" + count: 1 + path: src/Bundle/ChillMainBundle/CRUD/Controller/ApiController.php + + - + message: "#^PHPDoc tag @return with type void is incompatible with native type Symfony\\\\Component\\\\HttpFoundation\\\\Response\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/CRUD/Controller/ApiController.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and object will always evaluate to false\\.$#" + count: 2 + path: src/Bundle/ChillMainBundle/CRUD/Controller/ApiController.php + + - + message: "#^Unreachable statement \\- code above always terminates\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/CRUD/Controller/ApiController.php + + - + message: "#^Call to an undefined method Doctrine\\\\Persistence\\\\ObjectManager\\:\\:createQueryBuilder\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/CRUD/Controller/CRUDController.php + + - + message: "#^Call to method getQuery\\(\\) on an unknown class Chill\\\\MainBundle\\\\CRUD\\\\Controller\\\\type\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/CRUD/Controller/CRUDController.php + + - + message: "#^Instanceof between Symfony\\\\Component\\\\HttpFoundation\\\\RedirectResponse and Symfony\\\\Component\\\\HttpFoundation\\\\Response will always evaluate to true\\.$#" + count: 3 + path: src/Bundle/ChillMainBundle/CRUD/Controller/CRUDController.php + + - + message: "#^Method Chill\\\\MainBundle\\\\CRUD\\\\Controller\\\\CRUDController\\:\\:index\\(\\) has invalid return type Chill\\\\MainBundle\\\\CRUD\\\\Controller\\\\type\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/CRUD/Controller/CRUDController.php + + - + message: "#^Method Chill\\\\MainBundle\\\\CRUD\\\\Controller\\\\CRUDController\\:\\:queryEntities\\(\\) has invalid return type Chill\\\\MainBundle\\\\CRUD\\\\Controller\\\\type\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/CRUD/Controller/CRUDController.php + + - + message: "#^PHPDoc tag @throws with type Symfony\\\\Component\\\\Security\\\\Core\\\\Exception\\\\AccessDeniedHttpException is not subtype of Throwable$#" + count: 1 + path: src/Bundle/ChillMainBundle/CRUD/Controller/CRUDController.php + + - + message: "#^Parameter \\$formClass of method Chill\\\\MainBundle\\\\CRUD\\\\Controller\\\\CRUDController\\:\\:formCreateAction\\(\\) has invalid type Chill\\\\MainBundle\\\\CRUD\\\\Controller\\\\type\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/CRUD/Controller/CRUDController.php + + - + message: "#^Unreachable statement \\- code above always terminates\\.$#" + count: 3 + path: src/Bundle/ChillMainBundle/CRUD/Controller/CRUDController.php + + - + message: "#^Constant Chill\\\\MainBundle\\\\CRUD\\\\Routing\\\\CRUDRoutesLoader\\:\\:ALL_INDEX_METHODS is unused\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/CRUD/Routing/CRUDRoutesLoader.php + + - + message: "#^Constant Chill\\\\MainBundle\\\\CRUD\\\\Routing\\\\CRUDRoutesLoader\\:\\:ALL_SINGLE_METHODS is unused\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/CRUD/Routing/CRUDRoutesLoader.php + + - + message: "#^Strict comparison using \\=\\=\\= between 'CRUD' and null will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/CRUD/Routing/CRUDRoutesLoader.php + + - + message: "#^Strict comparison using \\=\\=\\= between 'collection' and 'single'\\|null will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/CRUD/Routing/CRUDRoutesLoader.php + + - + message: "#^Call to an undefined method Doctrine\\\\ORM\\\\EntityRepository\\\\:\\:findOneByName\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Command/ChillImportUsersCommand.php + + - + message: "#^Method Chill\\\\MainBundle\\\\Command\\\\ChillImportUsersCommand\\:\\:getCenters\\(\\) should return array\\ but returns null\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Command/ChillImportUsersCommand.php + + - + message: "#^Property Chill\\\\MainBundle\\\\Command\\\\ChillUserSendRenewPasswordCodeCommand\\:\\:\\$output is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Command/ChillUserSendRenewPasswordCodeCommand.php + + - + message: "#^Call to an undefined method Symfony\\\\Component\\\\Console\\\\Helper\\\\HelperInterface\\:\\:askHiddenResponse\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Command/SetPasswordCommand.php + + - + message: "#^Method Chill\\\\MainBundle\\\\Controller\\\\CenterController\\:\\:createCreateForm\\(\\) should return Symfony\\\\Component\\\\Form\\\\Form but returns Symfony\\\\Component\\\\Form\\\\FormInterface\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Controller/CenterController.php + + - + message: "#^Method Chill\\\\MainBundle\\\\Controller\\\\CenterController\\:\\:createEditForm\\(\\) should return Symfony\\\\Component\\\\Form\\\\Form but returns Symfony\\\\Component\\\\Form\\\\FormInterface\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Controller/CenterController.php + + - + message: "#^Instanceof between Chill\\\\MainBundle\\\\Export\\\\DirectExportInterface and Chill\\\\MainBundle\\\\Export\\\\DirectExportInterface will always evaluate to true\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Controller/ExportController.php + + - + message: "#^PHPDoc tag @param for parameter \\$request with type string is incompatible with native type Symfony\\\\Component\\\\HttpFoundation\\\\Request\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Controller/ExportController.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and string will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Controller/ExportController.php + + - + message: "#^Call to an undefined method Doctrine\\\\Persistence\\\\ObjectManager\\:\\:createQueryBuilder\\(\\)\\.$#" + count: 2 + path: src/Bundle/ChillMainBundle/Controller/PasswordController.php + + - + message: "#^Call to an undefined method Doctrine\\\\Persistence\\\\ObjectRepository\\\\:\\:findOneByUsernameCanonical\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Controller/PasswordController.php + + - + message: "#^Method Chill\\\\MainBundle\\\\Controller\\\\PasswordController\\:\\:passwordForm\\(\\) should return Symfony\\\\Component\\\\Form\\\\Form but returns Symfony\\\\Component\\\\Form\\\\FormInterface\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Controller/PasswordController.php + + - + message: "#^Method Symfony\\\\Contracts\\\\EventDispatcher\\\\EventDispatcherInterface\\:\\:dispatch\\(\\) invoked with 2 parameters, 1 required\\.$#" + count: 4 + path: src/Bundle/ChillMainBundle/Controller/PasswordController.php + + - + message: "#^Method Chill\\\\MainBundle\\\\Controller\\\\PermissionsGroupController\\:\\:addLinkRoleScopeAction\\(\\) has invalid return type Chill\\\\MainBundle\\\\Controller\\\\Respon\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Controller/PermissionsGroupController.php + + - + message: "#^Method Chill\\\\MainBundle\\\\Controller\\\\PermissionsGroupController\\:\\:addLinkRoleScopeAction\\(\\) should return Chill\\\\MainBundle\\\\Controller\\\\Respon but returns Symfony\\\\Component\\\\HttpFoundation\\\\RedirectResponse\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Controller/PermissionsGroupController.php + + - + message: "#^Method Chill\\\\MainBundle\\\\Controller\\\\PermissionsGroupController\\:\\:addLinkRoleScopeAction\\(\\) should return Chill\\\\MainBundle\\\\Controller\\\\Respon but returns Symfony\\\\Component\\\\HttpFoundation\\\\Response\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Controller/PermissionsGroupController.php + + - + message: "#^Method Chill\\\\MainBundle\\\\Controller\\\\PermissionsGroupController\\:\\:createAddRoleScopeForm\\(\\) should return Symfony\\\\Component\\\\Form\\\\Form but returns Symfony\\\\Component\\\\Form\\\\FormInterface\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Controller/PermissionsGroupController.php + + - + message: "#^Method Chill\\\\MainBundle\\\\Controller\\\\PermissionsGroupController\\:\\:createCreateForm\\(\\) should return Symfony\\\\Component\\\\Form\\\\Form but returns Symfony\\\\Component\\\\Form\\\\FormInterface\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Controller/PermissionsGroupController.php + + - + message: "#^Method Chill\\\\MainBundle\\\\Controller\\\\PermissionsGroupController\\:\\:createDeleteRoleScopeForm\\(\\) should return Symfony\\\\Component\\\\Form\\\\Form but returns Symfony\\\\Component\\\\Form\\\\FormInterface\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Controller/PermissionsGroupController.php + + - + message: "#^Method Chill\\\\MainBundle\\\\Controller\\\\PermissionsGroupController\\:\\:createEditForm\\(\\) should return Symfony\\\\Component\\\\Form\\\\Form but returns Symfony\\\\Component\\\\Form\\\\FormInterface\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Controller/PermissionsGroupController.php + + - + message: "#^Method Chill\\\\MainBundle\\\\Controller\\\\PermissionsGroupController\\:\\:deleteLinkRoleScopeAction\\(\\) has invalid return type Chill\\\\MainBundle\\\\Controller\\\\redirection\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Controller/PermissionsGroupController.php + + - + message: "#^Method Chill\\\\MainBundle\\\\Controller\\\\PermissionsGroupController\\:\\:deleteLinkRoleScopeAction\\(\\) should return Chill\\\\MainBundle\\\\Controller\\\\redirection but returns Symfony\\\\Component\\\\HttpFoundation\\\\RedirectResponse\\.$#" + count: 2 + path: src/Bundle/ChillMainBundle/Controller/PermissionsGroupController.php + + - + message: "#^PHPDoc tag @param for parameter \\$permissionsGroup with type mixed is not subtype of native type Chill\\\\MainBundle\\\\Entity\\\\PermissionsGroup\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Controller/PermissionsGroupController.php + + - + message: "#^PHPDoc tag @throws with type Chill\\\\MainBundle\\\\Controller\\\\type is not subtype of Throwable$#" + count: 1 + path: src/Bundle/ChillMainBundle/Controller/PermissionsGroupController.php + + - + message: "#^Call to an undefined method Doctrine\\\\Persistence\\\\ObjectManager\\:\\:createQuery\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Controller/PostalCodeController.php + + - + message: "#^Call to an undefined method Symfony\\\\Component\\\\HttpFoundation\\\\Session\\\\SessionInterface\\:\\:getFlashBag\\(\\)\\.$#" + count: 2 + path: src/Bundle/ChillMainBundle/Controller/SavedExportController.php + + - + message: "#^Method Chill\\\\MainBundle\\\\Controller\\\\ScopeController\\:\\:createCreateForm\\(\\) should return Symfony\\\\Component\\\\Form\\\\Form but returns Symfony\\\\Component\\\\Form\\\\FormInterface\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Controller/ScopeController.php + + - + message: "#^Method Chill\\\\MainBundle\\\\Controller\\\\ScopeController\\:\\:createEditForm\\(\\) should return Symfony\\\\Component\\\\Form\\\\Form but returns Symfony\\\\Component\\\\Form\\\\FormInterface\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Controller/ScopeController.php + + - + message: "#^Call to method buildForm\\(\\) on an unknown class Chill\\\\MainBundle\\\\Search\\\\HasAdvancedSearchForm\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Controller/SearchController.php + + - + message: "#^Call to method convertFormDataToQuery\\(\\) on an unknown class Chill\\\\MainBundle\\\\Search\\\\HasAdvancedSearchForm\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Controller/SearchController.php + + - + message: "#^Call to method convertTermsToFormData\\(\\) on an unknown class Chill\\\\MainBundle\\\\Search\\\\HasAdvancedSearchForm\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Controller/SearchController.php + + - + message: "#^Call to method getAdvancedSearchTitle\\(\\) on an unknown class Chill\\\\MainBundle\\\\Search\\\\HasAdvancedSearchForm\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Controller/SearchController.php + + - + message: "#^PHPDoc tag @var for variable \\$variable contains unknown class Chill\\\\MainBundle\\\\Controller\\\\Chill\\\\MainBundle\\\\Search\\\\HasAdvancedSearchFormInterface\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Controller/SearchController.php + + - + message: "#^PHPDoc tag @var for variable \\$variable contains unknown class Chill\\\\MainBundle\\\\Controller\\\\Chill\\\\MainBundle\\\\Search\\\\SearchProvider\\.$#" + count: 2 + path: src/Bundle/ChillMainBundle/Controller/SearchController.php + + - + message: "#^Variable \\$variable in PHPDoc tag @var does not match assigned variable \\$search\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Controller/SearchController.php + + - + message: "#^Variable \\$variable in PHPDoc tag @var does not match assigned variable \\$searchProvider\\.$#" + count: 2 + path: src/Bundle/ChillMainBundle/Controller/SearchController.php + + - + message: "#^Call to an undefined method Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface\\:\\:getGroupCenters\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Controller/TimelineCenterController.php + + - + message: "#^Instanceof between Symfony\\\\Component\\\\HttpFoundation\\\\RedirectResponse and Symfony\\\\Component\\\\HttpFoundation\\\\Response will always evaluate to true\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Controller/UserController.php + + - + message: "#^Unreachable statement \\- code above always terminates\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Controller/UserController.php + + - + message: "#^PHPDoc tag @return has invalid value \\(array\\<0\\: CronJobInterface\\[\\], 1\\: array\\\\>\\)\\: Unexpected token \"\\:\", expected '\\>' at offset 26$#" + count: 1 + path: src/Bundle/ChillMainBundle/Cron/CronManager.php + + - + message: "#^Property Chill\\\\MainBundle\\\\DataFixtures\\\\ORM\\\\LoadAddressReferences\\:\\:\\$container is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadAddressReferences.php + + - + message: "#^Property Chill\\\\MainBundle\\\\DataFixtures\\\\ORM\\\\LoadLocationType\\:\\:\\$container is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadLocationType.php + + - + message: "#^Property Chill\\\\MainBundle\\\\DataFixtures\\\\ORM\\\\LoadUsers\\:\\:\\$container is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadUsers.php + + - + message: "#^Parameter \\$factory of method Chill\\\\MainBundle\\\\DependencyInjection\\\\ChillMainExtension\\:\\:addWidgetFactory\\(\\) has invalid type Chill\\\\MainBundle\\\\DependencyInjection\\\\Widget\\\\WidgetFactoryInterface\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php + + - + message: "#^Method Symfony\\\\Component\\\\DependencyInjection\\\\Container\\:\\:getParameter\\(\\) invoked with 2 parameters, 1 required\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/ShortMessageCompilerPass.php + + - + message: "#^Offset 'queries' does not exist on array\\{scheme\\: 'ovh', host\\?\\: string, port\\?\\: int\\<0, 65535\\>, user\\?\\: string, pass\\?\\: string, path\\?\\: string, query\\?\\: string, fragment\\?\\: string\\}\\.$#" + count: 5 + path: src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/ShortMessageCompilerPass.php + + - + message: "#^Offset 'queries' does not exist on array\\{scheme\\?\\: string, host\\?\\: string, port\\?\\: int\\<0, 65535\\>, user\\?\\: string, pass\\?\\: string, path\\?\\: string, query\\?\\: string, fragment\\?\\: string\\}\\|false\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/ShortMessageCompilerPass.php + + - + message: "#^Call to an undefined method Symfony\\\\Component\\\\Config\\\\Definition\\\\Builder\\\\NodeDefinition\\:\\:canBeUnset\\(\\)\\.$#" + count: 2 + path: src/Bundle/ChillMainBundle/DependencyInjection/Configuration.php + + - + message: "#^Call to an undefined method Symfony\\\\Component\\\\Config\\\\Definition\\\\Builder\\\\NodeDefinition\\:\\:children\\(\\)\\.$#" + count: 3 + path: src/Bundle/ChillMainBundle/DependencyInjection/Configuration.php + + - + message: "#^Method Chill\\\\MainBundle\\\\DependencyInjection\\\\Configuration\\:\\:getWidgetAliasesbyPlace\\(\\) has invalid return type Chill\\\\MainBundle\\\\DependencyInjection\\\\Widget\\\\type\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/DependencyInjection/Configuration.php + + - + message: "#^Method Chill\\\\MainBundle\\\\DependencyInjection\\\\Configuration\\:\\:getWidgetAliasesbyPlace\\(\\) should return Chill\\\\MainBundle\\\\DependencyInjection\\\\Widget\\\\type but returns array\\\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/DependencyInjection/Configuration.php + + - + message: "#^Method Chill\\\\MainBundle\\\\DependencyInjection\\\\Configuration\\:\\:getWidgetFactories\\(\\) has invalid return type Chill\\\\MainBundle\\\\DependencyInjection\\\\Widget\\\\WidgetFactoryInterface\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/DependencyInjection/Configuration.php + + - + message: "#^Method Symfony\\\\Component\\\\Config\\\\Definition\\\\Builder\\\\TreeBuilder\\:\\:getRootNode\\(\\) invoked with 1 parameter, 0 required\\.$#" + count: 3 + path: src/Bundle/ChillMainBundle/DependencyInjection/Configuration.php + + - + message: "#^PHPDoc tag @param has invalid value \\(WidgetFactoryInterface\\[\\]\\)\\: Unexpected token \"\\\\n \", expected variable at offset 42$#" + count: 1 + path: src/Bundle/ChillMainBundle/DependencyInjection/Configuration.php + + - + message: "#^Parameter \\$place of method Chill\\\\MainBundle\\\\DependencyInjection\\\\Configuration\\:\\:getWidgetAliasesbyPlace\\(\\) has invalid type Chill\\\\MainBundle\\\\DependencyInjection\\\\Widget\\\\type\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/DependencyInjection/Configuration.php + + - + message: "#^Parameter \\$widgetFactories of method Chill\\\\MainBundle\\\\DependencyInjection\\\\Configuration\\:\\:setWidgetFactories\\(\\) has invalid type Chill\\\\MainBundle\\\\DependencyInjection\\\\Widget\\\\WidgetFactoryInterface\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/DependencyInjection/Configuration.php + + - + message: "#^Call to an undefined method Chill\\\\MainBundle\\\\DependencyInjection\\\\Widget\\\\Factory\\\\WidgetFactoryInterface\\:\\:getAllowedPlaces\\(\\)\\.$#" + count: 3 + path: src/Bundle/ChillMainBundle/DependencyInjection/Widget/AbstractWidgetsCompilerPass.php + + - + message: "#^Instanceof between Chill\\\\MainBundle\\\\DependencyInjection\\\\Widget\\\\HasWidgetFactoriesExtensionInterface and Chill\\\\MainBundle\\\\DependencyInjection\\\\Widget\\\\HasWidgetFactoriesExtensionInterface will always evaluate to true\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/DependencyInjection/Widget/AbstractWidgetsCompilerPass.php + + - + message: "#^Method Chill\\\\MainBundle\\\\DependencyInjection\\\\Widget\\\\AbstractWidgetsCompilerPass\\:\\:isPlaceAllowedForWidget\\(\\) has invalid return type Chill\\\\MainBundle\\\\DependencyInjection\\\\Widget\\\\unknown\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/DependencyInjection/Widget/AbstractWidgetsCompilerPass.php + + - + message: "#^Method Chill\\\\MainBundle\\\\DependencyInjection\\\\Widget\\\\AbstractWidgetsCompilerPass\\:\\:isPlaceAllowedForWidget\\(\\) should return Chill\\\\MainBundle\\\\DependencyInjection\\\\Widget\\\\unknown but returns false\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/DependencyInjection/Widget/AbstractWidgetsCompilerPass.php + + - + message: "#^Method Chill\\\\MainBundle\\\\DependencyInjection\\\\Widget\\\\AbstractWidgetsCompilerPass\\:\\:isPlaceAllowedForWidget\\(\\) should return Chill\\\\MainBundle\\\\DependencyInjection\\\\Widget\\\\unknown but returns true\\.$#" + count: 2 + path: src/Bundle/ChillMainBundle/DependencyInjection/Widget/AbstractWidgetsCompilerPass.php + + - + message: "#^Negated boolean expression is always false\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/DependencyInjection/Widget/AbstractWidgetsCompilerPass.php + + - + message: "#^Parameter \\$order of method Chill\\\\MainBundle\\\\DependencyInjection\\\\Widget\\\\Factory\\\\AbstractWidgetFactory\\:\\:createDefinition\\(\\) has invalid type Chill\\\\MainBundle\\\\DependencyInjection\\\\Widget\\\\Factory\\\\type\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/DependencyInjection/Widget/Factory/AbstractWidgetFactory.php + + - + message: "#^Parameter \\$place of method Chill\\\\MainBundle\\\\DependencyInjection\\\\Widget\\\\Factory\\\\AbstractWidgetFactory\\:\\:createDefinition\\(\\) has invalid type Chill\\\\MainBundle\\\\DependencyInjection\\\\Widget\\\\Factory\\\\type\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/DependencyInjection/Widget/Factory/AbstractWidgetFactory.php + + - + message: "#^Parameter \\$order of method Chill\\\\MainBundle\\\\DependencyInjection\\\\Widget\\\\Factory\\\\WidgetFactoryInterface\\:\\:createDefinition\\(\\) has invalid type Chill\\\\MainBundle\\\\DependencyInjection\\\\Widget\\\\Factory\\\\type\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/DependencyInjection/Widget/Factory/WidgetFactoryInterface.php + + - + message: "#^Parameter \\$place of method Chill\\\\MainBundle\\\\DependencyInjection\\\\Widget\\\\Factory\\\\WidgetFactoryInterface\\:\\:configureOptions\\(\\) has invalid type Chill\\\\MainBundle\\\\DependencyInjection\\\\Widget\\\\Factory\\\\type\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/DependencyInjection/Widget/Factory/WidgetFactoryInterface.php + + - + message: "#^Parameter \\$place of method Chill\\\\MainBundle\\\\DependencyInjection\\\\Widget\\\\Factory\\\\WidgetFactoryInterface\\:\\:createDefinition\\(\\) has invalid type Chill\\\\MainBundle\\\\DependencyInjection\\\\Widget\\\\Factory\\\\type\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/DependencyInjection/Widget/Factory/WidgetFactoryInterface.php + + - + message: "#^PHPDoc tag @param for parameter \\$factory with type Chill\\\\MainBundle\\\\DependencyInjection\\\\Widget\\\\WidgetFactoryInterface is not subtype of native type Chill\\\\MainBundle\\\\DependencyInjection\\\\Widget\\\\Factory\\\\WidgetFactoryInterface\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/DependencyInjection/Widget/HasWidgetFactoriesExtensionInterface.php + + - + message: "#^Parameter \\$factory of method Chill\\\\MainBundle\\\\DependencyInjection\\\\Widget\\\\HasWidgetFactoriesExtensionInterface\\:\\:addWidgetFactory\\(\\) has invalid type Chill\\\\MainBundle\\\\DependencyInjection\\\\Widget\\\\WidgetFactoryInterface\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/DependencyInjection/Widget/HasWidgetFactoriesExtensionInterface.php + + - + message: "#^Strict comparison using \\=\\=\\= between int\\<1, max\\> and 0 will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Doctrine/Type/NativeDateIntervalType.php + + - + message: "#^Instanceof between DateTime and DateTime will always evaluate to true\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Entity/Address.php + + - + message: "#^Property Chill\\\\MainBundle\\\\Entity\\\\Address\\:\\:\\$validTo \\(DateTime\\|null\\) does not accept DateTimeInterface\\|null\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Entity/Address.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and string will always evaluate to false\\.$#" + count: 2 + path: src/Bundle/ChillMainBundle/Entity/Address.php + + - + message: "#^Method Chill\\\\MainBundle\\\\Entity\\\\AddressReference\\:\\:setPostcode\\(\\) should return Chill\\\\MainBundle\\\\Entity\\\\Address but returns \\$this\\(Chill\\\\MainBundle\\\\Entity\\\\AddressReference\\)\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Entity/AddressReference.php + + - + message: "#^Property Chill\\\\MainBundle\\\\Entity\\\\AddressReference\\:\\:\\$addressCanonical is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Entity/AddressReference.php + + - + message: "#^Property Chill\\\\MainBundle\\\\Entity\\\\AddressReference\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Entity/AddressReference.php + + - + message: "#^Method Chill\\\\MainBundle\\\\Entity\\\\Country\\:\\:getCountryCode\\(\\) has invalid return type Chill\\\\MainBundle\\\\Entity\\\\the\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Entity/Country.php + + - + message: "#^Method Chill\\\\MainBundle\\\\Entity\\\\Country\\:\\:getCountryCode\\(\\) should return Chill\\\\MainBundle\\\\Entity\\\\the but returns string\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Entity/Country.php + + + - + message: "#^Property Chill\\\\MainBundle\\\\Entity\\\\GeographicalUnit\\:\\:\\$geom is unused\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Entity/GeographicalUnit.php + + - + message: "#^Property Chill\\\\MainBundle\\\\Entity\\\\GeographicalUnit\\:\\:\\$unitRefId is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Entity/GeographicalUnit.php + + - + message: "#^Method Chill\\\\MainBundle\\\\Entity\\\\GroupCenter\\:\\:getPermissionsGroup\\(\\) has invalid return type Chill\\\\MainBundle\\\\Entity\\\\PermissionGroup\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Entity/GroupCenter.php + + - + message: "#^Method Chill\\\\MainBundle\\\\Entity\\\\GroupCenter\\:\\:getPermissionsGroup\\(\\) should return Chill\\\\MainBundle\\\\Entity\\\\PermissionGroup but returns Chill\\\\MainBundle\\\\Entity\\\\PermissionsGroup\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Entity/GroupCenter.php + + - + message: "#^Property Chill\\\\MainBundle\\\\Entity\\\\GroupCenter\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Entity/GroupCenter.php + + - + message: "#^Property Chill\\\\MainBundle\\\\Entity\\\\GroupCenter\\:\\:\\$permissionsGroup \\(Chill\\\\MainBundle\\\\Entity\\\\PermissionsGroup\\) does not accept Doctrine\\\\Common\\\\Collections\\\\ArrayCollection\\<\\*NEVER\\*, \\*NEVER\\*\\>\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Entity/GroupCenter.php + + - + message: "#^Method Chill\\\\MainBundle\\\\Entity\\\\Language\\:\\:getName\\(\\) should return string but returns array\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Entity/Language.php + + - + message: "#^PHPDoc tag @param has invalid value \\(string array \\$name\\)\\: Unexpected token \"array\", expected variable at offset 49$#" + count: 1 + path: src/Bundle/ChillMainBundle/Entity/Language.php + + - + message: "#^PHPDoc tag @var for property Chill\\\\MainBundle\\\\Entity\\\\Language\\:\\:\\$name with type string is incompatible with native type array\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Entity/Language.php + + - + message: "#^Property Chill\\\\MainBundle\\\\Entity\\\\Location\\:\\:\\$createdAt \\(DateTimeImmutable\\|null\\) does not accept DateTimeInterface\\|null\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Entity/Location.php + + - + message: "#^Property Chill\\\\MainBundle\\\\Entity\\\\Location\\:\\:\\$updatedAt \\(DateTimeImmutable\\|null\\) does not accept DateTimeInterface\\|null\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Entity/Location.php + + - + message: "#^Property Chill\\\\MainBundle\\\\Entity\\\\Notification\\:\\:\\$updatedAt \\(DateTimeImmutable\\|null\\) does not accept DateTimeInterface\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Entity/Notification.php + + - + message: "#^Property Chill\\\\MainBundle\\\\Entity\\\\NotificationComment\\:\\:\\$createdAt \\(DateTimeImmutable\\|null\\) does not accept DateTimeInterface\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Entity/NotificationComment.php + + - + message: "#^Property Chill\\\\MainBundle\\\\Entity\\\\NotificationComment\\:\\:\\$updateAt \\(DateTimeImmutable\\|null\\) does not accept DateTimeInterface\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Entity/NotificationComment.php + + - + message: "#^Property Chill\\\\MainBundle\\\\Entity\\\\PermissionsGroup\\:\\:\\$groupCenters is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Entity/PermissionsGroup.php + + - + message: "#^Property Chill\\\\MainBundle\\\\Entity\\\\PermissionsGroup\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Entity/PermissionsGroup.php + + - + message: "#^Property Chill\\\\MainBundle\\\\Entity\\\\PostalCode\\:\\:\\$canonical is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Entity/PostalCode.php + + - + message: "#^Property Chill\\\\MainBundle\\\\Entity\\\\PostalCode\\:\\:\\$deletedAt is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Entity/PostalCode.php + + - + message: "#^Property Chill\\\\MainBundle\\\\Entity\\\\PostalCode\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Entity/PostalCode.php + + - + message: "#^PHPDoc tag @var for property Chill\\\\MainBundle\\\\Entity\\\\Regroupment\\:\\:\\$centers with type Chill\\\\MainBundle\\\\Entity\\\\Center is not subtype of native type Doctrine\\\\Common\\\\Collections\\\\Collection\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Entity/Regroupment.php + + - + message: "#^Property Chill\\\\MainBundle\\\\Entity\\\\RoleScope\\:\\:\\$permissionsGroups is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Entity/RoleScope.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and array will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Entity/User.php + + - + message: "#^Call to an undefined method Traversable\\<\\(int\\|string\\), mixed\\>\\:\\:current\\(\\)\\.$#" + count: 2 + path: src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflow.php + + - + message: "#^Call to an undefined method Traversable\\<\\(int\\|string\\), mixed\\>\\:\\:next\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflow.php + + - + message: "#^Call to an undefined method Traversable\\<\\(int\\|string\\), mixed\\>\\:\\:rewind\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflow.php + + - + message: "#^Call to an undefined method Traversable\\<\\(int\\|string\\), mixed\\>\\:\\:valid\\(\\)\\.$#" + count: 2 + path: src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflow.php + + - + message: "#^Unreachable statement \\- code above always terminates\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflowStep.php + + - + message: "#^Access to offset \\(int\\|string\\) on an unknown class Chill\\\\MainBundle\\\\Export\\\\type\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Export/ExportManager.php + + - + message: "#^Call to an undefined method Chill\\\\MainBundle\\\\Export\\\\ExportElementInterface\\:\\:requiredRole\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Export/ExportManager.php + + - + message: "#^Call to function is_iterable\\(\\) with array will always evaluate to true\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Export/ExportManager.php + + - + message: "#^Empty array passed to foreach\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Export/ExportManager.php + + - + message: "#^Instanceof between Doctrine\\\\ORM\\\\QueryBuilder and Doctrine\\\\ORM\\\\QueryBuilder will always evaluate to true\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Export/ExportManager.php + + - + message: "#^Iterating over an object of an unknown class Chill\\\\MainBundle\\\\Export\\\\type\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Export/ExportManager.php + + - + message: "#^PHPDoc tag @param for parameter \\$aliases with type Generator is incompatible with native type array\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Export/ExportManager.php + + - + message: "#^Parameter \\$data of method Chill\\\\MainBundle\\\\Export\\\\ExportManager\\:\\:handleAggregators\\(\\) has invalid type Chill\\\\MainBundle\\\\Export\\\\type\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Export/ExportManager.php + + - + message: "#^Parameter \\$data of method Chill\\\\MainBundle\\\\Export\\\\ExportManager\\:\\:retrieveUsedFilters\\(\\) has invalid type Chill\\\\MainBundle\\\\Export\\\\type\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Export/ExportManager.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and Chill\\\\MainBundle\\\\Export\\\\type will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Export/ExportManager.php + + - + message: "#^Yield can be used only with these return types\\: Generator, Iterator, Traversable, iterable\\.$#" + count: 5 + path: src/Bundle/ChillMainBundle/Export/ExportManager.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and array\\|string will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Export/Formatter/CSVFormatter.php + + - + message: "#^Variable \\$data in PHPDoc tag @var does not match assigned variable \\$contentData\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Export/Formatter/CSVFormatter.php + + - + message: "#^Parameter \\#2 \\$exportAlias \\(Chill\\\\MainBundle\\\\Export\\\\Formatter\\\\type\\) of method Chill\\\\MainBundle\\\\Export\\\\Formatter\\\\CSVListFormatter\\:\\:buildForm\\(\\) should be compatible with parameter \\$exportAlias \\(string\\) of method Chill\\\\MainBundle\\\\Export\\\\FormatterInterface\\:\\:buildForm\\(\\)$#" + count: 1 + path: src/Bundle/ChillMainBundle/Export/Formatter/CSVListFormatter.php + + - + message: "#^Parameter \\$exportAlias of method Chill\\\\MainBundle\\\\Export\\\\Formatter\\\\CSVListFormatter\\:\\:buildForm\\(\\) has invalid type Chill\\\\MainBundle\\\\Export\\\\Formatter\\\\type\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Export/Formatter/CSVListFormatter.php + + - + message: "#^Parameter \\#2 \\$exportAlias \\(Chill\\\\MainBundle\\\\Export\\\\Formatter\\\\type\\) of method Chill\\\\MainBundle\\\\Export\\\\Formatter\\\\CSVPivotedListFormatter\\:\\:buildForm\\(\\) should be compatible with parameter \\$exportAlias \\(string\\) of method Chill\\\\MainBundle\\\\Export\\\\FormatterInterface\\:\\:buildForm\\(\\)$#" + count: 1 + path: src/Bundle/ChillMainBundle/Export/Formatter/CSVPivotedListFormatter.php + + - + message: "#^Parameter \\$exportAlias of method Chill\\\\MainBundle\\\\Export\\\\Formatter\\\\CSVPivotedListFormatter\\:\\:buildForm\\(\\) has invalid type Chill\\\\MainBundle\\\\Export\\\\Formatter\\\\type\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Export/Formatter/CSVPivotedListFormatter.php + + - + message: "#^Access to offset 'format' on an unknown class Chill\\\\MainBundle\\\\Export\\\\Formatter\\\\type\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Export/Formatter/SpreadSheetFormatter.php + + - + message: "#^Access to offset mixed on an unknown class Chill\\\\MainBundle\\\\Export\\\\Formatter\\\\type\\.$#" + count: 2 + path: src/Bundle/ChillMainBundle/Export/Formatter/SpreadSheetFormatter.php + + - + message: "#^Iterating over an object of an unknown class Chill\\\\MainBundle\\\\Export\\\\Formatter\\\\type\\.$#" + count: 3 + path: src/Bundle/ChillMainBundle/Export/Formatter/SpreadSheetFormatter.php + + - + message: "#^Property Chill\\\\MainBundle\\\\Export\\\\Formatter\\\\SpreadSheetFormatter\\:\\:\\$aggregatorsData \\(Chill\\\\MainBundle\\\\Export\\\\Formatter\\\\type\\) does not accept array\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Export/Formatter/SpreadSheetFormatter.php + + - + message: "#^Property Chill\\\\MainBundle\\\\Export\\\\Formatter\\\\SpreadSheetFormatter\\:\\:\\$aggregatorsData has unknown class Chill\\\\MainBundle\\\\Export\\\\Formatter\\\\type as its type\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Export/Formatter/SpreadSheetFormatter.php + + - + message: "#^Property Chill\\\\MainBundle\\\\Export\\\\Formatter\\\\SpreadSheetFormatter\\:\\:\\$formatterData \\(Chill\\\\MainBundle\\\\Export\\\\Formatter\\\\type\\) does not accept array\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Export/Formatter/SpreadSheetFormatter.php + + - + message: "#^Property Chill\\\\MainBundle\\\\Export\\\\Formatter\\\\SpreadSheetFormatter\\:\\:\\$formatterData has unknown class Chill\\\\MainBundle\\\\Export\\\\Formatter\\\\type as its type\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Export/Formatter/SpreadSheetFormatter.php + + - + message: "#^Property Chill\\\\MainBundle\\\\Export\\\\Formatter\\\\SpreadSheetFormatter\\:\\:\\$result \\(Chill\\\\MainBundle\\\\Export\\\\Formatter\\\\type\\) does not accept array\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Export/Formatter/SpreadSheetFormatter.php + + - + message: "#^Property Chill\\\\MainBundle\\\\Export\\\\Formatter\\\\SpreadSheetFormatter\\:\\:\\$result has unknown class Chill\\\\MainBundle\\\\Export\\\\Formatter\\\\type as its type\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Export/Formatter/SpreadSheetFormatter.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and string will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Export/Formatter/SpreadSheetFormatter.php + + - + message: "#^Instanceof between string and DateTimeInterface will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Export/Formatter/SpreadsheetListFormatter.php + + - + message: "#^PHPDoc tag @param has invalid value \\(Array\\(String\\) \\$aggregatorAliases Array of the aliases of the aggregators\\. An aggregator do the \"group by\" on the data\\. \\$aggregatorAliases\\)\\: Unexpected token \"\\(\", expected variable at offset 343$#" + count: 1 + path: src/Bundle/ChillMainBundle/Export/FormatterInterface.php + + - + message: "#^PHPDoc tag @var for property Chill\\\\MainBundle\\\\Export\\\\Helper\\\\ExportAddressHelper\\:\\:\\$unitNamesKeysCache contains unresolvable type\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Export/Helper/ExportAddressHelper.php + + - + message: "#^PHPDoc tag @var for property Chill\\\\MainBundle\\\\Export\\\\Helper\\\\ExportAddressHelper\\:\\:\\$unitNamesKeysCache with type mixed is not subtype of native type array\\|null\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Export/Helper/ExportAddressHelper.php + + - + message: "#^Call to method createSearchForm\\(\\) on an unknown class Chill\\\\MainBundle\\\\Search\\\\HasAdvancedSearchForm\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Form/AdvancedSearchType.php + + - + message: "#^PHPDoc tag @param for parameter \\$resolver with type Chill\\\\MainBundle\\\\Form\\\\OptionsResolverInterface is not subtype of native type Symfony\\\\Component\\\\OptionsResolver\\\\OptionsResolver\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Form/CenterType.php + + - + message: "#^Parameter \\$resolver of method Chill\\\\MainBundle\\\\Form\\\\CenterType\\:\\:configureOptions\\(\\) has invalid type Chill\\\\MainBundle\\\\Form\\\\OptionsResolverInterface\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Form/CenterType.php + + - + message: "#^Instanceof between Chill\\\\MainBundle\\\\Entity\\\\Address and Chill\\\\MainBundle\\\\Entity\\\\Address will always evaluate to true\\.$#" + count: 2 + path: src/Bundle/ChillMainBundle/Form/DataMapper/AddressDataMapper.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and Chill\\\\MainBundle\\\\Entity\\\\Address will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Form/DataMapper/AddressDataMapper.php + + - + message: "#^Else branch is unreachable because previous condition is always true\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Form/DataMapper/ScopePickerDataMapper.php + + - + message: "#^Instanceof between Chill\\\\MainBundle\\\\Entity\\\\Scope and Chill\\\\MainBundle\\\\Entity\\\\Scope will always evaluate to true\\.$#" + count: 2 + path: src/Bundle/ChillMainBundle/Form/DataMapper/ScopePickerDataMapper.php + + - + message: "#^Unreachable statement \\- code above always terminates\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Form/DataMapper/ScopePickerDataMapper.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and string will always evaluate to false\\.$#" + count: 2 + path: src/Bundle/ChillMainBundle/Form/DataTransformer/IdToEntityDataTransformer.php + + - + message: "#^PHPDoc tag @param for parameter \\$resolver with type Chill\\\\MainBundle\\\\Form\\\\OptionsResolverInterface is not subtype of native type Symfony\\\\Component\\\\OptionsResolver\\\\OptionsResolver\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Form/LocationFormType.php + + - + message: "#^Parameter \\$resolver of method Chill\\\\MainBundle\\\\Form\\\\LocationFormType\\:\\:configureOptions\\(\\) has invalid type Chill\\\\MainBundle\\\\Form\\\\OptionsResolverInterface\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Form/LocationFormType.php + + - + message: "#^PHPDoc tag @param for parameter \\$resolver with type Chill\\\\MainBundle\\\\Form\\\\OptionsResolverInterface is not subtype of native type Symfony\\\\Component\\\\OptionsResolver\\\\OptionsResolver\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Form/PermissionsGroupType.php + + - + message: "#^Parameter \\$resolver of method Chill\\\\MainBundle\\\\Form\\\\PermissionsGroupType\\:\\:configureOptions\\(\\) has invalid type Chill\\\\MainBundle\\\\Form\\\\OptionsResolverInterface\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Form/PermissionsGroupType.php + + - + message: "#^PHPDoc tag @param for parameter \\$resolver with type Chill\\\\MainBundle\\\\Form\\\\OptionsResolverInterface is not subtype of native type Symfony\\\\Component\\\\OptionsResolver\\\\OptionsResolver\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Form/ScopeType.php + + - + message: "#^Parameter \\$resolver of method Chill\\\\MainBundle\\\\Form\\\\ScopeType\\:\\:configureOptions\\(\\) has invalid type Chill\\\\MainBundle\\\\Form\\\\OptionsResolverInterface\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Form/ScopeType.php + + - + message: "#^Property Chill\\\\MainBundle\\\\Form\\\\Type\\\\ChillPhoneNumberType\\:\\:\\$phoneNumberUtil is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Form/Type/ChillPhoneNumberType.php + + - + message: "#^Call to an undefined method Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface\\:\\:getId\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Form/Type/CommentType.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and array\\\\|Chill\\\\MainBundle\\\\Entity\\\\User will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Form/Type/DataTransformer/EntityToJsonTransformer.php + + - + message: "#^Method Chill\\\\MainBundle\\\\Form\\\\Type\\\\DataTransformer\\\\MultipleObjectsToIdTransformer\\:\\:transform\\(\\) should return Doctrine\\\\Common\\\\Collections\\\\ArrayCollection but returns array\\\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Form/Type/DataTransformer/MultipleObjectsToIdTransformer.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and string will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Form/Type/DataTransformer/ObjectToIdTransformer.php + + - + message: "#^Call to function is_int\\(\\) with int will always evaluate to true\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Form/Type/DataTransformer/PostalCodeToIdTransformer.php + + - + message: "#^Instanceof between Chill\\\\MainBundle\\\\Export\\\\ExportInterface and Chill\\\\MainBundle\\\\Export\\\\ExportInterface will always evaluate to true\\.$#" + count: 2 + path: src/Bundle/ChillMainBundle/Form/Type/Export/ExportType.php + + - + message: "#^Property Chill\\\\MainBundle\\\\Form\\\\Type\\\\Select2CountryType\\:\\:\\$requestStack is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Form/Type/Select2CountryType.php + + - + message: "#^Call to an undefined method Symfony\\\\Component\\\\OptionsResolver\\\\OptionsResolver\\:\\:replaceDefaults\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Form/Type/Select2EntityType.php + + - + message: "#^Property Chill\\\\MainBundle\\\\Form\\\\Type\\\\Select2LanguageType\\:\\:\\$requestStack is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Form/Type/Select2LanguageType.php + + - + message: "#^PHPDoc tag @param for parameter \\$resolver with type Chill\\\\MainBundle\\\\Form\\\\OptionsResolverInterface is not subtype of native type Symfony\\\\Component\\\\OptionsResolver\\\\OptionsResolver\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Form/UserType.php + + - + message: "#^Parameter \\$resolver of method Chill\\\\MainBundle\\\\Form\\\\UserType\\:\\:configureOptions\\(\\) has invalid type Chill\\\\MainBundle\\\\Form\\\\OptionsResolverInterface\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Form/UserType.php + + - + message: "#^Method Symfony\\\\Component\\\\OptionsResolver\\\\OptionsResolver\\:\\:setDefined\\(\\) invoked with 2 parameters, 1 required\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Form/WorkflowStepType.php + + - + message: "#^Property Chill\\\\MainBundle\\\\Notification\\\\Email\\\\NotificationMailer\\:\\:\\$translator is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Notification/Email/NotificationMailer.php + + - + message: "#^Property Chill\\\\MainBundle\\\\Notification\\\\NotificationHandlerManager\\:\\:\\$em is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Notification/NotificationHandlerManager.php + + - + message: "#^Strict comparison using \\=\\=\\= between 0 and float will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Pagination/Paginator.php + + - + message: "#^PHPDoc tag @param for parameter \\$phoneNumber with type string is incompatible with native type libphonenumber\\\\PhoneNumber\\|null\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Phonenumber/PhonenumberHelper.php + + - + message: "#^Expression on left side of \\?\\? is not nullable\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Phonenumber/Templating.php + + - + message: "#^Method Chill\\\\MainBundle\\\\Repository\\\\GeographicalUnitRepository\\:\\:findBy\\(\\) should return Chill\\\\MainBundle\\\\Entity\\\\GeographicalUnit\\|null but returns array\\\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Repository/GeographicalUnitRepository.php + + - + message: "#^Property Chill\\\\MainBundle\\\\Repository\\\\GeographicalUnitRepository\\:\\:\\$em is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Repository/GeographicalUnitRepository.php + + - + message: "#^Return type \\(Chill\\\\MainBundle\\\\Entity\\\\GeographicalUnit\\|null\\) of method Chill\\\\MainBundle\\\\Repository\\\\GeographicalUnitRepository\\:\\:findBy\\(\\) should be compatible with return type \\(array\\\\) of method Doctrine\\\\Persistence\\\\ObjectRepository\\\\:\\:findBy\\(\\)$#" + count: 2 + path: src/Bundle/ChillMainBundle/Repository/GeographicalUnitRepository.php + + - + message: "#^The @implements tag of class Chill\\\\MainBundle\\\\Repository\\\\SavedExportRepository describes Doctrine\\\\Persistence\\\\ObjectRepository but the class implements\\: Chill\\\\MainBundle\\\\Repository\\\\SavedExportRepositoryInterface$#" + count: 1 + path: src/Bundle/ChillMainBundle/Repository/SavedExportRepository.php + + - + message: "#^Interface Chill\\\\MainBundle\\\\Repository\\\\SavedExportRepositoryInterface has @implements tag, but can not implement any interface, must extend from it\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Repository/SavedExportRepositoryInterface.php + + - + message: "#^Strict comparison using \\!\\=\\= between null and array\\\\|Chill\\\\MainBundle\\\\Entity\\\\Center will always evaluate to true\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Repository/UserACLAwareRepository.php + + - + message: "#^Property Chill\\\\MainBundle\\\\Routing\\\\MenuComposer\\:\\:\\$routeCollection is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Routing/MenuComposer.php + + - + message: "#^Property Chill\\\\MainBundle\\\\Routing\\\\MenuTwig\\:\\:\\$container is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Routing/MenuTwig.php + + - + message: "#^Parameter \\$string of method Chill\\\\MainBundle\\\\Search\\\\AbstractSearch\\:\\:parseDate\\(\\) has invalid type Chill\\\\MainBundle\\\\Search\\\\type\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Search/AbstractSearch.php + + - + message: "#^Method Chill\\\\MainBundle\\\\Search\\\\SearchApi\\:\\:getResults\\(\\) has invalid return type Chill\\\\MainBundle\\\\Search\\\\Model\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Search/SearchApi.php + + - + message: "#^PHPDoc tag @return with type Chill\\\\MainBundle\\\\Search\\\\Model is not subtype of native type Chill\\\\MainBundle\\\\Serializer\\\\Model\\\\Collection\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Search/SearchApi.php + + - + message: "#^Property Chill\\\\MainBundle\\\\Search\\\\SearchApiNoQueryException\\:\\:\\$parameters is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Search/SearchApiNoQueryException.php + + - + message: "#^Property Chill\\\\MainBundle\\\\Search\\\\SearchApiNoQueryException\\:\\:\\$pattern is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Search/SearchApiNoQueryException.php + + - + message: "#^Property Chill\\\\MainBundle\\\\Search\\\\SearchApiNoQueryException\\:\\:\\$types is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Search/SearchApiNoQueryException.php + + - + message: "#^Property Chill\\\\MainBundle\\\\Search\\\\SearchApiResult\\:\\:\\$pertinence is unused\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Search/SearchApiResult.php + + - + message: "#^Else branch is unreachable because previous condition is always true\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Search/SearchProvider.php + + - + message: "#^If condition is always true\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Search/SearchProvider.php + + - + message: "#^Method Chill\\\\MainBundle\\\\Search\\\\SearchProvider\\:\\:getHasAdvancedFormByName\\(\\) has invalid return type Chill\\\\MainBundle\\\\Search\\\\HasAdvancedSearchForm\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Search/SearchProvider.php + + - + message: "#^Parameter \\$subject of method Chill\\\\MainBundle\\\\Search\\\\SearchProvider\\:\\:extractDomain\\(\\) has invalid type Chill\\\\MainBundle\\\\Search\\\\type\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Search/SearchProvider.php + + - + message: "#^Property Chill\\\\MainBundle\\\\Search\\\\SearchProvider\\:\\:\\$hasAdvancedFormSearchServices has unknown class Chill\\\\MainBundle\\\\Search\\\\HasAdvancedSearchForm as its type\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Search/SearchProvider.php + + - + message: "#^Strict comparison using \\!\\=\\= between null and string will always evaluate to true\\.$#" + count: 2 + path: src/Bundle/ChillMainBundle/Search/SearchProvider.php + + - + message: "#^Call to an undefined method Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface\\:\\:getGroupCenters\\(\\)\\.$#" + count: 2 + path: src/Bundle/ChillMainBundle/Security/Authorization/AuthorizationHelper.php + + - + message: "#^Empty array passed to foreach\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Security/Authorization/AuthorizationHelper.php + + - + message: "#^Instanceof between Chill\\\\MainBundle\\\\Entity\\\\Center\\|string and Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Security/Authorization/AuthorizationHelper.php + + - + message: "#^Instanceof between string and Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role will always evaluate to false\\.$#" + count: 2 + path: src/Bundle/ChillMainBundle/Security/Authorization/AuthorizationHelper.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and Chill\\\\MainBundle\\\\Entity\\\\Scope\\|iterable\\ will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Security/Authorization/AuthorizationHelper.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and null will always evaluate to true\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Security/Authorization/AuthorizationHelper.php + + - + message: "#^Unreachable statement \\- code above always terminates\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Security/Authorization/AuthorizationHelper.php + + - + message: "#^Property Chill\\\\MainBundle\\\\Security\\\\Authorization\\\\DefaultVoterHelper\\:\\:\\$centerResolverDispatcher is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Security/Authorization/DefaultVoterHelper.php + + - + message: "#^Method Chill\\\\MainBundle\\\\Workflow\\\\EntityWorkflowHandlerInterface\\:\\:getDeletionRoles\\(\\) invoked with 1 parameter, 0 required\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Security/Authorization/WorkflowEntityDeletionVoter.php + + - + message: "#^Instanceof between Chill\\\\MainBundle\\\\Entity\\\\User and Chill\\\\MainBundle\\\\Entity\\\\User will always evaluate to true\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Security/PasswordRecover/PasswordRecoverEvent.php + + - + message: "#^Parameter \\$ip of method Chill\\\\MainBundle\\\\Security\\\\PasswordRecover\\\\PasswordRecoverEvent\\:\\:__construct\\(\\) has invalid type Chill\\\\MainBundle\\\\Security\\\\PasswordRecover\\\\type\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Security/PasswordRecover/PasswordRecoverEvent.php + + - + message: "#^Parameter \\$token of method Chill\\\\MainBundle\\\\Security\\\\PasswordRecover\\\\PasswordRecoverEvent\\:\\:__construct\\(\\) has invalid type Chill\\\\MainBundle\\\\Security\\\\PasswordRecover\\\\type\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Security/PasswordRecover/PasswordRecoverEvent.php + + - + message: "#^Property Chill\\\\MainBundle\\\\Security\\\\PasswordRecover\\\\PasswordRecoverEvent\\:\\:\\$ip \\(string\\) does not accept Chill\\\\MainBundle\\\\Security\\\\PasswordRecover\\\\type\\|null\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Security/PasswordRecover/PasswordRecoverEvent.php + + - + message: "#^Property Chill\\\\MainBundle\\\\Security\\\\PasswordRecover\\\\PasswordRecoverEvent\\:\\:\\$token \\(string\\) does not accept Chill\\\\MainBundle\\\\Security\\\\PasswordRecover\\\\type\\|null\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Security/PasswordRecover/PasswordRecoverEvent.php + + - + message: "#^Call to function is_array\\(\\) with array\\ will always evaluate to true\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Security/Resolver/CenterResolverManager.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and array\\\\|Chill\\\\MainBundle\\\\Entity\\\\Center will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Security/Resolver/CenterResolverManager.php + + - + message: "#^Unreachable statement \\- code above always terminates\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Security/Resolver/CenterResolverManager.php + + - + message: "#^Instanceof between Chill\\\\MainBundle\\\\Entity\\\\HasCenterInterface and Chill\\\\MainBundle\\\\Entity\\\\HasCenterInterface will always evaluate to true\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Security/Resolver/DefaultCenterResolver.php + + - + message: "#^Unreachable statement \\- code above always terminates\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Security/Resolver/DefaultCenterResolver.php + + - + message: "#^Instanceof between Chill\\\\MainBundle\\\\Entity\\\\HasScopesInterface and Chill\\\\MainBundle\\\\Entity\\\\HasScopesInterface will always evaluate to true\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Security/Resolver/DefaultScopeResolver.php + + - + message: "#^Unreachable statement \\- code above always terminates\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Security/Resolver/DefaultScopeResolver.php + + - + message: "#^Method Chill\\\\MainBundle\\\\Security\\\\Resolver\\\\ResolverTwigExtension\\:\\:resolveCenter\\(\\) has invalid return type Chill\\\\MainBundle\\\\Security\\\\Resolver\\\\Center\\.$#" + count: 2 + path: src/Bundle/ChillMainBundle/Security/Resolver/ResolverTwigExtension.php + + - + message: "#^Method Chill\\\\MainBundle\\\\Security\\\\Resolver\\\\ResolverTwigExtension\\:\\:resolveCenter\\(\\) should return array\\\\|Chill\\\\MainBundle\\\\Security\\\\Resolver\\\\Center\\|null but returns array\\\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Security/Resolver/ResolverTwigExtension.php + + - + message: "#^Method Chill\\\\MainBundle\\\\Security\\\\Resolver\\\\ScopeResolverDispatcher\\:\\:resolveScope\\(\\) invoked with 0 parameters, 1\\-2 required\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Security/Resolver/ResolverTwigExtension.php + + - + message: "#^Method Chill\\\\MainBundle\\\\Security\\\\Resolver\\\\ScopeResolverDispatcher\\:\\:resolveScope\\(\\) should return Chill\\\\MainBundle\\\\Entity\\\\Scope\\|iterable\\ but returns null\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Security/Resolver/ScopeResolverDispatcher.php + + - + message: "#^Method Chill\\\\MainBundle\\\\Security\\\\RoleProvider\\:\\:getRoleTitle\\(\\) should return string but returns null\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Security/RoleProvider.php + + - + message: "#^Strict comparison using \\!\\=\\= between array\\ and null will always evaluate to true\\.$#" + count: 3 + path: src/Bundle/ChillMainBundle/Security/RoleProvider.php + + - + message: "#^Constant Chill\\\\MainBundle\\\\Serializer\\\\Normalizer\\\\AddressNormalizer\\:\\:NULL_POSTCODE_COUNTRY is unused\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Serializer/Normalizer/AddressNormalizer.php + + - + message: "#^Constant Chill\\\\MainBundle\\\\Serializer\\\\Normalizer\\\\AddressNormalizer\\:\\:NULL_VALUE is unused\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Serializer/Normalizer/AddressNormalizer.php + + - + message: "#^Instanceof between Chill\\\\MainBundle\\\\Entity\\\\Address and Chill\\\\MainBundle\\\\Entity\\\\Address will always evaluate to true\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Serializer/Normalizer/AddressNormalizer.php + + - + message: "#^Unreachable statement \\- code above always terminates\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Serializer/Normalizer/AddressNormalizer.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and Chill\\\\MainBundle\\\\Entity\\\\Embeddable\\\\CommentEmbeddable will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Serializer/Normalizer/CommentEmbeddableDocGenNormalizer.php + + - + message: "#^Method Chill\\\\MainBundle\\\\Serializer\\\\Normalizer\\\\DateNormalizer\\:\\:normalize\\(\\) should return array\\|ArrayObject\\|bool\\|float\\|int\\|string\\|null but return statement is missing\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Serializer/Normalizer/DateNormalizer.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and DateTimeInterface will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Serializer/Normalizer/DateNormalizer.php + + - + message: "#^Instanceof between Doctrine\\\\ORM\\\\Mapping\\\\ClassMetadata\\ and Doctrine\\\\ORM\\\\Mapping\\\\ClassMetadata will always evaluate to true\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Serializer/Normalizer/DoctrineExistingEntityNormalizer.php + + - + message: "#^Strict comparison using \\=\\=\\= between false and true will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Serializer/Normalizer/DoctrineExistingEntityNormalizer.php + + - + message: "#^Property Chill\\\\MainBundle\\\\Serializer\\\\Normalizer\\\\NotificationNormalizer\\:\\:\\$notificationHandlerManager is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Serializer/Normalizer/NotificationNormalizer.php + + - + message: "#^Result of && is always false\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Serializer/Normalizer/UserNormalizer.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and Chill\\\\MainBundle\\\\Entity\\\\User will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Serializer/Normalizer/UserNormalizer.php + + - + message: "#^Call to function is_int\\(\\) with int will always evaluate to true\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Service/Import/AddressReferenceFromBano.php + + - + message: "#^Call to an undefined method Symfony\\\\Component\\\\Mime\\\\RawMessage\\:\\:getSubject\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Service/Mailer/ChillMailer.php + + - + message: "#^Call to an undefined method Symfony\\\\Component\\\\Mime\\\\RawMessage\\:\\:getTo\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Service/Mailer/ChillMailer.php + + - + message: "#^Method Chill\\\\MainBundle\\\\Templating\\\\CSVCellTwig\\:\\:getName\\(\\) has invalid return type Chill\\\\MainBundle\\\\Templating\\\\The\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Templating/CSVCellTwig.php + + - + message: "#^Method Chill\\\\MainBundle\\\\Templating\\\\CSVCellTwig\\:\\:getName\\(\\) should return Chill\\\\MainBundle\\\\Templating\\\\The but returns string\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Templating/CSVCellTwig.php + + - + message: "#^Instanceof between string and DateTimeInterface will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Templating/ChillTwigHelper.php + + - + message: "#^PHPDoc tag @return has invalid value \\(array\\<'to'\\: DateTimeImmutable, 'from'\\: DateTimeImmutable\\>\\)\\: Unexpected token \"\\:\", expected '\\>' at offset 29$#" + count: 1 + path: src/Bundle/ChillMainBundle/Templating/Listing/FilterOrderHelper.php + + - + message: "#^Call to an undefined method Symfony\\\\Contracts\\\\Translation\\\\TranslatorInterface\\:\\:getFallbackLocales\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Templating/TranslatableStringHelper.php + + - + message: "#^Strict comparison using \\=\\=\\= between array\\{\\} and non\\-empty\\-array\\ will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Templating/TranslatableStringHelper.php + + - + message: "#^Method Chill\\\\MainBundle\\\\Templating\\\\TranslatableStringTwig\\:\\:getName\\(\\) has invalid return type Chill\\\\MainBundle\\\\Templating\\\\The\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Templating/TranslatableStringTwig.php + + - + message: "#^Method Chill\\\\MainBundle\\\\Templating\\\\TranslatableStringTwig\\:\\:getName\\(\\) should return Chill\\\\MainBundle\\\\Templating\\\\The but returns string\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Templating/TranslatableStringTwig.php + + - + message: "#^Call to method render\\(\\) on an unknown class Chill\\\\MainBundle\\\\Templating\\\\Widget\\\\Widget\\\\WidgetInterface\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Templating/Widget/WidgetRenderingTwig.php + + - + message: "#^Method Symfony\\\\Contracts\\\\EventDispatcher\\\\EventDispatcherInterface\\:\\:dispatch\\(\\) invoked with 2 parameters, 1 required\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Templating/Widget/WidgetRenderingTwig.php + + - + message: "#^PHPDoc tag @var for variable \\$widget contains unknown class Chill\\\\MainBundle\\\\Templating\\\\Widget\\\\Widget\\\\WidgetInterface\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Templating/Widget/WidgetRenderingTwig.php + + - + message: "#^Parameter \\$context of method Chill\\\\MainBundle\\\\Timeline\\\\TimelineBuilder\\:\\:countItems\\(\\) has invalid type Chill\\\\MainBundle\\\\Timeline\\\\unknown\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Timeline/TimelineBuilder.php + + - + message: "#^Unreachable statement \\- code above always terminates\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Timeline/TimelineBuilder.php + + - + message: "#^Parameter \\$context of method Chill\\\\MainBundle\\\\Timeline\\\\TimelineProviderInterface\\:\\:getEntityTemplate\\(\\) has invalid type Chill\\\\MainBundle\\\\Timeline\\\\type\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Timeline/TimelineProviderInterface.php + + - + message: "#^Parameter \\$entity of method Chill\\\\MainBundle\\\\Timeline\\\\TimelineProviderInterface\\:\\:getEntityTemplate\\(\\) has invalid type Chill\\\\MainBundle\\\\Timeline\\\\type\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Timeline/TimelineProviderInterface.php + + - + message: "#^Property Chill\\\\MainBundle\\\\Util\\\\DateRangeCovering\\:\\:\\$intervals is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Util/DateRangeCovering.php + + - + message: "#^Access to an undefined property Symfony\\\\Component\\\\Validator\\\\Constraint\\:\\:\\$messageDuplicateEmail\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Validation/Validator/UserUniqueEmailAndUsername.php + + - + message: "#^Access to an undefined property Symfony\\\\Component\\\\Validator\\\\Constraint\\:\\:\\$messageDuplicateUsername\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Validation/Validator/UserUniqueEmailAndUsername.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and string will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Validation/Validator/ValidPhonenumber.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and Chill\\\\MainBundle\\\\Entity\\\\User will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Validator/Constraints/Entity/UserCircleConsistencyValidator.php + + - + message: "#^Access to an undefined property Symfony\\\\Component\\\\Validator\\\\Constraint\\:\\:\\$element\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Validator/Constraints/Export/ExportElementConstraintValidator.php + + - + message: "#^Property Chill\\\\MainBundle\\\\Workflow\\\\Templating\\\\WorkflowTwigExtensionRuntime\\:\\:\\$entityWorkflowManager is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Workflow/Templating/WorkflowTwigExtensionRuntime.php + + - + message: "#^Instanceof between Chill\\\\MainBundle\\\\Entity\\\\Workflow\\\\EntityWorkflow and Chill\\\\MainBundle\\\\Entity\\\\Workflow\\\\EntityWorkflow will always evaluate to true\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Workflow/Validator/EntityWorkflowCreationValidator.php + + - + message: "#^Instanceof between Chill\\\\MainBundle\\\\Entity\\\\Workflow\\\\EntityWorkflowStep and Chill\\\\MainBundle\\\\Entity\\\\Workflow\\\\EntityWorkflowStep will always evaluate to true\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Workflow/Validator/StepDestValidValidator.php + + - + message: "#^PHPDoc tag @param for parameter \\$postSql with type Chill\\\\PersonBundle\\\\Actions\\\\type is incompatible with native type string\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Actions/ActionEvent.php + + - + message: "#^Parameter \\$postSql of method Chill\\\\PersonBundle\\\\Actions\\\\ActionEvent\\:\\:addPostSql\\(\\) has invalid type Chill\\\\PersonBundle\\\\Actions\\\\type\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Actions/ActionEvent.php + + - + message: "#^Method Chill\\\\PersonBundle\\\\Actions\\\\Remove\\\\PersonMove\\:\\:getSQL\\(\\) has invalid return type Chill\\\\PersonBundle\\\\Actions\\\\Remove\\\\type\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Actions/Remove/PersonMove.php + + - + message: "#^Method Chill\\\\PersonBundle\\\\Actions\\\\Remove\\\\PersonMove\\:\\:getSQL\\(\\) should return Chill\\\\PersonBundle\\\\Actions\\\\Remove\\\\type but returns array\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Actions/Remove/PersonMove.php + + - + message: "#^Method Symfony\\\\Contracts\\\\EventDispatcher\\\\EventDispatcherInterface\\:\\:dispatch\\(\\) invoked with 2 parameters, 1 required\\.$#" + count: 2 + path: src/Bundle/ChillPersonBundle/Actions/Remove/PersonMove.php + + - + message: "#^Method Chill\\\\PersonBundle\\\\CRUD\\\\Controller\\\\EntityPersonCRUDController\\:\\:filterQueryEntitiesByPerson\\(\\) has invalid return type Chill\\\\PersonBundle\\\\CRUD\\\\Controller\\\\QueryBuilder\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/CRUD/Controller/EntityPersonCRUDController.php + + - + message: "#^PHPDoc tag @param for parameter \\$qb with type Chill\\\\PersonBundle\\\\CRUD\\\\Controller\\\\QueryBuilder is not subtype of native type Doctrine\\\\ORM\\\\QueryBuilder\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/CRUD/Controller/EntityPersonCRUDController.php + + - + message: "#^PHPDoc tag @return with type Chill\\\\PersonBundle\\\\CRUD\\\\Controller\\\\QueryBuilder is not subtype of native type Doctrine\\\\ORM\\\\QueryBuilder\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/CRUD/Controller/EntityPersonCRUDController.php + + - + message: "#^PHPDoc tag @throws with type Chill\\\\PersonBundle\\\\CRUD\\\\Controller\\\\Symfony\\\\Component\\\\HttpKernel\\\\Exception\\\\NotFoundHttpException is not subtype of Throwable$#" + count: 1 + path: src/Bundle/ChillPersonBundle/CRUD/Controller/EntityPersonCRUDController.php + + - + message: "#^Parameter \\$action of method Chill\\\\PersonBundle\\\\CRUD\\\\Controller\\\\EntityPersonCRUDController\\:\\:createEntity\\(\\) has invalid type Chill\\\\MainBundle\\\\CRUD\\\\Controller\\\\string\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/CRUD/Controller/EntityPersonCRUDController.php + + - + message: "#^Parameter \\$qb of method Chill\\\\PersonBundle\\\\CRUD\\\\Controller\\\\EntityPersonCRUDController\\:\\:filterQueryEntitiesByPerson\\(\\) has invalid type Chill\\\\PersonBundle\\\\CRUD\\\\Controller\\\\QueryBuilder\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/CRUD/Controller/EntityPersonCRUDController.php + + - + message: "#^Call to an undefined method Symfony\\\\Component\\\\DependencyInjection\\\\Extension\\\\ExtensionInterface\\:\\:addWidgetFactory\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/ChillPersonBundle.php + + - + message: "#^Iterating over an object of an unknown class Chill\\\\PersonBundle\\\\Actions\\\\Remove\\\\type\\.$#" + count: 2 + path: src/Bundle/ChillPersonBundle/Command/ChillPersonMoveCommand.php + + - + message: "#^PHPDoc tag @var for property Chill\\\\PersonBundle\\\\Command\\\\ImportSocialWorkMetadata\\:\\:\\$importer with type Psr\\\\Log\\\\LoggerInterface is not subtype of native type Chill\\\\PersonBundle\\\\Service\\\\Import\\\\ChillImporter\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Command/ImportSocialWorkMetadata.php + + - + message: "#^Method Symfony\\\\Contracts\\\\EventDispatcher\\\\EventDispatcherInterface\\:\\:dispatch\\(\\) invoked with 2 parameters, 1 required\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseApiController.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Controller\\\\AccompanyingCourseApiController\\:\\:\\$accompanyingPeriodACLAwareRepository is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseApiController.php + + - + message: "#^Variable \\$accompanyingPeriod in PHPDoc tag @var does not match assigned variable \\$action\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseApiController.php + + - + message: "#^Strict comparison using \\!\\=\\= between null and Doctrine\\\\Common\\\\Collections\\\\Collection will always evaluate to true\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseController.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and Chill\\\\PersonBundle\\\\Entity\\\\AccompanyingPeriod will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseController.php + + - + message: "#^Call to an undefined method Symfony\\\\Component\\\\Serializer\\\\SerializerInterface\\:\\:normalize\\(\\)\\.$#" + count: 2 + path: src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseWorkController.php + + - + message: "#^Method Chill\\\\PersonBundle\\\\Controller\\\\AccompanyingCourseWorkController\\:\\:createDeleteForm\\(\\) should return Symfony\\\\Component\\\\Form\\\\Form but returns Symfony\\\\Component\\\\Form\\\\FormInterface\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseWorkController.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and Chill\\\\PersonBundle\\\\Entity\\\\AccompanyingPeriod\\\\AccompanyingPeriodWork will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseWorkController.php + + - + message: "#^Call to an undefined method Psr\\\\Container\\\\ContainerInterface\\:\\:getParameter\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Controller/AccompanyingPeriodController.php + + - + message: "#^Method Symfony\\\\Component\\\\Form\\\\FormInterface\\:\\:isValid\\(\\) invoked with 1 parameter, 0 required\\.$#" + count: 2 + path: src/Bundle/ChillPersonBundle/Controller/AccompanyingPeriodController.php + + - + message: "#^Method Symfony\\\\Contracts\\\\EventDispatcher\\\\EventDispatcherInterface\\:\\:dispatch\\(\\) invoked with 2 parameters, 1 required\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Controller/AccompanyingPeriodController.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and Chill\\\\PersonBundle\\\\Entity\\\\AccompanyingPeriod will always evaluate to false\\.$#" + count: 2 + path: src/Bundle/ChillPersonBundle/Controller/AccompanyingPeriodController.php + + - + message: "#^Call to an undefined method Symfony\\\\Component\\\\HttpFoundation\\\\Session\\\\SessionInterface\\:\\:getFlashBag\\(\\)\\.$#" + count: 2 + path: src/Bundle/ChillPersonBundle/Controller/HouseholdCompositionController.php + + - + message: "#^Call to an undefined method Doctrine\\\\Common\\\\Collections\\\\Collection&iterable\\\\:\\:initialize\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Controller/HouseholdController.php + + - + message: "#^Call to an undefined method Doctrine\\\\Persistence\\\\ObjectRepository\\\\:\\:findById\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Controller/HouseholdMemberController.php + + - + message: "#^Call to an undefined method Symfony\\\\Component\\\\Serializer\\\\SerializerInterface\\:\\:normalize\\(\\)\\.$#" + count: 3 + path: src/Bundle/ChillPersonBundle/Controller/HouseholdMemberController.php + + - + message: "#^Elseif condition is always true\\.$#" + count: 2 + path: src/Bundle/ChillPersonBundle/Controller/PersonAddressController.php + + - + message: "#^Method Chill\\\\PersonBundle\\\\Controller\\\\PersonAddressController\\:\\:createCreateForm\\(\\) should return Symfony\\\\Component\\\\Form\\\\Form but returns Symfony\\\\Component\\\\Form\\\\FormInterface\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Controller/PersonAddressController.php + + - + message: "#^Method Chill\\\\PersonBundle\\\\Controller\\\\PersonAddressController\\:\\:createEditForm\\(\\) should return Symfony\\\\Component\\\\Form\\\\Form but returns Symfony\\\\Component\\\\Form\\\\FormInterface\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Controller/PersonAddressController.php + + - + message: "#^PHPDoc tag @param for parameter \\$person with type Chill\\\\PersonBundle\\\\Controller\\\\Chill\\\\PersonBundle\\\\Entity\\\\Person is not subtype of native type Chill\\\\PersonBundle\\\\Entity\\\\Person\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Controller/PersonAddressController.php + + - + message: "#^Parameter \\$person of method Chill\\\\PersonBundle\\\\Controller\\\\PersonAddressController\\:\\:validatePerson\\(\\) has invalid type Chill\\\\PersonBundle\\\\Controller\\\\Chill\\\\PersonBundle\\\\Entity\\\\Person\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Controller/PersonAddressController.php + + - + message: "#^Call to an undefined method Doctrine\\\\ORM\\\\EntityRepository\\\\:\\:findOneByEntity\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Controller/PersonController.php + + - + message: "#^Call to an undefined method Symfony\\\\Component\\\\Form\\\\FormInterface\\:\\:isClicked\\(\\)\\.$#" + count: 3 + path: src/Bundle/ChillPersonBundle/Controller/PersonController.php + + - + message: "#^Call to an undefined method Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface\\:\\:getGroupCenters\\(\\)\\.$#" + count: 2 + path: src/Bundle/ChillPersonBundle/Controller/PersonController.php + + - + message: "#^Method Chill\\\\PersonBundle\\\\Controller\\\\PersonController\\:\\:_validatePersonAndAccompanyingPeriod\\(\\) is unused\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Controller/PersonController.php + + - + message: "#^Method Symfony\\\\Contracts\\\\EventDispatcher\\\\EventDispatcherInterface\\:\\:dispatch\\(\\) invoked with 2 parameters, 1 required\\.$#" + count: 2 + path: src/Bundle/ChillPersonBundle/Controller/PersonController.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Controller\\\\PersonController\\:\\:\\$logger is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Controller/PersonController.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and Chill\\\\PersonBundle\\\\Entity\\\\Person will always evaluate to false\\.$#" + count: 2 + path: src/Bundle/ChillPersonBundle/Controller/PersonController.php + + - + message: "#^Access to an undefined property Chill\\\\PersonBundle\\\\Entity\\\\Person\\:\\:\\$counters\\.$#" + count: 2 + path: src/Bundle/ChillPersonBundle/Controller/PersonDuplicateController.php + + - + message: "#^Call to an undefined method Doctrine\\\\Persistence\\\\ObjectRepository\\\\:\\:countByParameters\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Controller/PersonDuplicateController.php + + - + message: "#^Cannot access property \\$getId on null\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Controller/PersonDuplicateController.php + + - + message: "#^Iterating over an object of an unknown class Chill\\\\PersonBundle\\\\Actions\\\\Remove\\\\type\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Controller/PersonDuplicateController.php + + - + message: "#^Method Chill\\\\PersonBundle\\\\Controller\\\\PersonDuplicateController\\:\\:_getCounters\\(\\) never returns null so it can be removed from the return type\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Controller/PersonDuplicateController.php + + - + message: "#^Method Symfony\\\\Contracts\\\\EventDispatcher\\\\EventDispatcherInterface\\:\\:dispatch\\(\\) invoked with 2 parameters, 1 required\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Controller/PersonDuplicateController.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Controller\\\\PersonDuplicateController\\:\\:\\$translator \\(Symfony\\\\Component\\\\Translation\\\\TranslatorInterface\\) does not accept Symfony\\\\Contracts\\\\Translation\\\\TranslatorInterface\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Controller/PersonDuplicateController.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Controller\\\\PersonDuplicateController\\:\\:\\$translator is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Controller/PersonDuplicateController.php + + - + message: "#^Call to function is_int\\(\\) with int will always evaluate to true\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Controller/ReassignAccompanyingPeriodController.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Controller\\\\ReassignAccompanyingPeriodController\\:\\:\\$userRender is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Controller/ReassignAccompanyingPeriodController.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Controller\\\\RelationshipApiController\\:\\:\\$validator is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Controller/RelationshipApiController.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Controller\\\\SocialWorkGoalApiController\\:\\:\\$paginator is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Controller/SocialWorkGoalApiController.php + + - + message: "#^Method Symfony\\\\Contracts\\\\EventDispatcher\\\\EventDispatcherInterface\\:\\:dispatch\\(\\) invoked with 2 parameters, 1 required\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Controller/TimelinePersonController.php + + - + message: "#^Unreachable statement \\- code above always terminates\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadAccompanyingPeriodNotifications.php + + - + message: "#^Invalid array key type array\\\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadRelationships.php + + - + message: "#^Call to an undefined method Symfony\\\\Component\\\\Config\\\\Definition\\\\Builder\\\\NodeDefinition\\:\\:canBeDisabled\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/DependencyInjection/Configuration.php + + - + message: "#^Call to an undefined method Symfony\\\\Component\\\\Config\\\\Definition\\\\Builder\\\\NodeDefinition\\:\\:values\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/DependencyInjection/Configuration.php + + - + message: "#^Method Symfony\\\\Component\\\\Config\\\\Definition\\\\Builder\\\\TreeBuilder\\:\\:getRootNode\\(\\) invoked with 1 parameter, 0 required\\.$#" + count: 2 + path: src/Bundle/ChillPersonBundle/DependencyInjection/Configuration.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Doctrine\\\\DQL\\\\AddressPart\\:\\:\\$part is unused\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Doctrine/DQL/AddressPart.php + + - + message: "#^Result of method Doctrine\\\\ORM\\\\Query\\\\Parser\\:\\:match\\(\\) \\(void\\) is used\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Doctrine/DQL/AddressPart.php + + - + message: "#^Call to an undefined method Doctrine\\\\Common\\\\Collections\\\\Collection&iterable\\\\:\\:matching\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php + + - + message: "#^Call to an undefined method Doctrine\\\\Common\\\\Collections\\\\Collection\\:\\:matching\\(\\)\\.$#" + count: 2 + path: src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php + + - + message: "#^Instanceof between Chill\\\\PersonBundle\\\\Entity\\\\Person and Chill\\\\PersonBundle\\\\Entity\\\\Person will always evaluate to true\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php + + + - + message: "#^Method Chill\\\\PersonBundle\\\\Entity\\\\AccompanyingPeriod\\:\\:getCreatedAt\\(\\) should return DateTime\\|null but returns DateTimeInterface\\|null\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php + + - + message: "#^Method Chill\\\\PersonBundle\\\\Entity\\\\AccompanyingPeriod\\:\\:getRecursiveSocialActions\\(\\) has invalid return type Chill\\\\PersonBundle\\\\Entity\\\\SocialAction\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php + + - + message: "#^Method Chill\\\\PersonBundle\\\\Entity\\\\AccompanyingPeriod\\:\\:getRecursiveSocialIssues\\(\\) has invalid return type Chill\\\\PersonBundle\\\\Entity\\\\SocialIssues\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php + + - + message: "#^Negated boolean expression is always true\\.$#" + count: 2 + path: src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php + + - + message: "#^PHPDoc tag @return with type array\\ is incompatible with native type Doctrine\\\\Common\\\\Collections\\\\Collection\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php + + - + message: "#^PHPDoc tag @return with type iterable is not subtype of native type Doctrine\\\\Common\\\\Collections\\\\Collection\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Entity\\\\AccompanyingPeriod\\:\\:\\$updatedAt is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Entity\\\\AccompanyingPeriod\\:\\:\\$updatedBy is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and Chill\\\\PersonBundle\\\\Entity\\\\AccompanyingPeriod\\\\AccompanyingPeriodStepHistory will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php + + - + message: "#^Unreachable statement \\- code above always terminates\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Entity\\\\AccompanyingPeriod\\\\AccompanyingPeriodWork\\:\\:\\$createdAt \\(DateTimeImmutable\\|null\\) does not accept DateTimeInterface\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWork.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Entity\\\\AccompanyingPeriod\\\\AccompanyingPeriodWork\\:\\:\\$endDate \\(DateTimeImmutable\\|null\\) does not accept DateTimeInterface\\|null\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWork.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Entity\\\\AccompanyingPeriod\\\\AccompanyingPeriodWork\\:\\:\\$startDate \\(DateTimeImmutable\\|null\\) does not accept DateTimeInterface\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWork.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Entity\\\\AccompanyingPeriod\\\\AccompanyingPeriodWork\\:\\:\\$updatedAt \\(DateTimeImmutable\\|null\\) does not accept DateTimeInterface\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWork.php + + - + message: "#^PHPDoc tag @param for parameter \\$createdAt with type DateTimeImmutable\\|null is not subtype of native type DateTimeInterface\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWorkEvaluation.php + + - + message: "#^PHPDoc tag @param for parameter \\$updatedAt with type DateTimeImmutable\\|null is not subtype of native type DateTimeInterface\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWorkEvaluation.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Entity\\\\AccompanyingPeriod\\\\AccompanyingPeriodWorkEvaluation\\:\\:\\$createdAt \\(DateTimeImmutable\\|null\\) does not accept DateTimeInterface\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWorkEvaluation.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Entity\\\\AccompanyingPeriod\\\\AccompanyingPeriodWorkEvaluation\\:\\:\\$updatedAt \\(DateTimeImmutable\\|null\\) does not accept DateTimeInterface\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWorkEvaluation.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Entity\\\\AccompanyingPeriod\\\\Comment\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/Comment.php + + - + message: "#^Method Chill\\\\PersonBundle\\\\Entity\\\\AccompanyingPeriodParticipation\\:\\:checkSameStartEnd\\(\\) is unused\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriodParticipation.php + + - + message: "#^Call to an undefined method Doctrine\\\\Common\\\\Collections\\\\Collection&iterable\\\\:\\:matching\\(\\)\\.$#" + count: 3 + path: src/Bundle/ChillPersonBundle/Entity/Household/Household.php + + - + message: "#^Call to an undefined method Doctrine\\\\Common\\\\Collections\\\\Collection\\:\\:matching\\(\\)\\.$#" + count: 5 + path: src/Bundle/ChillPersonBundle/Entity/Household/Household.php + + - + message: "#^Call to an undefined method Traversable\\<\\(int\\|string\\), mixed\\>\\:\\:uasort\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/Household/Household.php + + - + message: "#^Cannot call method filter\\(\\) on array\\\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/Household/Household.php + + - + message: "#^Cannot call method toArray\\(\\) on array\\\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/Household/Household.php + + - + message: "#^Method Chill\\\\PersonBundle\\\\Entity\\\\Household\\\\Household\\:\\:getAddresses\\(\\) should return array\\ but returns Doctrine\\\\Common\\\\Collections\\\\Collection\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/Household/Household.php + + - + message: "#^PHPDoc tag @return with type array\\ is incompatible with native type Doctrine\\\\Common\\\\Collections\\\\Collection\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/Household/Household.php + + - + message: "#^PHPDoc tag @return with type array\\ is incompatible with native type Doctrine\\\\Common\\\\Collections\\\\Collection\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/Household/Household.php + + - + message: "#^Strict comparison using \\!\\=\\= between null and Chill\\\\PersonBundle\\\\Entity\\\\Household\\\\HouseholdMember will always evaluate to true\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/Household/Household.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and int will always evaluate to false\\.$#" + count: 2 + path: src/Bundle/ChillPersonBundle/Entity/Household/Household.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Entity\\\\Household\\\\PersonHouseholdAddress\\:\\:\\$address is never written, only read\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/Household/PersonHouseholdAddress.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Entity\\\\Household\\\\PersonHouseholdAddress\\:\\:\\$household is never written, only read\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/Household/PersonHouseholdAddress.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Entity\\\\Household\\\\PersonHouseholdAddress\\:\\:\\$person is never written, only read\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/Household/PersonHouseholdAddress.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Entity\\\\Household\\\\PersonHouseholdAddress\\:\\:\\$validFrom is never written, only read\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/Household/PersonHouseholdAddress.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Entity\\\\Household\\\\PersonHouseholdAddress\\:\\:\\$validTo is never written, only read\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/Household/PersonHouseholdAddress.php + + - + message: "#^PHPDoc tag @param has invalid value \\(string array \\$name\\)\\: Unexpected token \"array\", expected variable at offset 49$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/MaritalStatus.php + + - + message: "#^PHPDoc tag @return with type string is incompatible with native type array\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/MaritalStatus.php + + - + message: "#^Call to an undefined method Doctrine\\\\Common\\\\Collections\\\\Collection&iterable\\\\:\\:matching\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/Person.php + + - + message: "#^Call to an undefined method Doctrine\\\\Common\\\\Collections\\\\Collection&iterable\\\\:\\:matching\\(\\)\\.$#" + count: 3 + path: src/Bundle/ChillPersonBundle/Entity/Person.php + + - + message: "#^Call to an undefined method Doctrine\\\\Common\\\\Collections\\\\Collection&iterable\\\\:\\:matching\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/Person.php + + - + message: "#^Call to an undefined method Doctrine\\\\Common\\\\Collections\\\\Collection\\:\\:matching\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/Person.php + + - + message: "#^Instanceof between DateTime and DateTimeInterface will always evaluate to true\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/Person.php + + - + message: "#^Negated boolean expression is always true\\.$#" + count: 3 + path: src/Bundle/ChillPersonBundle/Entity/Person.php + + - + message: "#^PHPDoc tag @return with type array\\ is incompatible with native type Doctrine\\\\Common\\\\Collections\\\\Collection\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/Person.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Entity\\\\Person\\:\\:\\$calendars is unused\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/Person.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Entity\\\\Person\\:\\:\\$center is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/Person.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Entity\\\\Person\\:\\:\\$currentHouseholdAt is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/Person.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Entity\\\\Person\\:\\:\\$deathdate \\(DateTimeImmutable\\|null\\) does not accept DateTimeInterface\\|null\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/Person.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Entity\\\\Person\\:\\:\\$maritalStatusDate \\(DateTime\\|null\\) does not accept DateTimeInterface\\|null\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/Person.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Entity\\\\Person\\:\\:\\$periodLocatedOn is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/Person.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Entity\\\\Person\\:\\:\\$proxyAccompanyingPeriodOpenState is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/Person.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Entity\\\\Person\\:\\:\\$spokenLanguages \\(Doctrine\\\\Common\\\\Collections\\\\ArrayCollection\\) does not accept Doctrine\\\\Common\\\\Collections\\\\Collection\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/Person.php + + - + message: "#^Strict comparison using \\=\\=\\= between true and Chill\\\\PersonBundle\\\\Entity\\\\AccompanyingPeriodParticipation\\|null will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/Person.php + + - + message: "#^Unreachable statement \\- code above always terminates\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/Person.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Entity\\\\Person\\\\PersonResource\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/Person/PersonResource.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Entity\\\\Person\\\\ResidentialAddress\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/Person/ResidentialAddress.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Entity\\\\PersonAltName\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/PersonAltName.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Entity\\\\PersonPhone\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/PersonPhone.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Entity\\\\Relationships\\\\Relationship\\:\\:\\$createdAt \\(DateTimeImmutable\\|null\\) does not accept DateTimeInterface\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/Relationships/Relationship.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Entity\\\\Relationships\\\\Relationship\\:\\:\\$updatedAt \\(DateTimeImmutable\\|null\\) does not accept DateTimeInterface\\|null\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/Relationships/Relationship.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Entity\\\\SocialWork\\\\Result\\:\\:\\$desactivationDate \\(DateTime\\|null\\) does not accept DateTimeInterface\\|null\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/SocialWork/Result.php + + - + message: "#^If condition is always false\\.$#" + count: 2 + path: src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialAction.php + + - + message: "#^Negated boolean expression is always true\\.$#" + count: 2 + path: src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialAction.php + + - + message: "#^Call to an undefined method Chill\\\\PersonBundle\\\\Entity\\\\SocialWork\\\\SocialAction\\:\\:getSocialIssue\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialIssue.php + + - + message: "#^Call to an undefined method Chill\\\\PersonBundle\\\\Entity\\\\SocialWork\\\\SocialAction\\:\\:setSocialIssue\\(\\)\\.$#" + count: 2 + path: src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialIssue.php + + - + message: "#^If condition is always false\\.$#" + count: 2 + path: src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialIssue.php + + - + message: "#^Negated boolean expression is always true\\.$#" + count: 4 + path: src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialIssue.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Entity\\\\SocialWork\\\\SocialIssue\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialIssue.php + + - + message: "#^Strict comparison using \\=\\=\\= between DateTimeImmutable and null will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Event/Person/PersonAddressMoveEvent.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and DateTimeImmutable will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Event/Person/PersonAddressMoveEvent.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Export\\\\Aggregator\\\\AccompanyingCourseAggregators\\\\DurationAggregator\\:\\:\\$translator is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/DurationAggregator.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Export\\\\Aggregator\\\\EvaluationAggregators\\\\ByEndDateAggregator\\:\\:\\$translator is unused\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Export/Aggregator/EvaluationAggregators/ByEndDateAggregator.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Export\\\\Aggregator\\\\EvaluationAggregators\\\\ByMaxDateAggregator\\:\\:\\$translator is unused\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Export/Aggregator/EvaluationAggregators/ByMaxDateAggregator.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Export\\\\Aggregator\\\\EvaluationAggregators\\\\ByStartDateAggregator\\:\\:\\$translator is unused\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Export/Aggregator/EvaluationAggregators/ByStartDateAggregator.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Export\\\\Aggregator\\\\HouseholdAggregators\\\\ChildrenNumberAggregator\\:\\:\\$translator is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Export/Aggregator/HouseholdAggregators/ChildrenNumberAggregator.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Export\\\\Export\\\\ListAccompanyingPeriod\\:\\:\\$userHelper is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Export/Export/ListAccompanyingPeriod.php + + - + message: "#^Call to method extractOtherValue\\(\\) on an unknown class Chill\\\\CustomFieldsBundle\\\\Service\\\\CustomFieldInterface\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Export/Export/ListPerson.php + + - + message: "#^Call to method getChoices\\(\\) on an unknown class Chill\\\\CustomFieldsBundle\\\\Service\\\\CustomFieldInterface\\.$#" + count: 2 + path: src/Bundle/ChillPersonBundle/Export/Export/ListPerson.php + + - + message: "#^Call to method isChecked\\(\\) on an unknown class Chill\\\\CustomFieldsBundle\\\\Service\\\\CustomFieldInterface\\.$#" + count: 2 + path: src/Bundle/ChillPersonBundle/Export/Export/ListPerson.php + + - + message: "#^Call to method isMultiple\\(\\) on an unknown class Chill\\\\CustomFieldsBundle\\\\Service\\\\CustomFieldInterface\\.$#" + count: 2 + path: src/Bundle/ChillPersonBundle/Export/Export/ListPerson.php + + - + message: "#^Call to method render\\(\\) on an unknown class Chill\\\\CustomFieldsBundle\\\\Service\\\\CustomFieldInterface\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Export/Export/ListPerson.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Export\\\\Export\\\\ListPersonDuplicate\\:\\:\\$translator \\(Symfony\\\\Component\\\\Translation\\\\TranslatorInterface\\) does not accept Symfony\\\\Contracts\\\\Translation\\\\TranslatorInterface\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Export/Export/ListPersonDuplicate.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Export\\\\Filter\\\\AccompanyingCourseFilters\\\\AdministrativeLocationFilter\\:\\:\\$translatableStringHelper is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/AdministrativeLocationFilter.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Export\\\\Filter\\\\AccompanyingCourseFilters\\\\SocialActionFilter\\:\\:\\$translatableStringHelper is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/SocialActionFilter.php + + - + message: "#^Method Chill\\\\PersonBundle\\\\Export\\\\Filter\\\\AccompanyingCourseFilters\\\\UserScopeFilter\\:\\:getUserMainScope\\(\\) is unused\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/UserScopeFilter.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Export\\\\Filter\\\\SocialWorkFilters\\\\SocialWorkTypeFilter\\:\\:\\$socialActionRender is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/SocialWorkTypeFilter.php + + - + message: "#^PHPDoc tag @param for parameter \\$personRepository with type Chill\\\\PersonBundle\\\\Form\\\\ChoiceLoader\\\\EntityRepository is not subtype of native type Chill\\\\PersonBundle\\\\Repository\\\\PersonRepository\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Form/ChoiceLoader/PersonChoiceLoader.php + + - + message: "#^Parameter \\$personRepository of method Chill\\\\PersonBundle\\\\Form\\\\ChoiceLoader\\\\PersonChoiceLoader\\:\\:__construct\\(\\) has invalid type Chill\\\\PersonBundle\\\\Form\\\\ChoiceLoader\\\\EntityRepository\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Form/ChoiceLoader/PersonChoiceLoader.php + + - + message: "#^Method Symfony\\\\Contracts\\\\EventDispatcher\\\\EventDispatcherInterface\\:\\:dispatch\\(\\) invoked with 2 parameters, 1 required\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Form/CreationPersonType.php + + - + message: "#^Call to function is_array\\(\\) with Doctrine\\\\Common\\\\Collections\\\\Collection will always evaluate to false\\.$#" + count: 2 + path: src/Bundle/ChillPersonBundle/Form/DataMapper/PersonAltNameDataMapper.php + + - + message: "#^Call to method getData\\(\\) on an unknown class Chill\\\\PersonBundle\\\\Form\\\\DataMapper\\\\FormInterface\\.$#" + count: 3 + path: src/Bundle/ChillPersonBundle/Form/DataMapper/PersonAltNameDataMapper.php + + - + message: "#^Parameter \\#1 \\$forms \\(array\\\\) of method Chill\\\\PersonBundle\\\\Form\\\\DataMapper\\\\PersonAltNameDataMapper\\:\\:mapFormsToData\\(\\) should be compatible with parameter \\$forms \\(iterable\\&Traversable\\) of method Symfony\\\\Component\\\\Form\\\\DataMapperInterface\\:\\:mapFormsToData\\(\\)$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Form/DataMapper/PersonAltNameDataMapper.php + + - + message: "#^Parameter \\$forms of method Chill\\\\PersonBundle\\\\Form\\\\DataMapper\\\\PersonAltNameDataMapper\\:\\:mapFormsToData\\(\\) has invalid type Chill\\\\PersonBundle\\\\Form\\\\DataMapper\\\\FormInterface\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Form/DataMapper/PersonAltNameDataMapper.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Form\\\\PersonResourceType\\:\\:\\$personRender is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Form/PersonResourceType.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Form\\\\PersonResourceType\\:\\:\\$thirdPartyRender is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Form/PersonResourceType.php + + - + message: "#^PHPDoc tag @param for parameter \\$resolver with type Chill\\\\PersonBundle\\\\Form\\\\OptionsResolverInterface is not subtype of native type Symfony\\\\Component\\\\OptionsResolver\\\\OptionsResolver\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Form/PersonType.php + + - + message: "#^Parameter \\$resolver of method Chill\\\\PersonBundle\\\\Form\\\\PersonType\\:\\:configureOptions\\(\\) has invalid type Chill\\\\PersonBundle\\\\Form\\\\OptionsResolverInterface\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Form/PersonType.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Form\\\\PersonType\\:\\:\\$parameterBag is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Form/PersonType.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Form\\\\PersonType\\:\\:\\$translatableStringHelper \\(Chill\\\\MainBundle\\\\Templating\\\\TranslatableStringHelper\\) does not accept Chill\\\\MainBundle\\\\Templating\\\\TranslatableStringHelperInterface\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Form/PersonType.php + + - + message: "#^Strict comparison using \\=\\=\\= between 'create' and 'create' will always evaluate to true\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Form/SocialWork/SocialIssueType.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Form\\\\Type\\\\PickPersonType\\:\\:\\$user \\(Chill\\\\MainBundle\\\\Entity\\\\User\\) does not accept string\\|Stringable\\|Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Form/Type/PickPersonType.php + + - + message: "#^Call to an undefined method Doctrine\\\\Common\\\\Collections\\\\Collection&iterable\\\\:\\:matching\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Household/MembersEditor.php + + - + message: "#^Method Doctrine\\\\Common\\\\Collections\\\\ExpressionBuilder\\:\\:isNull\\(\\) invoked with 2 parameters, 1 required\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Household/MembersEditor.php + + - + message: "#^Cannot call method getId\\(\\) on string\\|Stringable\\|Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Privacy/PrivacyEventSubscriber.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Repository\\\\AccompanyingPeriod\\\\AccompanyingPeriodWorkGoalRepository\\:\\:\\$repository is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkGoalRepository.php + + - + message: "#^Call to an undefined method Doctrine\\\\ORM\\\\EntityRepository\\:\\:countByAccompanyingPeriod\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkRepository.php + + - + message: "#^Call to an undefined method Doctrine\\\\ORM\\\\EntityRepository\\:\\:findByAccompanyingPeriod\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkRepository.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Repository\\\\AccompanyingPeriod\\\\CommentRepository\\:\\:\\$repository is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/CommentRepository.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Repository\\\\AccompanyingPeriod\\\\OriginRepository\\:\\:\\$repository is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/OriginRepository.php + + - + message: "#^PHPDoc tag @param has invalid value \\(array\\|PostalCode\\[\\]\\)\\: Unexpected token \"\\\\n \\*\", expected variable at offset 36$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepository.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Repository\\\\AccompanyingPeriodParticipationRepository\\:\\:\\$repository is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodParticipationRepository.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Repository\\\\Household\\\\HouseholdMembersRepository\\:\\:\\$repository is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Repository/Household/HouseholdMembersRepository.php + + - + message: "#^Method Chill\\\\PersonBundle\\\\Repository\\\\Household\\\\PositionRepository\\:\\:findOneBy\\(\\) should return array\\ but returns object\\|null\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Repository/Household/PositionRepository.php + + - + message: "#^Return type \\(array\\\\) of method Chill\\\\PersonBundle\\\\Repository\\\\Household\\\\PositionRepository\\:\\:findOneBy\\(\\) should be compatible with return type \\(object\\|null\\) of method Doctrine\\\\Persistence\\\\ObjectRepository\\\\:\\:findOneBy\\(\\)$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Repository/Household/PositionRepository.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Repository\\\\PersonACLAwareRepository\\:\\:\\$countryRepository is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Repository/PersonACLAwareRepository.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Repository\\\\PersonAltNameRepository\\:\\:\\$repository is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Repository/PersonAltNameRepository.php + + - + message: "#^PHPDoc tag @return with type array\\\\|null is not subtype of native type array\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Repository/ResidentialAddressRepository.php + + - + message: "#^Method Chill\\\\PersonBundle\\\\Search\\\\PersonSearch\\:\\:renderResult\\(\\) should return string but returns array\\\\>\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Search/PersonSearch.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Search\\\\SearchHouseholdApiProvider\\:\\:\\$authorizationHelper is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Search/SearchHouseholdApiProvider.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Search\\\\SearchHouseholdApiProvider\\:\\:\\$security is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Search/SearchHouseholdApiProvider.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Search\\\\SearchPersonApiProvider\\:\\:\\$authorizationHelper is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Search/SearchPersonApiProvider.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Search\\\\SearchPersonApiProvider\\:\\:\\$security is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Search/SearchPersonApiProvider.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Security\\\\Authorization\\\\AccompanyingPeriodVoter\\:\\:\\$security is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Security/Authorization/AccompanyingPeriodVoter.php + + - + message: "#^PHPDoc tag @return with type bool\\|void is not subtype of native type bool\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Security/Authorization/AccompanyingPeriodWorkEvaluationDocumentVoter.php + + - + message: "#^Elseif branch is unreachable because previous condition is always true\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Security/Authorization/AccompanyingPeriodWorkVoter.php + + - + message: "#^Instanceof between \\*NEVER\\* and Chill\\\\PersonBundle\\\\Entity\\\\AccompanyingPeriod will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Security/Authorization/AccompanyingPeriodWorkVoter.php + + - + message: "#^Instanceof between Chill\\\\PersonBundle\\\\Entity\\\\AccompanyingPeriod\\\\AccompanyingPeriodWork and Chill\\\\PersonBundle\\\\Entity\\\\AccompanyingPeriod\\\\AccompanyingPeriodWork will always evaluate to true\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Security/Authorization/AccompanyingPeriodWorkVoter.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Security\\\\Authorization\\\\AccompanyingPeriodWorkVoter\\:\\:\\$voterHelper is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Security/Authorization/AccompanyingPeriodWorkVoter.php + + - + message: "#^Unreachable statement \\- code above always terminates\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Security/Authorization/AccompanyingPeriodWorkVoter.php + + - + message: "#^Constant Chill\\\\PersonBundle\\\\Serializer\\\\Normalizer\\\\AccompanyingPeriodDocGenNormalizer\\:\\:IGNORE_FIRST_PASS_KEY is unused\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/AccompanyingPeriodDocGenNormalizer.php + + - + message: "#^Strict comparison using \\!\\=\\= between Chill\\\\PersonBundle\\\\Entity\\\\Person\\|Chill\\\\ThirdPartyBundle\\\\Entity\\\\ThirdParty and null will always evaluate to true\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/AccompanyingPeriodDocGenNormalizer.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and null will always evaluate to true\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/AccompanyingPeriodDocGenNormalizer.php + + - + message: "#^Unreachable statement \\- code above always terminates\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/AccompanyingPeriodDocGenNormalizer.php + + - + message: "#^Unreachable statement \\- code above always terminates\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/AccompanyingPeriodParticipationNormalizer.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Serializer\\\\Normalizer\\\\AccompanyingPeriodWorkDenormalizer\\:\\:\\$em is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/AccompanyingPeriodWorkDenormalizer.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Serializer\\\\Normalizer\\\\AccompanyingPeriodWorkDenormalizer\\:\\:\\$workRepository is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/AccompanyingPeriodWorkDenormalizer.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Serializer\\\\Normalizer\\\\AccompanyingPeriodWorkEvaluationDocumentNormalizer\\:\\:\\$registry is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/AccompanyingPeriodWorkEvaluationDocumentNormalizer.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Serializer\\\\Normalizer\\\\AccompanyingPeriodWorkEvaluationNormalizer\\:\\:\\$registry is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/AccompanyingPeriodWorkEvaluationNormalizer.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Serializer\\\\Normalizer\\\\AccompanyingPeriodWorkNormalizer\\:\\:\\$registry is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/AccompanyingPeriodWorkNormalizer.php + + - + message: "#^Call to function is_array\\(\\) with 'concerned' will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/MembersEditorNormalizer.php + + - + message: "#^Expression on left side of \\?\\? is not nullable\\.$#" + count: 3 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/MembersEditorNormalizer.php + + - + message: "#^Left side of && is always false\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/MembersEditorNormalizer.php + + - + message: "#^Strict comparison using \\=\\=\\= between false and false will always evaluate to true\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/MembersEditorNormalizer.php + + - + message: "#^Instanceof between Chill\\\\PersonBundle\\\\Entity\\\\Person and Chill\\\\PersonBundle\\\\Entity\\\\Person will always evaluate to true\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonDocGenNormalizer.php + + - + message: "#^Method Chill\\\\PersonBundle\\\\Serializer\\\\Normalizer\\\\PersonDocGenNormalizer\\:\\:hasGroup\\(\\) is unused\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonDocGenNormalizer.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and Chill\\\\PersonBundle\\\\Entity\\\\Person will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonDocGenNormalizer.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Serializer\\\\Normalizer\\\\PersonJsonNormalizer\\:\\:\\$phoneNumberHelper is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonJsonNormalizer.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and Chill\\\\PersonBundle\\\\Entity\\\\Relationships\\\\Relationship will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/RelationshipDocGenNormalizer.php + + - + message: "#^Method Chill\\\\PersonBundle\\\\Serializer\\\\Normalizer\\\\SocialIssueNormalizer\\:\\:normalize\\(\\) should return array\\|ArrayObject\\|bool\\|float\\|int\\|string\\|null but return statement is missing\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/SocialIssueNormalizer.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and Chill\\\\PersonBundle\\\\Entity\\\\SocialWork\\\\SocialIssue will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/SocialIssueNormalizer.php + + - + message: "#^PHPDoc tag @return with type array\\|ArrayObject\\|bool\\|float\\|int\\|string\\|void\\|null is not subtype of native type array\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/WorkflowNormalizer.php + + - + message: "#^Call to an undefined method Chill\\\\DocStoreBundle\\\\Entity\\\\Document\\:\\:setCourse\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Service/DocGenerator/AccompanyingPeriodContext.php + + - + message: "#^Call to function array_key_exists\\(\\) with 'category' and array will always evaluate to true\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Service/DocGenerator/AccompanyingPeriodContext.php + + - + message: "#^Else branch is unreachable because ternary operator condition is always true\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Service/DocGenerator/AccompanyingPeriodContext.php + + - + message: "#^Call to an undefined method Chill\\\\DocStoreBundle\\\\Entity\\\\Document\\:\\:setPerson\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Service/DocGenerator/PersonContext.php + + - + message: "#^Call to function array_key_exists\\(\\) with 'category' and array will always evaluate to true\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Service/DocGenerator/PersonContext.php + + - + message: "#^Comparison operation \"\\<\" between 1 and array results in an error\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Service/DocGenerator/PersonContext.php + + - + message: "#^Else branch is unreachable because ternary operator condition is always true\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Service/DocGenerator/PersonContext.php + + - + message: "#^PHPDoc tag @param has invalid value \\(array\\\\|SocialIssue\\|string, SocialAction\\|null\\>\\|Evaluation\\|Goal\\|Result\\> \\$previousRow\\)\\: Unexpected token \"\\<\", expected type at offset 333$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Service/Import/SocialWorkMetadata.php + + - + message: "#^PHPDoc tag @return has invalid value \\(array\\\\|SocialIssue\\|string, SocialAction\\|null\\>\\|Evaluation\\|Goal\\|Result\\>\\)\\: Unexpected token \"\\<\", expected type at offset 505$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Service/Import/SocialWorkMetadata.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Service\\\\Import\\\\SocialWorkMetadata\\:\\:\\$socialActionRepository is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Service/Import/SocialWorkMetadata.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Service\\\\Import\\\\SocialWorkMetadata\\:\\:\\$socialIssueRepository is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Service/Import/SocialWorkMetadata.php + + - + message: "#^Call to method getId\\(\\) on an unknown class ChillPersonBundle\\:AccompanyingPeriod\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Timeline/AbstractTimelineAccompanyingPeriod.php + + - + message: "#^Cannot call method setKey\\(\\) on array\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Timeline/TimelineAccompanyingPeriodClosing.php + + - + message: "#^Parameter \\$context of method Chill\\\\PersonBundle\\\\Timeline\\\\TimelineAccompanyingPeriodClosing\\:\\:getEntityTemplate\\(\\) has invalid type Chill\\\\MainBundle\\\\Timeline\\\\type\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Timeline/TimelineAccompanyingPeriodClosing.php + + - + message: "#^Parameter \\$entity of method Chill\\\\PersonBundle\\\\Timeline\\\\TimelineAccompanyingPeriodClosing\\:\\:getEntityTemplate\\(\\) has invalid type Chill\\\\MainBundle\\\\Timeline\\\\type\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Timeline/TimelineAccompanyingPeriodClosing.php + + - + message: "#^Cannot call method setKey\\(\\) on array\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Timeline/TimelineAccompanyingPeriodOpening.php + + - + message: "#^Parameter \\$context of method Chill\\\\PersonBundle\\\\Timeline\\\\TimelineAccompanyingPeriodOpening\\:\\:getEntityTemplate\\(\\) has invalid type Chill\\\\MainBundle\\\\Timeline\\\\type\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Timeline/TimelineAccompanyingPeriodOpening.php + + - + message: "#^Parameter \\$entity of method Chill\\\\PersonBundle\\\\Timeline\\\\TimelineAccompanyingPeriodOpening\\:\\:getEntityTemplate\\(\\) has invalid type Chill\\\\MainBundle\\\\Timeline\\\\type\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Timeline/TimelineAccompanyingPeriodOpening.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Validator\\\\Constraints\\\\AccompanyingPeriod\\\\AccompanyingPeriodValidityValidator\\:\\:\\$token is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Validator/Constraints/AccompanyingPeriod/AccompanyingPeriodValidityValidator.php + + - + message: "#^Property Chill\\\\PersonBundle\\\\Validator\\\\Constraints\\\\AccompanyingPeriod\\\\ParticipationOverlapValidator\\:\\:\\$thirdpartyRender is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Validator/Constraints/AccompanyingPeriod/ParticipationOverlapValidator.php + + - + message: "#^Strict comparison using \\=\\=\\= between 0 and int\\<2, max\\> will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Validator/Constraints/AccompanyingPeriod/ParticipationOverlapValidator.php + + - + message: "#^Access to an undefined property Symfony\\\\Component\\\\Validator\\\\Constraint\\:\\:\\$message\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Validator/Constraints/Household/HouseholdMembershipSequentialValidator.php + + - + message: "#^Access to an undefined property Symfony\\\\Component\\\\Validator\\\\Constraint\\:\\:\\$message\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Validator/Constraints/Household/MaxHolderValidator.php + + - + message: "#^Access to an undefined property Symfony\\\\Component\\\\Validator\\\\Constraint\\:\\:\\$messageInfinity\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Validator/Constraints/Household/MaxHolderValidator.php + + - + message: "#^Access to an undefined property Symfony\\\\Component\\\\Validator\\\\Constraint\\:\\:\\$message\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Validator/Constraints/Person/BirthdateValidator.php + + - + message: "#^Access to an undefined property Symfony\\\\Component\\\\Validator\\\\Constraint\\:\\:\\$message\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Validator/Constraints/Person/PersonHasCenterValidator.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and string\\|Stringable\\|Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Widget/PersonListWidget.php + + - + message: "#^Call to an undefined method Symfony\\\\Component\\\\Config\\\\Definition\\\\Builder\\\\NodeParentInterface\\:\\:info\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Widget/PersonListWidgetFactory.php + + - + message: "#^Parameter \\$place of method Chill\\\\PersonBundle\\\\Widget\\\\PersonListWidgetFactory\\:\\:configureOptions\\(\\) has invalid type Chill\\\\MainBundle\\\\DependencyInjection\\\\Widget\\\\Factory\\\\type\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Widget/PersonListWidgetFactory.php + + - + message: "#^Call to an undefined method Doctrine\\\\Persistence\\\\ObjectManager\\:\\:createQuery\\(\\)\\.$#" + count: 2 + path: src/Bundle/ChillReportBundle/Controller/ReportController.php + + - + message: "#^Call to an undefined method Doctrine\\\\Persistence\\\\ObjectRepository\\\\:\\:findByCFGroup\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillReportBundle/Controller/ReportController.php + + - + message: "#^Call to an undefined method Doctrine\\\\Persistence\\\\ObjectRepository\\\\:\\:findByEntity\\(\\)\\.$#" + count: 2 + path: src/Bundle/ChillReportBundle/Controller/ReportController.php + + - + message: "#^Call to method getId\\(\\) on an unknown class ChillReportBundle\\:Report\\.$#" + count: 2 + path: src/Bundle/ChillReportBundle/Controller/ReportController.php + + - + message: "#^Call to method getPerson\\(\\) on an unknown class ChillReportBundle\\:Report\\.$#" + count: 3 + path: src/Bundle/ChillReportBundle/Controller/ReportController.php + + - + message: "#^Method Chill\\\\ReportBundle\\\\Controller\\\\ReportController\\:\\:createAction\\(\\) has invalid return type Chill\\\\ReportBundle\\\\Controller\\\\Response\\.$#" + count: 1 + path: src/Bundle/ChillReportBundle/Controller/ReportController.php + + - + message: "#^Method Chill\\\\ReportBundle\\\\Controller\\\\ReportController\\:\\:createAction\\(\\) should return Chill\\\\ReportBundle\\\\Controller\\\\Response but returns Symfony\\\\Component\\\\HttpFoundation\\\\RedirectResponse\\.$#" + count: 1 + path: src/Bundle/ChillReportBundle/Controller/ReportController.php + + - + message: "#^Method Chill\\\\ReportBundle\\\\Controller\\\\ReportController\\:\\:createAction\\(\\) should return Chill\\\\ReportBundle\\\\Controller\\\\Response but returns Symfony\\\\Component\\\\HttpFoundation\\\\Response\\.$#" + count: 1 + path: src/Bundle/ChillReportBundle/Controller/ReportController.php + + - + message: "#^Method Chill\\\\ReportBundle\\\\Controller\\\\ReportController\\:\\:createCreateForm\\(\\) should return Symfony\\\\Component\\\\Form\\\\Form but returns Symfony\\\\Component\\\\Form\\\\FormInterface\\.$#" + count: 1 + path: src/Bundle/ChillReportBundle/Controller/ReportController.php + + - + message: "#^Method Chill\\\\ReportBundle\\\\Controller\\\\ReportController\\:\\:createEditForm\\(\\) should return Symfony\\\\Component\\\\Form\\\\Form but returns Symfony\\\\Component\\\\Form\\\\FormInterface\\.$#" + count: 1 + path: src/Bundle/ChillReportBundle/Controller/ReportController.php + + - + message: "#^Method Chill\\\\ReportBundle\\\\Controller\\\\ReportController\\:\\:editAction\\(\\) has invalid return type Chill\\\\ReportBundle\\\\Controller\\\\Response\\.$#" + count: 1 + path: src/Bundle/ChillReportBundle/Controller/ReportController.php + + - + message: "#^Method Chill\\\\ReportBundle\\\\Controller\\\\ReportController\\:\\:editAction\\(\\) should return Chill\\\\ReportBundle\\\\Controller\\\\Response but returns Symfony\\\\Component\\\\HttpFoundation\\\\Response\\.$#" + count: 1 + path: src/Bundle/ChillReportBundle/Controller/ReportController.php + + - + message: "#^Method Chill\\\\ReportBundle\\\\Controller\\\\ReportController\\:\\:exportAction\\(\\) has invalid return type Chill\\\\ReportBundle\\\\Controller\\\\A\\.$#" + count: 1 + path: src/Bundle/ChillReportBundle/Controller/ReportController.php + + - + message: "#^Method Chill\\\\ReportBundle\\\\Controller\\\\ReportController\\:\\:exportAction\\(\\) should return Chill\\\\ReportBundle\\\\Controller\\\\A but returns Symfony\\\\Component\\\\HttpFoundation\\\\Response\\.$#" + count: 1 + path: src/Bundle/ChillReportBundle/Controller/ReportController.php + + - + message: "#^Method Chill\\\\ReportBundle\\\\Controller\\\\ReportController\\:\\:listAction\\(\\) has invalid return type Chill\\\\ReportBundle\\\\Controller\\\\Response\\.$#" + count: 1 + path: src/Bundle/ChillReportBundle/Controller/ReportController.php + + - + message: "#^Method Chill\\\\ReportBundle\\\\Controller\\\\ReportController\\:\\:listAction\\(\\) should return Chill\\\\ReportBundle\\\\Controller\\\\Response but returns Symfony\\\\Component\\\\HttpFoundation\\\\Response\\.$#" + count: 1 + path: src/Bundle/ChillReportBundle/Controller/ReportController.php + + - + message: "#^Method Chill\\\\ReportBundle\\\\Controller\\\\ReportController\\:\\:newAction\\(\\) has invalid return type Chill\\\\ReportBundle\\\\Controller\\\\Response\\.$#" + count: 1 + path: src/Bundle/ChillReportBundle/Controller/ReportController.php + + - + message: "#^Method Chill\\\\ReportBundle\\\\Controller\\\\ReportController\\:\\:newAction\\(\\) should return Chill\\\\ReportBundle\\\\Controller\\\\Response but returns Symfony\\\\Component\\\\HttpFoundation\\\\Response\\.$#" + count: 1 + path: src/Bundle/ChillReportBundle/Controller/ReportController.php + + - + message: "#^Method Chill\\\\ReportBundle\\\\Controller\\\\ReportController\\:\\:selectReportTypeAction\\(\\) has invalid return type Chill\\\\ReportBundle\\\\Controller\\\\Response\\.$#" + count: 1 + path: src/Bundle/ChillReportBundle/Controller/ReportController.php + + - + message: "#^Method Chill\\\\ReportBundle\\\\Controller\\\\ReportController\\:\\:selectReportTypeAction\\(\\) should return Chill\\\\ReportBundle\\\\Controller\\\\Response but returns Symfony\\\\Component\\\\HttpFoundation\\\\RedirectResponse\\.$#" + count: 2 + path: src/Bundle/ChillReportBundle/Controller/ReportController.php + + - + message: "#^Method Chill\\\\ReportBundle\\\\Controller\\\\ReportController\\:\\:selectReportTypeAction\\(\\) should return Chill\\\\ReportBundle\\\\Controller\\\\Response but returns Symfony\\\\Component\\\\HttpFoundation\\\\Response\\.$#" + count: 1 + path: src/Bundle/ChillReportBundle/Controller/ReportController.php + + - + message: "#^Method Chill\\\\ReportBundle\\\\Controller\\\\ReportController\\:\\:selectReportTypeForExportAction\\(\\) has invalid return type Chill\\\\ReportBundle\\\\Controller\\\\Response\\.$#" + count: 1 + path: src/Bundle/ChillReportBundle/Controller/ReportController.php + + - + message: "#^Method Chill\\\\ReportBundle\\\\Controller\\\\ReportController\\:\\:selectReportTypeForExportAction\\(\\) should return Chill\\\\ReportBundle\\\\Controller\\\\Response but returns Symfony\\\\Component\\\\HttpFoundation\\\\RedirectResponse\\.$#" + count: 2 + path: src/Bundle/ChillReportBundle/Controller/ReportController.php + + - + message: "#^Method Chill\\\\ReportBundle\\\\Controller\\\\ReportController\\:\\:selectReportTypeForExportAction\\(\\) should return Chill\\\\ReportBundle\\\\Controller\\\\Response but returns Symfony\\\\Component\\\\HttpFoundation\\\\Response\\.$#" + count: 1 + path: src/Bundle/ChillReportBundle/Controller/ReportController.php + + - + message: "#^Method Chill\\\\ReportBundle\\\\Controller\\\\ReportController\\:\\:updateAction\\(\\) has invalid return type Chill\\\\ReportBundle\\\\Controller\\\\Response\\.$#" + count: 1 + path: src/Bundle/ChillReportBundle/Controller/ReportController.php + + - + message: "#^Method Chill\\\\ReportBundle\\\\Controller\\\\ReportController\\:\\:updateAction\\(\\) should return Chill\\\\ReportBundle\\\\Controller\\\\Response but returns Symfony\\\\Component\\\\HttpFoundation\\\\RedirectResponse\\.$#" + count: 1 + path: src/Bundle/ChillReportBundle/Controller/ReportController.php + + - + message: "#^Method Chill\\\\ReportBundle\\\\Controller\\\\ReportController\\:\\:updateAction\\(\\) should return Chill\\\\ReportBundle\\\\Controller\\\\Response but returns Symfony\\\\Component\\\\HttpFoundation\\\\Response\\.$#" + count: 1 + path: src/Bundle/ChillReportBundle/Controller/ReportController.php + + - + message: "#^Method Chill\\\\ReportBundle\\\\Controller\\\\ReportController\\:\\:viewAction\\(\\) has invalid return type Chill\\\\ReportBundle\\\\Controller\\\\Response\\.$#" + count: 1 + path: src/Bundle/ChillReportBundle/Controller/ReportController.php + + - + message: "#^Method Chill\\\\ReportBundle\\\\Controller\\\\ReportController\\:\\:viewAction\\(\\) should return Chill\\\\ReportBundle\\\\Controller\\\\Response but returns Symfony\\\\Component\\\\HttpFoundation\\\\Response\\.$#" + count: 1 + path: src/Bundle/ChillReportBundle/Controller/ReportController.php + + - + message: "#^Method Symfony\\\\Contracts\\\\EventDispatcher\\\\EventDispatcherInterface\\:\\:dispatch\\(\\) invoked with 2 parameters, 1 required\\.$#" + count: 4 + path: src/Bundle/ChillReportBundle/Controller/ReportController.php + + - + message: "#^Negated boolean expression is always false\\.$#" + count: 1 + path: src/Bundle/ChillReportBundle/Controller/ReportController.php + + - + message: "#^Method Chill\\\\ReportBundle\\\\DataFixtures\\\\ORM\\\\LoadReports\\:\\:getRandomChoice\\(\\) never returns string so it can be removed from the return type\\.$#" + count: 1 + path: src/Bundle/ChillReportBundle/DataFixtures/ORM/LoadReports.php + + - + message: "#^Method Chill\\\\ReportBundle\\\\DataFixtures\\\\ORM\\\\LoadReports\\:\\:getRandomChoice\\(\\) should return array\\\\|string but returns array\\\\.$#" + count: 1 + path: src/Bundle/ChillReportBundle/DataFixtures/ORM/LoadReports.php + + - + message: "#^Method Chill\\\\ReportBundle\\\\DataFixtures\\\\ORM\\\\LoadReports\\:\\:pickChoice\\(\\) has invalid return type Chill\\\\ReportBundle\\\\DataFixtures\\\\ORM\\\\the\\.$#" + count: 1 + path: src/Bundle/ChillReportBundle/DataFixtures/ORM/LoadReports.php + + - + message: "#^Strict comparison using \\=\\=\\= between '_other' and Chill\\\\ReportBundle\\\\DataFixtures\\\\ORM\\\\the will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillReportBundle/DataFixtures/ORM/LoadReports.php + + - + message: "#^Strict comparison using \\=\\=\\= between DateTime and null will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillReportBundle/DataFixtures/ORM/LoadReports.php + + - + message: "#^Method Symfony\\\\Component\\\\Config\\\\Definition\\\\Builder\\\\TreeBuilder\\:\\:getRootNode\\(\\) invoked with 1 parameter, 0 required\\.$#" + count: 1 + path: src/Bundle/ChillReportBundle/DependencyInjection/Configuration.php + + - + message: "#^PHPDoc tag @param for parameter \\$scope with type string is incompatible with native type Chill\\\\MainBundle\\\\Entity\\\\Scope\\.$#" + count: 1 + path: src/Bundle/ChillReportBundle/Entity/Report.php + + - + message: "#^Property Chill\\\\ReportBundle\\\\Entity\\\\Report\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: src/Bundle/ChillReportBundle/Entity/Report.php + + - + message: "#^Call to method extractOtherValue\\(\\) on an unknown class Chill\\\\CustomFieldsBundle\\\\Service\\\\CustomFieldInterface\\.$#" + count: 1 + path: src/Bundle/ChillReportBundle/Export/Export/ReportList.php + + - + message: "#^Call to method getChoices\\(\\) on an unknown class Chill\\\\CustomFieldsBundle\\\\Service\\\\CustomFieldInterface\\.$#" + count: 2 + path: src/Bundle/ChillReportBundle/Export/Export/ReportList.php + + - + message: "#^Call to method isChecked\\(\\) on an unknown class Chill\\\\CustomFieldsBundle\\\\Service\\\\CustomFieldInterface\\.$#" + count: 2 + path: src/Bundle/ChillReportBundle/Export/Export/ReportList.php + + - + message: "#^Call to method isMultiple\\(\\) on an unknown class Chill\\\\CustomFieldsBundle\\\\Service\\\\CustomFieldInterface\\.$#" + count: 2 + path: src/Bundle/ChillReportBundle/Export/Export/ReportList.php + + - + message: "#^Call to method render\\(\\) on an unknown class Chill\\\\CustomFieldsBundle\\\\Service\\\\CustomFieldInterface\\.$#" + count: 1 + path: src/Bundle/ChillReportBundle/Export/Export/ReportList.php + + - + message: "#^Property Chill\\\\ReportBundle\\\\Form\\\\ReportType\\:\\:\\$user \\(Chill\\\\MainBundle\\\\Entity\\\\User\\) does not accept string\\|Stringable\\|Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface\\.$#" + count: 1 + path: src/Bundle/ChillReportBundle/Form/ReportType.php + + - + message: "#^Call to method getId\\(\\) on an unknown class ChillReportBundle\\:Report\\.$#" + count: 1 + path: src/Bundle/ChillReportBundle/Timeline/TimelineReportProvider.php + + - + message: "#^Parameter \\$context of method Chill\\\\ReportBundle\\\\Timeline\\\\TimelineReportProvider\\:\\:getEntityTemplate\\(\\) has invalid type Chill\\\\MainBundle\\\\Timeline\\\\type\\.$#" + count: 1 + path: src/Bundle/ChillReportBundle/Timeline/TimelineReportProvider.php + + - + message: "#^Parameter \\$entity of method Chill\\\\ReportBundle\\\\Timeline\\\\TimelineReportProvider\\:\\:getEntityTemplate\\(\\) has invalid type Chill\\\\MainBundle\\\\Timeline\\\\type\\.$#" + count: 1 + path: src/Bundle/ChillReportBundle/Timeline/TimelineReportProvider.php + + - + message: "#^Method Chill\\\\TaskBundle\\\\Controller\\\\SingleTaskController\\:\\:createDeleteForm\\(\\) should return Symfony\\\\Component\\\\Form\\\\Form but returns Symfony\\\\Component\\\\Form\\\\FormInterface\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Controller/SingleTaskController.php + + - + message: "#^Method Symfony\\\\Contracts\\\\EventDispatcher\\\\EventDispatcherInterface\\:\\:dispatch\\(\\) invoked with 2 parameters, 1 required\\.$#" + count: 6 + path: src/Bundle/ChillTaskBundle/Controller/SingleTaskController.php + + - + message: "#^Property Chill\\\\TaskBundle\\\\Controller\\\\SingleTaskController\\:\\:\\$centerResolverDispatcher is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Controller/SingleTaskController.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and int will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Controller/SingleTaskController.php + + - + message: "#^If condition is always true\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Controller/TaskController.php + + - + message: "#^Method Symfony\\\\Contracts\\\\EventDispatcher\\\\EventDispatcherInterface\\:\\:dispatch\\(\\) invoked with 2 parameters, 1 required\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Controller/TaskController.php + + - + message: "#^PHPDoc tag @param for parameter \\$task with type Chill\\\\TaskBundle\\\\Controller\\\\AbstractTask is not subtype of native type Chill\\\\TaskBundle\\\\Entity\\\\AbstractTask\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Controller/TaskController.php + + - + message: "#^Parameter \\$task of method Chill\\\\TaskBundle\\\\Controller\\\\TaskController\\:\\:createTransitionForm\\(\\) has invalid type Chill\\\\TaskBundle\\\\Controller\\\\AbstractTask\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Controller/TaskController.php + + - + message: "#^Method Symfony\\\\Component\\\\Config\\\\Definition\\\\Builder\\\\TreeBuilder\\:\\:getRootNode\\(\\) invoked with 1 parameter, 0 required\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/DependencyInjection/Configuration.php + + - + message: "#^Property Chill\\\\TaskBundle\\\\Entity\\\\AbstractTask\\:\\:\\$currentStates \\(Chill\\\\TaskBundle\\\\Entity\\\\json\\) does not accept array\\\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Entity/AbstractTask.php + + - + message: "#^Property Chill\\\\TaskBundle\\\\Entity\\\\AbstractTask\\:\\:\\$currentStates \\(Chill\\\\TaskBundle\\\\Entity\\\\json\\) does not accept default value of type array\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Entity/AbstractTask.php + + - + message: "#^Property Chill\\\\TaskBundle\\\\Entity\\\\AbstractTask\\:\\:\\$currentStates has unknown class Chill\\\\TaskBundle\\\\Entity\\\\json as its type\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Entity/AbstractTask.php + + - + message: "#^Unreachable statement \\- code above always terminates\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Entity/AbstractTask.php + + - + message: "#^Method Chill\\\\TaskBundle\\\\Entity\\\\RecurringTask\\:\\:getOccurenceStartDate\\(\\) has invalid return type Chill\\\\TaskBundle\\\\Entity\\\\dateinterval\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Entity/RecurringTask.php + + - + message: "#^Method Chill\\\\TaskBundle\\\\Entity\\\\RecurringTask\\:\\:getOccurenceWarningInterval\\(\\) has invalid return type Chill\\\\TaskBundle\\\\Entity\\\\dateinterval\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Entity/RecurringTask.php + + - + message: "#^Parameter \\$occurenceStartDate of method Chill\\\\TaskBundle\\\\Entity\\\\RecurringTask\\:\\:setOccurenceStartDate\\(\\) has invalid type Chill\\\\TaskBundle\\\\Entity\\\\dateinterval\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Entity/RecurringTask.php + + - + message: "#^Parameter \\$occurenceWarningInterval of method Chill\\\\TaskBundle\\\\Entity\\\\RecurringTask\\:\\:setOccurenceWarningInterval\\(\\) has invalid type Chill\\\\TaskBundle\\\\Entity\\\\dateinterval\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Entity/RecurringTask.php + + - + message: "#^Property Chill\\\\TaskBundle\\\\Entity\\\\RecurringTask\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Entity/RecurringTask.php + + - + message: "#^Property Chill\\\\TaskBundle\\\\Entity\\\\RecurringTask\\:\\:\\$occurenceStartDate has unknown class Chill\\\\TaskBundle\\\\Entity\\\\dateinterval as its type\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Entity/RecurringTask.php + + - + message: "#^Property Chill\\\\TaskBundle\\\\Entity\\\\RecurringTask\\:\\:\\$occurenceWarningInterval has unknown class Chill\\\\TaskBundle\\\\Entity\\\\dateinterval as its type\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Entity/RecurringTask.php + + - + message: "#^Property Chill\\\\TaskBundle\\\\Entity\\\\RecurringTask\\:\\:\\$singleTasks is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Entity/RecurringTask.php + + - + message: "#^Method Chill\\\\TaskBundle\\\\Entity\\\\SingleTask\\:\\:getWarningDate\\(\\) should return DateTimeImmutable but returns null\\.$#" + count: 2 + path: src/Bundle/ChillTaskBundle/Entity/SingleTask.php + + - + message: "#^Property Chill\\\\TaskBundle\\\\Entity\\\\SingleTask\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Entity/SingleTask.php + + - + message: "#^Property Chill\\\\TaskBundle\\\\Entity\\\\SingleTask\\:\\:\\$warningInterval \\(DateInterval\\) does not accept string\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Entity/SingleTask.php + + - + message: "#^Strict comparison using \\=\\=\\= between DateInterval and null will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Entity/SingleTask.php + + - + message: "#^Strict comparison using \\=\\=\\= between DateTime and null will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Entity/SingleTask.php + + - + message: "#^Method Chill\\\\TaskBundle\\\\Entity\\\\Task\\\\AbstractTaskPlaceEvent\\:\\:getDatetime\\(\\) has invalid return type Chill\\\\TaskBundle\\\\Entity\\\\Task\\\\datetime_immutable\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Entity/Task/AbstractTaskPlaceEvent.php + + - + message: "#^Parameter \\$datetime of method Chill\\\\TaskBundle\\\\Entity\\\\Task\\\\AbstractTaskPlaceEvent\\:\\:setDatetime\\(\\) has invalid type Chill\\\\TaskBundle\\\\Entity\\\\Task\\\\datetime_immutable\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Entity/Task/AbstractTaskPlaceEvent.php + + - + message: "#^Property Chill\\\\TaskBundle\\\\Entity\\\\Task\\\\AbstractTaskPlaceEvent\\:\\:\\$data \\(string\\) does not accept default value of type array\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Entity/Task/AbstractTaskPlaceEvent.php + + - + message: "#^Property Chill\\\\TaskBundle\\\\Entity\\\\Task\\\\AbstractTaskPlaceEvent\\:\\:\\$datetime \\(Chill\\\\TaskBundle\\\\Entity\\\\Task\\\\datetime_immutable\\) does not accept DateTimeImmutable\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Entity/Task/AbstractTaskPlaceEvent.php + + - + message: "#^Property Chill\\\\TaskBundle\\\\Entity\\\\Task\\\\AbstractTaskPlaceEvent\\:\\:\\$datetime has unknown class Chill\\\\TaskBundle\\\\Entity\\\\Task\\\\datetime_immutable as its type\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Entity/Task/AbstractTaskPlaceEvent.php + + - + message: "#^Property Chill\\\\TaskBundle\\\\Entity\\\\Task\\\\AbstractTaskPlaceEvent\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Entity/Task/AbstractTaskPlaceEvent.php + + - + message: "#^Call to an undefined method Chill\\\\TaskBundle\\\\Entity\\\\AbstractTask\\:\\:getTaskPlaceEvents\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Event/Lifecycle/TaskLifecycleEvent.php + + - + message: "#^Method Knp\\\\Menu\\\\MenuItem\\:\\:setExtras\\(\\) invoked with 2 parameters, 1 required\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Menu/MenuBuilder.php + + - + message: "#^Call to an undefined method Symfony\\\\Contracts\\\\Translation\\\\TranslatorInterface\\:\\:transChoice\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Menu/UserMenuBuilder.php + + - + message: "#^Method Chill\\\\TaskBundle\\\\Repository\\\\SingleTaskRepository\\:\\:findByParameters\\(\\) has invalid return type Chill\\\\TaskBundle\\\\Repository\\\\type\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Repository/SingleTaskRepository.php + + - + message: "#^Parameter \\$params of method Chill\\\\TaskBundle\\\\Repository\\\\SingleTaskRepository\\:\\:findByParameters\\(\\) has invalid type Chill\\\\TaskBundle\\\\Repository\\\\type\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Repository/SingleTaskRepository.php + + - + message: "#^Property Chill\\\\TaskBundle\\\\Security\\\\Authorization\\\\AuthorizationEvent\\:\\:\\$subject has unknown class Chill\\\\TaskBundle\\\\Security\\\\Authorization\\\\Chill\\\\TaskBundle\\\\Entity\\\\AbstractTask as its type\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Security/Authorization/AuthorizationEvent.php + + - + message: "#^Property Chill\\\\TaskBundle\\\\Security\\\\Authorization\\\\AuthorizationEvent\\:\\:\\$vote \\(bool\\) does not accept null\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Security/Authorization/AuthorizationEvent.php + + - + message: "#^Method Symfony\\\\Contracts\\\\EventDispatcher\\\\EventDispatcherInterface\\:\\:dispatch\\(\\) invoked with 2 parameters, 1 required\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Security/Authorization/TaskVoter.php + + - + message: "#^Call to method deleteItem\\(\\) on an unknown class Chill\\\\TaskBundle\\\\Templating\\\\UI\\\\CacheItempPoolInterface\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Templating/UI/CountNotificationTask.php + + - + message: "#^Call to method getItem\\(\\) on an unknown class Chill\\\\TaskBundle\\\\Templating\\\\UI\\\\CacheItempPoolInterface\\.$#" + count: 2 + path: src/Bundle/ChillTaskBundle/Templating/UI/CountNotificationTask.php + + - + message: "#^Call to method save\\(\\) on an unknown class Chill\\\\TaskBundle\\\\Templating\\\\UI\\\\CacheItempPoolInterface\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Templating/UI/CountNotificationTask.php + + - + message: "#^Property Chill\\\\TaskBundle\\\\Templating\\\\UI\\\\CountNotificationTask\\:\\:\\$cachePool \\(Chill\\\\TaskBundle\\\\Templating\\\\UI\\\\CacheItempPoolInterface\\) does not accept Psr\\\\Cache\\\\CacheItemPoolInterface\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Templating/UI/CountNotificationTask.php + + - + message: "#^Property Chill\\\\TaskBundle\\\\Templating\\\\UI\\\\CountNotificationTask\\:\\:\\$cachePool has unknown class Chill\\\\TaskBundle\\\\Templating\\\\UI\\\\CacheItempPoolInterface as its type\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Templating/UI/CountNotificationTask.php + + - + message: "#^Call to method getData\\(\\) on an unknown class Chill\\\\MainBundle\\\\Timeline\\\\type\\.$#" + count: 2 + path: src/Bundle/ChillTaskBundle/Timeline/SingleTaskTaskLifeCycleEventTimelineProvider.php + + - + message: "#^Call to method getTask\\(\\) on an unknown class Chill\\\\MainBundle\\\\Timeline\\\\type\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Timeline/SingleTaskTaskLifeCycleEventTimelineProvider.php + + - + message: "#^Call to method getTransition\\(\\) on an unknown class Chill\\\\MainBundle\\\\Timeline\\\\type\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Timeline/SingleTaskTaskLifeCycleEventTimelineProvider.php + + - + message: "#^Parameter \\$context of method Chill\\\\TaskBundle\\\\Timeline\\\\SingleTaskTaskLifeCycleEventTimelineProvider\\:\\:getEntityTemplate\\(\\) has invalid type Chill\\\\MainBundle\\\\Timeline\\\\type\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Timeline/SingleTaskTaskLifeCycleEventTimelineProvider.php + + - + message: "#^Parameter \\$entity of method Chill\\\\TaskBundle\\\\Timeline\\\\SingleTaskTaskLifeCycleEventTimelineProvider\\:\\:getEntityTemplate\\(\\) has invalid type Chill\\\\MainBundle\\\\Timeline\\\\type\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Timeline/SingleTaskTaskLifeCycleEventTimelineProvider.php + + - + message: "#^Call to method getData\\(\\) on an unknown class Chill\\\\MainBundle\\\\Timeline\\\\type\\.$#" + count: 2 + path: src/Bundle/ChillTaskBundle/Timeline/TaskLifeCycleEventTimelineProvider.php + + - + message: "#^Call to method getTask\\(\\) on an unknown class Chill\\\\MainBundle\\\\Timeline\\\\type\\.$#" + count: 2 + path: src/Bundle/ChillTaskBundle/Timeline/TaskLifeCycleEventTimelineProvider.php + + - + message: "#^Call to method getTransition\\(\\) on an unknown class Chill\\\\MainBundle\\\\Timeline\\\\type\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Timeline/TaskLifeCycleEventTimelineProvider.php + + - + message: "#^Parameter \\$context of method Chill\\\\TaskBundle\\\\Timeline\\\\TaskLifeCycleEventTimelineProvider\\:\\:getEntityTemplate\\(\\) has invalid type Chill\\\\MainBundle\\\\Timeline\\\\type\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Timeline/TaskLifeCycleEventTimelineProvider.php + + - + message: "#^Parameter \\$entity of method Chill\\\\TaskBundle\\\\Timeline\\\\TaskLifeCycleEventTimelineProvider\\:\\:getEntityTemplate\\(\\) has invalid type Chill\\\\MainBundle\\\\Timeline\\\\type\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Timeline/TaskLifeCycleEventTimelineProvider.php + + - + message: "#^Call to an undefined method Chill\\\\TaskBundle\\\\Entity\\\\AbstractTask\\:\\:getId\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Workflow/TaskWorkflowManager.php + + - + message: "#^Call to an undefined method Chill\\\\TaskBundle\\\\Workflow\\\\TaskWorkflowDefinition\\:\\:getAssociatedWorkflowName\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Workflow/TaskWorkflowManager.php + + - + message: "#^Call to an undefined method Chill\\\\TaskBundle\\\\Workflow\\\\TaskWorkflowDefinition\\:\\:getWorkflowMetadata\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Workflow/TaskWorkflowManager.php + + - + message: "#^Call to an undefined method Chill\\\\TaskBundle\\\\Workflow\\\\TaskWorkflowDefinition\\:\\:isClosed\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Workflow/TaskWorkflowManager.php + + - + message: "#^Call to an undefined method Chill\\\\TaskBundle\\\\Workflow\\\\TaskWorkflowDefinition\\:\\:supports\\(\\)\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Workflow/TaskWorkflowManager.php + + - + message: "#^Method Symfony\\\\Component\\\\Config\\\\Definition\\\\Builder\\\\TreeBuilder\\:\\:getRootNode\\(\\) invoked with 1 parameter, 0 required\\.$#" + count: 1 + path: src/Bundle/ChillThirdPartyBundle/DependencyInjection/Configuration.php + + - + message: "#^PHPDoc tag @return with type DateTime\\|null is not subtype of native type DateTimeImmutable\\|null\\.$#" + count: 1 + path: src/Bundle/ChillThirdPartyBundle/Entity/ThirdParty.php + + - + message: "#^PHPDoc tag @var for property Chill\\\\ThirdPartyBundle\\\\Entity\\\\ThirdParty\\:\\:\\$categories with type Chill\\\\ThirdPartyBundle\\\\Entity\\\\ThirdPartyCategory is not subtype of native type Doctrine\\\\Common\\\\Collections\\\\Collection\\.$#" + count: 1 + path: src/Bundle/ChillThirdPartyBundle/Entity/ThirdParty.php + + - + message: "#^Property Chill\\\\ThirdPartyBundle\\\\Entity\\\\ThirdParty\\:\\:\\$canonicalized is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillThirdPartyBundle/Entity/ThirdParty.php + + - + message: "#^Property Chill\\\\ThirdPartyBundle\\\\Entity\\\\ThirdParty\\:\\:\\$createdBy is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillThirdPartyBundle/Entity/ThirdParty.php + + - + message: "#^Property Chill\\\\ThirdPartyBundle\\\\Repository\\\\ThirdPartyACLAwareRepository\\:\\:\\$authorizationHelper is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillThirdPartyBundle/Repository/ThirdPartyACLAwareRepository.php + + - + message: "#^Property Chill\\\\ThirdPartyBundle\\\\Repository\\\\ThirdPartyACLAwareRepository\\:\\:\\$security is never read, only written\\.$#" + count: 1 + path: src/Bundle/ChillThirdPartyBundle/Repository/ThirdPartyACLAwareRepository.php + + - + message: "#^Argument of an invalid type string supplied for foreach, only iterables are supported\\.$#" + count: 1 + path: src/Bundle/ChillThirdPartyBundle/Repository/ThirdPartyRepository.php + + - + message: "#^Method Chill\\\\ThirdPartyBundle\\\\Search\\\\ThirdPartySearch\\:\\:renderResult\\(\\) should return string but returns array\\\\.$#" + count: 1 + path: src/Bundle/ChillThirdPartyBundle/Search/ThirdPartySearch.php + + - + message: "#^Unreachable statement \\- code above always terminates\\.$#" + count: 1 + path: src/Bundle/ChillThirdPartyBundle/Security/Voter/ThirdPartyVoter.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and Chill\\\\DocStoreBundle\\\\Entity\\\\StoredObject will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillWopiBundle/src/Controller/Editor.php + + - + message: "#^Strict comparison using \\=\\=\\= between false and array will always evaluate to false\\.$#" + count: 1 + path: src/Bundle/ChillWopiBundle/src/Service/Wopi/AuthorizationManager.php + + - + message: "#^Default value of the parameter \\#2 \\$properties \\(array\\{\\}\\) of method Chill\\\\WopiBundle\\\\Service\\\\Wopi\\\\ChillDocumentManager\\:\\:write\\(\\) is incompatible with type array\\{content\\: string, size\\: int\\}\\.$#" + count: 1 + path: src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentManager.php + + - + message: "#^Method ChampsLibres\\\\WopiLib\\\\Contract\\\\Service\\\\WopiInterface\\:\\:checkFileInfo\\(\\) invoked with 4 parameters, 3 required\\.$#" + count: 1 + path: src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillWopi.php diff --git a/phpstan-baseline-level-5.neon b/phpstan-baseline-level-5.neon new file mode 100644 index 000000000..23321ba68 --- /dev/null +++ b/phpstan-baseline-level-5.neon @@ -0,0 +1,686 @@ +parameters: + ignoreErrors: + - + message: "#^Parameter \\#1 \\$event of method Symfony\\\\Contracts\\\\EventDispatcher\\\\EventDispatcherInterface\\:\\:dispatch\\(\\) expects object, string given\\.$#" + count: 1 + path: src/Bundle/ChillActivityBundle/Controller/ActivityController.php + + - + message: "#^Parameter \\#1 \\$context of method Chill\\\\ActivityBundle\\\\Timeline\\\\TimelineActivityProvider\\:\\:checkContext\\(\\) expects string, Chill\\\\MainBundle\\\\Timeline\\\\type given\\.$#" + count: 1 + path: src/Bundle/ChillActivityBundle/Timeline/TimelineActivityProvider.php + + - + message: "#^Parameter \\#1 \\$callback of function array_map expects \\(callable\\(Chill\\\\BudgetBundle\\\\Repository\\\\ChargeType\\)\\: mixed\\)\\|null, Closure\\(Chill\\\\BudgetBundle\\\\Entity\\\\ChargeKind\\)\\: int\\|null given\\.$#" + count: 1 + path: src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudget.php + + - + message: "#^Parameter \\#1 \\$callback of function array_map expects \\(callable\\(Chill\\\\BudgetBundle\\\\Repository\\\\ResourceType\\)\\: mixed\\)\\|null, Closure\\(Chill\\\\BudgetBundle\\\\Entity\\\\ResourceKind\\)\\: int\\|null given\\.$#" + count: 1 + path: src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudget.php + + - + message: "#^Parameter \\#2 \\$byUser of class Chill\\\\CalendarBundle\\\\Messenger\\\\Message\\\\InviteUpdateMessage constructor expects Chill\\\\MainBundle\\\\Entity\\\\User, Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface\\|null given\\.$#" + count: 1 + path: src/Bundle/ChillCalendarBundle/Controller/InviteApiController.php + + - + message: "#^Parameter \\#2 \\$byUser of class Chill\\\\CalendarBundle\\\\Messenger\\\\Message\\\\CalendarRemovedMessage constructor expects Chill\\\\MainBundle\\\\Entity\\\\User\\|null, Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface\\|null given\\.$#" + count: 1 + path: src/Bundle/ChillCalendarBundle/Messenger/Doctrine/CalendarEntityListener.php + + - + message: "#^Parameter \\#3 \\$byUser of class Chill\\\\CalendarBundle\\\\Messenger\\\\Message\\\\CalendarMessage constructor expects Chill\\\\MainBundle\\\\Entity\\\\User, Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface\\|null given\\.$#" + count: 2 + path: src/Bundle/ChillCalendarBundle/Messenger/Doctrine/CalendarEntityListener.php + + - + message: "#^Parameter \\#2 \\$byUser of class Chill\\\\CalendarBundle\\\\Messenger\\\\Message\\\\CalendarRangeRemovedMessage constructor expects Chill\\\\MainBundle\\\\Entity\\\\User\\|null, Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface\\|null given\\.$#" + count: 1 + path: src/Bundle/ChillCalendarBundle/Messenger/Doctrine/CalendarRangeEntityListener.php + + - + message: "#^Parameter \\#3 \\$byUser of class Chill\\\\CalendarBundle\\\\Messenger\\\\Message\\\\CalendarRangeMessage constructor expects Chill\\\\MainBundle\\\\Entity\\\\User\\|null, Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface\\|null given\\.$#" + count: 2 + path: src/Bundle/ChillCalendarBundle/Messenger/Doctrine/CalendarRangeEntityListener.php + + - + message: "#^Parameter \\#1 \\$token of method Chill\\\\CalendarBundle\\\\RemoteCalendar\\\\Connector\\\\MSGraph\\\\OnBehalfOfUserTokenStorage\\:\\:setToken\\(\\) expects TheNetworg\\\\OAuth2\\\\Client\\\\Token\\\\AccessToken, League\\\\OAuth2\\\\Client\\\\Token\\\\AccessTokenInterface given\\.$#" + count: 1 + path: src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/OnBehalfOfUserTokenStorage.php + + - + message: "#^Parameter \\#2 \\$code of class Exception constructor expects int, array\\ given\\.$#" + count: 1 + path: src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraphRemoteCalendarConnector.php + + - + message: "#^Parameter \\#4 \\$offset of method Chill\\\\CalendarBundle\\\\Repository\\\\CalendarRepository\\:\\:findBy\\(\\) expects int\\|null, array\\|null given\\.$#" + count: 1 + path: src/Bundle/ChillCalendarBundle/Repository/CalendarRepository.php + + - + message: "#^Parameter \\#1 \\$prefix of function uniqid expects string, int\\<0, max\\> given\\.$#" + count: 1 + path: src/Bundle/ChillCustomFieldsBundle/Form/Type/ChoicesListType.php + + - + message: "#^Parameter \\#2 \\$callback of function array_filter expects callable\\(Symfony\\\\Component\\\\Serializer\\\\Mapping\\\\AttributeMetadataInterface\\)\\: mixed, Closure\\(Symfony\\\\Component\\\\Serializer\\\\Mapping\\\\AttributeMetadata\\)\\: bool given\\.$#" + count: 1 + path: src/Bundle/ChillDocGeneratorBundle/Serializer/Normalizer/DocGenObjectNormalizer.php + + - + message: "#^Parameter \\#3 \\$metadata of method Chill\\\\DocGeneratorBundle\\\\Serializer\\\\Normalizer\\\\DocGenObjectNormalizer\\:\\:normalizeNullData\\(\\) expects Symfony\\\\Component\\\\Serializer\\\\Mapping\\\\ClassMetadata, Symfony\\\\Component\\\\Serializer\\\\Mapping\\\\ClassMetadataInterface given\\.$#" + count: 1 + path: src/Bundle/ChillDocGeneratorBundle/Serializer/Normalizer/DocGenObjectNormalizer.php + + - + message: "#^Parameter \\#4 \\$attributes of method Chill\\\\DocGeneratorBundle\\\\Serializer\\\\Normalizer\\\\DocGenObjectNormalizer\\:\\:normalizeNullData\\(\\) expects array\\, array\\ given\\.$#" + count: 1 + path: src/Bundle/ChillDocGeneratorBundle/Serializer/Normalizer/DocGenObjectNormalizer.php + + - + message: "#^Parameter \\#5 \\$metadata of method Chill\\\\DocGeneratorBundle\\\\Serializer\\\\Normalizer\\\\DocGenObjectNormalizer\\:\\:normalizeObject\\(\\) expects Symfony\\\\Component\\\\Serializer\\\\Mapping\\\\ClassMetadata, Symfony\\\\Component\\\\Serializer\\\\Mapping\\\\ClassMetadataInterface given\\.$#" + count: 1 + path: src/Bundle/ChillDocGeneratorBundle/Serializer/Normalizer/DocGenObjectNormalizer.php + + - + message: "#^Parameter \\#6 \\$attributes of method Chill\\\\DocGeneratorBundle\\\\Serializer\\\\Normalizer\\\\DocGenObjectNormalizer\\:\\:normalizeObject\\(\\) expects array\\, array\\ given\\.$#" + count: 1 + path: src/Bundle/ChillDocGeneratorBundle/Serializer/Normalizer/DocGenObjectNormalizer.php + + - + message: "#^Parameter \\#1 \\$event of method Symfony\\\\Contracts\\\\EventDispatcher\\\\EventDispatcherInterface\\:\\:dispatch\\(\\) expects object, string given\\.$#" + count: 4 + path: src/Bundle/ChillDocStoreBundle/Controller/DocumentPersonController.php + + - + message: "#^Parameter \\#1 \\$event of method Symfony\\\\Contracts\\\\EventDispatcher\\\\EventDispatcherInterface\\:\\:dispatch\\(\\) expects object, string given\\.$#" + count: 1 + path: src/Bundle/ChillEventBundle/Controller/EventController.php + + - + message: "#^Parameter \\#1 \\$name of method Symfony\\\\Component\\\\Form\\\\FormFactoryInterface\\:\\:createNamedBuilder\\(\\) expects string, null given\\.$#" + count: 1 + path: src/Bundle/ChillEventBundle/Controller/EventController.php + + - + message: "#^Parameter \\#1 \\$value of function count expects array\\|Countable, Chill\\\\MainBundle\\\\Entity\\\\Center given\\.$#" + count: 1 + path: src/Bundle/ChillEventBundle/Controller/EventController.php + + - + message: "#^Parameter \\#1 \\$participations of method Chill\\\\EventBundle\\\\Controller\\\\ParticipationController\\:\\:createEditFormMultiple\\(\\) expects ArrayIterator, Traversable given\\.$#" + count: 2 + path: src/Bundle/ChillEventBundle/Controller/ParticipationController.php + + - + message: "#^Parameter \\#1 \\$callback of function call_user_func expects callable\\(\\)\\: mixed, null given\\.$#" + count: 2 + path: src/Bundle/ChillEventBundle/Form/ChoiceLoader/EventChoiceLoader.php + + - + message: "#^Parameter \\#2 \\$previous of class Symfony\\\\Component\\\\HttpKernel\\\\Exception\\\\BadRequestHttpException constructor expects Throwable\\|null, int given\\.$#" + count: 3 + path: src/Bundle/ChillMainBundle/CRUD/Controller/ApiController.php + + - + message: "#^Parameter \\#3 \\$code of class Symfony\\\\Component\\\\HttpKernel\\\\Exception\\\\BadRequestHttpException constructor expects int, Symfony\\\\Component\\\\Serializer\\\\Exception\\\\NotEncodableValueException given\\.$#" + count: 2 + path: src/Bundle/ChillMainBundle/CRUD/Controller/ApiController.php + + - + message: "#^Parameter \\#3 \\$code of class Symfony\\\\Component\\\\HttpKernel\\\\Exception\\\\BadRequestHttpException constructor expects int, Symfony\\\\Component\\\\Serializer\\\\Exception\\\\UnexpectedValueException given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/CRUD/Controller/ApiController.php + + - + message: "#^Parameter \\#2 \\$role of method Chill\\\\MainBundle\\\\Security\\\\Authorization\\\\AuthorizationHelper\\:\\:getReachableCenters\\(\\) expects string, Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/CRUD/Controller/CRUDController.php + + - + message: "#^Parameter \\#3 \\$formClass of method Chill\\\\MainBundle\\\\CRUD\\\\Controller\\\\CRUDController\\:\\:createFormFor\\(\\) expects string\\|null, Chill\\\\MainBundle\\\\CRUD\\\\Controller\\\\type\\|null given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/CRUD/Controller/CRUDController.php + + - + message: "#^Parameter \\#3 \\$scope of method Chill\\\\MainBundle\\\\Security\\\\Authorization\\\\AuthorizationHelper\\:\\:getReachableCenters\\(\\) expects Chill\\\\MainBundle\\\\Entity\\\\Scope\\|null, Chill\\\\MainBundle\\\\CRUD\\\\Controller\\\\Scope\\|null given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/CRUD/Controller/CRUDController.php + + - + message: "#^Parameter \\#1 \\$name of method Chill\\\\MainBundle\\\\Entity\\\\Country\\:\\:setName\\(\\) expects string, array\\ given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Command/LoadCountriesCommand.php + + - + message: "#^Parameter \\#4 \\.\\.\\.\\$values of function sprintf expects bool\\|float\\|int\\|string\\|null, Chill\\\\MainBundle\\\\Entity\\\\the given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Command/LoadPostalCodesCommand.php + + - + message: "#^Parameter \\#1 \\$alias of method Chill\\\\MainBundle\\\\Export\\\\ExportManager\\:\\:getExport\\(\\) expects string, Symfony\\\\Component\\\\HttpFoundation\\\\Request given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Controller/ExportController.php + + - + message: "#^Parameter \\#1 \\$name of method Symfony\\\\Component\\\\Form\\\\FormFactoryInterface\\:\\:createNamedBuilder\\(\\) expects string, null given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Controller/ExportController.php + + - + message: "#^Parameter \\#3 \\$alias of method Chill\\\\MainBundle\\\\Controller\\\\ExportController\\:\\:exportFormStep\\(\\) expects string, Symfony\\\\Component\\\\HttpFoundation\\\\Request given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Controller/ExportController.php + + - + message: "#^Parameter \\#3 \\$alias of method Chill\\\\MainBundle\\\\Controller\\\\ExportController\\:\\:formatterFormStep\\(\\) expects string, Symfony\\\\Component\\\\HttpFoundation\\\\Request given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Controller/ExportController.php + + - + message: "#^Parameter \\#3 \\$alias of method Chill\\\\MainBundle\\\\Controller\\\\ExportController\\:\\:forwardToGenerate\\(\\) expects string, Symfony\\\\Component\\\\HttpFoundation\\\\Request given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Controller/ExportController.php + + - + message: "#^Parameter \\#3 \\$alias of method Chill\\\\MainBundle\\\\Controller\\\\ExportController\\:\\:selectCentersStep\\(\\) expects string, Symfony\\\\Component\\\\HttpFoundation\\\\Request given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Controller/ExportController.php + + - + message: "#^Parameter \\#1 \\$user of method Chill\\\\MainBundle\\\\Repository\\\\NotificationRepository\\:\\:countUnreadByUser\\(\\) expects Chill\\\\MainBundle\\\\Entity\\\\User, Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface\\|null given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Controller/NotificationApiController.php + + - + message: "#^Parameter \\#1 \\$user of method Chill\\\\MainBundle\\\\Repository\\\\NotificationRepository\\:\\:findUnreadByUser\\(\\) expects Chill\\\\MainBundle\\\\Entity\\\\User, Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface\\|null given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Controller/NotificationApiController.php + + - + message: "#^Parameter \\#1 \\$addressee of method Chill\\\\MainBundle\\\\Repository\\\\NotificationRepository\\:\\:countAllForAttendee\\(\\) expects Chill\\\\MainBundle\\\\Entity\\\\User, Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface\\|null given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Controller/NotificationController.php + + - + message: "#^Parameter \\#1 \\$addressee of method Chill\\\\MainBundle\\\\Repository\\\\NotificationRepository\\:\\:findAllForAttendee\\(\\) expects Chill\\\\MainBundle\\\\Entity\\\\User, Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface\\|null given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Controller/NotificationController.php + + - + message: "#^Parameter \\#1 \\$sender of method Chill\\\\MainBundle\\\\Repository\\\\NotificationRepository\\:\\:countAllForSender\\(\\) expects Chill\\\\MainBundle\\\\Entity\\\\User, Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface\\|null given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Controller/NotificationController.php + + - + message: "#^Parameter \\#1 \\$sender of method Chill\\\\MainBundle\\\\Repository\\\\NotificationRepository\\:\\:findAllForSender\\(\\) expects Chill\\\\MainBundle\\\\Entity\\\\User, Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface\\|null given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Controller/NotificationController.php + + - + message: "#^Parameter \\#1 \\$user of method Chill\\\\MainBundle\\\\Repository\\\\NotificationRepository\\:\\:countUnreadByUserWhereAddressee\\(\\) expects Chill\\\\MainBundle\\\\Entity\\\\User, Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface\\|null given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Controller/NotificationController.php + + - + message: "#^Parameter \\#1 \\$user of method Chill\\\\MainBundle\\\\Repository\\\\NotificationRepository\\:\\:countUnreadByUserWhereSender\\(\\) expects Chill\\\\MainBundle\\\\Entity\\\\User, Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface\\|null given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Controller/NotificationController.php + + - + message: "#^Parameter \\#1 \\$event of method Symfony\\\\Contracts\\\\EventDispatcher\\\\EventDispatcherInterface\\:\\:dispatch\\(\\) expects object, string given\\.$#" + count: 4 + path: src/Bundle/ChillMainBundle/Controller/PasswordController.php + + - + message: "#^Parameter \\#1 \\$token of class Chill\\\\MainBundle\\\\Security\\\\PasswordRecover\\\\PasswordRecoverEvent constructor expects Chill\\\\MainBundle\\\\Security\\\\PasswordRecover\\\\type\\|null, string given\\.$#" + count: 2 + path: src/Bundle/ChillMainBundle/Controller/PasswordController.php + + - + message: "#^Parameter \\#3 \\$ip of class Chill\\\\MainBundle\\\\Security\\\\PasswordRecover\\\\PasswordRecoverEvent constructor expects Chill\\\\MainBundle\\\\Security\\\\PasswordRecover\\\\type\\|null, string\\|null given\\.$#" + count: 4 + path: src/Bundle/ChillMainBundle/Controller/PasswordController.php + + - + message: "#^Parameter \\#1 \\$context of method Chill\\\\MainBundle\\\\Timeline\\\\TimelineBuilder\\:\\:countItems\\(\\) expects Chill\\\\MainBundle\\\\Timeline\\\\unknown, string given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Controller/TimelineCenterController.php + + - + message: "#^Parameter \\#1 \\$user of method Chill\\\\MainBundle\\\\Entity\\\\Workflow\\\\EntityWorkflow\\:\\:addSubscriberToFinal\\(\\) expects Chill\\\\MainBundle\\\\Entity\\\\User, Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface\\|null given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Controller/WorkflowApiController.php + + - + message: "#^Parameter \\#1 \\$user of method Chill\\\\MainBundle\\\\Entity\\\\Workflow\\\\EntityWorkflow\\:\\:addSubscriberToStep\\(\\) expects Chill\\\\MainBundle\\\\Entity\\\\User, Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface\\|null given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Controller/WorkflowApiController.php + + - + message: "#^Parameter \\#1 \\$user of method Chill\\\\MainBundle\\\\Entity\\\\Workflow\\\\EntityWorkflow\\:\\:isUserSubscribedToFinal\\(\\) expects Chill\\\\MainBundle\\\\Entity\\\\User, Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface\\|null given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Controller/WorkflowApiController.php + + - + message: "#^Parameter \\#1 \\$user of method Chill\\\\MainBundle\\\\Entity\\\\Workflow\\\\EntityWorkflow\\:\\:isUserSubscribedToStep\\(\\) expects Chill\\\\MainBundle\\\\Entity\\\\User, Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface\\|null given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Controller/WorkflowApiController.php + + - + message: "#^Parameter \\#1 \\$user of method Chill\\\\MainBundle\\\\Entity\\\\Workflow\\\\EntityWorkflow\\:\\:removeSubscriberToFinal\\(\\) expects Chill\\\\MainBundle\\\\Entity\\\\User, Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface\\|null given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Controller/WorkflowApiController.php + + - + message: "#^Parameter \\#1 \\$user of method Chill\\\\MainBundle\\\\Entity\\\\Workflow\\\\EntityWorkflow\\:\\:removeSubscriberToStep\\(\\) expects Chill\\\\MainBundle\\\\Entity\\\\User, Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface\\|null given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Controller/WorkflowApiController.php + + - + message: "#^Parameter \\#1 \\$place of method Chill\\\\MainBundle\\\\DependencyInjection\\\\Configuration\\:\\:filterWidgetByPlace\\(\\) expects string, Chill\\\\MainBundle\\\\DependencyInjection\\\\Widget\\\\type given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/DependencyInjection/Configuration.php + + - + message: "#^Parameter \\#1 \\$place of method Chill\\\\MainBundle\\\\DependencyInjection\\\\Configuration\\:\\:getWidgetAliasesbyPlace\\(\\) expects Chill\\\\MainBundle\\\\DependencyInjection\\\\Widget\\\\type, string given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/DependencyInjection/Configuration.php + + - + message: "#^Parameter \\#2 \\$array of function implode expects array\\|null, Chill\\\\MainBundle\\\\DependencyInjection\\\\Widget\\\\type given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/DependencyInjection/Configuration.php + + - + message: "#^Parameter \\#2 \\$place of method Chill\\\\MainBundle\\\\DependencyInjection\\\\Widget\\\\Factory\\\\WidgetFactoryInterface\\:\\:createDefinition\\(\\) expects Chill\\\\MainBundle\\\\DependencyInjection\\\\Widget\\\\Factory\\\\type, string given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/DependencyInjection/Widget/AbstractWidgetsCompilerPass.php + + - + message: "#^Parameter \\#3 \\$order of method Chill\\\\MainBundle\\\\DependencyInjection\\\\Widget\\\\Factory\\\\WidgetFactoryInterface\\:\\:createDefinition\\(\\) expects Chill\\\\MainBundle\\\\DependencyInjection\\\\Widget\\\\Factory\\\\type, float given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/DependencyInjection/Widget/AbstractWidgetsCompilerPass.php + + - + message: "#^Parameter \\#2 \\$place of method Chill\\\\MainBundle\\\\DependencyInjection\\\\Widget\\\\Factory\\\\WidgetFactoryInterface\\:\\:getServiceId\\(\\) expects string, Chill\\\\MainBundle\\\\DependencyInjection\\\\Widget\\\\Factory\\\\type given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/DependencyInjection/Widget/Factory/AbstractWidgetFactory.php + + - + message: "#^Parameter \\#3 \\$order of method Chill\\\\MainBundle\\\\DependencyInjection\\\\Widget\\\\Factory\\\\WidgetFactoryInterface\\:\\:getServiceId\\(\\) expects float, Chill\\\\MainBundle\\\\DependencyInjection\\\\Widget\\\\Factory\\\\type given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/DependencyInjection/Widget/Factory/AbstractWidgetFactory.php + + - + message: "#^Parameter \\#1 \\$iterator of function iterator_to_array expects Traversable, array\\ given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Export/ExportManager.php + + - + message: "#^Parameter \\#2 \\$nbAggregators of method Chill\\\\MainBundle\\\\Export\\\\Formatter\\\\CSVFormatter\\:\\:appendAggregatorForm\\(\\) expects string, int\\<0, max\\> given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Export/Formatter/CSVFormatter.php + + - + message: "#^Parameter \\#2 \\$array of function array_map expects array, Chill\\\\MainBundle\\\\Export\\\\Formatter\\\\type given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Export/Formatter/SpreadSheetFormatter.php + + - + message: "#^Parameter \\#2 \\$nbAggregators of method Chill\\\\MainBundle\\\\Export\\\\Formatter\\\\SpreadSheetFormatter\\:\\:appendAggregatorForm\\(\\) expects string, int\\<0, max\\> given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Export/Formatter/SpreadSheetFormatter.php + + - + message: "#^Parameter \\#1 \\$callback of function call_user_func expects callable\\(\\)\\: mixed, null given\\.$#" + count: 2 + path: src/Bundle/ChillMainBundle/Form/ChoiceLoader/PostalCodeChoiceLoader.php + + - + message: "#^Parameter \\#1 \\$user of method Chill\\\\MainBundle\\\\Entity\\\\Embeddable\\\\PrivateCommentEmbeddable\\:\\:getCommentForUser\\(\\) expects Chill\\\\MainBundle\\\\Entity\\\\User, Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface\\|null given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Form/DataMapper/PrivateCommentDataMapper.php + + - + message: "#^Parameter \\#1 \\$em of class Chill\\\\MainBundle\\\\Form\\\\Type\\\\DataTransformer\\\\ObjectToIdTransformer constructor expects Doctrine\\\\ORM\\\\EntityManagerInterface, Doctrine\\\\Persistence\\\\ObjectManager given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Form/Type/Select2CountryType.php + + - + message: "#^Parameter \\#1 \\$em of class Chill\\\\MainBundle\\\\Form\\\\Type\\\\DataTransformer\\\\MultipleObjectsToIdTransformer constructor expects Doctrine\\\\ORM\\\\EntityManagerInterface, Doctrine\\\\Persistence\\\\ObjectManager given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Form/Type/Select2LanguageType.php + + - + message: "#^Parameter \\#1 \\$translatableStrings of method Chill\\\\MainBundle\\\\Templating\\\\TranslatableStringHelper\\:\\:localize\\(\\) expects array, string given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Form/Type/Select2LanguageType.php + + - + message: "#^Parameter \\#1 \\$datetime of class DateTime constructor expects string, Chill\\\\MainBundle\\\\Search\\\\type given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Search/AbstractSearch.php + + - + message: "#^Parameter \\#4 \\$paginator of method Chill\\\\MainBundle\\\\Search\\\\SearchApi\\:\\:fetchRawResult\\(\\) expects Chill\\\\MainBundle\\\\Pagination\\\\Paginator, Chill\\\\MainBundle\\\\Pagination\\\\PaginatorInterface given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Search/SearchApi.php + + - + message: "#^Parameter \\#2 \\$callback of function uasort expects callable\\(Chill\\\\MainBundle\\\\Search\\\\HasAdvancedSearchForm, Chill\\\\MainBundle\\\\Search\\\\HasAdvancedSearchForm\\)\\: int, Closure\\(Chill\\\\MainBundle\\\\Search\\\\SearchInterface, Chill\\\\MainBundle\\\\Search\\\\SearchInterface\\)\\: \\-1\\|0\\|1 given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Search/SearchProvider.php + + - + message: "#^Parameter \\#2 \\$subject of function preg_match_all expects string, Chill\\\\MainBundle\\\\Search\\\\type given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Search/SearchProvider.php + + - + message: "#^Parameter \\#1 \\$object of function get_class expects object, array\\ given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Security/Authorization/AuthorizationHelper.php + + - + message: "#^Parameter \\#1 \\$user of method Chill\\\\MainBundle\\\\Entity\\\\Notification\\:\\:isReadBy\\(\\) expects Chill\\\\MainBundle\\\\Entity\\\\User, Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface\\|null given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Serializer/Normalizer/NotificationNormalizer.php + + - + message: "#^Parameter \\#1 \\$user of method Chill\\\\MainBundle\\\\Entity\\\\Embeddable\\\\PrivateCommentEmbeddable\\:\\:setCommentForUser\\(\\) expects Chill\\\\MainBundle\\\\Entity\\\\User, Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface\\|null given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Serializer/Normalizer/PrivateCommentEmbeddableNormalizer.php + + - + message: "#^Parameter \\#1 \\$event of method Symfony\\\\Contracts\\\\EventDispatcher\\\\EventDispatcherInterface\\:\\:dispatch\\(\\) expects object, string given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Templating/Widget/WidgetRenderingTwig.php + + - + message: "#^Parameter \\#1 \\$context of method Chill\\\\MainBundle\\\\Timeline\\\\TimelineBuilder\\:\\:buildUnionQuery\\(\\) expects string, Chill\\\\MainBundle\\\\Timeline\\\\unknown given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Timeline/TimelineBuilder.php + + - + message: "#^Parameter \\#2 \\$context of method Chill\\\\MainBundle\\\\Timeline\\\\TimelineProviderInterface\\:\\:getEntityTemplate\\(\\) expects Chill\\\\MainBundle\\\\Timeline\\\\type, string given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Timeline/TimelineBuilder.php + + - + message: "#^Parameter \\#1 \\$phoneNumber of method Chill\\\\MainBundle\\\\Phonenumber\\\\PhoneNumberHelperInterface\\:\\:format\\(\\) expects libphonenumber\\\\PhoneNumber\\|null, string given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Validation/Validator/ValidPhonenumber.php + + - + message: "#^Parameter \\#1 \\$transitionBy of method Chill\\\\MainBundle\\\\Entity\\\\Workflow\\\\EntityWorkflowStep\\:\\:setTransitionBy\\(\\) expects Chill\\\\MainBundle\\\\Entity\\\\User\\|null, Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface\\|null given\\.$#" + count: 1 + path: src/Bundle/ChillMainBundle/Workflow/EventSubscriber/EntityWorkflowTransitionEventSubscriber.php + + - + message: "#^Parameter \\#1 \\$event of method Symfony\\\\Contracts\\\\EventDispatcher\\\\EventDispatcherInterface\\:\\:dispatch\\(\\) expects object, string given\\.$#" + count: 2 + path: src/Bundle/ChillPersonBundle/Actions/Remove/PersonMove.php + + - + message: "#^Parameter \\#1 \\$event of method Symfony\\\\Contracts\\\\EventDispatcher\\\\EventDispatcherInterface\\:\\:dispatch\\(\\) expects object, string given\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseApiController.php + + - + message: "#^Parameter \\#1 \\$event of method Symfony\\\\Contracts\\\\EventDispatcher\\\\EventDispatcherInterface\\:\\:dispatch\\(\\) expects object, string given\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Controller/AccompanyingPeriodController.php + + - + message: "#^Parameter \\#2 \\$callback of function usort expects callable\\(mixed, mixed\\)\\: int, Closure\\(mixed, mixed\\)\\: bool given\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Controller/AccompanyingPeriodController.php + + - + message: "#^Parameter \\#1 \\$user of method Chill\\\\PersonBundle\\\\Repository\\\\AccompanyingPeriod\\\\AccompanyingPeriodWorkEvaluationRepository\\:\\:countNearMaxDateByUser\\(\\) expects Chill\\\\MainBundle\\\\Entity\\\\User, Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface\\|null given\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Controller/AccompanyingPeriodWorkEvaluationApiController.php + + - + message: "#^Parameter \\#1 \\$user of method Chill\\\\PersonBundle\\\\Repository\\\\AccompanyingPeriod\\\\AccompanyingPeriodWorkEvaluationRepository\\:\\:findNearMaxDateByUser\\(\\) expects Chill\\\\MainBundle\\\\Entity\\\\User, Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface\\|null given\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Controller/AccompanyingPeriodWorkEvaluationApiController.php + + - + message: "#^Parameter \\#1 \\$object of static method DateTimeImmutable\\:\\:createFromMutable\\(\\) expects DateTime, DateTimeInterface given\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Controller/HouseholdApiController.php + + - + message: "#^Parameter \\#2 \\$callback of function usort expects callable\\(mixed, mixed\\)\\: int, Closure\\(mixed, mixed\\)\\: bool given\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Controller/HouseholdController.php + + - + message: "#^Parameter \\#2 \\$previous of class Symfony\\\\Component\\\\HttpKernel\\\\Exception\\\\BadRequestHttpException constructor expects Throwable\\|null, int given\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Controller/HouseholdMemberController.php + + - + message: "#^Parameter \\#3 \\$code of class Symfony\\\\Component\\\\HttpKernel\\\\Exception\\\\BadRequestHttpException constructor expects int, Symfony\\\\Component\\\\Serializer\\\\Exception\\\\InvalidArgumentException\\|Symfony\\\\Component\\\\Serializer\\\\Exception\\\\UnexpectedValueException given\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Controller/HouseholdMemberController.php + + - + message: "#^Parameter \\#1 \\$event of method Symfony\\\\Contracts\\\\EventDispatcher\\\\EventDispatcherInterface\\:\\:dispatch\\(\\) expects object, string given\\.$#" + count: 2 + path: src/Bundle/ChillPersonBundle/Controller/PersonController.php + + - + message: "#^Parameter \\#1 \\$form of method Chill\\\\PersonBundle\\\\Controller\\\\PersonController\\:\\:isLastPostDataChanges\\(\\) expects Symfony\\\\Component\\\\Form\\\\Form, Symfony\\\\Component\\\\Form\\\\FormInterface given\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Controller/PersonController.php + + - + message: "#^Parameter \\#1 \\$event of method Symfony\\\\Contracts\\\\EventDispatcher\\\\EventDispatcherInterface\\:\\:dispatch\\(\\) expects object, string given\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Controller/PersonDuplicateController.php + + - + message: "#^Parameter \\#2 \\$precision of method Chill\\\\PersonBundle\\\\Search\\\\SimilarPersonMatcher\\:\\:matchPerson\\(\\) expects float, Chill\\\\PersonBundle\\\\Repository\\\\PersonNotDuplicateRepository given\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Controller/PersonDuplicateController.php + + - + message: "#^Parameter \\#3 \\$orderBy of method Chill\\\\PersonBundle\\\\Search\\\\SimilarPersonMatcher\\:\\:matchPerson\\(\\) expects string, float given\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Controller/PersonDuplicateController.php + + - + message: "#^Parameter \\#4 \\$addYearComparison of method Chill\\\\PersonBundle\\\\Search\\\\SimilarPersonMatcher\\:\\:matchPerson\\(\\) expects bool, string given\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Controller/PersonDuplicateController.php + + - + message: "#^Parameter \\#1 \\$context of method Chill\\\\MainBundle\\\\Timeline\\\\TimelineBuilder\\:\\:countItems\\(\\) expects Chill\\\\MainBundle\\\\Timeline\\\\unknown, string given\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Controller/TimelinePersonController.php + + - + message: "#^Parameter \\#1 \\$event of method Symfony\\\\Contracts\\\\EventDispatcher\\\\EventDispatcherInterface\\:\\:dispatch\\(\\) expects object, string given\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Controller/TimelinePersonController.php + + - + message: "#^Parameter \\#1 \\$period of method Chill\\\\PersonBundle\\\\Entity\\\\AccompanyingPeriod\\\\AccompanyingPeriodLocationHistory\\:\\:setPeriod\\(\\) expects Chill\\\\PersonBundle\\\\Entity\\\\AccompanyingPeriod, null given\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php + + - + message: "#^Parameter \\#1 \\$now of method Chill\\\\PersonBundle\\\\Entity\\\\Household\\\\Household\\:\\:getCurrentMembers\\(\\) expects DateTimeImmutable\\|null, DateTimeInterface\\|null given\\.$#" + count: 2 + path: src/Bundle/ChillPersonBundle/Entity/Household/Household.php + + - + message: "#^Parameter \\#1 \\$now of method Chill\\\\PersonBundle\\\\Entity\\\\Household\\\\Household\\:\\:getNonCurrentMembers\\(\\) expects DateTimeImmutable\\|null, DateTimeInterface\\|null given\\.$#" + count: 2 + path: src/Bundle/ChillPersonBundle/Entity/Household/Household.php + + - + message: "#^Parameter \\#1 \\$key of method Doctrine\\\\Common\\\\Collections\\\\Collection\\<\\(int\\|string\\),mixed\\>\\:\\:remove\\(\\) expects \\(int\\|string\\), Chill\\\\PersonBundle\\\\Entity\\\\SocialWork\\\\SocialAction given\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Entity/SocialWork/Evaluation.php + + - + message: "#^Parameter \\#1 \\$translatableStrings of method Chill\\\\MainBundle\\\\Templating\\\\TranslatableStringHelper\\:\\:localize\\(\\) expects array, string given\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Export/Helper/ListPersonHelper.php + + - + message: "#^Parameter \\#1 \\$callback of function call_user_func expects callable\\(\\)\\: mixed, null given\\.$#" + count: 2 + path: src/Bundle/ChillPersonBundle/Form/ChoiceLoader/PersonChoiceLoader.php + + - + message: "#^Parameter \\#1 \\$entityName of method Doctrine\\\\ORM\\\\EntityManager\\:\\:getRepository\\(\\) expects class\\-string\\, string given\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Timeline/AbstractTimelineAccompanyingPeriod.php + + - + message: "#^Parameter \\#1 \\$user of method Chill\\\\MainBundle\\\\Security\\\\Authorization\\\\AuthorizationHelper\\:\\:filterReachableCenters\\(\\) expects Chill\\\\MainBundle\\\\Entity\\\\User, Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface\\|null given\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Timeline/AbstractTimelineAccompanyingPeriod.php + + - + message: "#^Parameter \\#3 \\$context of method Chill\\\\PersonBundle\\\\Timeline\\\\AbstractTimelineAccompanyingPeriod\\:\\:getBasicEntityTemplate\\(\\) expects string, Chill\\\\MainBundle\\\\Timeline\\\\type given\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Timeline/TimelineAccompanyingPeriodClosing.php + + - + message: "#^Parameter \\#3 \\$context of method Chill\\\\PersonBundle\\\\Timeline\\\\AbstractTimelineAccompanyingPeriod\\:\\:getBasicEntityTemplate\\(\\) expects string, Chill\\\\MainBundle\\\\Timeline\\\\type given\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Timeline/TimelineAccompanyingPeriodOpening.php + + - + message: "#^Parameter \\#2 \\$role of method Chill\\\\MainBundle\\\\Security\\\\Authorization\\\\AuthorizationHelperInterface\\:\\:getReachableCenters\\(\\) expects string, Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role given\\.$#" + count: 1 + path: src/Bundle/ChillPersonBundle/Widget/PersonListWidget.php + + - + message: "#^Parameter \\#1 \\$className of method Doctrine\\\\Persistence\\\\ObjectManager\\:\\:getRepository\\(\\) expects class\\-string\\, string given\\.$#" + count: 4 + path: src/Bundle/ChillReportBundle/Controller/ReportController.php + + - + message: "#^Parameter \\#1 \\$entity of method Chill\\\\ReportBundle\\\\Controller\\\\ReportController\\:\\:createEditForm\\(\\) expects Chill\\\\ReportBundle\\\\Entity\\\\Report, ChillReportBundle\\:Report given\\.$#" + count: 1 + path: src/Bundle/ChillReportBundle/Controller/ReportController.php + + - + message: "#^Parameter \\#1 \\$event of method Symfony\\\\Contracts\\\\EventDispatcher\\\\EventDispatcherInterface\\:\\:dispatch\\(\\) expects object, string given\\.$#" + count: 4 + path: src/Bundle/ChillReportBundle/Controller/ReportController.php + + - + message: "#^Parameter \\#2 \\$role of method Chill\\\\MainBundle\\\\Security\\\\Authorization\\\\AuthorizationHelper\\:\\:getReachableScopes\\(\\) expects string, Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role given\\.$#" + count: 1 + path: src/Bundle/ChillReportBundle/Controller/ReportController.php + + - + message: "#^Parameter \\#1 \\$em of class Chill\\\\MainBundle\\\\Form\\\\Type\\\\DataTransformer\\\\ScopeTransformer constructor expects Doctrine\\\\ORM\\\\EntityManagerInterface, Doctrine\\\\Persistence\\\\ObjectManager given\\.$#" + count: 1 + path: src/Bundle/ChillReportBundle/Form/ReportType.php + + - + message: "#^Parameter \\#2 \\$role of method Chill\\\\MainBundle\\\\Security\\\\Authorization\\\\AuthorizationHelper\\:\\:getReachableScopes\\(\\) expects string, Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role given\\.$#" + count: 1 + path: src/Bundle/ChillReportBundle/Form/ReportType.php + + - + message: "#^Parameter \\#2 \\$role of method Chill\\\\MainBundle\\\\Security\\\\Authorization\\\\AuthorizationHelper\\:\\:getReachableCenters\\(\\) expects string, Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role given\\.$#" + count: 1 + path: src/Bundle/ChillReportBundle/Search/ReportSearch.php + + - + message: "#^Parameter \\#2 \\$role of method Chill\\\\MainBundle\\\\Security\\\\Authorization\\\\AuthorizationHelper\\:\\:getReachableScopes\\(\\) expects string, Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role given\\.$#" + count: 1 + path: src/Bundle/ChillReportBundle/Search/ReportSearch.php + + - + message: "#^Parameter \\#1 \\$context of method Chill\\\\ReportBundle\\\\Timeline\\\\TimelineReportProvider\\:\\:checkContext\\(\\) expects string, Chill\\\\MainBundle\\\\Timeline\\\\type given\\.$#" + count: 1 + path: src/Bundle/ChillReportBundle/Timeline/TimelineReportProvider.php + + - + message: "#^Parameter \\#1 \\$entity of method Chill\\\\ReportBundle\\\\Timeline\\\\TimelineReportProvider\\:\\:getFieldsToRender\\(\\) expects Chill\\\\ReportBundle\\\\Entity\\\\Report, Chill\\\\MainBundle\\\\Timeline\\\\type given\\.$#" + count: 1 + path: src/Bundle/ChillReportBundle/Timeline/TimelineReportProvider.php + + - + message: "#^Parameter \\#1 \\$entityName of method Doctrine\\\\ORM\\\\EntityManager\\:\\:getRepository\\(\\) expects class\\-string\\, string given\\.$#" + count: 1 + path: src/Bundle/ChillReportBundle/Timeline/TimelineReportProvider.php + + - + message: "#^Parameter \\#1 \\$event of method Symfony\\\\Contracts\\\\EventDispatcher\\\\EventDispatcherInterface\\:\\:dispatch\\(\\) expects object, string given\\.$#" + count: 6 + path: src/Bundle/ChillTaskBundle/Controller/SingleTaskController.php + + - + message: "#^Parameter \\#1 \\$event of method Symfony\\\\Contracts\\\\EventDispatcher\\\\EventDispatcherInterface\\:\\:dispatch\\(\\) expects object, string given\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Controller/TaskController.php + + - + message: "#^Parameter \\#1 \\$keys of function array_fill_keys expects array, Chill\\\\TaskBundle\\\\Entity\\\\json given\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Entity/AbstractTask.php + + - + message: "#^Parameter \\#1 \\$task of method Chill\\\\TaskBundle\\\\Entity\\\\Task\\\\SingleTaskPlaceEvent\\:\\:setTask\\(\\) expects Chill\\\\TaskBundle\\\\Entity\\\\SingleTask, Chill\\\\TaskBundle\\\\Entity\\\\AbstractTask given\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Event/Lifecycle/TaskLifecycleEvent.php + + - + message: "#^Parameter \\#1 \\$repository of class Chill\\\\PersonBundle\\\\Form\\\\DataTransformer\\\\PersonToIdTransformer constructor expects Chill\\\\PersonBundle\\\\Repository\\\\PersonRepository, Doctrine\\\\ORM\\\\EntityManagerInterface given\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Form/SingleTaskListType.php + + - + message: "#^Parameter \\#2 \\$role of method Chill\\\\MainBundle\\\\Security\\\\Authorization\\\\AuthorizationHelper\\:\\:getReachableCenters\\(\\) expects string, Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role given\\.$#" + count: 4 + path: src/Bundle/ChillTaskBundle/Form/SingleTaskListType.php + + - + message: "#^Parameter \\#1 \\$extras of method Knp\\\\Menu\\\\MenuItem\\:\\:setExtras\\(\\) expects array\\, string given\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Menu/MenuBuilder.php + + - + message: "#^Parameter \\#2 \\$role of method Chill\\\\MainBundle\\\\Security\\\\Authorization\\\\AuthorizationHelper\\:\\:getReachableCenters\\(\\) expects string, Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role given\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Repository/SingleTaskRepository.php + + - + message: "#^Parameter \\#1 \\$event of method Symfony\\\\Contracts\\\\EventDispatcher\\\\EventDispatcherInterface\\:\\:dispatch\\(\\) expects object, string given\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Security/Authorization/TaskVoter.php + + - + message: "#^Parameter \\#2 \\$role of method Chill\\\\MainBundle\\\\Security\\\\Authorization\\\\AuthorizationHelper\\:\\:getReachableCenters\\(\\) expects string, Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role given\\.$#" + count: 1 + path: src/Bundle/ChillTaskBundle/Timeline/TaskLifeCycleEventTimelineProvider.php + + - + message: "#^Parameter \\#1 \\$storedObject of method Chill\\\\WopiBundle\\\\Service\\\\Wopi\\\\ChillDocumentManager\\:\\:getContent\\(\\) expects Chill\\\\DocStoreBundle\\\\Entity\\\\StoredObject, ChampsLibres\\\\WopiLib\\\\Contract\\\\Entity\\\\Document given\\.$#" + count: 3 + path: src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentManager.php + + - + message: "#^Parameter \\#1 \\$storedObject of method Chill\\\\WopiBundle\\\\Service\\\\Wopi\\\\ChillDocumentManager\\:\\:setContent\\(\\) expects Chill\\\\DocStoreBundle\\\\Entity\\\\StoredObject, ChampsLibres\\\\WopiLib\\\\Contract\\\\Entity\\\\Document given\\.$#" + count: 1 + path: src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentManager.php + + - + message: "#^Parameter \\#1 \\$type of method Chill\\\\DocStoreBundle\\\\Entity\\\\StoredObject\\:\\:setType\\(\\) expects string\\|null, false given\\.$#" + count: 1 + path: src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentManager.php diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 20970a799..a89386f29 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,10 +1,5 @@ parameters: ignoreErrors: - - - message: "#^Variable property access on \\$this\\(Chill\\\\ActivityBundle\\\\Entity\\\\ActivityType\\)\\.$#" - count: 3 - path: src/Bundle/ChillActivityBundle/Entity/ActivityType.php - - message: "#^Foreach overwrites \\$key with its key variable\\.$#" count: 1 @@ -20,11 +15,6 @@ parameters: count: 1 path: src/Bundle/ChillEventBundle/Form/ChoiceLoader/EventChoiceLoader.php - - - message: "#^Variable method call on object\\.$#" - count: 2 - path: src/Bundle/ChillMainBundle/CRUD/Controller/ApiController.php - - message: "#^Cannot unset offset '_token' on array\\{formatter\\: mixed, export\\: mixed, centers\\: mixed, alias\\: string\\}\\.$#" count: 1 @@ -50,46 +40,11 @@ parameters: count: 1 path: src/Bundle/ChillMainBundle/Form/ChoiceLoader/PostalCodeChoiceLoader.php - - - message: "#^Variable \\$message on left side of \\?\\? always exists and is not nullable\\.$#" - count: 1 - path: src/Bundle/ChillMainBundle/Templating/ChillTwigHelper.php - - - - message: "#^Variable \\$sqls on left side of \\?\\? always exists and is not nullable\\.$#" - count: 1 - path: src/Bundle/ChillPersonBundle/Actions/Remove/PersonMove.php - - - - message: "#^Class Chill\\\\PersonBundle\\\\Entity\\\\Person constructor invoked with 1 parameter, 0 required\\.$#" - count: 1 - path: src/Bundle/ChillPersonBundle/Command/ImportPeopleFromCSVCommand.php - - - - message: "#^Variable \\$street1Value might not be defined\\.$#" - count: 1 - path: src/Bundle/ChillPersonBundle/Command/ImportPeopleFromCSVCommand.php - - - - message: "#^Variable method call on mixed\\.$#" - count: 1 - path: src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadPeople.php - - message: "#^Foreach overwrites \\$value with its value variable\\.$#" count: 1 path: src/Bundle/ChillPersonBundle/Form/ChoiceLoader/PersonChoiceLoader.php - - - message: "#^Foreach overwrites \\$action with its value variable\\.$#" - count: 1 - path: src/Bundle/ChillPersonBundle/Repository/SocialWork/GoalRepository.php - - - - message: "#^Foreach overwrites \\$action with its value variable\\.$#" - count: 1 - path: src/Bundle/ChillPersonBundle/Repository/SocialWork/ResultRepository.php - - message: "#^Foreach overwrites \\$value with its value variable\\.$#" count: 1 diff --git a/phpstan-critical.neon b/phpstan-critical.neon index 1dc516834..158c7384b 100644 --- a/phpstan-critical.neon +++ b/phpstan-critical.neon @@ -1,86 +1,6 @@ parameters: ignoreErrors: - - - message: "#^Implicit array creation is not allowed \\- variable \\$centers might not exist\\.$#" - count: 1 - path: src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php - - - - message: "#^Access to an undefined property Chill\\\\PersonBundle\\\\Entity\\\\Household\\\\PersonHouseholdAddress\\:\\:\\$relation\\.$#" - count: 1 - path: src/Bundle/ChillPersonBundle/Entity/Household/PersonHouseholdAddress.php - - - - message: "#^Access to an undefined property Chill\\\\PersonBundle\\\\Entity\\\\AccompanyingPeriod\\:\\:\\$work\\.$#" - count: 1 - path: src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php - - - - message: "#^Undefined variable\\: \\$person$#" - count: 1 - path: src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php - - - - message: "#^Variable variables are not allowed\\.$#" - count: 4 - path: src/Bundle/ChillPersonBundle/Search/PersonSearch.php - - - - message: "#^Function Chill\\\\PersonBundle\\\\Serializer\\\\Normalizer\\\\·\\\\is_array not found\\.$#" - count: 1 - path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/MembersEditorNormalizer.php - - - - message: "#^Undefined variable\\: \\$choiceSlug$#" - count: 1 - path: src/Bundle/ChillPersonBundle/Export/Export/ListPerson.php - - - - message: "#^Undefined variable\\: \\$choiceSlug$#" - count: 1 - path: src/Bundle/ChillReportBundle/Export/Export/ReportList.php - - - - message: "#^Undefined variable\\: \\$type$#" - count: 1 - path: src/Bundle/ChillTaskBundle/Controller/TaskController.php - - - - message: "#^Call to an undefined method Chill\\\\MainBundle\\\\CRUD\\\\Controller\\\\AbstractCRUDController\\:\\:getRoleFor\\(\\)\\.$#" - count: 1 - path: src/Bundle/ChillMainBundle/CRUD/Controller/AbstractCRUDController.php - - message: "#^Call to an undefined method Chill\\\\MainBundle\\\\Controller\\\\UserController\\:\\:createEditForm\\(\\)\\.$#" count: 1 path: src/Bundle/ChillMainBundle/Controller/UserController.php - - - - message: "#^Undefined variable\\: \\$current$#" - count: 1 - path: src/Bundle/ChillMainBundle/Pagination/PageGenerator.php - - - - message: "#^Call to an undefined method Chill\\\\MainBundle\\\\Security\\\\Authorization\\\\AbstractChillVoter\\:\\:getSupportedAttributes\\(\\)\\.$#" - count: 1 - path: src/Bundle/ChillMainBundle/Security/Authorization/AbstractChillVoter.php - - - - message: "#^Call to an undefined method Chill\\\\MainBundle\\\\Security\\\\Authorization\\\\AbstractChillVoter\\:\\:getSupportedClasses\\(\\)\\.$#" - count: 1 - path: src/Bundle/ChillMainBundle/Security/Authorization/AbstractChillVoter.php - - - - message: "#^Call to an undefined method Chill\\\\MainBundle\\\\Security\\\\Authorization\\\\AbstractChillVoter\\:\\:isGranted\\(\\)\\.$#" - count: 1 - path: src/Bundle/ChillMainBundle/Security/Authorization/AbstractChillVoter.php - - - - message: "#^Access to an undefined property Chill\\\\PersonBundle\\\\Controller\\\\PersonController\\:\\:\\$security\\.$#" - count: 3 - path: src/Bundle/ChillPersonBundle/Controller/PersonController.php - - - - message: "#^Call to an undefined method Chill\\\\ThirdPartyBundle\\\\Form\\\\Type\\\\PickThirdPartyTypeCategoryType\\:\\:transform\\(\\)\\.$#" - count: 1 - path: src/Bundle/ChillThirdPartyBundle/Form/Type/PickThirdPartyTypeCategoryType.php diff --git a/phpstan-deprecations.neon b/phpstan-deprecations.neon index 42981a551..c83feeb59 100644 --- a/phpstan-deprecations.neon +++ b/phpstan-deprecations.neon @@ -1,11 +1,10 @@ parameters: ignoreErrors: - - message: - """ - #^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ + message: """ + #^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: + since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# + """ count: 2 path: src/Bundle/ChillActivityBundle/Controller/ActivityController.php @@ -25,209 +24,35 @@ parameters: path: src/Bundle/ChillActivityBundle/Entity/ActivityReasonCategory.php - - message: - """ - #^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ + message: """ + #^Fetching class constant class of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: + since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# + """ count: 1 - path: src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityReasonAggregator.php + path: src/Bundle/ChillActivityBundle/Form/ActivityType.php + - - message: - """ - #^Return type of method Chill\\\\ActivityBundle\\\\Export\\\\Aggregator\\\\ActivityReasonAggregator\\:\\:addRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ - count: 1 - path: src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityReasonAggregator.php - - - - message: - """ - #^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ - count: 1 - path: src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityTypeAggregator.php - - - - message: - """ - #^Return type of method Chill\\\\ActivityBundle\\\\Export\\\\Aggregator\\\\ActivityTypeAggregator\\:\\:addRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ - count: 1 - path: src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityTypeAggregator.php - - - - message: - """ - #^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ - count: 1 - path: src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUserAggregator.php - - - - message: - """ - #^Return type of method Chill\\\\ActivityBundle\\\\Export\\\\Aggregator\\\\ActivityUserAggregator\\:\\:addRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ - count: 1 - path: src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUserAggregator.php - - - - message: - """ - #^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ - count: 1 - path: src/Bundle/ChillActivityBundle/Export/Export/CountActivity.php - - - - message: - """ - #^Return type of method Chill\\\\ActivityBundle\\\\Export\\\\Export\\\\CountActivity\\:\\:requiredRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ - count: 1 - path: src/Bundle/ChillActivityBundle/Export/Export/CountActivity.php - - - - message: - """ - #^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ - count: 1 - path: src/Bundle/ChillActivityBundle/Export/Export/ListActivity.php - - - - message: - """ - #^Return type of method Chill\\\\ActivityBundle\\\\Export\\\\Export\\\\ListActivity\\:\\:requiredRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ - count: 1 - path: src/Bundle/ChillActivityBundle/Export/Export/ListActivity.php - - - - message: - """ - #^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ - count: 1 - path: src/Bundle/ChillActivityBundle/Export/Export/StatActivityDuration.php - - - - message: - """ - #^Return type of method Chill\\\\ActivityBundle\\\\Export\\\\Export\\\\StatActivityDuration\\:\\:requiredRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ - count: 1 - path: src/Bundle/ChillActivityBundle/Export/Export/StatActivityDuration.php - - - - message: - """ - #^Return type of method Chill\\\\ActivityBundle\\\\Export\\\\Filter\\\\ActivityDateFilter\\:\\:addRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ - count: 1 - path: src/Bundle/ChillActivityBundle/Export/Filter/ActivityDateFilter.php - - - - message: - """ - #^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ - count: 1 - path: src/Bundle/ChillActivityBundle/Export/Filter/ActivityReasonFilter.php - - - - message: - """ - #^Return type of method Chill\\\\ActivityBundle\\\\Export\\\\Filter\\\\ActivityReasonFilter\\:\\:addRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ - count: 1 - path: src/Bundle/ChillActivityBundle/Export/Filter/ActivityReasonFilter.php - - - - message: - """ - #^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ - count: 1 - path: src/Bundle/ChillActivityBundle/Export/Filter/ActivityTypeFilter.php - - - - message: - """ - #^Return type of method Chill\\\\ActivityBundle\\\\Export\\\\Filter\\\\ActivityTypeFilter\\:\\:addRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ - count: 1 - path: src/Bundle/ChillActivityBundle/Export/Filter/ActivityTypeFilter.php - - - - message: - """ - #^Return type of method Chill\\\\ActivityBundle\\\\Export\\\\Filter\\\\PersonHavingActivityBetweenDateFilter\\:\\:addRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ - count: 1 - path: src/Bundle/ChillActivityBundle/Export/Filter/PersonHavingActivityBetweenDateFilter.php - - - - message: - """ - #^Call to deprecated method getReachableScopes\\(\\) of class Chill\\\\MainBundle\\\\Security\\\\Authorization\\\\AuthorizationHelper\\: - Use getReachableCircles$# - """ + message: """ + #^Parameter \\$centerResolverDispatcher of method Chill\\\\ActivityBundle\\\\Repository\\\\ActivityACLAwareRepository\\:\\:__construct\\(\\) has typehint with deprecated interface Chill\\\\MainBundle\\\\Security\\\\Resolver\\\\CenterResolverDispatcherInterface\\: + Use CenterResolverManager and its interface CenterResolverManagerInterface$# + """ count: 1 path: src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php - - message: - """ - #^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ - count: 1 - path: src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php - - - - message: - """ - #^Parameter \\$centerResolverDispatcher of method Chill\\\\ActivityBundle\\\\Repository\\\\ActivityACLAwareRepository\\:\\:__construct\\(\\) has typehint with deprecated interface Chill\\\\MainBundle\\\\Security\\\\Resolver\\\\CenterResolverDispatcherInterface\\: - Use CenterResolverManager and its interface CenterResolverManagerInterface$# - """ - count: 1 - path: src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php - - - - message: - """ - #^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ + message: """ + #^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: + since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# + """ count: 1 path: src/Bundle/ChillActivityBundle/Timeline/TimelineActivityProvider.php - - message: - """ - #^Parameter \\$templating of method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldChoice\\:\\:__construct\\(\\) has typehint with deprecated class Symfony\\\\Bridge\\\\Twig\\\\TwigEngine\\: - since version 4\\.3, to be removed in 5\\.0; use Twig instead\\.$# - """ + message: """ + #^Parameter \\$templating of method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldChoice\\:\\:__construct\\(\\) has typehint with deprecated class Symfony\\\\Bridge\\\\Twig\\\\TwigEngine\\: + since version 4\\.3, to be removed in 5\\.0; use Twig instead\\.$# + """ count: 1 path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldChoice.php @@ -237,47 +62,42 @@ parameters: path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldChoice.php - - message: - """ - #^Parameter \\$templating of method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldDate\\:\\:__construct\\(\\) has typehint with deprecated class Symfony\\\\Bundle\\\\TwigBundle\\\\TwigEngine\\: - since version 4\\.3, to be removed in 5\\.0; use Twig instead\\.$# - """ + message: """ + #^Parameter \\$templating of method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldDate\\:\\:__construct\\(\\) has typehint with deprecated class Symfony\\\\Bundle\\\\TwigBundle\\\\TwigEngine\\: + since version 4\\.3, to be removed in 5\\.0; use Twig instead\\.$# + """ count: 1 path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldDate.php - - message: - """ - #^Parameter \\$twigEngine of method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldLongChoice\\:\\:__construct\\(\\) has typehint with deprecated class Symfony\\\\Bridge\\\\Twig\\\\TwigEngine\\: - since version 4\\.3, to be removed in 5\\.0; use Twig instead\\.$# - """ + message: """ + #^Parameter \\$twigEngine of method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldLongChoice\\:\\:__construct\\(\\) has typehint with deprecated class Symfony\\\\Bridge\\\\Twig\\\\TwigEngine\\: + since version 4\\.3, to be removed in 5\\.0; use Twig instead\\.$# + """ count: 1 path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldLongChoice.php - - message: - """ - #^Parameter \\$templating of method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldNumber\\:\\:__construct\\(\\) has typehint with deprecated class Symfony\\\\Bundle\\\\TwigBundle\\\\TwigEngine\\: - since version 4\\.3, to be removed in 5\\.0; use Twig instead\\.$# - """ + message: """ + #^Parameter \\$templating of method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldNumber\\:\\:__construct\\(\\) has typehint with deprecated class Symfony\\\\Bundle\\\\TwigBundle\\\\TwigEngine\\: + since version 4\\.3, to be removed in 5\\.0; use Twig instead\\.$# + """ count: 1 path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldNumber.php - - message: - """ - #^Parameter \\$templating of method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldText\\:\\:__construct\\(\\) has typehint with deprecated class Symfony\\\\Bundle\\\\TwigBundle\\\\TwigEngine\\: - since version 4\\.3, to be removed in 5\\.0; use Twig instead\\.$# - """ + message: """ + #^Parameter \\$templating of method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldText\\:\\:__construct\\(\\) has typehint with deprecated class Symfony\\\\Bundle\\\\TwigBundle\\\\TwigEngine\\: + since version 4\\.3, to be removed in 5\\.0; use Twig instead\\.$# + """ count: 1 path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldText.php - - message: - """ - #^Parameter \\$templating of method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldTitle\\:\\:__construct\\(\\) has typehint with deprecated class Symfony\\\\Bundle\\\\TwigBundle\\\\TwigEngine\\: - since version 4\\.3, to be removed in 5\\.0; use Twig instead\\.$# - """ + message: """ + #^Parameter \\$templating of method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldTitle\\:\\:__construct\\(\\) has typehint with deprecated class Symfony\\\\Bundle\\\\TwigBundle\\\\TwigEngine\\: + since version 4\\.3, to be removed in 5\\.0; use Twig instead\\.$# + """ count: 1 path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldTitle.php @@ -292,182 +112,114 @@ parameters: path: src/Bundle/ChillCustomFieldsBundle/Entity/CustomFieldsGroup.php - - message: - """ - #^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ + message: """ + #^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: + since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# + """ count: 6 path: src/Bundle/ChillEventBundle/Controller/EventController.php - - message: - """ - #^Fetching class constant class of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ + message: """ + #^Fetching class constant class of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: + since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# + """ count: 1 path: src/Bundle/ChillEventBundle/Form/EventType.php - - message: - """ - #^Fetching class constant class of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ + message: """ + #^Fetching class constant class of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: + since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# + """ count: 1 path: src/Bundle/ChillEventBundle/Form/Type/PickEventType.php - - message: - """ - #^Call to deprecated method getReachableScopes\\(\\) of class Chill\\\\MainBundle\\\\Security\\\\Authorization\\\\AuthorizationHelper\\: - Use getReachableCircles$# - """ - count: 1 - path: src/Bundle/ChillEventBundle/Search/EventSearch.php - - - - message: - """ - #^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ + message: """ + #^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: + since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# + """ count: 1 path: src/Bundle/ChillFamilyMembersBundle/Security/Voter/FamilyMemberVoter.php - - message: - """ - #^Parameter \\$role of method Chill\\\\MainBundle\\\\CRUD\\\\Controller\\\\CRUDController\\:\\:getReachableCenters\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ + message: """ + #^Parameter \\$role of method Chill\\\\MainBundle\\\\CRUD\\\\Controller\\\\CRUDController\\:\\:getReachableCenters\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: + since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# + """ count: 1 path: src/Bundle/ChillMainBundle/CRUD/Controller/CRUDController.php - - message: - """ - #^Call to deprecated method getLanguageBundle\\(\\) of class Symfony\\\\Component\\\\Intl\\\\Intl\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use \\{@see Languages\\} or \\{@see Scripts\\} instead\\.$# - """ + message: """ + #^Call to deprecated method getLanguageBundle\\(\\) of class Symfony\\\\Component\\\\Intl\\\\Intl\\: + since Symfony 4\\.3, to be removed in 5\\.0\\. Use \\{@see Languages\\} or \\{@see Scripts\\} instead\\.$# + """ count: 1 path: src/Bundle/ChillMainBundle/Command/LoadAndUpdateLanguagesCommand.php - - message: - """ - #^Call to deprecated method getRegionBundle\\(\\) of class Symfony\\\\Component\\\\Intl\\\\Intl\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use \\{@see Countries\\} instead\\.$# - """ + message: """ + #^Call to deprecated method getRegionBundle\\(\\) of class Symfony\\\\Component\\\\Intl\\\\Intl\\: + since Symfony 4\\.3, to be removed in 5\\.0\\. Use \\{@see Countries\\} instead\\.$# + """ count: 1 path: src/Bundle/ChillMainBundle/Command/LoadCountriesCommand.php - - message: - """ - #^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ + message: """ + #^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: + since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# + """ count: 1 path: src/Bundle/ChillMainBundle/Controller/PermissionsGroupController.php - - message: - """ - #^Parameter \\$role of anonymous function has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ + message: """ + #^Parameter \\$role of anonymous function has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: + since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# + """ count: 1 path: src/Bundle/ChillMainBundle/Controller/PermissionsGroupController.php - - message: - """ - #^Call to deprecated method getLanguageBundle\\(\\) of class Symfony\\\\Component\\\\Intl\\\\Intl\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use \\{@see Languages\\} or \\{@see Scripts\\} instead\\.$# - """ + message: """ + #^Call to deprecated method getLanguageBundle\\(\\) of class Symfony\\\\Component\\\\Intl\\\\Intl\\: + since Symfony 4\\.3, to be removed in 5\\.0\\. Use \\{@see Languages\\} or \\{@see Scripts\\} instead\\.$# + """ count: 2 path: src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadLanguages.php - - message: - """ - #^Class Chill\\\\MainBundle\\\\Entity\\\\User implements deprecated interface Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\AdvancedUserInterface\\: - since Symfony 4\\.1$# - """ + message: """ + #^Return type of method Chill\\\\MainBundle\\\\Entity\\\\User\\:\\:getRoles\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: + since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# + """ count: 1 path: src/Bundle/ChillMainBundle/Entity/User.php - - message: - """ - #^Return type of method Chill\\\\MainBundle\\\\Entity\\\\User\\:\\:getRoles\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ - count: 1 - path: src/Bundle/ChillMainBundle/Entity/User.php - - - - message: - """ - #^Return type of method Chill\\\\MainBundle\\\\Export\\\\DirectExportInterface\\:\\:requiredRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ - count: 1 - path: src/Bundle/ChillMainBundle/Export/DirectExportInterface.php - - - - message: - """ - #^Return type of method Chill\\\\MainBundle\\\\Export\\\\ExportInterface\\:\\:requiredRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ - count: 1 - path: src/Bundle/ChillMainBundle/Export/ExportInterface.php - - - - message: - """ - #^Call to deprecated method getReachableScopes\\(\\) of class Chill\\\\MainBundle\\\\Security\\\\Authorization\\\\AuthorizationHelper\\: - Use getReachableCircles$# - """ - count: 1 - path: src/Bundle/ChillMainBundle/Export/ExportManager.php - - - - message: - """ - #^Return type of method Chill\\\\MainBundle\\\\Export\\\\ModifierInterface\\:\\:addRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ - count: 1 - path: src/Bundle/ChillMainBundle/Export/ModifierInterface.php - - - - message: - """ - #^Class Chill\\\\MainBundle\\\\Form\\\\Event\\\\CustomizeFormEvent extends deprecated class Symfony\\\\Component\\\\EventDispatcher\\\\Event\\: - since Symfony 4\\.3, use "Symfony\\\\Contracts\\\\EventDispatcher\\\\Event" instead$# - """ + message: """ + #^Class Chill\\\\MainBundle\\\\Form\\\\Event\\\\CustomizeFormEvent extends deprecated class Symfony\\\\Component\\\\EventDispatcher\\\\Event\\: + since Symfony 4\\.3, use "Symfony\\\\Contracts\\\\EventDispatcher\\\\Event" instead$# + """ count: 1 path: src/Bundle/ChillMainBundle/Form/Event/CustomizeFormEvent.php - - message: - """ - #^Fetching class constant class of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ + message: """ + #^Fetching class constant class of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: + since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# + """ count: 1 path: src/Bundle/ChillMainBundle/Form/Type/ScopePickerType.php - - message: - """ - #^Fetching class constant class of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ + message: """ + #^Fetching class constant class of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: + since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# + """ count: 1 path: src/Bundle/ChillMainBundle/Form/Type/UserPickerType.php @@ -477,74 +229,66 @@ parameters: path: src/Bundle/ChillMainBundle/Repository/NotificationRepository.php - - message: - """ - #^Parameter \\$attribute of method Chill\\\\MainBundle\\\\Security\\\\Authorization\\\\AuthorizationHelper\\:\\:userHasAccess\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ + message: """ + #^Parameter \\$attribute of method Chill\\\\MainBundle\\\\Security\\\\Authorization\\\\AuthorizationHelper\\:\\:userHasAccess\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: + since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# + """ count: 1 path: src/Bundle/ChillMainBundle/Security/Authorization/AuthorizationHelper.php - - message: - """ - #^Parameter \\$centerResolverDispatcher of method Chill\\\\MainBundle\\\\Security\\\\Authorization\\\\AuthorizationHelper\\:\\:__construct\\(\\) has typehint with deprecated interface Chill\\\\MainBundle\\\\Security\\\\Resolver\\\\CenterResolverDispatcherInterface\\: - Use CenterResolverManager and its interface CenterResolverManagerInterface$# - """ + message: """ + #^Parameter \\$centerResolverDispatcher of method Chill\\\\MainBundle\\\\Security\\\\Authorization\\\\AuthorizationHelper\\:\\:__construct\\(\\) has typehint with deprecated interface Chill\\\\MainBundle\\\\Security\\\\Resolver\\\\CenterResolverDispatcherInterface\\: + Use CenterResolverManager and its interface CenterResolverManagerInterface$# + """ count: 1 path: src/Bundle/ChillMainBundle/Security/Authorization/AuthorizationHelper.php - - message: - """ - #^Parameter \\$role of method Chill\\\\MainBundle\\\\Security\\\\Authorization\\\\AuthorizationHelper\\:\\:getReachableCircles\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ + message: """ + #^Parameter \\$role of method Chill\\\\MainBundle\\\\Security\\\\Authorization\\\\AuthorizationHelper\\:\\:getReachableCircles\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: + since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# + """ count: 1 path: src/Bundle/ChillMainBundle/Security/Authorization/AuthorizationHelper.php - - message: - """ - #^Parameter \\$centerResolverDispatcher of method Chill\\\\MainBundle\\\\Security\\\\Authorization\\\\DefaultVoterHelper\\:\\:__construct\\(\\) has typehint with deprecated interface Chill\\\\MainBundle\\\\Security\\\\Resolver\\\\CenterResolverDispatcherInterface\\: - Use CenterResolverManager and its interface CenterResolverManagerInterface$# - """ + message: """ + #^Parameter \\$centerResolverDispatcher of method Chill\\\\MainBundle\\\\Security\\\\Authorization\\\\DefaultVoterHelper\\:\\:__construct\\(\\) has typehint with deprecated interface Chill\\\\MainBundle\\\\Security\\\\Resolver\\\\CenterResolverDispatcherInterface\\: + Use CenterResolverManager and its interface CenterResolverManagerInterface$# + """ count: 1 path: src/Bundle/ChillMainBundle/Security/Authorization/DefaultVoterHelper.php - - message: - """ - #^Parameter \\$centerResolverDispatcher of method Chill\\\\MainBundle\\\\Security\\\\Authorization\\\\DefaultVoterHelperFactory\\:\\:__construct\\(\\) has typehint with deprecated interface Chill\\\\MainBundle\\\\Security\\\\Resolver\\\\CenterResolverDispatcherInterface\\: - Use CenterResolverManager and its interface CenterResolverManagerInterface$# - """ + message: """ + #^Parameter \\$centerResolverDispatcher of method Chill\\\\MainBundle\\\\Security\\\\Authorization\\\\DefaultVoterHelperFactory\\:\\:__construct\\(\\) has typehint with deprecated interface Chill\\\\MainBundle\\\\Security\\\\Resolver\\\\CenterResolverDispatcherInterface\\: + Use CenterResolverManager and its interface CenterResolverManagerInterface$# + """ count: 1 path: src/Bundle/ChillMainBundle/Security/Authorization/DefaultVoterHelperFactory.php - - message: - """ - #^Parameter \\$centerResolverDispatcher of method Chill\\\\MainBundle\\\\Security\\\\Authorization\\\\DefaultVoterHelperGenerator\\:\\:__construct\\(\\) has typehint with deprecated interface Chill\\\\MainBundle\\\\Security\\\\Resolver\\\\CenterResolverDispatcherInterface\\: - Use CenterResolverManager and its interface CenterResolverManagerInterface$# - """ + message: """ + #^Parameter \\$centerResolverDispatcher of method Chill\\\\MainBundle\\\\Security\\\\Authorization\\\\DefaultVoterHelperGenerator\\:\\:__construct\\(\\) has typehint with deprecated interface Chill\\\\MainBundle\\\\Security\\\\Resolver\\\\CenterResolverDispatcherInterface\\: + Use CenterResolverManager and its interface CenterResolverManagerInterface$# + """ count: 1 path: src/Bundle/ChillMainBundle/Security/Authorization/DefaultVoterHelperGenerator.php - - message: - """ - #^Class Chill\\\\MainBundle\\\\Security\\\\PasswordRecover\\\\PasswordRecoverEvent extends deprecated class Symfony\\\\Component\\\\EventDispatcher\\\\Event\\: - since Symfony 4\\.3, use "Symfony\\\\Contracts\\\\EventDispatcher\\\\Event" instead$# - """ + message: """ + #^Class Chill\\\\MainBundle\\\\Security\\\\PasswordRecover\\\\PasswordRecoverEvent extends deprecated class Symfony\\\\Component\\\\EventDispatcher\\\\Event\\: + since Symfony 4\\.3, use "Symfony\\\\Contracts\\\\EventDispatcher\\\\Event" instead$# + """ count: 1 path: src/Bundle/ChillMainBundle/Security/PasswordRecover/PasswordRecoverEvent.php - - message: - """ - #^Class Chill\\\\MainBundle\\\\Security\\\\Resolver\\\\CenterResolverDispatcher implements deprecated interface Chill\\\\MainBundle\\\\Security\\\\Resolver\\\\CenterResolverDispatcherInterface\\: - Use CenterResolverManager and its interface CenterResolverManagerInterface$# - """ + message: """ + #^Class Chill\\\\MainBundle\\\\Security\\\\Resolver\\\\CenterResolverDispatcher implements deprecated interface Chill\\\\MainBundle\\\\Security\\\\Resolver\\\\CenterResolverDispatcherInterface\\: + Use CenterResolverManager and its interface CenterResolverManagerInterface$# + """ count: 1 path: src/Bundle/ChillMainBundle/Security/Resolver/CenterResolverDispatcher.php @@ -554,65 +298,42 @@ parameters: path: src/Bundle/ChillMainBundle/Templating/ChillTwigRoutingHelper.php - - message: - """ - #^Class Chill\\\\MainBundle\\\\Templating\\\\Events\\\\DelegatedBlockRenderingEvent extends deprecated class Symfony\\\\Component\\\\EventDispatcher\\\\Event\\: - since Symfony 4\\.3, use "Symfony\\\\Contracts\\\\EventDispatcher\\\\Event" instead$# - """ + message: """ + #^Class Chill\\\\MainBundle\\\\Templating\\\\Events\\\\DelegatedBlockRenderingEvent extends deprecated class Symfony\\\\Component\\\\EventDispatcher\\\\Event\\: + since Symfony 4\\.3, use "Symfony\\\\Contracts\\\\EventDispatcher\\\\Event" instead$# + """ count: 1 path: src/Bundle/ChillMainBundle/Templating/Events/DelegatedBlockRenderingEvent.php - - message: - """ - #^Class Chill\\\\PersonBundle\\\\Actions\\\\ActionEvent extends deprecated class Symfony\\\\Component\\\\EventDispatcher\\\\Event\\: - since Symfony 4\\.3, use "Symfony\\\\Contracts\\\\EventDispatcher\\\\Event" instead$# - """ + message: """ + #^Class Chill\\\\PersonBundle\\\\Actions\\\\ActionEvent extends deprecated class Symfony\\\\Component\\\\EventDispatcher\\\\Event\\: + since Symfony 4\\.3, use "Symfony\\\\Contracts\\\\EventDispatcher\\\\Event" instead$# + """ count: 1 path: src/Bundle/ChillPersonBundle/Actions/ActionEvent.php - - message: - """ - #^Call to deprecated method getCurrentAccompanyingPeriod\\(\\) of class Chill\\\\PersonBundle\\\\Entity\\\\Person\\: - since 1\\.1 use `getOpenedAccompanyingPeriod instead$# - """ - count: 1 - path: src/Bundle/ChillPersonBundle/Command/ImportPeopleFromCSVCommand.php - - - - message: - """ - #^Instantiation of deprecated class Symfony\\\\Component\\\\EventDispatcher\\\\Event\\: - since Symfony 4\\.3, use "Symfony\\\\Contracts\\\\EventDispatcher\\\\Event" instead$# - """ - count: 1 - path: src/Bundle/ChillPersonBundle/Command/ImportPeopleFromCSVCommand.php - - - - message: - """ - #^Class Chill\\\\PersonBundle\\\\Controller\\\\AccompanyingCourseController extends deprecated class Symfony\\\\Bundle\\\\FrameworkBundle\\\\Controller\\\\Controller\\: - since Symfony 4\\.2, use "Symfony\\\\Bundle\\\\FrameworkBundle\\\\Controller\\\\AbstractController" instead\\.$# - """ + message: """ + #^Class Chill\\\\PersonBundle\\\\Controller\\\\AccompanyingCourseController extends deprecated class Symfony\\\\Bundle\\\\FrameworkBundle\\\\Controller\\\\Controller\\: + since Symfony 4\\.2, use "Symfony\\\\Bundle\\\\FrameworkBundle\\\\Controller\\\\AbstractController" instead\\.$# + """ count: 1 path: src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseController.php - - message: - """ - #^Call to deprecated method getCurrentAccompanyingPeriod\\(\\) of class Chill\\\\PersonBundle\\\\Entity\\\\Person\\: - since 1\\.1 use `getOpenedAccompanyingPeriod instead$# - """ + message: """ + #^Call to deprecated method getCurrentAccompanyingPeriod\\(\\) of class Chill\\\\PersonBundle\\\\Entity\\\\Person\\: + since 1\\.1 use `getOpenedAccompanyingPeriod instead$# + """ count: 1 path: src/Bundle/ChillPersonBundle/Controller/AccompanyingPeriodController.php - - message: - """ - #^Class Chill\\\\PersonBundle\\\\Controller\\\\PersonDuplicateController extends deprecated class Symfony\\\\Bundle\\\\FrameworkBundle\\\\Controller\\\\Controller\\: - since Symfony 4\\.2, use "Symfony\\\\Bundle\\\\FrameworkBundle\\\\Controller\\\\AbstractController" instead\\.$# - """ + message: """ + #^Class Chill\\\\PersonBundle\\\\Controller\\\\PersonDuplicateController extends deprecated class Symfony\\\\Bundle\\\\FrameworkBundle\\\\Controller\\\\Controller\\: + since Symfony 4\\.2, use "Symfony\\\\Bundle\\\\FrameworkBundle\\\\Controller\\\\AbstractController" instead\\.$# + """ count: 1 path: src/Bundle/ChillPersonBundle/Controller/PersonDuplicateController.php @@ -622,164 +343,10 @@ parameters: path: src/Bundle/ChillPersonBundle/Entity/Person.php - - message: - """ - #^Return type of method Chill\\\\PersonBundle\\\\Export\\\\Aggregator\\\\AgeAggregator\\:\\:addRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ - count: 1 - path: src/Bundle/ChillPersonBundle/Export/Aggregator/AgeAggregator.php - - - - message: - """ - #^Return type of method Chill\\\\PersonBundle\\\\Export\\\\Aggregator\\\\CountryOfBirthAggregator\\:\\:addRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ - count: 1 - path: src/Bundle/ChillPersonBundle/Export/Aggregator/CountryOfBirthAggregator.php - - - - message: - """ - #^Return type of method Chill\\\\PersonBundle\\\\Export\\\\Aggregator\\\\GenderAggregator\\:\\:addRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ - count: 1 - path: src/Bundle/ChillPersonBundle/Export/Aggregator/GenderAggregator.php - - - - message: - """ - #^Return type of method Chill\\\\PersonBundle\\\\Export\\\\Aggregator\\\\NationalityAggregator\\:\\:addRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ - count: 1 - path: src/Bundle/ChillPersonBundle/Export/Aggregator/NationalityAggregator.php - - - - message: - """ - #^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ - count: 1 - path: src/Bundle/ChillPersonBundle/Export/Export/CountPerson.php - - - - message: - """ - #^Return type of method Chill\\\\PersonBundle\\\\Export\\\\Export\\\\CountPerson\\:\\:requiredRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ - count: 1 - path: src/Bundle/ChillPersonBundle/Export/Export/CountPerson.php - - - - message: - """ - #^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ - count: 1 - path: src/Bundle/ChillPersonBundle/Export/Export/ListPerson.php - - - - message: - """ - #^Return type of method Chill\\\\PersonBundle\\\\Export\\\\Export\\\\ListPerson\\:\\:requiredRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ - count: 1 - path: src/Bundle/ChillPersonBundle/Export/Export/ListPerson.php - - - - message: - """ - #^Call to deprecated method execute\\(\\) of class Doctrine\\\\DBAL\\\\Statement\\: - Statement\\:\\:execute\\(\\) is deprecated, use Statement\\:\\:executeQuery\\(\\) or executeStatement\\(\\) instead$# - """ - count: 1 - path: src/Bundle/ChillPersonBundle/Export/Export/ListPersonDuplicate.php - - - - message: - """ - #^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ - count: 1 - path: src/Bundle/ChillPersonBundle/Export/Export/ListPersonDuplicate.php - - - - message: - """ - #^Return type of method Chill\\\\PersonBundle\\\\Export\\\\Export\\\\ListPersonDuplicate\\:\\:requiredRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ - count: 1 - path: src/Bundle/ChillPersonBundle/Export/Export/ListPersonDuplicate.php - - - - message: - """ - #^Return type of method Chill\\\\PersonBundle\\\\Export\\\\Filter\\\\AccompanyingPeriodClosingFilter\\:\\:addRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ - count: 1 - path: src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingPeriodClosingFilter.php - - - - message: - """ - #^Return type of method Chill\\\\PersonBundle\\\\Export\\\\Filter\\\\AccompanyingPeriodFilter\\:\\:addRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ - count: 1 - path: src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingPeriodFilter.php - - - - message: - """ - #^Return type of method Chill\\\\PersonBundle\\\\Export\\\\Filter\\\\AccompanyingPeriodOpeningFilter\\:\\:addRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ - count: 1 - path: src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingPeriodOpeningFilter.php - - - - message: - """ - #^Return type of method Chill\\\\PersonBundle\\\\Export\\\\Filter\\\\BirthdateFilter\\:\\:addRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ - count: 1 - path: src/Bundle/ChillPersonBundle/Export/Filter/BirthdateFilter.php - - - - message: - """ - #^Return type of method Chill\\\\PersonBundle\\\\Export\\\\Filter\\\\GenderFilter\\:\\:addRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ - count: 1 - path: src/Bundle/ChillPersonBundle/Export/Filter/GenderFilter.php - - - - message: - """ - #^Return type of method Chill\\\\PersonBundle\\\\Export\\\\Filter\\\\NationalityFilter\\:\\:addRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ - count: 1 - path: src/Bundle/ChillPersonBundle/Export/Filter/NationalityFilter.php - - - - message: - """ - #^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ + message: """ + #^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: + since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# + """ count: 1 path: src/Bundle/ChillPersonBundle/Form/AccompanyingPeriodType.php @@ -789,254 +356,156 @@ parameters: path: src/Bundle/ChillPersonBundle/Form/PersonType.php - - message: - """ - #^Fetching class constant class of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ + message: """ + #^Fetching class constant class of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: + since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# + """ count: 1 path: src/Bundle/ChillPersonBundle/Form/Type/PickPersonType.php - - message: - """ - #^Class Chill\\\\PersonBundle\\\\Privacy\\\\AccompanyingPeriodPrivacyEvent extends deprecated class Symfony\\\\Component\\\\EventDispatcher\\\\Event\\: - since Symfony 4\\.3, use "Symfony\\\\Contracts\\\\EventDispatcher\\\\Event" instead$# - """ + message: """ + #^Class Chill\\\\PersonBundle\\\\Privacy\\\\AccompanyingPeriodPrivacyEvent extends deprecated class Symfony\\\\Component\\\\EventDispatcher\\\\Event\\: + since Symfony 4\\.3, use "Symfony\\\\Contracts\\\\EventDispatcher\\\\Event" instead$# + """ count: 1 path: src/Bundle/ChillPersonBundle/Privacy/AccompanyingPeriodPrivacyEvent.php - - message: - """ - #^Class Chill\\\\PersonBundle\\\\Privacy\\\\PrivacyEvent extends deprecated class Symfony\\\\Component\\\\EventDispatcher\\\\Event\\: - since Symfony 4\\.3, use "Symfony\\\\Contracts\\\\EventDispatcher\\\\Event" instead$# - """ + message: """ + #^Class Chill\\\\PersonBundle\\\\Privacy\\\\PrivacyEvent extends deprecated class Symfony\\\\Component\\\\EventDispatcher\\\\Event\\: + since Symfony 4\\.3, use "Symfony\\\\Contracts\\\\EventDispatcher\\\\Event" instead$# + """ count: 1 path: src/Bundle/ChillPersonBundle/Privacy/PrivacyEvent.php - - message: - """ - #^Parameter \\$centerResolverDispatcher of method Chill\\\\PersonBundle\\\\Repository\\\\AccompanyingPeriodACLAwareRepository\\:\\:__construct\\(\\) has typehint with deprecated interface Chill\\\\MainBundle\\\\Security\\\\Resolver\\\\CenterResolverDispatcherInterface\\: - Use CenterResolverManager and its interface CenterResolverManagerInterface$# - """ + message: """ + #^Parameter \\$centerResolverDispatcher of method Chill\\\\PersonBundle\\\\Repository\\\\AccompanyingPeriodACLAwareRepository\\:\\:__construct\\(\\) has typehint with deprecated interface Chill\\\\MainBundle\\\\Security\\\\Resolver\\\\CenterResolverDispatcherInterface\\: + Use CenterResolverManager and its interface CenterResolverManagerInterface$# + """ count: 1 path: src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepository.php - - message: - """ - #^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ + message: """ + #^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: + since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# + """ count: 1 path: src/Bundle/ChillPersonBundle/Widget/PersonListWidget.php - - message: - """ - #^Call to deprecated method getReachableScopes\\(\\) of class Chill\\\\MainBundle\\\\Security\\\\Authorization\\\\AuthorizationHelper\\: - Use getReachableCircles$# - """ - count: 1 - path: src/Bundle/ChillReportBundle/Controller/ReportController.php - - - - message: - """ - #^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ + message: """ + #^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: + since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# + """ count: 3 path: src/Bundle/ChillReportBundle/Controller/ReportController.php - - - message: - """ - #^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ - count: 1 - path: src/Bundle/ChillReportBundle/Export/Export/ReportList.php - - message: - """ - #^Return type of method Chill\\\\ReportBundle\\\\Export\\\\Export\\\\ReportList\\:\\:requiredRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ - count: 1 - path: src/Bundle/ChillReportBundle/Export/Export/ReportList.php - - - - message: - """ - #^Return type of method Chill\\\\ReportBundle\\\\Export\\\\Filter\\\\ReportDateFilter\\:\\:addRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ - count: 1 - path: src/Bundle/ChillReportBundle/Export/Filter/ReportDateFilter.php - - - - message: - """ - #^Call to deprecated method getReachableScopes\\(\\) of class Chill\\\\MainBundle\\\\Security\\\\Authorization\\\\AuthorizationHelper\\: - Use getReachableCircles$# - """ + message: """ + #^Parameter \\$role of method Chill\\\\ReportBundle\\\\Form\\\\ReportType\\:\\:appendScopeChoices\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: + since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# + """ count: 1 path: src/Bundle/ChillReportBundle/Form/ReportType.php - - message: - """ - #^Parameter \\$role of method Chill\\\\ReportBundle\\\\Form\\\\ReportType\\:\\:appendScopeChoices\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ - count: 1 - path: src/Bundle/ChillReportBundle/Form/ReportType.php - - - - message: - """ - #^Call to deprecated method getReachableScopes\\(\\) of class Chill\\\\MainBundle\\\\Security\\\\Authorization\\\\AuthorizationHelper\\: - Use getReachableCircles$# - """ + message: """ + #^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: + since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# + """ count: 1 path: src/Bundle/ChillReportBundle/Search/ReportSearch.php - - - message: - """ - #^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ - count: 1 - path: src/Bundle/ChillReportBundle/Search/ReportSearch.php - - message: - """ - #^Call to deprecated method getReachableScopes\\(\\) of class Chill\\\\MainBundle\\\\Security\\\\Authorization\\\\AuthorizationHelper\\: - Use getReachableCircles$# - """ - count: 2 - path: src/Bundle/ChillReportBundle/Timeline/TimelineReportProvider.php - - - - message: - """ - #^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ + message: """ + #^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: + since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# + """ count: 2 path: src/Bundle/ChillTaskBundle/Controller/SingleTaskController.php - - message: - """ - #^Parameter \\$centerResolverDispatcher of method Chill\\\\TaskBundle\\\\Controller\\\\SingleTaskController\\:\\:__construct\\(\\) has typehint with deprecated interface Chill\\\\MainBundle\\\\Security\\\\Resolver\\\\CenterResolverDispatcherInterface\\: - Use CenterResolverManager and its interface CenterResolverManagerInterface$# - """ + message: """ + #^Parameter \\$centerResolverDispatcher of method Chill\\\\TaskBundle\\\\Controller\\\\SingleTaskController\\:\\:__construct\\(\\) has typehint with deprecated interface Chill\\\\MainBundle\\\\Security\\\\Resolver\\\\CenterResolverDispatcherInterface\\: + Use CenterResolverManager and its interface CenterResolverManagerInterface$# + """ count: 1 path: src/Bundle/ChillTaskBundle/Controller/SingleTaskController.php - - message: - """ - #^Parameter \\$role of method Chill\\\\TaskBundle\\\\Controller\\\\SingleTaskController\\:\\:setCreateForm\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ + message: """ + #^Parameter \\$role of method Chill\\\\TaskBundle\\\\Controller\\\\SingleTaskController\\:\\:setCreateForm\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: + since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# + """ count: 1 path: src/Bundle/ChillTaskBundle/Controller/SingleTaskController.php - - message: - """ - #^Class Chill\\\\TaskBundle\\\\Event\\\\TaskEvent extends deprecated class Symfony\\\\Component\\\\EventDispatcher\\\\Event\\: - since Symfony 4\\.3, use "Symfony\\\\Contracts\\\\EventDispatcher\\\\Event" instead$# - """ + message: """ + #^Class Chill\\\\TaskBundle\\\\Event\\\\TaskEvent extends deprecated class Symfony\\\\Component\\\\EventDispatcher\\\\Event\\: + since Symfony 4\\.3, use "Symfony\\\\Contracts\\\\EventDispatcher\\\\Event" instead$# + """ count: 1 path: src/Bundle/ChillTaskBundle/Event/TaskEvent.php - - message: - """ - #^Class Chill\\\\TaskBundle\\\\Event\\\\UI\\\\UIEvent extends deprecated class Symfony\\\\Component\\\\EventDispatcher\\\\Event\\: - since Symfony 4\\.3, use "Symfony\\\\Contracts\\\\EventDispatcher\\\\Event" instead$# - """ + message: """ + #^Class Chill\\\\TaskBundle\\\\Event\\\\UI\\\\UIEvent extends deprecated class Symfony\\\\Component\\\\EventDispatcher\\\\Event\\: + since Symfony 4\\.3, use "Symfony\\\\Contracts\\\\EventDispatcher\\\\Event" instead$# + """ count: 1 path: src/Bundle/ChillTaskBundle/Event/UI/UIEvent.php - - message: - """ - #^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ + message: """ + #^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: + since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# + """ count: 4 path: src/Bundle/ChillTaskBundle/Form/SingleTaskListType.php - - message: - """ - #^Fetching class constant class of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ + message: """ + #^Fetching class constant class of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: + since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# + """ count: 1 path: src/Bundle/ChillTaskBundle/Form/SingleTaskType.php - - message: - """ - #^Parameter \\$centerResolverDispatcher of method Chill\\\\TaskBundle\\\\Form\\\\SingleTaskType\\:\\:__construct\\(\\) has typehint with deprecated interface Chill\\\\MainBundle\\\\Security\\\\Resolver\\\\CenterResolverDispatcherInterface\\: - Use CenterResolverManager and its interface CenterResolverManagerInterface$# - """ + message: """ + #^Parameter \\$centerResolverDispatcher of method Chill\\\\TaskBundle\\\\Form\\\\SingleTaskType\\:\\:__construct\\(\\) has typehint with deprecated interface Chill\\\\MainBundle\\\\Security\\\\Resolver\\\\CenterResolverDispatcherInterface\\: + Use CenterResolverManager and its interface CenterResolverManagerInterface$# + """ count: 1 path: src/Bundle/ChillTaskBundle/Form/SingleTaskType.php - - message: - """ - #^Parameter \\$centerResolverDispatcher of method Chill\\\\TaskBundle\\\\Repository\\\\SingleTaskAclAwareRepository\\:\\:__construct\\(\\) has typehint with deprecated interface Chill\\\\MainBundle\\\\Security\\\\Resolver\\\\CenterResolverDispatcherInterface\\: - Use CenterResolverManager and its interface CenterResolverManagerInterface$# - """ - count: 1 - path: src/Bundle/ChillTaskBundle/Repository/SingleTaskAclAwareRepository.php - - - - message: - """ - #^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ + message: """ + #^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: + since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# + """ count: 1 path: src/Bundle/ChillTaskBundle/Repository/SingleTaskRepository.php - - message: - """ - #^Class Chill\\\\TaskBundle\\\\Security\\\\Authorization\\\\AuthorizationEvent extends deprecated class Symfony\\\\Component\\\\EventDispatcher\\\\Event\\: - since Symfony 4\\.3, use "Symfony\\\\Contracts\\\\EventDispatcher\\\\Event" instead$# - """ + message: """ + #^Class Chill\\\\TaskBundle\\\\Security\\\\Authorization\\\\AuthorizationEvent extends deprecated class Symfony\\\\Component\\\\EventDispatcher\\\\Event\\: + since Symfony 4\\.3, use "Symfony\\\\Contracts\\\\EventDispatcher\\\\Event" instead$# + """ count: 1 path: src/Bundle/ChillTaskBundle/Security/Authorization/AuthorizationEvent.php - - message: - """ - #^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ + message: """ + #^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: + since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# + """ count: 3 path: src/Bundle/ChillTaskBundle/Timeline/TaskLifeCycleEventTimelineProvider.php - - - message: - """ - #^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: - since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$# - """ - count: 1 - path: src/Bundle/ChillThirdPartyBundle/Search/ThirdPartySearch.php - diff --git a/phpstan-types.neon b/phpstan-types.neon index dc3f67a73..3368c94d2 100644 --- a/phpstan-types.neon +++ b/phpstan-types.neon @@ -10,31 +10,11 @@ parameters: count: 1 path: src/Bundle/ChillActivityBundle/Entity/ActivityReasonCategory.php - - - message: "#^Method Chill\\\\ActivityBundle\\\\Export\\\\Export\\\\StatActivityDuration\\:\\:getDescription\\(\\) should return string but return statement is missing\\.$#" - count: 1 - path: src/Bundle/ChillActivityBundle/Export/Export/StatActivityDuration.php - - - - message: "#^Method Chill\\\\ActivityBundle\\\\Export\\\\Export\\\\StatActivityDuration\\:\\:getTitle\\(\\) should return string but return statement is missing\\.$#" - count: 1 - path: src/Bundle/ChillActivityBundle/Export/Export/StatActivityDuration.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 1 path: src/Bundle/ChillActivityBundle/Form/ActivityType.php - - - message: "#^Only booleans are allowed in &&, mixed given on the right side\\.$#" - count: 3 - path: src/Bundle/ChillActivityBundle/Form/ActivityType.php - - - - message: "#^Only booleans are allowed in an if condition, mixed given\\.$#" - count: 2 - path: src/Bundle/ChillActivityBundle/Form/ActivityType.php - - message: "#^Only booleans are allowed in an if condition, mixed given\\.$#" count: 3 @@ -120,11 +100,6 @@ parameters: count: 1 path: src/Bundle/ChillDocStoreBundle/Entity/DocumentCategory.php - - - message: "#^Method Chill\\\\EventBundle\\\\Entity\\\\Participation\\:\\:offsetGet\\(\\) should return mixed but return statement is missing\\.$#" - count: 1 - path: src/Bundle/ChillEventBundle/Entity/Participation.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 1 @@ -255,11 +230,6 @@ parameters: count: 1 path: src/Bundle/ChillMainBundle/Entity/User.php - - - message: "#^Only booleans are allowed in a ternary operator condition, mixed given\\.$#" - count: 1 - path: src/Bundle/ChillMainBundle/Export/Formatter/SpreadsheetListFormatter.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 1 @@ -340,26 +310,6 @@ parameters: count: 1 path: src/Bundle/ChillPersonBundle/Command/ChillPersonMoveCommand.php - - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" - count: 6 - path: src/Bundle/ChillPersonBundle/Command/ImportPeopleFromCSVCommand.php - - - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" - count: 1 - path: src/Bundle/ChillPersonBundle/Export/Aggregator/CountryOfBirthAggregator.php - - - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" - count: 1 - path: src/Bundle/ChillPersonBundle/Export/Aggregator/NationalityAggregator.php - - - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" - count: 2 - path: src/Bundle/ChillPersonBundle/Export/Export/ListPerson.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 1 @@ -380,21 +330,11 @@ parameters: count: 1 path: src/Bundle/ChillPersonBundle/Form/Type/PersonPhoneType.php - - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" - count: 3 - path: src/Bundle/ChillPersonBundle/Search/PersonSearch.php - - message: "#^Method Chill\\\\PersonBundle\\\\Search\\\\PersonSearch\\:\\:renderResult\\(\\) should return string but return statement is missing\\.$#" count: 1 path: src/Bundle/ChillPersonBundle/Search/PersonSearch.php - - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" - count: 2 - path: src/Bundle/ChillPersonBundle/Templating/Entity/ClosingMotiveRender.php - - message: "#^Method Chill\\\\ReportBundle\\\\DataFixtures\\\\ORM\\\\LoadReports\\:\\:getRandomChoice\\(\\) should return array\\\\|string but return statement is missing\\.$#" count: 1 @@ -440,16 +380,6 @@ parameters: count: 1 path: src/Bundle/ChillTaskBundle/Timeline/TaskLifeCycleEventTimelineProvider.php - - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" - count: 1 - path: src/Bundle/ChillThirdPartyBundle/Form/ChoiceLoader/ThirdPartyChoiceLoader.php - - - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" - count: 1 - path: src/Bundle/ChillThirdPartyBundle/Repository/ThirdPartyRepository.php - - message: "#^Method Chill\\\\ThirdPartyBundle\\\\Search\\\\ThirdPartySearch\\:\\:renderResult\\(\\) should return string but return statement is missing\\.$#" count: 1 diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 3322f9539..56b7c2228 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -1,7 +1,9 @@ parameters: - level: 1 + level: 5 paths: - src/ + tmpDir: .cache/ + reportUnmatchedIgnoredErrors: false excludePaths: - .php_cs* - docs/ @@ -24,4 +26,8 @@ includes: - phpstan-critical.neon - phpstan-deprecations.neon - phpstan-types.neon + - phpstan-baseline-level-2.neon + - phpstan-baseline-level-3.neon + - phpstan-baseline-level-4.neon + - phpstan-baseline-level-5.neon diff --git a/psalm.xml b/psalm.xml index 40e5d78e8..77193f05b 100644 --- a/psalm.xml +++ b/psalm.xml @@ -9,9 +9,10 @@ cacheDirectory="./.psalm" > - + - + + @@ -20,9 +21,27 @@ - + + + + + + + diff --git a/rector.php b/rector.php new file mode 100644 index 000000000..ec9a0c684 --- /dev/null +++ b/rector.php @@ -0,0 +1,60 @@ +paths([ + __DIR__ . '/docs', + __DIR__ . '/src', + ]); + + //$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 + ]); + + // skip some path... + $rectorConfig->skip([ + // make rector stuck for some files + \Rector\Php56\Rector\FunctionLike\AddDefaultValueForUndefinedVariableRector::class, + + // we need to discuss this: are we going to have FALSE in tests instead of an error ? + \Rector\Php71\Rector\FuncCall\CountOnNullRector::class, + + // must merge MR500 and review a typing of "ArrayCollection" in entities + \Rector\TypeDeclaration\Rector\Property\TypedPropertyFromAssignsRector::class, + + // remove all PHP80 rules, in order to activate them one by one + \Rector\Php80\Rector\ClassMethod\AddParamBasedOnParentClassMethodRector::class, + \Rector\Php80\Rector\Class_\AnnotationToAttributeRector::class, + \Rector\Php80\Rector\Switch_\ChangeSwitchToMatchRector::class, + \Rector\Php80\Rector\FuncCall\ClassOnObjectRector::class, + \Rector\Php80\Rector\ClassConstFetch\ClassOnThisVariableObjectRector::class, + \Rector\Php80\Rector\Class_\ClassPropertyAssignToConstructorPromotionRector::class, + \Rector\Php80\Rector\Class_\DoctrineAnnotationClassToAttributeRector::class, + \Rector\Php80\Rector\ClassMethod\FinalPrivateToPrivateVisibilityRector::class, + \Rector\Php80\Rector\Ternary\GetDebugTypeRector::class, + \Rector\Php80\Rector\FunctionLike\MixedTypeRector::class, + \Rector\Php80\Rector\Property\NestedAnnotationToAttributeRector::class, + \Rector\Php80\Rector\FuncCall\Php8ResourceReturnToObjectRector::class, + \Rector\Php80\Rector\Catch_\RemoveUnusedVariableInCatchRector::class, + \Rector\Php80\Rector\ClassMethod\SetStateToStaticRector::class, + \Rector\Php80\Rector\NotIdentical\StrContainsRector::class, + \Rector\Php80\Rector\Identical\StrEndsWithRector::class, + \Rector\Php80\Rector\Identical\StrStartsWithRector::class, + \Rector\Php80\Rector\Class_\StringableForToStringRector::class, + \Rector\Php80\Rector\FuncCall\TokenGetAllToObjectRector::class, + \Rector\Php80\Rector\FunctionLike\UnionTypesRector::class + ]); +}; diff --git a/src/Bundle/ChillActivityBundle/ChillActivityBundle.php b/src/Bundle/ChillActivityBundle/ChillActivityBundle.php index 21245504f..5f872a7dc 100644 --- a/src/Bundle/ChillActivityBundle/ChillActivityBundle.php +++ b/src/Bundle/ChillActivityBundle/ChillActivityBundle.php @@ -1,14 +1,14 @@ activityACLAwareRepository = $activityACLAwareRepository; $this->activityTypeRepository = $activityTypeRepository; @@ -96,6 +106,9 @@ final class ActivityController extends AbstractController $this->eventDispatcher = $eventDispatcher; $this->logger = $logger; $this->serializer = $serializer; + $this->userRepository = $userRepository; + $this->centerResolver = $centerResolver; + $this->translator = $translator; } /** @@ -151,7 +164,7 @@ final class ActivityController extends AbstractController $this->entityManager->remove($activity); $this->entityManager->flush(); - $this->addFlash('success', $this->get('translator') + $this->addFlash('success', $this->translator ->trans('The activity has been successfully removed.')); $params = $this->buildParamsToUrl($person, $accompanyingPeriod); @@ -198,8 +211,8 @@ final class ActivityController extends AbstractController // $this->denyAccessUnlessGranted('CHILL_ACTIVITY_UPDATE', $entity); $form = $this->createForm(ActivityType::class, $entity, [ - 'center' => $entity->getCenter(), - 'role' => new Role('CHILL_ACTIVITY_UPDATE'), + 'center' => $this->centerResolver->resolveCenters($entity)[0] ?? null, + 'role' => 'CHILL_ACTIVITY_UPDATE', 'activityType' => $entity->getActivityType(), 'accompanyingPeriod' => $accompanyingPeriod, ]); @@ -236,7 +249,7 @@ final class ActivityController extends AbstractController ); } - $this->addFlash('success', $this->get('translator')->trans('Success : activity updated!')); + $this->addFlash('success', $this->translator->trans('Success : activity updated!')); return $this->redirectToRoute('chill_activity_activity_show', $params); } @@ -354,6 +367,7 @@ final class ActivityController extends AbstractController if ($person instanceof Person) { $entity->setPerson($person); + $entity->getPersons()->add($person); } if ($accompanyingPeriod instanceof AccompanyingPeriod) { @@ -366,7 +380,7 @@ final class ActivityController extends AbstractController if ($request->query->has('activityData')) { $activityData = $request->query->get('activityData'); - if (array_key_exists('durationTime', $activityData)) { + if (array_key_exists('durationTime', $activityData) && $activityType->getDurationTimeVisible() > 0) { $durationTimeInMinutes = $activityData['durationTime']; $hours = floor($durationTimeInMinutes / 60); $minutes = $durationTimeInMinutes % 60; @@ -385,26 +399,36 @@ final class ActivityController extends AbstractController } } - if (array_key_exists('personsId', $activityData)) { + 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)) { + 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('location', $activityData)) { + if (array_key_exists('usersId', $activityData) && $activityType->getUsersVisible() > 0) { + foreach ($activityData['usersId'] as $userId) { + $user = $this->userRepository->find($userId); + + if (null !== $user) { + $entity->addUser($user); + } + } + } + + if (array_key_exists('location', $activityData) && $activityType->getLocationVisible() > 0) { $location = $this->locationRepository->find($activityData['location']); $entity->setLocation($location); } - if (array_key_exists('comment', $activityData)) { + if (array_key_exists('comment', $activityData) && $activityType->getCommentVisible() > 0) { $comment = new CommentEmbeddable(); $comment->setComment($activityData['comment']); $comment->setUserId($this->getUser()->getid()); @@ -416,8 +440,8 @@ final class ActivityController extends AbstractController $this->denyAccessUnlessGranted(ActivityVoter::CREATE, $entity); $form = $this->createForm(ActivityType::class, $entity, [ - 'center' => $entity->getCenter(), - 'role' => new Role('CHILL_ACTIVITY_CREATE'), + 'center' => $this->centerResolver->resolveCenters($entity)[0] ?? null, + 'role' => 'CHILL_ACTIVITY_CREATE', 'activityType' => $entity->getActivityType(), 'accompanyingPeriod' => $accompanyingPeriod, ]); @@ -453,7 +477,7 @@ final class ActivityController extends AbstractController ); } - $this->addFlash('success', $this->get('translator')->trans('Success : activity created!')); + $this->addFlash('success', $this->translator->trans('Success : activity created!')); $params = $this->buildParamsToUrl($person, $accompanyingPeriod); @@ -626,8 +650,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 d367d5803..32cec74a5 100644 --- a/src/Bundle/ChillActivityBundle/Controller/ActivityReasonCategoryController.php +++ b/src/Bundle/ChillActivityBundle/Controller/ActivityReasonCategoryController.php @@ -1,14 +1,14 @@ activityReasonRepository = $activityReasonRepository; + } + /** * Creates a new ActivityReason entity. */ @@ -56,8 +65,8 @@ class ActivityReasonController extends AbstractController $entity = $em->getRepository(\Chill\ActivityBundle\Entity\ActivityReason::class)->find($id); - if (!$entity) { - throw $this->createNotFoundException('Unable to find ActivityReason entity.'); + if (null === $entity) { + throw new NotFoundHttpException('Unable to find ActivityReason entity.'); } $editForm = $this->createEditForm($entity); @@ -75,7 +84,7 @@ class ActivityReasonController extends AbstractController { $em = $this->getDoctrine()->getManager(); - $entities = $em->getRepository(\Chill\ActivityBundle\Entity\ActivityReason::class)->findAll(); + $entities = $this->activityReasonRepository->findAll(); return $this->render('ChillActivityBundle:ActivityReason:index.html.twig', [ 'entities' => $entities, diff --git a/src/Bundle/ChillActivityBundle/Controller/AdminActivityPresenceController.php b/src/Bundle/ChillActivityBundle/Controller/AdminActivityPresenceController.php index 790f59be4..3a39ca072 100644 --- a/src/Bundle/ChillActivityBundle/Controller/AdminActivityPresenceController.php +++ b/src/Bundle/ChillActivityBundle/Controller/AdminActivityPresenceController.php @@ -1,14 +1,14 @@ 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 +75,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/LoadActivityNotifications.php b/src/Bundle/ChillActivityBundle/DataFixtures/ORM/LoadActivityNotifications.php index 63eeb965c..ee999d7f1 100644 --- a/src/Bundle/ChillActivityBundle/DataFixtures/ORM/LoadActivityNotifications.php +++ b/src/Bundle/ChillActivityBundle/DataFixtures/ORM/LoadActivityNotifications.php @@ -1,14 +1,14 @@ [ActivityVoter::SEE_DETAILS], ActivityVoter::SEE_DETAILS => [ActivityVoter::SEE], ActivityVoter::FULL => [ - ActivityVoter::CREATE_PERSON, - ActivityVoter::CREATE_ACCOMPANYING_COURSE, ActivityVoter::DELETE, ActivityVoter::UPDATE, ], diff --git a/src/Bundle/ChillActivityBundle/DependencyInjection/Configuration.php b/src/Bundle/ChillActivityBundle/DependencyInjection/Configuration.php index 4e4e6f3c6..57a1ec578 100644 --- a/src/Bundle/ChillActivityBundle/DependencyInjection/Configuration.php +++ b/src/Bundle/ChillActivityBundle/DependencyInjection/Configuration.php @@ -1,14 +1,14 @@ getRootNode('chill_activity'); + $rootNode = $treeBuilder->getRootNode(); $rootNode ->children() @@ -59,9 +59,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 5fa0bca35..32ceb2e74 100644 --- a/src/Bundle/ChillActivityBundle/Entity/Activity.php +++ b/src/Bundle/ChillActivityBundle/Entity/Activity.php @@ -1,23 +1,27 @@ socialIssues[] = $socialIssue; } + if ($this->getAccompanyingPeriod() !== null) { + $this->getAccompanyingPeriod()->addSocialIssue($socialIssue); + } + return $this; } @@ -306,13 +316,17 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac * get the center * center is extracted from person. */ - public function getCenter(): ?Center + public function getCenters(): iterable { if ($this->person instanceof Person) { - return $this->person->getCenter(); + return [$this->person->getCenter()]; } - return null; + if ($this->getAccompanyingPeriod() instanceof AccompanyingPeriod) { + return $this->getAccompanyingPeriod()->getCenters() ?? []; + } + + return []; } public function getComment(): CommentEmbeddable @@ -422,6 +436,19 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac return $this->scope; } + public function getScopes(): iterable + { + if (null !== $this->getAccompanyingPeriod()) { + return $this->getAccompanyingPeriod()->getScopes(); + } + + if (null !== $this->getPerson()) { + return [$this->scope]; + } + + return []; + } + public function getSentReceived(): string { return $this->sentReceived; @@ -467,7 +494,7 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac return $this->activityType; } - public function getUser(): User + public function getUser(): ?User { return $this->user; } @@ -525,6 +552,10 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac { $this->accompanyingPeriod = $accompanyingPeriod; + foreach ($this->getSocialIssues() as $issue) { + $this->accompanyingPeriod->addSocialIssue($issue); + } + return $this; } @@ -650,14 +681,14 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac return $this; } - public function setUser(UserInterface $user): self + public function setUser(?User $user): self { $this->user = $user; return $this; } - public function setUsers(?Collection $users): self + public function setUsers(Collection $users): self { $this->users = $users; diff --git a/src/Bundle/ChillActivityBundle/Entity/ActivityPresence.php b/src/Bundle/ChillActivityBundle/Entity/ActivityPresence.php index 89f616dc2..b0154e509 100644 --- a/src/Bundle/ChillActivityBundle/Entity/ActivityPresence.php +++ b/src/Bundle/ChillActivityBundle/Entity/ActivityPresence.php @@ -1,14 +1,14 @@ category; } @@ -107,6 +105,11 @@ class ActivityReason return $this->name; } + public function isActiveAndParentActive(): bool + { + return $this->active && null !== $this->getCategory() && $this->getCategory()->getActive(); + } + /** * Set active. * diff --git a/src/Bundle/ChillActivityBundle/Entity/ActivityReasonCategory.php b/src/Bundle/ChillActivityBundle/Entity/ActivityReasonCategory.php index f6d35b520..e8c2e245d 100644 --- a/src/Bundle/ChillActivityBundle/Entity/ActivityReasonCategory.php +++ b/src/Bundle/ChillActivityBundle/Entity/ActivityReasonCategory.php @@ -1,14 +1,14 @@ {$property}; } @@ -516,6 +517,11 @@ class ActivityType return $this->userVisible; } + public function hasCategory(): bool + { + return null !== $this->getCategory(); + } + /** * Is active * return true if the type is active. @@ -533,6 +539,7 @@ class ActivityType throw new InvalidArgumentException('Field "' . $field . '" not found'); } + /** @phpstan-ignore-next-line */ return self::FIELD_REQUIRED === $this->{$property}; } @@ -544,6 +551,7 @@ class ActivityType throw new InvalidArgumentException('Field "' . $field . '" not found'); } + /** @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 37b85ce61..2cf6972c7 100644 --- a/src/Bundle/ChillActivityBundle/Entity/ActivityTypeCategory.php +++ b/src/Bundle/ChillActivityBundle/Entity/ActivityTypeCategory.php @@ -1,14 +1,14 @@ addSelect('(SELECT COUNT(activity.id) FROM ' . Activity::class . ' activity WHERE activity.accompanyingPeriod = acp) AS activity_by_number_aggregator') + ->addGroupBy('activity_by_number_aggregator'); + } + + public function applyOn(): string + { + return Declarations::ACP_TYPE; + } + + public function buildForm(FormBuilderInterface $builder): void + { + // No form needed + } + + public function getLabels($key, array $values, $data) + { + return static function ($value) { + if ('_header' === $value) { + return ''; + } + + if (null === $value) { + return ''; + } + + return $value; + }; + } + + public function getQueryKeys($data): array + { + return ['activity_by_number_aggregator']; + } + + public function getTitle(): string + { + return 'Group acp by activity number'; + } +} diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByCreatorAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByCreatorAggregator.php new file mode 100644 index 000000000..69149737b --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByCreatorAggregator.php @@ -0,0 +1,82 @@ +userRepository = $userRepository; + $this->userRender = $userRender; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + $qb->addSelect('IDENTITY(activity.createdBy) AS creator_aggregator'); + $qb->addGroupBy('creator_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 'Created by'; + } + + if (null === $value || '' === $value) { + return ''; + } + + $u = $this->userRepository->find($value); + + return $this->userRender->renderString($u, []); + }; + } + + public function getQueryKeys($data): array + { + return ['creator_aggregator']; + } + + public function getTitle(): string + { + return 'Group activity by creator'; + } +} diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/BySocialActionAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/BySocialActionAggregator.php new file mode 100644 index 000000000..89732412d --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/BySocialActionAggregator.php @@ -0,0 +1,87 @@ +actionRender = $actionRender; + $this->actionRepository = $actionRepository; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + if (!in_array('actsocialaction', $qb->getAllAliases(), true)) { + $qb->leftJoin('activity.socialActions', 'actsocialaction'); + } + + $qb->addSelect('actsocialaction.id AS socialaction_aggregator'); + $qb->addGroupBy('socialaction_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) { + if ('_header' === $value) { + return 'Social action'; + } + + if (null === $value || '' === $value) { + return ''; + } + + $sa = $this->actionRepository->find($value); + + return $this->actionRender->renderString($sa, []); + }; + } + + public function getQueryKeys($data): array + { + return ['socialaction_aggregator']; + } + + public function getTitle(): string + { + return 'Group activity by linked socialaction'; + } +} diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/BySocialIssueAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/BySocialIssueAggregator.php new file mode 100644 index 000000000..158e87664 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/BySocialIssueAggregator.php @@ -0,0 +1,87 @@ +issueRepository = $issueRepository; + $this->issueRender = $issueRender; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + if (!in_array('actsocialissue', $qb->getAllAliases(), true)) { + $qb->leftJoin('activity.socialIssues', 'actsocialissue'); + } + + $qb->addSelect('actsocialissue.id AS socialissue_aggregator'); + $qb->addGroupBy('socialissue_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 'Social issues'; + } + + if (null === $value || '' === $value) { + return ''; + } + + $i = $this->issueRepository->find($value); + + return $this->issueRender->renderString($i, []); + }; + } + + public function getQueryKeys($data): array + { + return ['socialissue_aggregator']; + } + + public function getTitle(): string + { + return 'Group activity by linked socialissue'; + } +} diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByThirdpartyAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByThirdpartyAggregator.php new file mode 100644 index 000000000..c3ca6d59c --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByThirdpartyAggregator.php @@ -0,0 +1,87 @@ +thirdPartyRepository = $thirdPartyRepository; + $this->thirdPartyRender = $thirdPartyRender; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + if (!in_array('acttparty', $qb->getAllAliases(), true)) { + $qb->leftJoin('activity.thirdParties', 'acttparty'); + } + + $qb->addSelect('acttparty.id AS thirdparty_aggregator'); + $qb->addGroupBy('thirdparty_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 'Accepted thirdparty'; + } + + if (null === $value || '' === $value) { + return ''; + } + + $tp = $this->thirdPartyRepository->find($value); + + return $this->thirdPartyRender->renderString($tp, []); + }; + } + + public function getQueryKeys($data): array + { + return ['thirdparty_aggregator']; + } + + public function getTitle(): string + { + return 'Group activity by linked thirdparties'; + } +} diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/CreatorScopeAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/CreatorScopeAggregator.php new file mode 100644 index 000000000..2c7ec1483 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/CreatorScopeAggregator.php @@ -0,0 +1,89 @@ +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/ACPAggregators/DateAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/DateAggregator.php new file mode 100644 index 000000000..b4b23dc0b --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/DateAggregator.php @@ -0,0 +1,125 @@ + 'month', + 'by week' => 'week', + 'by year' => 'year', + ]; + + private const DEFAULT_CHOICE = 'year'; + + private TranslatorInterface $translator; + + public function __construct( + TranslatorInterface $translator + ) { + $this->translator = $translator; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + $order = null; + + switch ($data['frequency']) { + case 'month': + $fmt = 'YYYY-MM'; + + break; + + case 'week': + $fmt = 'YYYY-IW'; + + break; + + case 'year': + $fmt = 'YYYY'; + $order = 'DESC'; + + break; // order DESC does not works ! + + default: + throw new RuntimeException(sprintf("The frequency data '%s' is invalid.", $data['frequency'])); + } + + $qb->addSelect(sprintf("TO_CHAR(activity.date, '%s') AS date_aggregator", $fmt)); + $qb->addGroupBy('date_aggregator'); + $qb->addOrderBy('date_aggregator', $order); + } + + public function applyOn(): string + { + return Declarations::ACTIVITY_ACP; + } + + public function buildForm(FormBuilderInterface $builder) + { + $builder->add('frequency', ChoiceType::class, [ + 'choices' => self::CHOICES, + 'multiple' => false, + 'expanded' => true, + 'empty_data' => self::DEFAULT_CHOICE, + 'data' => self::DEFAULT_CHOICE, + ]); + } + + public function getLabels($key, array $values, $data) + { + return static function ($value) use ($data): string { + if ('_header' === $value) { + 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; + } + }; + } + + public function getQueryKeys($data): array + { + return ['date_aggregator']; + } + + public function getTitle(): string + { + return 'Group activity by date'; + } +} diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/LocationTypeAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/LocationTypeAggregator.php new file mode 100644 index 000000000..ec4ce6315 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/LocationTypeAggregator.php @@ -0,0 +1,91 @@ +locationTypeRepository = $locationTypeRepository; + $this->translatableStringHelper = $translatableStringHelper; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + if (!in_array('actloc', $qb->getAllAliases(), true)) { + $qb->leftJoin('activity.location', 'actloc'); + } + + $qb->addSelect('IDENTITY(actloc.locationType) AS locationtype_aggregator'); + $qb->addGroupBy('locationtype_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 'Accepted locationtype'; + } + + if (null === $value || '' === $value) { + return ''; + } + + if (null === $lt = $this->locationTypeRepository->find($value)) { + return ''; + } + + return $this->translatableStringHelper->localize( + $lt->getTitle() + ); + }; + } + + public function getQueryKeys($data): array + { + return ['locationtype_aggregator']; + } + + public function getTitle(): string + { + return 'Group activity by locationtype'; + } +} diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityTypeAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityTypeAggregator.php index 83b5f71e2..7cd16718e 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityTypeAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityTypeAggregator.php @@ -1,59 +1,59 @@ activityTypeRepository = $activityTypeRepository; $this->translatableStringHelper = $translatableStringHelper; } - public function addRole() + public function addRole(): ?string { - return new Role(ActivityStatsVoter::STATS); + return null; } public function alterQuery(QueryBuilder $qb, $data) { - // add select element - $qb->addSelect(sprintf('IDENTITY(activity.type) AS %s', self::KEY)); + if (!in_array('acttype', $qb->getAllAliases(), true)) { + $qb->leftJoin('activity.activityType', 'acttype'); + } - // add the "group by" part + $qb->addSelect(sprintf('IDENTITY(activity.activityType) AS %s', self::KEY)); $qb->addGroupBy(self::KEY); } - public function applyOn() + public function applyOn(): string { - return 'activity'; + return Declarations::ACTIVITY; } public function buildForm(FormBuilderInterface $builder) @@ -71,6 +71,10 @@ class ActivityTypeAggregator implements AggregatorInterface return 'Activity type'; } + if (null === $value || '' === $value) { + return ''; + } + $t = $this->activityTypeRepository->find($value); return $this->translatableStringHelper->localize($t->getName()); @@ -86,23 +90,4 @@ class ActivityTypeAggregator implements AggregatorInterface { return 'Aggregate by activity type'; } - - /** - * Check if a join between Activity and another alias. - * - * @param Join[] $joins - * @param string $alias the alias to search for - * - * @return bool - */ - private function checkJoinAlreadyDefined(array $joins, $alias) - { - foreach ($joins as $join) { - if ($join->getAlias() === $alias) { - return true; - } - } - - return false; - } } diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUserAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUserAggregator.php index 312456104..9bde692c6 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUserAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUserAggregator.php @@ -1,39 +1,43 @@ userRepository = $userRepository; + $this->userRender = $userRender; } - public function addRole() + public function addRole(): ?string { - return new Role(ActivityStatsVoter::STATS); + return null; } public function alterQuery(QueryBuilder $qb, $data) @@ -47,7 +51,7 @@ class ActivityUserAggregator implements AggregatorInterface public function applyOn(): string { - return 'activity'; + return Declarations::ACTIVITY; } public function buildForm(FormBuilderInterface $builder) @@ -57,15 +61,18 @@ class ActivityUserAggregator implements AggregatorInterface public function getLabels($key, $values, $data): Closure { - // preload users at once - $this->userRepository->findBy(['id' => $values]); - return function ($value) { if ('_header' === $value) { - return 'activity user'; + return 'Activity user'; } - return $this->userRepository->find($value)->getUsername(); + if (null === $value || '' === $value) { + return ''; + } + + $u = $this->userRepository->find($value); + + return $this->userRender->renderString($u, []); }; } diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersAggregator.php new file mode 100644 index 000000000..139f2743e --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersAggregator.php @@ -0,0 +1,86 @@ +userRepository = $userRepository; + $this->userRender = $userRender; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + if (!in_array('actusers', $qb->getAllAliases(), true)) { + $qb->leftJoin('activity.users', 'actusers'); + } + + $qb + ->addSelect('actusers.id AS activity_users_aggregator') + ->addGroupBy('activity_users_aggregator'); + } + + public function applyOn(): string + { + return Declarations::ACTIVITY; + } + + public function buildForm(FormBuilderInterface $builder) + { + // nothing to add on the form + } + + public function getLabels($key, array $values, $data) + { + return function ($value) { + if ('_header' === $value) { + return 'Activity users'; + } + + if (null === $value || '' === $value) { + return ''; + } + + $u = $this->userRepository->find($value); + + return $this->userRender->renderString($u, []); + }; + } + + public function getQueryKeys($data) + { + return ['activity_users_aggregator']; + } + + public function getTitle() + { + return 'Aggregate by activity users'; + } +} diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersJobAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersJobAggregator.php new file mode 100644 index 000000000..5741a0e58 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersJobAggregator.php @@ -0,0 +1,87 @@ +userJobRepository = $userJobRepository; + $this->translatableStringHelper = $translatableStringHelper; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + if (!in_array('actusers', $qb->getAllAliases(), true)) { + $qb->leftJoin('activity.users', 'actusers'); + } + + $qb + ->addSelect('IDENTITY(actusers.userJob) AS activity_users_job_aggregator') + ->addGroupBy('activity_users_job_aggregator'); + } + + public function applyOn() + { + return Declarations::ACTIVITY; + } + + public function buildForm(FormBuilderInterface $builder) + { + // nothing to add in the form + } + + public function getLabels($key, array $values, $data) + { + return function ($value): string { + if ('_header' === $value) { + return 'Users \'s job'; + } + + if (null === $value || '' === $value) { + return ''; + } + + $j = $this->userJobRepository->find($value); + + return $this->translatableStringHelper->localize( + $j->getLabel() + ); + }; + } + + public function getQueryKeys($data): array + { + return ['activity_users_job_aggregator']; + } + + public function getTitle() + { + return 'Aggregate by users job'; + } +} diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersScopeAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersScopeAggregator.php new file mode 100644 index 000000000..15da300be --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersScopeAggregator.php @@ -0,0 +1,87 @@ +scopeRepository = $scopeRepository; + $this->translatableStringHelper = $translatableStringHelper; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + if (!in_array('actusers', $qb->getAllAliases(), true)) { + $qb->leftJoin('activity.users', 'actusers'); + } + + $qb + ->addSelect('IDENTITY(actusers.mainScope) AS activity_users_main_scope_aggregator') + ->addGroupBy('activity_users_main_scope_aggregator'); + } + + public function applyOn() + { + return Declarations::ACTIVITY; + } + + public function buildForm(FormBuilderInterface $builder) + { + // nothing to add in the form + } + + public function getLabels($key, array $values, $data) + { + return function ($value): string { + if ('_header' === $value) { + return 'Users \'s scope'; + } + + if (null === $value || '' === $value) { + return ''; + } + + $s = $this->scopeRepository->find($value); + + return $this->translatableStringHelper->localize( + $s->getName() + ); + }; + } + + public function getQueryKeys($data): array + { + return ['activity_users_main_scope_aggregator']; + } + + public function getTitle() + { + return 'Aggregate by users scope'; + } +} diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityReasonAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/PersonAggregators/ActivityReasonAggregator.php similarity index 77% rename from src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityReasonAggregator.php rename to src/Bundle/ChillActivityBundle/Export/Aggregator/PersonAggregators/ActivityReasonAggregator.php index 19811f584..eaccf95cb 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityReasonAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/PersonAggregators/ActivityReasonAggregator.php @@ -1,19 +1,19 @@ translatableStringHelper = $translatableStringHelper; } - public function addRole() + public function addRole(): ?string { - return new Role(ActivityStatsVoter::STATS); + return null; } public function alterQuery(QueryBuilder $qb, $data) { // add select element if ('reasons' === $data['level']) { - $elem = 'reasons.id'; + $elem = 'actreasons.id'; $alias = 'activity_reasons_id'; } elseif ('categories' === $data['level']) { - $elem = 'category.id'; + $elem = 'actreasoncat.id'; $alias = 'activity_categories_id'; } else { throw new RuntimeException('The data provided are not recognized.'); @@ -68,29 +67,15 @@ class ActivityReasonAggregator implements AggregatorInterface, ExportElementVali $qb->addSelect($elem . ' as ' . $alias); // make a jointure only if needed - $join = $qb->getDQLPart('join'); - - if ( - ( - array_key_exists('activity', $join) - && !$this->checkJoinAlreadyDefined($join['activity'], 'reasons') - ) - || (!array_key_exists('activity', $join)) - ) { - $qb->add( - 'join', - [ - 'activity' => new Join(Join::INNER_JOIN, 'activity.reasons', 'reasons'), - ], - 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 (!$this->checkJoinAlreadyDefined($qb->getDQLPart('join')['activity'], 'category')) { - $qb->join('reasons.category', 'category'); + if (!in_array('actreasoncat', $qb->getAllAliases(), true)) { + $qb->join('actreasons.category', 'actreasoncat'); } } @@ -104,9 +89,9 @@ class ActivityReasonAggregator implements AggregatorInterface, ExportElementVali } } - public function applyOn() + public function applyOn(): string { - return 'activity'; + return Declarations::ACTIVITY_PERSON; } public function buildForm(FormBuilderInterface $builder) @@ -149,6 +134,10 @@ class ActivityReasonAggregator implements AggregatorInterface, ExportElementVali return 'reasons' === $data['level'] ? 'Group by reasons' : 'Group by categories of reason'; } + if (null === $value || '' === $value) { + return ''; + } + switch ($data['level']) { case 'reasons': $r = $this->activityReasonRepository->find($value); @@ -194,23 +183,4 @@ class ActivityReasonAggregator implements AggregatorInterface, ExportElementVali ->addViolation(); } } - - /** - * Check if a join between Activity and another alias. - * - * @param Join[] $joins - * @param string $alias the alias to search for - * - * @return bool - */ - private function checkJoinAlreadyDefined(array $joins, $alias) - { - foreach ($joins as $join) { - if ($join->getAlias() === $alias) { - return true; - } - } - - return false; - } } diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/SentReceivedAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/SentReceivedAggregator.php new file mode 100644 index 000000000..5f772e156 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/SentReceivedAggregator.php @@ -0,0 +1,84 @@ +translator = $translator; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data): void + { + $qb->addSelect('activity.sentReceived AS activity_sentreceived_aggregator') + ->addGroupBy('activity_sentreceived_aggregator'); + } + + public function applyOn(): string + { + return Declarations::ACTIVITY; + } + + public function buildForm(FormBuilderInterface $builder): void + { + // No form needed + } + + public function getLabels($key, array $values, $data): callable + { + return function (?string $value): string { + if ('_header' === $value) { + return 'export.aggregator.activity.by_sent_received.Sent or received'; + } + + switch ($value) { + case null: + case '': + return ''; + + case 'sent': + return $this->translator->trans('export.aggregator.activity.by_sent_received.is sent'); + + case 'received': + return $this->translator->trans('export.aggregator.activity.by_sent_received.is received'); + + default: + throw new LogicException(sprintf('The value %s is not valid', $value)); + } + }; + } + + public function getQueryKeys($data): array + { + return ['activity_sentreceived_aggregator']; + } + + public function getTitle(): string + { + return 'export.aggregator.activity.by_sent_received.Group activity by sentreceived'; + } +} diff --git a/src/Bundle/ChillActivityBundle/Export/Declarations.php b/src/Bundle/ChillActivityBundle/Export/Declarations.php new file mode 100644 index 000000000..79afb09c8 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Export/Declarations.php @@ -0,0 +1,24 @@ +repository = $em->getRepository(Activity::class); + } + + public function buildForm(FormBuilderInterface $builder) + { + } + + public function getAllowedFormattersTypes(): array + { + return [FormatterInterface::TYPE_TABULAR]; + } + + public function getDescription(): string + { + return 'Average activities linked to an accompanying period duration by various parameters.'; + } + + public function getGroup(): string + { + return 'Exports of activities linked to an accompanying period'; + } + + 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"); + } + + return static fn ($value) => '_header' === $value ? 'Average activities linked to an accompanying period duration' : $value; + } + + public function getQueryKeys($data): array + { + return ['export_avg_activity_duration']; + } + + public function getResult($query, $data) + { + return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); + } + + public function getTitle(): string + { + return 'Average activity linked to an accompanying period duration'; + } + + public function getType(): string + { + return Declarations::ACTIVITY; + } + + public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + { + $centers = array_map(static fn ($el) => $el['center'], $acl); + + $qb = $this->repository->createQueryBuilder('activity'); + + $qb + ->join('activity.accompanyingPeriod', 'acp') + ->select('AVG(activity.durationTime) as export_avg_activity_duration') + ->andWhere($qb->expr()->isNotNull('activity.durationTime')); + + $qb + ->andWhere( + $qb->expr()->exists( + 'SELECT 1 FROM ' . AccompanyingPeriodParticipation::class . ' acl_count_part + JOIN ' . PersonCenterHistory::class . ' acl_count_person_history WITH IDENTITY(acl_count_person_history.person) = IDENTITY(acl_count_part.person) + WHERE acl_count_part.accompanyingPeriod = acp.id AND acl_count_person_history.center IN (:authorized_centers) + ' + ) + ) + ->setParameter('authorized_centers', $centers); + + return $qb; + } + + public function requiredRole(): string + { + return ActivityStatsVoter::STATS; + } + + public function supportsModifiers(): array + { + return [ + Declarations::ACTIVITY, + Declarations::ACTIVITY_ACP, + PersonDeclarations::ACP_TYPE, + ]; + } +} diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/AvgActivityVisitDuration.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/AvgActivityVisitDuration.php new file mode 100644 index 000000000..df21362cd --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/AvgActivityVisitDuration.php @@ -0,0 +1,126 @@ +repository = $em->getRepository(Activity::class); + } + + public function buildForm(FormBuilderInterface $builder) + { + // TODO: Implement buildForm() method. + } + + public function getAllowedFormattersTypes(): array + { + return [FormatterInterface::TYPE_TABULAR]; + } + + public function getDescription(): string + { + return 'Average activities linked to an accompanying period visit duration by various parameters.'; + } + + public function getGroup(): string + { + return 'Exports of activities linked to an accompanying period'; + } + + 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"); + } + + return static fn ($value) => '_header' === $value ? 'Average activities linked to an accompanying period visit duration' : $value; + } + + public function getQueryKeys($data): array + { + return ['export_avg_activity_visit_duration']; + } + + public function getResult($query, $data) + { + return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); + } + + public function getTitle(): string + { + return 'Average activity linked to an accompanying period visit duration'; + } + + public function getType(): string + { + return Declarations::ACTIVITY; + } + + public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + { + $centers = array_map(static fn ($el) => $el['center'], $acl); + + $qb = $this->repository->createQueryBuilder('activity'); + + $qb + ->join('activity.accompanyingPeriod', 'acp') + ->select('AVG(activity.travelTime) as export_avg_activity_visit_duration') + ->andWhere($qb->expr()->isNotNull('activity.travelTime')); + + $qb + ->andWhere( + $qb->expr()->exists( + 'SELECT 1 FROM ' . AccompanyingPeriodParticipation::class . ' acl_count_part + JOIN ' . PersonCenterHistory::class . ' acl_count_person_history WITH IDENTITY(acl_count_person_history.person) = IDENTITY(acl_count_part.person) + WHERE acl_count_part.accompanyingPeriod = acp.id AND acl_count_person_history.center IN (:authorized_centers) + ' + ) + ) + ->setParameter('authorized_centers', $centers); + + return $qb; + } + + public function requiredRole(): string + { + return ActivityStatsVoter::STATS; + } + + public function supportsModifiers(): array + { + return [ + Declarations::ACTIVITY, + Declarations::ACTIVITY_ACP, + PersonDeclarations::ACP_TYPE, + ]; + } +} diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/CountActivity.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/CountActivity.php new file mode 100644 index 000000000..6b7b1562d --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/CountActivity.php @@ -0,0 +1,124 @@ +repository = $em->getRepository(Activity::class); + } + + public function buildForm(FormBuilderInterface $builder) + { + } + + public function getAllowedFormattersTypes(): array + { + return [FormatterInterface::TYPE_TABULAR]; + } + + public function getDescription(): string + { + return 'Count activities linked to an accompanying period by various parameters.'; + } + + public function getGroup(): string + { + return 'Exports of activities linked to an accompanying period'; + } + + public function getLabels($key, array $values, $data) + { + if ('export_count_activity' !== $key) { + throw new LogicException("the key {$key} is not used by this export"); + } + + return static fn ($value) => '_header' === $value ? 'Number of activities linked to an accompanying period' : $value; + } + + public function getQueryKeys($data): array + { + return ['export_count_activity']; + } + + public function getResult($query, $data) + { + return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); + } + + public function getTitle(): string + { + return 'Count activities linked to an accompanying period'; + } + + public function getType(): string + { + return Declarations::ACTIVITY; + } + + public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + { + $centers = array_map(static fn ($el) => $el['center'], $acl); + + $qb = $this->repository + ->createQueryBuilder('activity') + ->join('activity.accompanyingPeriod', 'acp'); + + $qb + ->andWhere( + $qb->expr()->exists( + 'SELECT 1 FROM ' . AccompanyingPeriodParticipation::class . ' acl_count_part + JOIN ' . PersonCenterHistory::class . ' acl_count_person_history WITH IDENTITY(acl_count_person_history.person) = IDENTITY(acl_count_part.person) + WHERE acl_count_part.accompanyingPeriod = acp.id AND acl_count_person_history.center IN (:authorized_centers) + ' + ) + ) + ->setParameter('authorized_centers', $centers); + + $qb->select('COUNT(DISTINCT activity.id) as export_count_activity'); + + return $qb; + } + + public function requiredRole(): string + { + return ActivityStatsVoter::STATS; + } + + public function supportsModifiers(): array + { + return [ + Declarations::ACTIVITY, + Declarations::ACTIVITY_ACP, + PersonDeclarations::ACP_TYPE, + ]; + } +} diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/ListActivity.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/ListActivity.php new file mode 100644 index 000000000..026e16f90 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/ListActivity.php @@ -0,0 +1,163 @@ +helper = $helper; + $this->entityManager = $entityManager; + $this->translatableStringExportLabelHelper = $translatableStringExportLabelHelper; + } + + public function buildForm(FormBuilderInterface $builder) + { + $this->helper->buildForm($builder); + } + + public function getAllowedFormattersTypes() + { + return $this->helper->getAllowedFormattersTypes(); + } + + public function getDescription() + { + return ListActivityHelper::MSG_KEY . 'List activities linked to an accompanying course'; + } + + public function getGroup(): string + { + return 'Exports of activities linked to an accompanying period'; + } + + 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 $value ?? ''; + }; + + case 'scopesNames': + return $this->translatableStringExportLabelHelper->getLabelMulti($key, $values, ListActivityHelper::MSG_KEY . 'course circles'); + + default: + return $this->helper->getLabels($key, $values, $data); + } + } + + public function getQueryKeys($data) + { + return + array_merge( + $this->helper->getQueryKeys($data), + [ + 'acpId', + 'scopesNames', + ] + ); + } + + public function getResult($query, $data) + { + return $this->helper->getResult($query, $data); + } + + public function getTitle() + { + return ListActivityHelper::MSG_KEY . 'List activity linked to a course'; + } + + public function getType() + { + return $this->helper->getType(); + } + + public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + { + $centers = array_map(static fn ($el) => $el['center'], $acl); + + $qb = $this->entityManager->createQueryBuilder(); + + $qb + ->distinct() + ->from(Activity::class, 'activity') + ->join('activity.accompanyingPeriod', 'acp') + ->leftJoin('acp.participations', 'acppart') + ->leftJoin('acppart.person', 'person') + ->andWhere('acppart.startDate != acppart.endDate OR acppart.endDate IS NULL') + ->andWhere( + $qb->expr()->exists( + 'SELECT 1 + FROM ' . PersonCenterHistory::class . ' acl_count_person_history + WHERE acl_count_person_history.person = person + AND acl_count_person_history.center IN (:authorized_centers) + ' + ) + ) + // some grouping are necessary + ->addGroupBy('acp.id') + ->addOrderBy('activity.date') + ->addOrderBy('activity.id') + ->setParameter('authorized_centers', $centers); + + $this->helper->addSelect($qb); + + // add select for this step + $qb + ->addSelect('acp.id AS acpId') + ->addSelect('(SELECT AGGREGATE(acpScope.name) FROM ' . Scope::class . ' acpScope WHERE acpScope MEMBER OF acp.scopes) AS scopesNames') + ->addGroupBy('scopesNames'); + + return $qb; + } + + public function requiredRole(): string + { + return ActivityStatsVoter::LISTS; + } + + public function supportsModifiers() + { + return array_merge( + $this->helper->supportsModifiers(), + [ + \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 new file mode 100644 index 000000000..e916cab54 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/SumActivityDuration.php @@ -0,0 +1,126 @@ +repository = $em->getRepository(Activity::class); + } + + public function buildForm(FormBuilderInterface $builder) + { + // TODO: Implement buildForm() method. + } + + public function getAllowedFormattersTypes(): array + { + return [FormatterInterface::TYPE_TABULAR]; + } + + public function getDescription(): string + { + return 'Sum activities linked to an accompanying period duration by various parameters.'; + } + + public function getGroup(): string + { + return 'Exports of activities linked to an accompanying period'; + } + + 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"); + } + + return static fn ($value) => '_header' === $value ? 'Sum activities linked to an accompanying period duration' : $value; + } + + public function getQueryKeys($data): array + { + return ['export_sum_activity_duration']; + } + + public function getResult($query, $data) + { + return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); + } + + public function getTitle(): string + { + return 'Sum activity linked to an accompanying period duration'; + } + + public function getType(): string + { + return Declarations::ACTIVITY; + } + + public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + { + $centers = array_map(static fn ($el) => $el['center'], $acl); + + $qb = $this->repository + ->createQueryBuilder('activity') + ->join('activity.accompanyingPeriod', 'acp'); + + $qb->select('SUM(activity.durationTime) as export_sum_activity_duration') + ->andWhere($qb->expr()->isNotNull('activity.durationTime')); + + $qb + ->andWhere( + $qb->expr()->exists( + 'SELECT 1 FROM ' . AccompanyingPeriodParticipation::class . ' acl_count_part + JOIN ' . PersonCenterHistory::class . ' acl_count_person_history WITH IDENTITY(acl_count_person_history.person) = IDENTITY(acl_count_part.person) + WHERE acl_count_part.accompanyingPeriod = acp.id AND acl_count_person_history.center IN (:authorized_centers) + ' + ) + ) + ->setParameter('authorized_centers', $centers); + + return $qb; + } + + public function requiredRole(): string + { + return ActivityStatsVoter::STATS; + } + + public function supportsModifiers(): array + { + return [ + Declarations::ACTIVITY, + Declarations::ACTIVITY_ACP, + PersonDeclarations::ACP_TYPE, + ]; + } +} diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/SumActivityVisitDuration.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/SumActivityVisitDuration.php new file mode 100644 index 000000000..18a47289c --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/SumActivityVisitDuration.php @@ -0,0 +1,126 @@ +repository = $em->getRepository(Activity::class); + } + + public function buildForm(FormBuilderInterface $builder) + { + // TODO: Implement buildForm() method. + } + + public function getAllowedFormattersTypes(): array + { + return [FormatterInterface::TYPE_TABULAR]; + } + + public function getDescription(): string + { + return 'Sum activities linked to an accompanying period visit duration by various parameters.'; + } + + public function getGroup(): string + { + return 'Exports of activities linked to an accompanying period'; + } + + 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"); + } + + return static fn ($value) => '_header' === $value ? 'Sum activities linked to an accompanying period visit duration' : $value; + } + + public function getQueryKeys($data): array + { + return ['export_sum_activity_visit_duration']; + } + + public function getResult($query, $data) + { + return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); + } + + public function getTitle(): string + { + return 'Sum activity linked to an accompanying period visit duration'; + } + + public function getType(): string + { + return Declarations::ACTIVITY; + } + + public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + { + $centers = array_map(static fn ($el) => $el['center'], $acl); + + $qb = $this->repository + ->createQueryBuilder('activity') + ->join('activity.accompanyingPeriod', 'acp'); + + $qb->select('SUM(activity.travelTime) as export_sum_activity_visit_duration') + ->andWhere($qb->expr()->isNotNull('activity.travelTime')); + + $qb + ->andWhere( + $qb->expr()->exists( + 'SELECT 1 FROM ' . AccompanyingPeriodParticipation::class . ' acl_count_part + JOIN ' . PersonCenterHistory::class . ' acl_count_person_history WITH IDENTITY(acl_count_person_history.person) = IDENTITY(acl_count_part.person) + WHERE acl_count_part.accompanyingPeriod = acp.id AND acl_count_person_history.center IN (:authorized_centers) + ' + ) + ) + ->setParameter('authorized_centers', $centers); + + return $qb; + } + + public function requiredRole(): string + { + return ActivityStatsVoter::STATS; + } + + public function supportsModifiers(): array + { + return [ + Declarations::ACTIVITY, + Declarations::ACTIVITY_ACP, + PersonDeclarations::ACP_TYPE, + ]; + } +} diff --git a/src/Bundle/ChillActivityBundle/Export/Export/CountActivity.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/CountActivity.php similarity index 51% rename from src/Bundle/ChillActivityBundle/Export/Export/CountActivity.php rename to src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/CountActivity.php index 3824b059f..4246df173 100644 --- a/src/Bundle/ChillActivityBundle/Export/Export/CountActivity.php +++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/CountActivity.php @@ -1,26 +1,28 @@ '_header' === $value ? 'Number of activities' : $value; + return static fn ($value) => '_header' === $value ? 'Number of activities linked to a person' : $value; } public function getQueryKeys($data) @@ -58,45 +65,59 @@ class CountActivity implements ExportInterface return ['export_count_activity']; } - public function getResult($qb, $data) + public function getResult($query, $data) { - return $qb->getQuery()->getResult(Query::HYDRATE_SCALAR); + return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } public function getTitle() { - return 'Count activities'; + return 'Count activities linked to a person'; } - public function getType() + public function getType(): string { - return 'activity'; + return Declarations::ACTIVITY; } public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) { $centers = array_map(static fn ($el) => $el['center'], $acl); - $qb = $this - ->activityRepository + $qb = $this->activityRepository ->createQueryBuilder('activity') - ->select('COUNT(activity.id) as export_count_activity') - ->join('activity.person', 'person'); + ->join('activity.person', 'person') + ->join('person.centerHistory', 'centerHistory'); + + $qb->select('COUNT(activity.id) as export_count_activity'); $qb - ->where($qb->expr()->in('person.center', ':centers')) + ->where( + $qb->expr()->andX( + $qb->expr()->lte('centerHistory.startDate', 'activity.date'), + $qb->expr()->orX( + $qb->expr()->isNull('centerHistory.endDate'), + $qb->expr()->gt('centerHistory.endDate', 'activity.date') + ) + ) + ) + ->andWhere($qb->expr()->in('centerHistory.center', ':centers')) ->setParameter('centers', $centers); return $qb; } - public function requiredRole() + public function requiredRole(): string { - return new Role(ActivityStatsVoter::STATS); + return ActivityStatsVoter::STATS; } public function supportsModifiers() { - return ['person', 'activity']; + return [ + Declarations::ACTIVITY, + Declarations::ACTIVITY_PERSON, + PersonDeclarations::PERSON_TYPE, + ]; } } diff --git a/src/Bundle/ChillActivityBundle/Export/Export/ListActivity.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/ListActivity.php similarity index 72% rename from src/Bundle/ChillActivityBundle/Export/Export/ListActivity.php rename to src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/ListActivity.php index a99decc6c..60110c9a8 100644 --- a/src/Bundle/ChillActivityBundle/Export/Export/ListActivity.php +++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/ListActivity.php @@ -1,29 +1,31 @@ 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': @@ -145,7 +150,7 @@ class ListActivity implements ListInterface return 'circle'; } - return $this->translatableStringHelper->localize(json_decode($value, true)); + return $this->translatableStringHelper->localize(json_decode($value, true, 512, JSON_THROW_ON_ERROR)); }; case 'type_name': @@ -154,7 +159,7 @@ class ListActivity implements ListInterface return 'activity type'; } - return $this->translatableStringHelper->localize(json_decode($value, true)); + return $this->translatableStringHelper->localize(json_decode($value, true, 512, JSON_THROW_ON_ERROR)); }; default: @@ -180,19 +185,17 @@ class ListActivity implements ListInterface public function getTitle() { - return 'List activities'; + return 'List activity linked to a person'; } - public function getType() + public function getType(): string { - return 'activity'; + return Declarations::ACTIVITY; } 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)) { @@ -203,10 +206,20 @@ class ListActivity implements ListInterface $qb ->from('ChillActivityBundle:Activity', 'activity') - ->join('activity.person', 'person') - ->join('person.center', 'center') - ->andWhere('center IN (:authorized_centers)') - ->setParameter('authorized_centers', $centers); + ->join('activity.person', 'actperson') + ->join('actperson.centerHistory', 'centerHistory'); + + $qb->where( + $qb->expr()->andX( + $qb->expr()->lte('centerHistory.startDate', 'activity.date'), + $qb->expr()->orX( + $qb->expr()->isNull('centerHistory.endDate'), + $qb->expr()->gt('centerHistory.endDate', 'activity.date') + ) + ) + ) + ->andWhere($qb->expr()->in('centerHistory.center', ':centers')) + ->setParameter('centers', $centers); foreach ($this->fields as $f) { if (in_array($f, $data['fields'], true)) { @@ -217,23 +230,23 @@ class ListActivity implements ListInterface break; case 'person_firstname': - $qb->addSelect('person.firstName AS person_firstname'); + $qb->addSelect('actperson.firstName AS person_firstname'); break; case 'person_lastname': - $qb->addSelect('person.lastName AS person_lastname'); + $qb->addSelect('actperson.lastName AS person_lastname'); break; case 'person_id': - $qb->addSelect('person.id AS person_id'); + $qb->addSelect('actperson.id AS person_id'); break; case 'user_username': - $qb->join('activity.user', 'user'); - $qb->addSelect('user.username AS user_username'); + $qb->join('activity.user', 'actuser'); + $qb->addSelect('actuser.username AS user_username'); break; @@ -244,7 +257,7 @@ class ListActivity implements ListInterface break; case 'type_name': - $qb->join('activity.type', 'type'); + $qb->join('activity.activityType', 'type'); $qb->addSelect('type.name AS type_name'); break; @@ -256,6 +269,11 @@ class ListActivity implements ListInterface break; + case 'attendee': + $qb->addSelect('IDENTITY(activity.attendee) AS attendee'); + + break; + default: $qb->addSelect(sprintf('activity.%s as %s', $f, $f)); @@ -267,13 +285,17 @@ class ListActivity implements ListInterface return $qb; } - public function requiredRole() + public function requiredRole(): string { - return new Role(ActivityStatsVoter::LISTS); + return ActivityStatsVoter::LISTS; } public function supportsModifiers() { - return ['activity', 'person']; + return [ + Declarations::ACTIVITY, + Declarations::ACTIVITY_PERSON, + //PersonDeclarations::PERSON_TYPE, + ]; } } diff --git a/src/Bundle/ChillActivityBundle/Export/Export/StatActivityDuration.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/StatActivityDuration.php similarity index 54% rename from src/Bundle/ChillActivityBundle/Export/Export/StatActivityDuration.php rename to src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/StatActivityDuration.php index 3e6122621..050034954 100644 --- a/src/Bundle/ChillActivityBundle/Export/Export/StatActivityDuration.php +++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/StatActivityDuration.php @@ -1,31 +1,34 @@ action) { - return 'Sum activities duration by various parameters.'; + return 'Sum activities linked to a person duration by various parameters.'; } + + throw new LogicException('this action is not supported: ' . $this->action); + } + + public function getGroup(): string + { + return 'Exports of activities linked to a person'; } public function getLabels($key, array $values, $data) @@ -69,7 +79,7 @@ class StatActivityDuration implements ExportInterface throw new LogicException(sprintf('The key %s is not used by this export', $key)); } - $header = self::SUM === $this->action ? 'Sum of activities duration' : false; + $header = self::SUM === $this->action ? 'Sum activities linked to a person duration' : false; return static fn (string $value) => '_header' === $value ? $header : $value; } @@ -79,27 +89,29 @@ class StatActivityDuration implements ExportInterface return ['export_stat_activity']; } - public function getResult($qb, $data) + public function getResult($query, $data) { - return $qb->getQuery()->getResult(Query::HYDRATE_SCALAR); + return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } public function getTitle() { if (self::SUM === $this->action) { - return 'Sum activity duration'; + return 'Sum activity linked to a person duration'; } + + throw new LogicException('This action is not supported: ' . $this->action); } - public function getType() + public function getType(): string { - return 'activity'; + return Declarations::ACTIVITY; } public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) { $centers = array_map( - static fn (array $el): string => $el['center'], + static fn (array $el): Center => $el['center'], $acl ); @@ -111,20 +123,37 @@ class StatActivityDuration implements ExportInterface $select = 'SUM(activity.durationTime) AS export_stat_activity'; } - return $qb->select($select) + $qb->select($select) ->join('activity.person', 'person') - ->join('person.center', 'center') - ->where($qb->expr()->in('center', ':centers')) - ->setParameter(':centers', $centers); + ->join('person.centerHistory', 'centerHistory'); + + $qb + ->where( + $qb->expr()->andX( + $qb->expr()->lte('centerHistory.startDate', 'activity.date'), + $qb->expr()->orX( + $qb->expr()->isNull('centerHistory.endDate'), + $qb->expr()->gt('centerHistory.endDate', 'activity.date') + ) + ) + ) + ->andWhere($qb->expr()->in('centerHistory.center', ':centers')) + ->setParameter('centers', $centers); + + return $qb; } - public function requiredRole() + public function requiredRole(): string { - return new Role(ActivityStatsVoter::STATS); + return ActivityStatsVoter::STATS; } public function supportsModifiers() { - return ['person', 'activity']; + return [ + Declarations::ACTIVITY, + Declarations::ACTIVITY_PERSON, + //PersonDeclarations::PERSON_TYPE, + ]; } } diff --git a/src/Bundle/ChillActivityBundle/Export/Export/ListActivityHelper.php b/src/Bundle/ChillActivityBundle/Export/Export/ListActivityHelper.php new file mode 100644 index 000000000..fae6ea6a6 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Export/Export/ListActivityHelper.php @@ -0,0 +1,269 @@ +activityPresenceRepository = $activityPresenceRepository; + $this->activityTypeRepository = $activityTypeRepository; + $this->dateTimeHelper = $dateTimeHelper; + $this->labelPersonHelper = $labelPersonHelper; + $this->labelThirdPartyHelper = $labelThirdPartyHelper; + $this->translator = $translator; + $this->translatableStringHelper = $translatableStringHelper; + $this->translatableStringLabelHelper = $translatableStringLabelHelper; + $this->userHelper = $userHelper; + } + + public function addSelect(QueryBuilder $qb): void + { + $qb + ->addSelect('activity.id AS id') + ->addSelect('activity.date') + ->addSelect('IDENTITY(activity.activityType) AS typeName') + ->leftJoin('activity.reasons', 'reasons') + ->addSelect('AGGREGATE(reasons.name) AS listReasons') + ->leftJoin('activity.persons', 'actPerson') + ->addSelect('AGGREGATE(actPerson.id) AS personsIds') + ->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') + ->leftJoin('activity.thirdParties', 'thirdparty') + ->addSelect('AGGREGATE(thirdparty.id) AS thirdPartiesIds') + ->addSelect('AGGREGATE(thirdparty.id) AS thirdPartiesNames') + ->addSelect('IDENTITY(activity.attendee) AS attendeeName') + ->addSelect('activity.durationTime') + ->addSelect('activity.travelTime') + ->addSelect('activity.emergency') + ->leftJoin('activity.location', 'location') + ->addSelect('location.name AS locationName') + ->addSelect('activity.sentReceived') + ->addSelect('IDENTITY(activity.createdBy) AS createdBy') + ->addSelect('activity.createdAt') + ->addSelect('IDENTITY(activity.updatedBy) AS updatedBy') + ->addSelect('activity.updatedAt') + ->addGroupBy('activity.id') + ->addGroupBy('location.id'); + } + + public function buildForm(FormBuilderInterface $builder) + { + } + + public function getAllowedFormattersTypes() + { + return [FormatterInterface::TYPE_LIST]; + } + + public function getLabels($key, array $values, $data) + { + switch ($key) { + case 'createdAt': + case 'updatedAt': + return $this->dateTimeHelper->getLabel($key); + + case 'createdBy': + case 'updatedBy': + return $this->userHelper->getLabel($key, $values, $key); + + case 'date': + return $this->dateTimeHelper->getLabel(self::MSG_KEY . $key); + + case 'attendeeName': + return function ($value) { + if ('_header' === $value) { + return 'Attendee'; + } + + if (null === $value || null === $presence = $this->activityPresenceRepository->find($value)) { + return ''; + } + + return $this->translatableStringHelper->localize($presence->getName()); + }; + + case 'listReasons': + return $this->translatableStringLabelHelper->getLabelMulti($key, $values, 'Activity Reasons'); + + case 'typeName': + return function ($value) { + if ('_header' === $value) { + return 'Activity type'; + } + + if (null === $value || null === $type = $this->activityTypeRepository->find($value)) { + return ''; + } + + return $this->translatableStringHelper->localize($type->getName()); + }; + + case 'usersNames': + return $this->userHelper->getLabelMulti($key, $values, self::MSG_KEY . 'users name'); + + case 'usersIds': + case 'thirdPartiesIds': + case 'personsIds': + return static function ($value) use ($key) { + if ('_header' === $value) { + switch ($key) { + case 'usersIds': + return self::MSG_KEY . 'users ids'; + + case 'thirdPartiesIds': + return self::MSG_KEY . 'third parties ids'; + + case 'personsIds': + return self::MSG_KEY . 'persons ids'; + + default: + throw new LogicException('key not supported'); + } + } + + $decoded = json_decode($value, null, 512, JSON_THROW_ON_ERROR); + + return implode( + '|', + array_unique( + array_filter($decoded, static fn (?int $id) => null !== $id), + SORT_NUMERIC + ) + ); + }; + + case 'personsNames': + return $this->labelPersonHelper->getLabelMulti($key, $values, self::MSG_KEY . 'persons name'); + + case 'thirdPartiesNames': + return $this->labelThirdPartyHelper->getLabelMulti($key, $values, self::MSG_KEY . 'thirds parties'); + + case 'sentReceived': + return function ($value) { + if ('_header' === $value) { + return self::MSG_KEY . 'sent received'; + } + + if (null === $value) { + return ''; + } + + return $this->translator->trans($value); + }; + + default: + return function ($value) use ($key) { + if ('_header' === $value) { + return self::MSG_KEY . $key; + } + + if (null === $value) { + return ''; + } + + return $this->translator->trans($value); + }; + } + } + + public function getQueryKeys($data) + { + return [ + 'id', + 'date', + 'typeName', + 'listReasons', + 'attendeeName', + 'durationTime', + 'travelTime', + 'emergency', + 'locationName', + 'sentReceived', + 'personsIds', + 'personsNames', + 'usersIds', + 'usersNames', + 'thirdPartiesIds', + 'thirdPartiesNames', + 'createdBy', + 'createdAt', + 'updatedBy', + 'updatedAt', + ]; + } + + public function getResult($query, $data) + { + return $query->getQuery()->getResult(AbstractQuery::HYDRATE_SCALAR); + } + + public function getType(): string + { + return Declarations::ACTIVITY; + } + + public function supportsModifiers() + { + return [ + Declarations::ACTIVITY, + ]; + } +} diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/ActivityTypeFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/ActivityTypeFilter.php new file mode 100644 index 000000000..c6616a4c6 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/ActivityTypeFilter.php @@ -0,0 +1,89 @@ +activityTypeRepository = $activityTypeRepository; + $this->translatableStringHelper = $translatableStringHelper; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + $qb->andWhere( + $qb->expr()->exists( + 'SELECT 1 FROM ' . Activity::class . ' act_type_filter_activity + WHERE act_type_filter_activity.activityType IN (:act_type_filter_activity_types) AND act_type_filter_activity.accompanyingPeriod = acp' + ) + ); + $qb->setParameter('act_type_filter_activity_types', $data['accepted_activitytypes']); + } + + public function applyOn() + { + return Declarations::ACP_TYPE; + } + + public function buildForm(FormBuilderInterface $builder) + { + $builder->add('accepted_activitytypes', EntityType::class, [ + 'class' => ActivityType::class, + 'choices' => $this->activityTypeRepository->findAllActive(), + 'choice_label' => fn (ActivityType $aty) => ($aty->hasCategory() ? $this->translatableStringHelper->localize($aty->getCategory()->getName()) . ' > ' : '') + . + $this->translatableStringHelper->localize($aty->getName()), + 'multiple' => true, + 'expanded' => true, + ]); + } + + public function describeAction($data, $format = 'string'): array + { + $types = []; + + foreach ($data['accepted_activitytypes'] as $aty) { + $types[] = $this->translatableStringHelper->localize($aty->getName()); + } + + return ['Filtered by activity types: only %activitytypes%', [ + '%activitytypes%' => implode(', ', $types), + ]]; + } + + public function getTitle(): string + { + return 'Filter accompanying course by activity type'; + } +} diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/ByCreatorFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/ByCreatorFilter.php new file mode 100644 index 000000000..322393f32 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/ByCreatorFilter.php @@ -0,0 +1,73 @@ +userRender = $userRender; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + $qb + ->andWhere( + $qb->expr()->in('activity.createdBy', ':users') + ) + ->setParameter('users', $data['accepted_users']); + } + + public function applyOn(): string + { + return Declarations::ACTIVITY_ACP; + } + + public function buildForm(FormBuilderInterface $builder) + { + $builder->add('accepted_users', PickUserDynamicType::class, [ + 'multiple' => true, + ]); + } + + public function describeAction($data, $format = 'string'): array + { + $users = []; + + foreach ($data['accepted_users'] as $u) { + $users[] = $this->userRender->renderString($u, []); + } + + return ['Filtered activity by creator: only %users%', [ + '%users%' => implode(', ', $users), + ]]; + } + + public function getTitle(): string + { + return 'Filter activity by creator'; + } +} diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/BySocialActionFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/BySocialActionFilter.php new file mode 100644 index 000000000..d0c1b0fc7 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/BySocialActionFilter.php @@ -0,0 +1,83 @@ +actionRender = $actionRender; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + if (!in_array('actsocialaction', $qb->getAllAliases(), true)) { + $qb->join('activity.socialActions', 'actsocialaction'); + } + + $clause = $qb->expr()->in('actsocialaction.id', ':socialactions'); + + $qb->andWhere($clause) + ->setParameter( + 'socialactions', + SocialAction::getDescendantsWithThisForActions($data['accepted_socialactions']) + ); + } + + public function applyOn(): string + { + return Declarations::ACTIVITY_ACP; + } + + public function buildForm(FormBuilderInterface $builder) + { + $builder->add('accepted_socialactions', PickSocialActionType::class, [ + 'multiple' => true, + ]); + } + + public function describeAction($data, $format = 'string'): array + { + $actions = []; + + foreach ($data['accepted_socialactions'] as $action) { + $actions[] = $this->actionRender->renderString($action, [ + 'show_and_children' => true, + ]); + } + + return ['Filtered activity by linked socialaction: only %actions%', [ + '%actions%' => implode(', ', $actions), + ]]; + } + + public function getTitle(): string + { + return 'Filter activity by linked socialaction'; + } +} diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/BySocialIssueFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/BySocialIssueFilter.php new file mode 100644 index 000000000..bbb882a65 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/BySocialIssueFilter.php @@ -0,0 +1,83 @@ +issueRender = $issueRender; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + if (!in_array('actsocialissue', $qb->getAllAliases(), true)) { + $qb->join('activity.socialIssues', 'actsocialissue'); + } + + $clause = $qb->expr()->in('actsocialissue.id', ':socialissues'); + + $qb->andWhere($clause) + ->setParameter( + 'socialissues', + SocialIssue::getDescendantsWithThisForIssues($data['accepted_socialissues']) + ); + } + + public function applyOn(): string + { + return Declarations::ACTIVITY_ACP; + } + + public function buildForm(FormBuilderInterface $builder) + { + $builder->add('accepted_socialissues', PickSocialIssueType::class, [ + 'multiple' => true, + ]); + } + + public function describeAction($data, $format = 'string'): array + { + $issues = []; + + foreach ($data['accepted_socialissues'] as $issue) { + $issues[] = $this->issueRender->renderString($issue, [ + 'show_and_children' => true, + ]); + } + + return ['Filtered activity by linked socialissue: only %issues%', [ + '%issues%' => implode(', ', $issues), + ]]; + } + + public function getTitle(): string + { + return 'Filter activity by linked socialissue'; + } +} diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/EmergencyFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/EmergencyFilter.php new file mode 100644 index 000000000..b79c2ca10 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/EmergencyFilter.php @@ -0,0 +1,90 @@ + true, + 'activity is not emergency' => false, + ]; + + private const DEFAULT_CHOICE = false; + + private TranslatorInterface $translator; + + public function __construct(TranslatorInterface $translator) + { + $this->translator = $translator; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + $where = $qb->getDQLPart('where'); + + $clause = $qb->expr()->eq('activity.emergency', ':emergency'); + + if ($where instanceof Andx) { + $where->add($clause); + } else { + $where = $qb->expr()->andX($clause); + } + + $qb->add('where', $where); + $qb->setParameter('emergency', $data['accepted_emergency']); + } + + public function applyOn(): string + { + return Declarations::ACTIVITY_ACP; + } + + public function buildForm(FormBuilderInterface $builder) + { + $builder->add('accepted_emergency', ChoiceType::class, [ + 'choices' => self::CHOICES, + 'multiple' => false, + 'expanded' => true, + 'empty_data' => self::DEFAULT_CHOICE, + 'data' => self::DEFAULT_CHOICE, + ]); + } + + public function describeAction($data, $format = 'string'): array + { + return [ + 'Filtered by emergency: only %emergency%', [ + '%emergency%' => $this->translator->trans( + $data['accepted_emergency'] ? 'is emergency' : 'is not emergency' + ), + ], + ]; + } + + public function getTitle(): string + { + return 'Filter activity by emergency'; + } +} diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/HasNoActivityFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/HasNoActivityFilter.php new file mode 100644 index 000000000..570f42ae0 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/HasNoActivityFilter.php @@ -0,0 +1,57 @@ +andWhere(' + NOT EXISTS ( + SELECT 1 FROM ' . Activity::class . ' activity + WHERE activity.accompanyingPeriod = acp + ) + '); + } + + public function applyOn(): string + { + return Declarations::ACP_TYPE; + } + + public function buildForm(FormBuilderInterface $builder) + { + //no form needed + } + + public function describeAction($data, $format = 'string'): array + { + return ['Filtered acp which has no activities', []]; + } + + public function getTitle(): string + { + return 'Filter acp which has no activity'; + } +} diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/LocationFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/LocationFilter.php new file mode 100644 index 000000000..3d69d1633 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/LocationFilter.php @@ -0,0 +1,67 @@ +andWhere( + $qb->expr()->in('activity.location', ':location') + ); + + $qb->setParameter('location', $data['accepted_location']); + } + + public function applyOn(): string + { + return Declarations::ACTIVITY_ACP; + } + + public function buildForm(FormBuilderInterface $builder) + { + $builder->add('accepted_location', PickUserLocationType::class, [ + 'multiple' => true, + 'label' => 'pick location', + ]); + } + + public function describeAction($data, $format = 'string'): array + { + $locations = []; + + foreach ($data['accepted_location'] as $location) { + $locations[] = $location->getName(); + } + + return ['Filtered activity by location: only %locations%', [ + '%locations%' => implode(', ', $locations), + ]]; + } + + public function getTitle(): string + { + return 'Filter activity by location'; + } +} diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/LocationTypeFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/LocationTypeFilter.php new file mode 100644 index 000000000..5fe928b6c --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/LocationTypeFilter.php @@ -0,0 +1,88 @@ +translatableStringHelper = $translatableStringHelper; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + if (!in_array('actloc', $qb->getAllAliases(), true)) { + $qb->join('activity.location', 'actloc'); + } + + $where = $qb->getDQLPart('where'); + $clause = $qb->expr()->in('actloc.locationType', ':locationtype'); + + if ($where instanceof Andx) { + $where->add($clause); + } else { + $where = $qb->expr()->andX($clause); + } + + $qb->add('where', $where); + $qb->setParameter('locationtype', $data['accepted_locationtype']); + } + + public function applyOn(): string + { + return Declarations::ACTIVITY_ACP; + } + + public function buildForm(FormBuilderInterface $builder) + { + $builder->add('accepted_locationtype', PickLocationTypeType::class, [ + 'multiple' => true, + //'label' => false, + ]); + } + + public function describeAction($data, $format = 'string'): array + { + $types = []; + + foreach ($data['accepted_locationtype'] as $type) { + $types[] = $this->translatableStringHelper->localize( + $type->getTitle() + ); + } + + return ['Filtered activity by locationtype: only %types%', [ + '%types%' => implode(', ', $types), + ]]; + } + + public function getTitle(): string + { + return 'Filter activity by locationtype'; + } +} diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/SentReceivedFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/SentReceivedFilter.php new file mode 100644 index 000000000..8daa7a781 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/SentReceivedFilter.php @@ -0,0 +1,89 @@ + Activity::SENTRECEIVED_SENT, + 'is received' => Activity::SENTRECEIVED_RECEIVED, + ]; + + private const DEFAULT_CHOICE = Activity::SENTRECEIVED_SENT; + + private TranslatorInterface $translator; + + public function __construct(TranslatorInterface $translator) + { + $this->translator = $translator; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + $where = $qb->getDQLPart('where'); + + $clause = $qb->expr()->eq('activity.sentReceived', ':sentreceived'); + + if ($where instanceof Andx) { + $where->add($clause); + } else { + $where = $qb->expr()->andX($clause); + } + + $qb->add('where', $where); + $qb->setParameter('sentreceived', $data['accepted_sentreceived']); + } + + public function applyOn(): string + { + return Declarations::ACTIVITY_ACP; + } + + public function buildForm(FormBuilderInterface $builder) + { + $builder->add('accepted_sentreceived', ChoiceType::class, [ + 'choices' => self::CHOICES, + 'multiple' => false, + 'expanded' => true, + 'empty_data' => self::DEFAULT_CHOICE, + 'data' => self::DEFAULT_CHOICE, + ]); + } + + public function describeAction($data, $format = 'string'): array + { + $sentreceived = array_flip(self::CHOICES)[$data['accepted_sentreceived']]; + + return ['Filtered activity by sentreceived: only %sentreceived%', [ + '%sentreceived%' => $this->translator->trans($sentreceived), + ]]; + } + + public function getTitle(): string + { + return 'Filter activity by sentreceived'; + } +} diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/UserFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/UserFilter.php new file mode 100644 index 000000000..6350f3ace --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/UserFilter.php @@ -0,0 +1,82 @@ +userRender = $userRender; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + $where = $qb->getDQLPart('where'); + + $clause = $qb->expr()->in('activity.user', ':users'); + + if ($where instanceof Andx) { + $where->add($clause); + } else { + $where = $qb->expr()->andX($clause); + } + + $qb->add('where', $where); + $qb->setParameter('users', $data['accepted_users']); + } + + public function applyOn(): string + { + return Declarations::ACTIVITY_ACP; + } + + public function buildForm(FormBuilderInterface $builder) + { + $builder->add('accepted_users', PickUserDynamicType::class, [ + 'multiple' => true, + 'label' => 'Creators', + ]); + } + + public function describeAction($data, $format = 'string'): array + { + $users = []; + + foreach ($data['accepted_users'] as $u) { + $users[] = $this->userRender->renderString($u, []); + } + + return ['Filtered activity by user: only %users%', [ + '%users%' => implode(', ', $users), + ]]; + } + + public function getTitle(): string + { + return 'Filter activity by user'; + } +} diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/UserScopeFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/UserScopeFilter.php new file mode 100644 index 000000000..4319c100a --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/UserScopeFilter.php @@ -0,0 +1,94 @@ +translatableStringHelper = $translatableStringHelper; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + if (!in_array('actuser', $qb->getAllAliases(), true)) { + $qb->join('activity.user', 'actuser'); + } + + $where = $qb->getDQLPart('where'); + + $clause = $qb->expr()->in('actuser.mainScope', ':userscope'); + + if ($where instanceof Andx) { + $where->add($clause); + } else { + $where = $qb->expr()->andX($clause); + } + + $qb->add('where', $where); + $qb->setParameter('userscope', $data['accepted_userscope']); + } + + public function applyOn(): string + { + return Declarations::ACTIVITY_ACP; + } + + public function buildForm(FormBuilderInterface $builder) + { + $builder->add('accepted_userscope', EntityType::class, [ + 'class' => Scope::class, + 'choice_label' => fn (Scope $s) => $this->translatableStringHelper->localize( + $s->getName() + ), + 'multiple' => true, + 'expanded' => true, + ]); + } + + public function 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 6048a1cc0..f2216c929 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/ActivityDateFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/ActivityDateFilter.php @@ -1,22 +1,24 @@ translator = $translator; + $this->rollingDateConverter = $rollingDateConverter; } - public function addRole() + public function addRole(): ?string { return null; } @@ -53,40 +60,32 @@ class ActivityDateFilter implements FilterInterface } $qb->add('where', $where); - $qb->setParameter('date_from', $data['date_from']); - $qb->setParameter('date_to', $data['date_to']); + $qb->setParameter( + 'date_from', + $this->rollingDateConverter->convert($data['date_from']) + ); + $qb->setParameter( + 'date_to', + $this->rollingDateConverter->convert($data['date_to']) + ); } - public function applyOn() + public function applyOn(): string { - return 'activity'; + return Declarations::ACTIVITY; } public function buildForm(FormBuilderInterface $builder) { - $builder->add( - 'date_from', - DateType::class, - [ + $builder + ->add('date_from', PickRollingDateType::class, [ 'label' => 'Activities after this date', - 'data' => new DateTime(), - 'attr' => ['class' => 'datepicker'], - 'widget' => 'single_text', - 'format' => 'dd-MM-yyyy', - ] - ); - - $builder->add( - 'date_to', - DateType::class, - [ + 'data' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START), + ]) + ->add('date_to', PickRollingDateType::class, [ 'label' => 'Activities before this date', - 'data' => new DateTime(), - 'attr' => ['class' => 'datepicker'], - 'widget' => 'single_text', - 'format' => 'dd-MM-yyyy', - ] - ); + 'data' => new RollingDate(RollingDate::T_TODAY), + ]); $builder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) { /** @var \Symfony\Component\Form\FormInterface $filterForm */ @@ -134,8 +133,8 @@ class ActivityDateFilter implements FilterInterface return [ 'Filtered by date of activity: only between %date_from% and %date_to%', [ - '%date_from%' => $data['date_from']->format('d-m-Y'), - '%date_to%' => $data['date_to']->format('d-m-Y'), + '%date_from%' => $this->rollingDateConverter->convert($data['date_from'])->format('d-m-Y'), + '%date_to%' => $this->rollingDateConverter->convert($data['date_to'])->format('d-m-Y'), ], ]; } diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ActivityTypeFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ActivityTypeFilter.php index d3f35a746..b9d39c3ce 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/ActivityTypeFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/ActivityTypeFilter.php @@ -1,99 +1,95 @@ translatableStringHelper = $translatableStringHelper; $this->activityTypeRepository = $activityTypeRepository; } - public function addRole() + public function addRole(): ?string { - return new Role(ActivityStatsVoter::STATS); + return null; } public function alterQuery(QueryBuilder $qb, $data) { - $where = $qb->getDQLPart('where'); - $clause = $qb->expr()->in('activity.type', ':selected_activity_types'); + $clause = $qb->expr()->in('activity.activityType', ':selected_activity_types'); - if ($where instanceof Expr\Andx) { - $where->add($clause); - } else { - $where = $qb->expr()->andX($clause); - } - - $qb->add('where', $where); + $qb->andWhere($clause); $qb->setParameter('selected_activity_types', $data['types']); } - public function applyOn() + public function applyOn(): string { - return 'activity'; + return Declarations::ACTIVITY; } public function buildForm(FormBuilderInterface $builder) { - $builder->add( - 'types', - EntityType::class, - [ - 'class' => ActivityType::class, - 'choice_label' => fn (ActivityType $type) => $this->translatableStringHelper->localize($type->getName()), - 'multiple' => true, - 'expanded' => false, - ] - ); + $builder->add('types', EntityType::class, [ + 'choices' => $this->activityTypeRepository->findAllActive(), + 'class' => ActivityType::class, + 'choice_label' => fn (ActivityType $aty) => ($aty->hasCategory() ? $this->translatableStringHelper->localize($aty->getCategory()->getName()) . ' > ' : '') + . + $this->translatableStringHelper->localize($aty->getName()), + 'group_by' => function (ActivityType $type) { + if (!$type->hasCategory()) { + return null; + } + + return $this->translatableStringHelper->localize($type->getCategory()->getName()); + }, + 'multiple' => true, + 'expanded' => false, + 'attr' => [ + 'class' => 'select2', + ], + ]); } public function describeAction($data, $format = 'string') { // collect all the reasons'name used in this filter in one array $reasonsNames = array_map( - fn (ActivityType $t): string => '"' . $this->translatableStringHelper->localize($t->getName()) . '"', + fn (ActivityType $t): string => $this->translatableStringHelper->localize($t->getName()), $this->activityTypeRepository->findBy(['id' => $data['types']->toArray()]) ); - return [ - 'Filtered by activity type: only %list%', - [ - '%list%' => implode(', ', $reasonsNames), - ], - ]; + return ['Filtered by activity type: only %list%', [ + '%list%' => implode(', ', $reasonsNames), + ]]; } public function getTitle() @@ -109,23 +105,4 @@ class ActivityTypeFilter implements ExportElementValidatedInterface, FilterInter ->addViolation(); } } - - /** - * Check if a join between Activity and Reason is already defined. - * - * @param Join[] $joins - * @param mixed $alias - * - * @return bool - */ - private function checkJoinAlreadyDefined(array $joins, $alias) - { - foreach ($joins as $join) { - if ($join->getAlias() === $alias) { - return true; - } - } - - return false; - } } diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ActivityUsersFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ActivityUsersFilter.php new file mode 100644 index 000000000..2f6cd8462 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Export/Filter/ActivityUsersFilter.php @@ -0,0 +1,77 @@ +userRender = $userRender; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + $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); + } + + $qb->andWhere($orX); + } + + public function applyOn() + { + return Declarations::ACTIVITY; + } + + public function buildForm(FormBuilderInterface $builder) + { + $builder->add('accepted_users', PickUserDynamicType::class, [ + 'multiple' => true, + 'label' => 'Users', + ]); + } + + public function describeAction($data, $format = 'string') + { + $users = []; + + foreach ($data['accepted_users'] as $u) { + $users[] = $this->userRender->renderString($u, []); + } + + return ['Filtered activity by users: only %users%', [ + '%users%' => implode(', ', $users), + ]]; + } + + public function getTitle(): string + { + return 'Filter activity by users'; + } +} diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ActivityReasonFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/PersonFilters/ActivityReasonFilter.php similarity index 70% rename from src/Bundle/ChillActivityBundle/Export/Filter/ActivityReasonFilter.php rename to src/Bundle/ChillActivityBundle/Export/Filter/PersonFilters/ActivityReasonFilter.php index 467471a8d..c55d579e4 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/ActivityReasonFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/PersonFilters/ActivityReasonFilter.php @@ -1,33 +1,31 @@ activityReasonRepository = $activityReasonRepository; } - public function addRole() + public function addRole(): ?string { - return new Role(ActivityStatsVoter::STATS); + return null; } public function alterQuery(QueryBuilder $qb, $data) { $where = $qb->getDQLPart('where'); $join = $qb->getDQLPart('join'); - $clause = $qb->expr()->in('reasons', ':selected_activity_reasons'); - //dump($join); - // add a join to reasons only if needed - if ( - ( - array_key_exists('activity', $join) - && !$this->checkJoinAlreadyDefined($join['activity'], 'reasons') - ) - || (!array_key_exists('activity', $join)) - ) { - $qb->add( - 'join', - ['activity' => new Join(Join::INNER_JOIN, 'activity.reasons', 'reasons')], - true - ); + $clause = $qb->expr()->in('actreasons', ':selected_activity_reasons'); + + if (!in_array('actreasons', $qb->getAllAliases(), true)) { + $qb->join('activity.reasons', 'actreasons'); } if ($where instanceof Expr\Andx) { @@ -79,9 +66,9 @@ class ActivityReasonFilter implements ExportElementValidatedInterface, FilterInt $qb->setParameter('selected_activity_reasons', $data['reasons']); } - public function applyOn() + public function applyOn(): string { - return 'activity'; + return Declarations::ACTIVITY_PERSON; } public function buildForm(FormBuilderInterface $builder) @@ -90,6 +77,7 @@ class ActivityReasonFilter implements ExportElementValidatedInterface, FilterInt 'class' => ActivityReason::class, 'choice_label' => fn (ActivityReason $reason) => $this->translatableStringHelper->localize($reason->getName()), 'group_by' => fn (ActivityReason $reason) => $this->translatableStringHelper->localize($reason->getCategory()->getName()), + 'attr' => ['class' => 'select2 '], 'multiple' => true, 'expanded' => false, ]); @@ -124,21 +112,4 @@ class ActivityReasonFilter implements ExportElementValidatedInterface, FilterInt ->addViolation(); } } - - /** - * Check if a join between Activity and Reason is already defined. - * - * @param Join[] $joins - * @param mixed $alias - */ - private function checkJoinAlreadyDefined(array $joins, $alias): bool - { - foreach ($joins as $join) { - if ($join->getAlias() === $alias) { - return true; - } - } - - return false; - } } diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/PersonHavingActivityBetweenDateFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/PersonFilters/PersonHavingActivityBetweenDateFilter.php similarity index 95% rename from src/Bundle/ChillActivityBundle/Export/Filter/PersonHavingActivityBetweenDateFilter.php rename to src/Bundle/ChillActivityBundle/Export/Filter/PersonFilters/PersonHavingActivityBetweenDateFilter.php index 312486b49..e3c85fe9c 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/PersonHavingActivityBetweenDateFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/PersonFilters/PersonHavingActivityBetweenDateFilter.php @@ -1,16 +1,17 @@ translator = $translator; } - public function addRole() + public function addRole(): ?string { return null; } public function alterQuery(QueryBuilder $qb, $data) { - // create a query for activity + // create a subquery for activity $sqb = $qb->getEntityManager()->createQueryBuilder(); $sqb->select('person_person_having_activity.id') - ->from('ChillActivityBundle:Activity', 'activity_person_having_activity') + ->from(Activity::class, 'activity_person_having_activity') ->join('activity_person_having_activity.person', 'person_person_having_activity'); // add clause between date @@ -102,7 +103,7 @@ class PersonHavingActivityBetweenDateFilter implements ExportElementValidatedInt $qb->setParameter('person_having_activity_reasons', $data['reasons']); } - public function applyOn() + public function applyOn(): string { return Declarations::PERSON_IMPLIED_IN; } @@ -197,7 +198,7 @@ class PersonHavingActivityBetweenDateFilter implements ExportElementValidatedInt public function getTitle() { - return 'Filtered by person having an activity in a period'; + return 'Filter by person having an activity in a period'; } public function validateForm($data, ExecutionContextInterface $context) diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/UsersJobFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/UsersJobFilter.php new file mode 100644 index 000000000..b52ef441c --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Export/Filter/UsersJobFilter.php @@ -0,0 +1,81 @@ +translatableStringHelper = $translatableStringHelper; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + $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 ' + ) + ) + ->setParameter('activity_users_job_filter_jobs', $data['jobs']); + } + + public function applyOn() + { + return Declarations::ACTIVITY; + } + + public function buildForm(FormBuilderInterface $builder) + { + $builder->add('jobs', EntityType::class, [ + 'class' => UserJob::class, + 'choice_label' => fn (UserJob $j) => $this->translatableStringHelper->localize($j->getLabel()), + 'multiple' => true, + 'expanded' => true, + ]); + } + + public function describeAction($data, $format = 'string') + { + return ['export.filter.activity.by_usersjob.Filtered activity by users job: only %jobs%', [ + '%jobs%' => implode( + ', ', + array_map( + fn (UserJob $job) => $this->translatableStringHelper->localize($job->getLabel()), + $data['jobs']->toArray() + ) + ), + ]]; + } + + public function getTitle() + { + return 'export.filter.activity.by_usersjob.Filter by users job'; + } +} diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/UsersScopeFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/UsersScopeFilter.php new file mode 100644 index 000000000..61b12264e --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Export/Filter/UsersScopeFilter.php @@ -0,0 +1,88 @@ +scopeRepository = $scopeRepository; + $this->translatableStringHelper = $translatableStringHelper; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + $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 ' + ) + ) + ->setParameter('activity_users_scope_filter_scopes', $data['scopes']); + } + + public function applyOn() + { + return Declarations::ACTIVITY; + } + + public function buildForm(FormBuilderInterface $builder) + { + $builder->add('scopes', EntityType::class, [ + 'class' => Scope::class, + 'choices' => $this->scopeRepository->findAllActive(), + 'choice_label' => fn (Scope $s) => $this->translatableStringHelper->localize($s->getName()), + 'multiple' => true, + 'expanded' => true, + ]); + } + + public function describeAction($data, $format = 'string') + { + return ['export.filter.activity.by_usersscope.Filtered activity by users scope: only %scopes%', [ + '%scopes%' => implode( + ', ', + array_map( + fn (Scope $s) => $this->translatableStringHelper->localize($s->getName()), + $data['scopes']->toArray() + ) + ), + ]]; + } + + public function getTitle() + { + return 'export.filter.activity.by_usersscope.Filter by users scope'; + } +} diff --git a/src/Bundle/ChillActivityBundle/Form/ActivityPresenceType.php b/src/Bundle/ChillActivityBundle/Form/ActivityPresenceType.php index 5c467a575..b0ee15c10 100644 --- a/src/Bundle/ChillActivityBundle/Form/ActivityPresenceType.php +++ b/src/Bundle/ChillActivityBundle/Form/ActivityPresenceType.php @@ -1,14 +1,14 @@ setDefaults([ - 'data_class' => 'Chill\ActivityBundle\Entity\ActivityReasonCategory', + 'data_class' => \Chill\ActivityBundle\Entity\ActivityReasonCategory::class, ]); } diff --git a/src/Bundle/ChillActivityBundle/Form/ActivityReasonType.php b/src/Bundle/ChillActivityBundle/Form/ActivityReasonType.php index 01543d18c..f47a101bd 100644 --- a/src/Bundle/ChillActivityBundle/Form/ActivityReasonType.php +++ b/src/Bundle/ChillActivityBundle/Form/ActivityReasonType.php @@ -1,17 +1,18 @@ add('name', TranslatableStringFormType::class) ->add('active', CheckboxType::class, ['required' => false]) - ->add('category', TranslatableActivityReasonCategory::class); + ->add('category', TranslatableActivityReasonCategoryType::class); } public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ - 'data_class' => 'Chill\ActivityBundle\Entity\ActivityReason', + 'data_class' => ActivityReason::class, ]); } diff --git a/src/Bundle/ChillActivityBundle/Form/ActivityType.php b/src/Bundle/ChillActivityBundle/Form/ActivityType.php index 898f39e56..a74178afd 100644 --- a/src/Bundle/ChillActivityBundle/Form/ActivityType.php +++ b/src/Bundle/ChillActivityBundle/Form/ActivityType.php @@ -1,30 +1,33 @@ getPerson()) { $builder->add('scope', ScopePickerType::class, [ 'center' => $options['center'], - 'role' => $options['role'], - // TODO make required again once scope and rights are fixed - 'required' => false, + 'role' => ActivityVoter::CREATE === (string) $options['role'] ? ActivityVoter::CREATE_PERSON : (string) $options['role'], + 'required' => true, ]); } /** @var ? \Chill\PersonBundle\Entity\AccompanyingPeriod $accompanyingPeriod */ $accompanyingPeriod = null; - if ($options['accompanyingPeriod']) { + if ($options['accompanyingPeriod'] instanceof AccompanyingPeriod) { $accompanyingPeriod = $options['accompanyingPeriod']; } @@ -208,39 +211,25 @@ 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']) { - $builder->add('user', UserPickerType::class, [ + if ($activityType->isVisible('user') && $options['center'] instanceof Center) { + $builder->add('user', PickUserDynamicType::class, [ 'label' => $activityType->getLabel('user'), 'required' => $activityType->isRequired('user'), - 'center' => $options['center'], - 'role' => $options['role'], + 'multiple' => false, ]); } if ($activityType->isVisible('reasons')) { - $builder->add('reasons', EntityType::class, [ + $builder->add('reasons', PickActivityReasonType::class, [ 'label' => $activityType->getLabel('reasons'), 'required' => $activityType->isRequired('reasons'), - 'class' => ActivityReason::class, 'multiple' => true, - 'choice_label' => function (ActivityReason $activityReason) { - return $this->translatableStringHelper->localize($activityReason->getName()); - }, - 'attr' => ['class' => 'select2 '], - 'query_builder' => static function (EntityRepository $er) { - return $er->createQueryBuilder('a') - ->where('a.active = true'); - }, ]); } @@ -364,9 +353,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]) )); } @@ -408,9 +395,7 @@ class ActivityType extends AbstractType // 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(); + $data = $formEvent->getData() ?? DateTime::createFromFormat('U', '300'); $seconds = $data->getTimezone()->getOffset($data); $data->setTimeZone($timezoneUTC); $data->add(new DateInterval('PT' . $seconds . 'S')); @@ -442,8 +427,8 @@ class ActivityType extends AbstractType $resolver ->setRequired(['center', 'role', 'activityType', 'accompanyingPeriod']) - ->setAllowedTypes('center', ['null', 'Chill\MainBundle\Entity\Center']) - ->setAllowedTypes('role', 'Symfony\Component\Security\Core\Role\Role') + ->setAllowedTypes('center', ['null', Center::class]) + ->setAllowedTypes('role', [Role::class, 'string']) ->setAllowedTypes('activityType', \Chill\ActivityBundle\Entity\ActivityType::class) ->setAllowedTypes('accompanyingPeriod', [\Chill\PersonBundle\Entity\AccompanyingPeriod::class, 'null']); } diff --git a/src/Bundle/ChillActivityBundle/Form/ActivityTypeCategoryType.php b/src/Bundle/ChillActivityBundle/Form/ActivityTypeCategoryType.php index 510d27913..c4d79f522 100644 --- a/src/Bundle/ChillActivityBundle/Form/ActivityTypeCategoryType.php +++ b/src/Bundle/ChillActivityBundle/Form/ActivityTypeCategoryType.php @@ -1,14 +1,14 @@ 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, diff --git a/src/Bundle/ChillActivityBundle/Form/Type/ActivityFieldPresence.php b/src/Bundle/ChillActivityBundle/Form/Type/ActivityFieldPresence.php index 5e0bd4561..42db8c4ac 100644 --- a/src/Bundle/ChillActivityBundle/Form/Type/ActivityFieldPresence.php +++ b/src/Bundle/ChillActivityBundle/Form/Type/ActivityFieldPresence.php @@ -1,14 +1,14 @@ translatableStringHelper = $translatableStringHelper; + $this->activityReasonRepository = $activityReasonRepository; $this->reasonRender = $reasonRender; + $this->translatableStringHelper = $translatableStringHelper; } public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults( [ - 'class' => 'ChillActivityBundle:ActivityReason', - 'choice_label' => function (ActivityReason $choice) { - return $this->reasonRender->renderString($choice, []); - }, + 'class' => ActivityReason::class, + '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()); @@ -57,10 +53,7 @@ class TranslatableActivityReason extends AbstractType return null; }, - 'query_builder' => static function (EntityRepository $er) { - return $er->createQueryBuilder('r') - ->where('r.active = true'); - }, + 'choices' => $this->activityReasonRepository->findAll(), 'attr' => ['class' => ' select2 '], ] ); diff --git a/src/Bundle/ChillActivityBundle/Form/Type/TranslatableActivityReasonCategory.php b/src/Bundle/ChillActivityBundle/Form/Type/TranslatableActivityReasonCategory.php deleted file mode 100644 index 2b501864f..000000000 --- a/src/Bundle/ChillActivityBundle/Form/Type/TranslatableActivityReasonCategory.php +++ /dev/null @@ -1,59 +0,0 @@ -requestStack = $requestStack; - } - - public function configureOptions(OptionsResolver $resolver) - { - $locale = $this->requestStack->getCurrentRequest()->getLocale(); - $resolver->setDefaults( - [ - 'class' => 'ChillActivityBundle:ActivityReasonCategory', - 'choice_label' => 'name[' . $locale . ']', - 'query_builder' => static function (EntityRepository $er) { - return $er->createQueryBuilder('c') - ->where('c.active = true'); - }, - ] - ); - } - - public function getBlockPrefix() - { - return 'translatable_activity_reason_category'; - } - - public function getParent() - { - return EntityType::class; - } -} diff --git a/src/Bundle/ChillActivityBundle/Form/Type/TranslatableActivityReasonCategoryType.php b/src/Bundle/ChillActivityBundle/Form/Type/TranslatableActivityReasonCategoryType.php new file mode 100644 index 000000000..4aa259097 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Form/Type/TranslatableActivityReasonCategoryType.php @@ -0,0 +1,56 @@ +translatableStringHelper = $translatableStringHelper; + $this->translator = $translator; + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults( + [ + 'class' => ActivityReasonCategory::class, + '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 dc6328709..d4807b82d 100644 --- a/src/Bundle/ChillActivityBundle/Form/Type/TranslatableActivityType.php +++ b/src/Bundle/ChillActivityBundle/Form/Type/TranslatableActivityType.php @@ -1,62 +1,45 @@ translatableStringHelper = $helper; $this->activityTypeRepository = $activityTypeRepository; } - public function buildForm(FormBuilderInterface $builder, array $options) - { - /** @var QueryBuilder $qb */ - $qb = $options['query_builder']; - - if (true === $options['active_only']) { - $qb->where($qb->expr()->eq('at.active', ':active')); - $qb->setParameter('active', true, Types::BOOLEAN); - } - } - public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults( [ 'class' => ActivityType::class, 'active_only' => true, - 'query_builder' => $this->activityTypeRepository - ->createQueryBuilder('at'), - 'choice_label' => function (ActivityType $type) { - return $this->translatableStringHelper->localize($type->getName()); - }, + 'choices' => $this->activityTypeRepository->findAllActive(), + '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 636d03488..9884450ec 100644 --- a/src/Bundle/ChillActivityBundle/Menu/AccompanyingCourseMenuBuilder.php +++ b/src/Bundle/ChillActivityBundle/Menu/AccompanyingCourseMenuBuilder.php @@ -1,14 +1,14 @@ + */ class AccompanyingCourseMenuBuilder implements LocalMenuBuilderInterface { protected Security $security; diff --git a/src/Bundle/ChillActivityBundle/Menu/AdminMenuBuilder.php b/src/Bundle/ChillActivityBundle/Menu/AdminMenuBuilder.php index 1d887fead..4b2d348d7 100644 --- a/src/Bundle/ChillActivityBundle/Menu/AdminMenuBuilder.php +++ b/src/Bundle/ChillActivityBundle/Menu/AdminMenuBuilder.php @@ -1,20 +1,23 @@ + */ final class AdminMenuBuilder implements LocalMenuBuilderInterface { private Security $security; @@ -36,7 +39,6 @@ final class AdminMenuBuilder implements LocalMenuBuilderInterface ->setAttribute('class', 'list-group-item-header') ->setExtras([ 'order' => 5000, - 'icons' => ['exchange'], ]); $menu->addChild('Activity Reasons', [ diff --git a/src/Bundle/ChillActivityBundle/Menu/PersonMenuBuilder.php b/src/Bundle/ChillActivityBundle/Menu/PersonMenuBuilder.php index 56c21051f..03a1b09ab 100644 --- a/src/Bundle/ChillActivityBundle/Menu/PersonMenuBuilder.php +++ b/src/Bundle/ChillActivityBundle/Menu/PersonMenuBuilder.php @@ -1,23 +1,27 @@ + */ +final class PersonMenuBuilder implements LocalMenuBuilderInterface { /** * @var AuthorizationCheckerInterface @@ -44,7 +48,7 @@ class PersonMenuBuilder implements LocalMenuBuilderInterface if ($this->authorizationChecker->isGranted(ActivityVoter::SEE, $person)) { $menu->addChild( - $this->translator->trans('Activity list'), + $this->translator->trans('Activities'), [ 'route' => 'chill_activity_activity_list', 'routeParameters' => ['person_id' => $person->getId()], diff --git a/src/Bundle/ChillActivityBundle/Notification/ActivityNotificationHandler.php b/src/Bundle/ChillActivityBundle/Notification/ActivityNotificationHandler.php index f391de1ff..ab26da81d 100644 --- a/src/Bundle/ChillActivityBundle/Notification/ActivityNotificationHandler.php +++ b/src/Bundle/ChillActivityBundle/Notification/ActivityNotificationHandler.php @@ -1,14 +1,14 @@ getAssociationMapping('center')['joinColumns'][0]['name']; // acls: - $role = new Role(ActivityVoter::SEE); $reachableCenters = $this->authorizationHelper->getReachableCenters( $this->tokenStorage->getToken()->getUser(), - $role + ActivityVoter::SEE ); if (count($reachableCenters) === 0) { @@ -239,7 +238,7 @@ final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInte if ('person' === $context) { // we start with activities having the person_id linked to person $where .= sprintf('%s = ? AND ', $activityToPerson); - $parameters[] = $person->getId(); + $parameters[] = $args['context']->getId(); } // we add acl (reachable center and scopes) @@ -252,12 +251,10 @@ final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInte continue; } // we get all the reachable scopes for this center - $reachableScopes = $this->authorizationHelper->getReachableScopes($this->tokenStorage->getToken()->getUser(), $role, $center); + $reachableScopes = $this->authorizationHelper->getReachableScopes($this->tokenStorage->getToken()->getUser(), 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 ); diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepositoryInterface.php b/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepositoryInterface.php index 1fc6d22b1..8cdb83524 100644 --- a/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepositoryInterface.php +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepositoryInterface.php @@ -1,14 +1,14 @@ repository = $entityManager->getRepository($this->getClassName()); + } + + public function find($id): ?ActivityPresence + { + return $this->repository->find($id); + } + + public function findAll(): array + { + return $this->repository->findAll(); + } + + public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array + { + return $this->findBy($criteria, $orderBy, $limit, $offset); + } + + public function findOneBy(array $criteria): ?ActivityPresence + { + return $this->findOneBy($criteria); + } + + public function getClassName(): string + { + return ActivityPresence::class; + } +} diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityPresenceRepositoryInterface.php b/src/Bundle/ChillActivityBundle/Repository/ActivityPresenceRepositoryInterface.php new file mode 100644 index 000000000..228d70856 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityPresenceRepositoryInterface.php @@ -0,0 +1,33 @@ +requestStack = $requestStack; + } + + /** + * @return ActivityReason[] + */ + public function findAll(): array + { + $qb = $this->createQueryBuilder('ar'); + $qb->select(['ar']) + ->leftJoin('ar.category', 'category') + ->addOrderBy('JSON_EXTRACT(category.name, :lang)') + ->addOrderBy('JSON_EXTRACT(ar.name, :lang)') + ->setParameter('lang', $this->requestStack->getCurrentRequest()->getLocale() ?? 'fr'); + + return $qb->getQuery()->getResult(); } } diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityRepository.php b/src/Bundle/ChillActivityBundle/Repository/ActivityRepository.php index 312d601ad..5a6e16cd5 100644 --- a/src/Bundle/ChillActivityBundle/Repository/ActivityRepository.php +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityRepository.php @@ -1,14 +1,14 @@ repository = $em->getRepository(ActivityType::class); + } + + public function find($id): ?ActivityType + { + return $this->repository->find($id); + } + + /** + * @return array|ActivityType[] + */ + public function findAll(): array + { + return $this->repository->findAll(); + } + + /** + * @return array|ActivityType[] + */ + public function findAllActive(): array + { + return $this->findBy(['active' => true]); + } + + /** + * @return array|ActivityType[] + */ + public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array + { + return $this->repository->findBy($criteria, $orderBy, $limit, $offset); + } + + public function findOneBy(array $criteria): ?ActivityType + { + return $this->repository->findOneBy($criteria); + } + + public function getClassName(): string + { + return ActivityType::class; } } diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityTypeRepositoryInterface.php b/src/Bundle/ChillActivityBundle/Repository/ActivityTypeRepositoryInterface.php new file mode 100644 index 000000000..2148a02f5 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityTypeRepositoryInterface.php @@ -0,0 +1,23 @@ + fetchResults('/api/1.0/main/location.json'); const getLocationTypes = () => fetchResults('/api/1.0/main/location-type.json'); -const getUserCurrentLocation = +const getUserCurrentLocation = () => fetch('/api/1.0/main/user-current-location.json') .then(response => { if (response.ok) { return response.json(); } @@ -35,6 +35,13 @@ const getLocationTypeByDefaultFor = (entity) => { ); }; +/** + * Post a location + * + * **NOTE**: also in use for Calendar + * @param body + * @returns {Promise} + */ const postLocation = (body) => { const url = `/api/1.0/main/location.json`; return fetch(url, { diff --git a/src/Bundle/ChillActivityBundle/Resources/public/vuejs/Activity/components/Location/NewLocation.vue b/src/Bundle/ChillActivityBundle/Resources/public/vuejs/Activity/components/Location/NewLocation.vue index 6a6289875..568c8747a 100644 --- a/src/Bundle/ChillActivityBundle/Resources/public/vuejs/Activity/components/Location/NewLocation.vue +++ b/src/Bundle/ChillActivityBundle/Resources/public/vuejs/Activity/components/Location/NewLocation.vue @@ -117,7 +117,8 @@ export default { target: { //name, id }, edit: false, - addressId: null + addressId: null, + defaults: window.addaddress } } } diff --git a/src/Bundle/ChillActivityBundle/Resources/public/vuejs/Activity/store.js b/src/Bundle/ChillActivityBundle/Resources/public/vuejs/Activity/store.js index ca56a5dae..9f710abeb 100644 --- a/src/Bundle/ChillActivityBundle/Resources/public/vuejs/Activity/store.js +++ b/src/Bundle/ChillActivityBundle/Resources/public/vuejs/Activity/store.js @@ -30,7 +30,7 @@ const store = createStore({ }, getters: { suggestedEntities(state) { - if (typeof state.activity.accompanyingPeriod === "undefined") { + if (typeof state.activity.accompanyingPeriod === "undefined" || state.activity.accompanyingPeriod === null) { return []; } const allEntities = [ diff --git a/src/Bundle/ChillActivityBundle/Resources/public/vuejs/Activity/store.locations.js b/src/Bundle/ChillActivityBundle/Resources/public/vuejs/Activity/store.locations.js index 6125140a5..7a216f628 100644 --- a/src/Bundle/ChillActivityBundle/Resources/public/vuejs/Activity/store.locations.js +++ b/src/Bundle/ChillActivityBundle/Resources/public/vuejs/Activity/store.locations.js @@ -39,6 +39,9 @@ const makeConcernedThirdPartiesLocation = (locationType, store) => { return locations; }; const makeAccompanyingPeriodLocation = (locationType, store) => { + if (store.state.activity.accompanyingPeriod === null) { + return {}; + } const accPeriodLocation = store.state.activity.accompanyingPeriod.location; return { type: 'location', @@ -55,7 +58,7 @@ const makeAccompanyingPeriodLocation = (locationType, store) => { export default function prepareLocations(store) { -// find the locations + // find the locations let allLocations = getLocations().then( (results) => { store.commit('addAvailableLocationGroup', { @@ -111,7 +114,7 @@ export default function prepareLocations(store) { if (window.default_location_id) { for (let group of store.state.availableLocations) { let location = group.locations.find((l) => l.id === window.default_location_id); - if (location !== undefined & store.state.activity.location === null) { + if (location !== undefined && store.state.activity.location === null) { store.dispatch('updateLocation', location); break; } 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 3a4749f3c..8d1aba2b3 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/Activity/_list_item.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/Activity/_list_item.html.twig @@ -63,12 +63,12 @@ {% endif %} - {% if activity.user and t.userVisible %} + {% if activity.user is not null and t.userVisible %}

{{ 'Referrer'|trans }}

- {{ activity.user|chill_entity_render_box }} + {{ activity.user|chill_entity_render_box }}

@@ -137,19 +137,42 @@ {{ activity.comment|chill_entity_render_box({ 'disable_markdown': false, 'limit_lines': 3, - 'metadata': false + 'metadata': false, }) }} {% endif %} - {# Only if ACL SEE_DETAILS AND/OR only on template SHOW ?? - durationTime - travelTime - comment - documents - attendee - #} + {% if is_granted('CHILL_ACTIVITY_SEE_DETAILS', activity) and activity.privateComment.hasCommentForUser(app.user) %} +
+
+

{{ 'Private comment'|trans }}

+
+
+
+
+ {{ activity.privateComment.comments[app.user.id]|chill_markdown_to_html }} +
+
+
+
+ {% endif %} + + {% if is_granted('CHILL_ACTIVITY_SEE_DETAILS', activity) and activity.documents|length > 0 %} +
+
+

{{ 'Documents'|trans }}

+
+
+
    + {% for d in activity.documents %} +
  • {{ d.title|chill_print_or_message('document.Any title') }} {{ d|chill_document_button_group(d.title, is_granted('CHILL_ACTIVITY_UPDATE', activity), {small: true}) }}
  • + {% endfor %} +
+
+
+ {% endif %} + diff --git a/src/Bundle/ChillActivityBundle/Resources/views/Activity/concernedGroups.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/Activity/concernedGroups.html.twig index d9d72845a..48eb839d7 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/Activity/concernedGroups.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/Activity/concernedGroups.html.twig @@ -1,3 +1,14 @@ +{# + WARNING: this file is in use in both ActivityBundle and CalendarBundle. + + Take care when editing this file. + + Maybe should we think about abstracting this file a bit more ? Moving it to PersonBundle ? +#} +{% if context == 'calendar_accompanyingCourse' or context == 'calendar_person' %} + {% import "@ChillCalendar/_invite.html.twig" as invite %} +{% endif %} + {% macro href(pathname, key, value) %} {% set parms = { (key): value } %} {{ path(pathname, parms) }} @@ -18,7 +29,7 @@ {% endmacro %} {% set blocks = [] %} -{% if entity.activityType.personsVisible %} +{% if context == 'calendar_accompanyingCourse' or context == 'calendar_person' or entity.activityType.personsVisible %} {% if context == 'person' %} {% set blocks = blocks|merge([{ 'title': 'Others persons'|trans, @@ -43,7 +54,7 @@ }]) %} {% endif %} {% endif %} -{% if entity.activityType.thirdPartiesVisible %} +{% if context == 'calendar_accompanyingCourse' or context == 'calendar_person' or entity.activityType.thirdPartiesVisible %} {% set blocks = blocks|merge([{ 'title': 'Third parties'|trans, 'items': entity.thirdParties, @@ -52,7 +63,7 @@ 'key' : 'id', }]) %} {% endif %} -{% if entity.activityType.usersVisible %} +{% if context == 'calendar_accompanyingCourse' or context == 'calendar_person' or entity.activityType.usersVisible %} {% set blocks = blocks|merge([{ 'title': 'Users concerned'|trans, 'items': entity.users, @@ -132,6 +143,12 @@ {% if bloc.type == 'user' %} {{ item|chill_entity_render_box({'render': 'raw', 'addAltNames': false }) }} + {%- if context == 'calendar_accompanyingCourse' or context == 'calendar_person' %} + {% set invite = entity.inviteForUser(item) %} + {% if invite is not null %} + {{ invite.invite_span(invite) }} + {% endif %} + {%- endif -%} {% else %} {{ _self.insert_onthefly(bloc.type, item) }} diff --git a/src/Bundle/ChillActivityBundle/Resources/views/Activity/edit.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/Activity/edit.html.twig index 8d9ee878c..a000b0c7e 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/Activity/edit.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/Activity/edit.html.twig @@ -120,3 +120,11 @@ {{ form_end(edit_form) }} {# {{ form(delete_form) }} #} + +{% block js %} + {{ encore_entry_script_tags('mod_pickentity_type') }} +{% endblock %} + +{% block css %} + {{ encore_entry_link_tags('mod_pickentity_type') }} +{% endblock %} \ No newline at end of file diff --git a/src/Bundle/ChillActivityBundle/Resources/views/Activity/listAccompanyingCourse.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/Activity/listAccompanyingCourse.html.twig index a666f183d..bdf55d86f 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/Activity/listAccompanyingCourse.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/Activity/listAccompanyingCourse.html.twig @@ -8,11 +8,13 @@ {% block js %} {{ parent() }} {{ encore_entry_script_tags('mod_notification_toggle_read_status') }} + {{ encore_entry_script_tags('mod_document_action_buttons_group') }} {% endblock %} {% block css %} {{ parent() }} {{ encore_entry_link_tags('mod_notification_toggle_read_status') }} + {{ encore_entry_link_tags('mod_document_action_buttons_group') }} {% endblock %} {% block content %} diff --git a/src/Bundle/ChillActivityBundle/Resources/views/Activity/listPerson.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/Activity/listPerson.html.twig index f55c68fcc..0c1afdac9 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/Activity/listPerson.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/Activity/listPerson.html.twig @@ -23,11 +23,13 @@ {% block js %} {{ parent() }} {{ encore_entry_script_tags('mod_notification_toggle_read_status') }} + {{ encore_entry_script_tags('mod_document_action_buttons_group') }} {% endblock %} {% block css %} {{ parent() }} {{ encore_entry_link_tags('mod_notification_toggle_read_status') }} + {{ encore_entry_link_tags('mod_document_action_buttons_group') }} {% endblock %} {% block content %} @@ -46,7 +48,7 @@ {% include 'ChillActivityBundle:Activity:list.html.twig' with {'context': 'person'} %} - {% if is_granted('CHILL_ACTIVITY_CREATE_PERSON', person) %} + {% if is_granted('CHILL_ACTIVITY_CREATE', person) %} {{ form_end(form) }} + +{% block js %} + {{ encore_entry_script_tags('mod_pickentity_type') }} +{% endblock %} + +{% block css %} + {{ encore_entry_link_tags('mod_pickentity_type') }} +{% endblock %} \ No newline at end of file diff --git a/src/Bundle/ChillActivityBundle/Resources/views/Activity/show.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/Activity/show.html.twig index 49e71bfad..fca6a7658 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/Activity/show.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/Activity/show.html.twig @@ -34,8 +34,12 @@
-
{{ 'Referrer'|trans|capitalize }}
-
{{ entity.user|chill_entity_render_box }}
+ {%- if entity.user is not null %} +
{{ 'Referrer'|trans|capitalize }}
+
+ {{ entity.user|chill_entity_render_box }} +
+ {% endif %} {%- if entity.scope -%}
{{ 'Scope'|trans }}
@@ -156,7 +160,7 @@
- {{ entity.privateComment.comments[userId] }} + {{ entity.privateComment.comments[userId]|chill_markdown_to_html }}
@@ -168,11 +172,11 @@ {% if entity.documents|length > 0 %}
    {% for d in entity.documents %} -
  • {{ d.title }}{{ m.download_button(d) }}
  • +
  • {{ d.title|chill_print_or_message('document.Any title') }} {{ d|chill_document_button_group(d.title, is_granted('CHILL_ACTIVITY_UPDATE', entity), {small: true}) }}
  • {% endfor %}
{% else %} - {{ 'Any document found'|trans }} + {{ 'No document found'|trans }} {% endif %} {% endif %} diff --git a/src/Bundle/ChillActivityBundle/Resources/views/Activity/showAccompanyingCourse.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/Activity/showAccompanyingCourse.html.twig index fbd7b20b4..3486f47bc 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/Activity/showAccompanyingCourse.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/Activity/showAccompanyingCourse.html.twig @@ -8,12 +8,14 @@ {{ parent() }} {{ encore_entry_script_tags('mod_notification_toggle_read_status') }} {{ encore_entry_script_tags('mod_async_upload') }} + {{ encore_entry_script_tags('mod_document_action_buttons_group') }} {% endblock %} {% block css %} {{ parent() }} {{ encore_entry_link_tags('mod_notification_toggle_read_status') }} {{ encore_entry_link_tags('mod_async_upload') }} + {{ encore_entry_link_tags('mod_document_action_buttons_group') }} {% endblock %} {% import 'ChillActivityBundle:ActivityReason:macro.html.twig' as m %} diff --git a/src/Bundle/ChillActivityBundle/Resources/views/Activity/showPerson.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/Activity/showPerson.html.twig index 5776eddb5..43a8eb86b 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/Activity/showPerson.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/Activity/showPerson.html.twig @@ -7,13 +7,13 @@ {% block js %} {{ parent() }} {{ encore_entry_script_tags('mod_notification_toggle_read_status') }} - {{ encore_entry_link_tags('mod_async_upload') }} + {{ encore_entry_script_tags('mod_document_action_buttons_group') }} {% endblock %} {% block css %} {{ parent() }} {{ encore_entry_link_tags('mod_notification_toggle_read_status') }} - {{ encore_entry_link_tags('mod_async_upload') }} + {{ encore_entry_link_tags('mod_document_action_buttons_group') }} {% endblock %} {% import 'ChillActivityBundle:ActivityReason:macro.html.twig' as m %} diff --git a/src/Bundle/ChillActivityBundle/Resources/views/ActivityReason/index.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/ActivityReason/index.html.twig index 7d9e5f71d..855c9386d 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/ActivityReason/index.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/ActivityReason/index.html.twig @@ -7,22 +7,34 @@ {{ 'Name'|trans }} + {{ 'Active'|trans }} {{ 'Actions'|trans }} {% for entity in entities %} -
{{ entity.name|localize_translatable_string }} -
    -
  • - -
  • -
  • - -
  • -
+ {% if entity.category is not null -%} + {{ entity.category.name|localize_translatable_string }} > + {% endif -%} + {{ entity.name|localize_translatable_string }} + + + + {% if entity.active and not entity.isActiveAndParentActive %} + {{ 'Associated activity reason category is inactive'|trans }} + {% endif %} + + +
    +
  • + +
  • +
  • + +
  • +
{% endfor %} diff --git a/src/Bundle/ChillActivityBundle/Resources/views/ActivityReasonCategory/index.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/ActivityReasonCategory/index.html.twig index 473059124..5f48180b3 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/ActivityReasonCategory/index.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/ActivityReasonCategory/index.html.twig @@ -7,6 +7,7 @@ {{ 'Name'|trans }} + {{ 'Active'|trans }} {{ 'Actions'|trans }} @@ -14,7 +15,11 @@ {% for entity in entities %} - {{ entity.name|localize_translatable_string }} + {{ entity.name|localize_translatable_string }} + + + +
  • diff --git a/src/Bundle/ChillActivityBundle/Security/Authorization/ActivityStatsVoter.php b/src/Bundle/ChillActivityBundle/Security/Authorization/ActivityStatsVoter.php index 789e634e5..2e55f862c 100644 --- a/src/Bundle/ChillActivityBundle/Security/Authorization/ActivityStatsVoter.php +++ b/src/Bundle/ChillActivityBundle/Security/Authorization/ActivityStatsVoter.php @@ -1,22 +1,22 @@ helper = $helper; + $this->helper = $voterHelperFactory + ->generate(self::class) + ->addCheckFor(Center::class, [self::STATS, self::LISTS]) + ->build(); } public function getRoles(): array @@ -49,30 +49,14 @@ class ActivityStatsVoter extends AbstractChillVoter implements ProvideRoleHierar return $this->getAttributes(); } - protected function getSupportedClasses() - { - return [Center::class]; - } - - protected function isGranted($attribute, $object, $user = null) - { - if (!$user instanceof \Symfony\Component\Security\Core\User\UserInterface) { - return false; - } - - return $this->helper->userHasAccess($user, $object, $attribute); - } - protected function supports($attribute, $subject) { - if ( - $subject instanceof Center - && in_array($attribute, $this->getAttributes(), true) - ) { - return true; - } + return $this->helper->supports($attribute, $subject); + } - return false; + protected function voteOnAttribute($attribute, $subject, TokenInterface $token) + { + return $this->helper->voteOnAttribute($attribute, $subject, $token); } private function getAttributes() diff --git a/src/Bundle/ChillActivityBundle/Security/Authorization/ActivityVoter.php b/src/Bundle/ChillActivityBundle/Security/Authorization/ActivityVoter.php index 9911bf9fd..15d2441d5 100644 --- a/src/Bundle/ChillActivityBundle/Security/Authorization/ActivityVoter.php +++ b/src/Bundle/ChillActivityBundle/Security/Authorization/ActivityVoter.php @@ -1,14 +1,14 @@ voterHelper->voteOnAttribute(self::CREATE_PERSON, $subject->getPerson(), $token); } } elseif ($subject->getAccompanyingPeriod() instanceof AccompanyingPeriod) { if (!$this->security->isGranted(AccompanyingPeriodVoter::SEE, $subject->getAccompanyingPeriod())) { @@ -144,7 +144,8 @@ class ActivityVoter extends AbstractChillVoter implements ProvideRoleHierarchyIn if (AccompanyingPeriod::STEP_CLOSED === $subject->getAccompanyingPeriod()->getStep()) { return false; } - $attribute = self::CREATE_ACCOMPANYING_COURSE; + + return $this->voterHelper->voteOnAttribute(self::CREATE_ACCOMPANYING_COURSE, $subject->getAccompanyingPeriod(), $token); } } else { throw new RuntimeException('Could not determine context of activity.'); @@ -158,12 +159,12 @@ class ActivityVoter extends AbstractChillVoter implements ProvideRoleHierarchyIn // transform the attribute if (self::CREATE === $attribute) { - $attribute = self::CREATE_ACCOMPANYING_COURSE; + return $this->voterHelper->voteOnAttribute(self::CREATE_ACCOMPANYING_COURSE, $subject, $token); } } elseif ($subject instanceof Person) { // transform the attribute if (self::CREATE === $attribute) { - $attribute = self::CREATE_PERSON; + return $this->voterHelper->voteOnAttribute(self::CREATE_PERSON, $subject, $token); } } diff --git a/src/Bundle/ChillActivityBundle/Service/DocGenerator/ActivityContext.php b/src/Bundle/ChillActivityBundle/Service/DocGenerator/ActivityContext.php index 55d64ef93..624859eda 100644 --- a/src/Bundle/ChillActivityBundle/Service/DocGenerator/ActivityContext.php +++ b/src/Bundle/ChillActivityBundle/Service/DocGenerator/ActivityContext.php @@ -1,14 +1,14 @@ + */ class ActivityContext implements DocGeneratorContextWithAdminFormInterface, DocGeneratorContextWithPublicFormInterface @@ -45,6 +49,8 @@ class ActivityContext implements private PersonRenderInterface $personRender; + private PersonRepository $personRepository; + private TranslatableStringHelperInterface $translatableStringHelper; private TranslatorInterface $translator; @@ -55,6 +61,7 @@ class ActivityContext implements TranslatableStringHelperInterface $translatableStringHelper, EntityManagerInterface $em, PersonRenderInterface $personRender, + PersonRepository $personRepository, TranslatorInterface $translator, BaseContextData $baseContextData ) { @@ -63,6 +70,7 @@ class ActivityContext implements $this->translatableStringHelper = $translatableStringHelper; $this->em = $em; $this->personRender = $personRender; + $this->personRepository = $personRepository; $this->translator = $translator; $this->baseContextData = $baseContextData; } @@ -126,17 +134,43 @@ 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'], + 'placeholder' => $this->translator->trans('Any person selected'), ]); } } } + public function contextGenerationDataDenormalize(DocGeneratorTemplate $template, $entity, array $data): array + { + $denormalized = []; + + foreach (['mainPerson', 'person1', 'person2'] as $k) { + if (null !== ($id = ($data[$k] ?? null))) { + $denormalized[$k] = $this->personRepository->find($id); + } else { + $denormalized[$k] = null; + } + } + + return $denormalized; + } + + public function contextGenerationDataNormalize(DocGeneratorTemplate $template, $entity, array $data): array + { + $normalized = []; + + foreach (['mainPerson', 'person1', 'person2'] as $k) { + $normalized[$k] = null === $data[$k] ? null : $data[$k]->getId(); + } + + return $normalized; + } + public function getData(DocGeneratorTemplate $template, $entity, array $contextGenerationData = []): array { if (!$entity instanceof Activity) { @@ -145,7 +179,7 @@ class ActivityContext implements $options = $template->getOptions(); $data = []; - $data = array_merge($data, $this->baseContextData->getData()); + $data = array_merge($data, $this->baseContextData->getData($contextGenerationData['creator'] ?? null)); $data['activity'] = $this->normalizer->normalize($entity, 'docgen', ['docgen:expects' => Activity::class, 'groups' => 'docgen:read']); $data['course'] = $this->normalizer->normalize($entity->getAccompanyingPeriod(), 'docgen', ['docgen:expects' => AccompanyingPeriod::class, 'groups' => 'docgen:read']); @@ -204,9 +238,6 @@ class ActivityContext implements return $options['mainPerson'] || $options['person1'] || $options['person2']; } - /** - * @param Activity $entity - */ public function storeGenerated(DocGeneratorTemplate $template, StoredObject $storedObject, object $entity, array $contextGenerationData): void { $storedObject->setTitle($this->translatableStringHelper->localize($template->getName())); diff --git a/src/Bundle/ChillActivityBundle/Service/DocGenerator/ListActivitiesByAccompanyingPeriodContext.php b/src/Bundle/ChillActivityBundle/Service/DocGenerator/ListActivitiesByAccompanyingPeriodContext.php index 0a442cec7..3da451f2e 100644 --- a/src/Bundle/ChillActivityBundle/Service/DocGenerator/ListActivitiesByAccompanyingPeriodContext.php +++ b/src/Bundle/ChillActivityBundle/Service/DocGenerator/ListActivitiesByAccompanyingPeriodContext.php @@ -1,14 +1,14 @@ + * @implements DocGeneratorContextWithAdminFormInterface + */ class ListActivitiesByAccompanyingPeriodContext implements DocGeneratorContextWithAdminFormInterface, DocGeneratorContextWithPublicFormInterface @@ -69,7 +75,7 @@ class ListActivitiesByAccompanyingPeriodContext implements SocialIssueRepository $socialIssueRepository, ThirdPartyRepository $thirdPartyRepository, TranslatableStringHelperInterface $translatableStringHelper, - UserRepository $userRepository + UserRepository $userRepository, ) { $this->accompanyingPeriodContext = $accompanyingPeriodContext; $this->activityACLAwareRepository = $activityACLAwareRepository; @@ -100,14 +106,81 @@ class ListActivitiesByAccompanyingPeriodContext implements public function buildPublicForm(FormBuilderInterface $builder, DocGeneratorTemplate $template, $entity): void { $this->accompanyingPeriodContext->buildPublicForm($builder, $template, $entity); + + $builder + ->add('myActivitiesOnly', CheckboxType::class, [ + 'required' => false, + 'label' => 'docgen.myActivitiesOnly', + ]) + ->add('myWorksOnly', CheckboxType::class, [ + 'required' => false, + 'label' => 'docgen.myWorksOnly', + ]); } - public function getData(DocGeneratorTemplate $template, $entity, array $contextGenerationData = []): array + public function contextGenerationDataDenormalize(DocGeneratorTemplate $template, $entity, array $data): array + { + $denormalized = $this->accompanyingPeriodContext->contextGenerationDataDenormalize($template, $entity, $data); + + foreach (['myActivitiesOnly', 'myWorksOnly'] as $k) { + $denormalized[$k] = $data[$k]; + } + + return $denormalized; + } + + public function contextGenerationDataNormalize(DocGeneratorTemplate $template, $entity, array $data): array + { + $normalized = $this->accompanyingPeriodContext->contextGenerationDataNormalize($template, $entity, $data); + + foreach (['myActivitiesOnly', 'myWorksOnly'] as $k) { + $normalized[$k] = $data[$k] ?? false; + } + + return $normalized; + } + + private function filterActivitiesByUser(array $activities, User $user): array + { + return array_filter( + $activities, + function ($activity) use ($user) { + $activityUsernames = array_map(static fn ($user) => $user['username'], $activity['users'] ?? []); + return in_array($user->getUsername(), $activityUsernames, true); + } + ); + } + + private function filterWorksByUser(array $works, User $user): array + { + return array_filter( + $works, + function ($work) use ($user) { + $workUsernames = array_map(static fn ($user) => $user['username'], $work['referrers'] ?? []); + + return in_array($user->getUsername(), $workUsernames, true); + } + ); + } + + public function getData(DocGeneratorTemplate $template, object $entity, array $contextGenerationData = []): array { $data = $this->accompanyingPeriodContext->getData($template, $entity, $contextGenerationData); - $data['activities'] = $this->getActivitiesSimplified($entity); + $activities = $this->getActivitiesSimplified($entity); + $myActivitiesOnly = $contextGenerationData['myActivitiesOnly']; + if ($myActivitiesOnly && isset($contextGenerationData['creator'])) { + $activities = $this->filterActivitiesByUser($activities, $contextGenerationData['creator']); + } + + $data['activities'] = $activities; + + $myWorksOnly = $contextGenerationData['myWorksOnly']; + + if ($myWorksOnly && isset($contextGenerationData['creator'])) { + $data['course']['works'] = $this->filterWorksByUser($data['course']['works'], $contextGenerationData['creator']); + } return $data; } @@ -143,7 +216,7 @@ class ListActivitiesByAccompanyingPeriodContext implements public function hasPublicForm(DocGeneratorTemplate $template, $entity): bool { - return $this->accompanyingPeriodContext->hasPublicForm($template, $entity); + return true; } public function storeGenerated(DocGeneratorTemplate $template, StoredObject $storedObject, object $entity, array $contextGenerationData): void 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 @@ + */ -class ActivityReasonRender extends AbstractChillEntityRender +class ActivityReasonRender implements ChillEntityRenderInterface { + use BoxUtilsChillEntityRenderTrait; /** * @var TranslatableStringHelper */ @@ -51,9 +56,6 @@ class ActivityReasonRender extends AbstractChillEntityRender $this->getDefaultClosingBox(); } - /** - * @param ActivityReason $entity - */ public function renderString($entity, array $options): string { $category = ''; diff --git a/src/Bundle/ChillActivityBundle/Test/PrepareActivityTrait.php b/src/Bundle/ChillActivityBundle/Test/PrepareActivityTrait.php index 91f96dad8..3f0b0f4e6 100644 --- a/src/Bundle/ChillActivityBundle/Test/PrepareActivityTrait.php +++ b/src/Bundle/ChillActivityBundle/Test/PrepareActivityTrait.php @@ -1,14 +1,14 @@ getResponse()->getStatusCode(), 'Unexpected HTTP status code for GET /activity/' ); - $crawler = $client->click($crawler->selectLink('Ajouter une nouvelle activité') + $crawler = $client->click($crawler->selectLink('Ajouter un nouvel échange') ->link()); $reason1 = $this->getRandomActivityReason(); $reason2 = $this->getRandomActivityReason([$reason1->getId()]); // Fill in the form and submit it - $form = $crawler->selectButton('Ajouter une nouvelle activité')->form([ + $form = $crawler->selectButton('Ajouter un nouvel échange')->form([ 'chill_activitybundle_activity' => [ 'date' => '15-01-2015', 'durationTime' => 600, @@ -152,9 +152,9 @@ final class ActivityControllerTest extends WebTestCase ); // Edit the entity - $crawler = $client->click($crawler->selectLink("Modifier l'activité")->link()); + $crawler = $client->click($crawler->selectLink("Modifier l'échange")->link()); - $form = $crawler->selectButton("Sauver l'activité")->form([ + $form = $crawler->selectButton("Sauver l'échange")->form([ 'chill_activitybundle_activity' => [ 'date' => '25-01-2015', // 'remark' => 'Foo' @@ -234,7 +234,7 @@ final class ActivityControllerTest extends WebTestCase $user = new \Chill\MainBundle\Entity\User(); $user ->setPassword($container->get('security.password_encoder') - ->encodePassword($user, 'password')) + ->encodePassword($user, 'password')) ->setUsername($username) ->addGroupCenter($groupCenter); @@ -369,8 +369,8 @@ final class ActivityControllerTest extends WebTestCase $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) { diff --git a/src/Bundle/ChillActivityBundle/Tests/Controller/ActivityReasonCategoryControllerTest.php b/src/Bundle/ChillActivityBundle/Tests/Controller/ActivityReasonCategoryControllerTest.php index 0d64d0b65..14f75e863 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Controller/ActivityReasonCategoryControllerTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Controller/ActivityReasonCategoryControllerTest.php @@ -1,14 +1,14 @@ aggregator = self::$container->get('chill.activity.export.bysocialaction_aggregator'); + } + + public function getAggregator() + { + return $this->aggregator; + } + + public function getFormData(): array + { + return [ + [], + ]; + } + + public function getQueryBuilders(): array + { + if (null === self::$kernel) { + self::bootKernel(); + } + + $em = self::$container->get(EntityManagerInterface::class); + + return [ + $em->createQueryBuilder() + ->select('count(activity.id)') + ->from(Activity::class, 'activity') + ->join('activity.accompanyingPeriod', 'acp') + ->join('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 new file mode 100644 index 000000000..c8374d370 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ACPAggregators/BySocialIssueAggregatorTest.php @@ -0,0 +1,62 @@ +aggregator = self::$container->get('chill.activity.export.bysocialissue_aggregator'); + } + + public function getAggregator() + { + return $this->aggregator; + } + + public function getFormData(): array + { + return [ + [], + ]; + } + + public function getQueryBuilders(): array + { + if (null === self::$kernel) { + self::bootKernel(); + } + + $em = self::$container->get(EntityManagerInterface::class); + + return [ + $em->createQueryBuilder() + ->select('count(activity.id)') + ->from(Activity::class, 'activity') + ->join('activity.accompanyingPeriod', 'acp') + ->join('activity.socialIssues', 'actsocialissue'), + ]; + } +} diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ACPAggregators/ByThirdpartyAggregatorTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ACPAggregators/ByThirdpartyAggregatorTest.php new file mode 100644 index 000000000..e3bbde25f --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ACPAggregators/ByThirdpartyAggregatorTest.php @@ -0,0 +1,62 @@ +aggregator = self::$container->get('chill.activity.export.bythirdparty_aggregator'); + } + + public function getAggregator() + { + return $this->aggregator; + } + + public function getFormData(): array + { + return [ + [], + ]; + } + + public function getQueryBuilders(): array + { + if (null === self::$kernel) { + self::bootKernel(); + } + + $em = self::$container->get(EntityManagerInterface::class); + + return [ + $em->createQueryBuilder() + ->select('count(activity.id)') + ->from(Activity::class, 'activity') + ->join('activity.accompanyingPeriod', 'acp') + ->join('activity.thirdParties', 'acttparty'), + ]; + } +} diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ACPAggregators/ByUserAggregatorTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ACPAggregators/ByUserAggregatorTest.php new file mode 100644 index 000000000..ff4f42ec4 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ACPAggregators/ByUserAggregatorTest.php @@ -0,0 +1,62 @@ +aggregator = self::$container->get('chill.activity.export.byuser_aggregator'); + } + + public function getAggregator() + { + return $this->aggregator; + } + + public function getFormData(): array + { + return [ + [], + ]; + } + + public function getQueryBuilders(): array + { + if (null === self::$kernel) { + self::bootKernel(); + } + + $em = self::$container->get(EntityManagerInterface::class); + + return [ + $em->createQueryBuilder() + ->select('count(activity.id)') + ->from(Activity::class, 'activity') + ->join('activity.accompanyingPeriod', 'acp') + ->join('activity.users', 'actusers'), + ]; + } +} diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ACPAggregators/DateAggregatorTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ACPAggregators/DateAggregatorTest.php new file mode 100644 index 000000000..feb5ed970 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ACPAggregators/DateAggregatorTest.php @@ -0,0 +1,69 @@ +aggregator = self::$container->get('chill.activity.export.date_aggregator'); + } + + public function getAggregator() + { + return $this->aggregator; + } + + public function getFormData(): array + { + return [ + [ + 'frequency' => 'month', + ], + [ + 'frequency' => 'week', + ], + [ + 'frequency' => 'year', + ], + ]; + } + + public function getQueryBuilders(): array + { + if (null === self::$kernel) { + self::bootKernel(); + } + + $em = self::$container->get(EntityManagerInterface::class); + + return [ + $em->createQueryBuilder() + ->select('count(activity.id)') + ->from(Activity::class, 'activity') + ->join('activity.accompanyingPeriod', 'acp'), + ]; + } +} diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ACPAggregators/LocationTypeAggregatorTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ACPAggregators/LocationTypeAggregatorTest.php new file mode 100644 index 000000000..c384eb772 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ACPAggregators/LocationTypeAggregatorTest.php @@ -0,0 +1,62 @@ +aggregator = self::$container->get('chill.activity.export.locationtype_aggregator'); + } + + public function getAggregator() + { + return $this->aggregator; + } + + public function getFormData(): array + { + return [ + [], + ]; + } + + public function getQueryBuilders(): array + { + if (null === self::$kernel) { + self::bootKernel(); + } + + $em = self::$container->get(EntityManagerInterface::class); + + return [ + $em->createQueryBuilder() + ->select('count(activity.id)') + ->from(Activity::class, 'activity') + ->join('activity.accompanyingPeriod', 'acp') + ->join('activity.location', 'actloc'), + ]; + } +} diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ACPAggregators/UserScopeAggregatorTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ACPAggregators/UserScopeAggregatorTest.php new file mode 100644 index 000000000..1265804f9 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ACPAggregators/UserScopeAggregatorTest.php @@ -0,0 +1,62 @@ +aggregator = self::$container->get('chill.activity.export.userscope_aggregator'); + } + + public function getAggregator() + { + return $this->aggregator; + } + + public function getFormData(): array + { + return [ + [], + ]; + } + + public function getQueryBuilders(): array + { + if (null === self::$kernel) { + self::bootKernel(); + } + + $em = self::$container->get(EntityManagerInterface::class); + + return [ + $em->createQueryBuilder() + ->select('count(activity.id)') + ->from(Activity::class, 'activity') + ->join('activity.accompanyingPeriod', 'acp') + ->join('activity.user', 'actuser'), + ]; + } +} diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ActivityTypeAggregatorTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ActivityTypeAggregatorTest.php index f6efe17a5..31633b9a3 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ActivityTypeAggregatorTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ActivityTypeAggregatorTest.php @@ -1,17 +1,20 @@ getContainer(); + $this->aggregator = self::$container->get('chill.activity.export.type_aggregator'); - $this->aggregator = $container->get('chill.activity.export.type_aggregator'); + $request = $this->prophesize() + ->willExtend(\Symfony\Component\HttpFoundation\Request::class); - // add a fake request with a default locale (used in translatable string) - $prophet = new \Prophecy\Prophet(); - $request = $prophet->prophesize(); - $request->willExtend(\Symfony\Component\HttpFoundation\Request::class); $request->getLocale()->willReturn('fr'); - $container->get('request_stack') + self::$container->get('request_stack') ->push($request->reveal()); } @@ -62,8 +61,7 @@ final class ActivityTypeAggregatorTest extends AbstractAggregatorTest self::bootKernel(); } - $em = self::$kernel->getContainer() - ->get('doctrine.orm.entity_manager'); + $em = self::$container->get(EntityManagerInterface::class); return [ $em->createQueryBuilder() @@ -72,12 +70,7 @@ final class ActivityTypeAggregatorTest extends AbstractAggregatorTest $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'), + ->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 1447f473b..06542aa41 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ActivityUserAggregatorTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ActivityUserAggregatorTest.php @@ -1,17 +1,20 @@ getContainer(); + $this->aggregator = self::$container->get('chill.activity.export.user_aggregator'); - $this->aggregator = $container->get('chill.activity.export.user_aggregator'); + $request = $this->prophesize() + ->willExtend(\Symfony\Component\HttpFoundation\Request::class); - // add a fake request with a default locale (used in translatable string) - $prophet = new \Prophecy\Prophet(); - $request = $prophet->prophesize(); - $request->willExtend(\Symfony\Component\HttpFoundation\Request::class); $request->getLocale()->willReturn('fr'); - $container->get('request_stack') + self::$container->get('request_stack') ->push($request->reveal()); } @@ -49,35 +48,25 @@ final class ActivityUserAggregatorTest extends AbstractAggregatorTest return $this->aggregator; } - public function getFormData() + public function getFormData(): array { return [ [], ]; } - public function getQueryBuilders() + public function getQueryBuilders(): array { if (null === self::$kernel) { 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'), ]; } } diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ActivityReasonAggregatorTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/PersonAggregators/ActivityReasonAggregatorTest.php similarity index 57% rename from src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ActivityReasonAggregatorTest.php rename to src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/PersonAggregators/ActivityReasonAggregatorTest.php index 436bfc697..0a61e983f 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ActivityReasonAggregatorTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/PersonAggregators/ActivityReasonAggregatorTest.php @@ -1,46 +1,43 @@ getContainer(); + $this->aggregator = self::$container->get('chill.activity.export.reason_aggregator'); - $this->aggregator = $container->get('chill.activity.export.reason_aggregator'); + $request = $this->prophesize() + ->willExtend(\Symfony\Component\HttpFoundation\Request::class); - // add a fake request with a default locale (used in translatable string) - $prophet = new \Prophecy\Prophet(); - $request = $prophet->prophesize(); - $request->willExtend(\Symfony\Component\HttpFoundation\Request::class); $request->getLocale()->willReturn('fr'); - $container->get('request_stack') + self::$container->get('request_stack') ->push($request->reveal()); } @@ -49,7 +46,7 @@ final class ActivityReasonAggregatorTest extends AbstractAggregatorTest return $this->aggregator; } - public function getFormData() + public function getFormData(): array { return [ ['level' => 'reasons'], @@ -57,14 +54,13 @@ final class ActivityReasonAggregatorTest extends AbstractAggregatorTest ]; } - public function getQueryBuilders() + public function getQueryBuilders(): array { if (null === self::$kernel) { self::bootKernel(); } - $em = self::$kernel->getContainer() - ->get('doctrine.orm.entity_manager'); + $em = self::$container->get(EntityManagerInterface::class); return [ $em->createQueryBuilder() @@ -73,12 +69,12 @@ final class ActivityReasonAggregatorTest extends AbstractAggregatorTest $em->createQueryBuilder() ->select('count(activity.id)') ->from('ChillActivityBundle:Activity', 'activity') - ->join('activity.reasons', 'reasons'), + ->join('activity.reasons', 'actreasons'), $em->createQueryBuilder() ->select('count(activity.id)') ->from('ChillActivityBundle:Activity', 'activity') - ->join('activity.reasons', 'reasons') - ->join('reasons.category', 'category'), + ->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 new file mode 100644 index 000000000..2eb2176b0 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/AvgActivityDurationTest.php @@ -0,0 +1,51 @@ +export = self::$container->get('chill.activity.export.avg_activity_duration_linked_to_acp'); + } + + public function getExport() + { + return $this->export; + } + + public function getFormData(): array + { + return [ + [], + ]; + } + + public function getModifiersCombination(): array + { + return [ + ['activity'], + ['activity', 'accompanying_period'], + ]; + } +} diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/AvgActivityVisitDurationTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/AvgActivityVisitDurationTest.php new file mode 100644 index 000000000..59b4384cf --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/AvgActivityVisitDurationTest.php @@ -0,0 +1,51 @@ +export = self::$container->get('chill.activity.export.avg_activity_visit_duration_linked_to_acp'); + } + + public function getExport() + { + return $this->export; + } + + public function getFormData(): array + { + return [ + [], + ]; + } + + public function getModifiersCombination(): array + { + return [ + ['activity'], + ['activity', 'accompanying_period'], + ]; + } +} diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/CountActivityTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/CountActivityTest.php new file mode 100644 index 000000000..f03bbb956 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/CountActivityTest.php @@ -0,0 +1,51 @@ +export = self::$container->get('chill.activity.export.count_activity_linked_to_acp'); + } + + public function getExport() + { + return $this->export; + } + + public function getFormData(): array + { + return [ + [], + ]; + } + + public function getModifiersCombination(): array + { + return [ + ['activity'], + ['activity', 'accompanying_period'], + ]; + } +} diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/SumActivityDurationTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/SumActivityDurationTest.php new file mode 100644 index 000000000..24a5133b6 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/SumActivityDurationTest.php @@ -0,0 +1,51 @@ +export = self::$container->get('chill.activity.export.sum_activity_duration_linked_to_acp'); + } + + public function getExport() + { + return $this->export; + } + + public function getFormData(): array + { + return [ + [], + ]; + } + + public function getModifiersCombination(): array + { + return [ + ['activity'], + ['activity', 'accompanying_period'], + ]; + } +} diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/SumActivityVisitDurationTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/SumActivityVisitDurationTest.php new file mode 100644 index 000000000..1940140ac --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToACP/SumActivityVisitDurationTest.php @@ -0,0 +1,51 @@ +export = self::$container->get('chill.activity.export.sum_activity_visit_duration_linked_to_acp'); + } + + public function getExport() + { + return $this->export; + } + + public function getFormData(): array + { + return [ + [], + ]; + } + + public function getModifiersCombination(): array + { + return [ + ['activity'], + ['activity', 'accompanying_period'], + ]; + } +} diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Export/CountActivityTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToPerson/CountActivityTest.php similarity index 62% rename from src/Bundle/ChillActivityBundle/Tests/Export/Export/CountActivityTest.php rename to src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToPerson/CountActivityTest.php index cbf562dfa..864ff55ba 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Export/CountActivityTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToPerson/CountActivityTest.php @@ -1,16 +1,17 @@ getContainer(); - - $this->export = $container->get('chill.activity.export.count_activity'); + $this->export = self::$container->get('chill.activity.export.count_activity_linked_to_person'); } public function getExport() @@ -39,14 +34,14 @@ final class CountActivityTest extends AbstractExportTest return $this->export; } - public function getFormData() + public function getFormData(): array { return [ [], ]; } - public function getModifiersCombination() + public function getModifiersCombination(): array { return [ ['activity'], diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Export/ListActivityTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToPerson/ListActivityTest.php similarity index 65% rename from src/Bundle/ChillActivityBundle/Tests/Export/Export/ListActivityTest.php rename to src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToPerson/ListActivityTest.php index 46d23b023..afa3da111 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Export/ListActivityTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToPerson/ListActivityTest.php @@ -1,17 +1,19 @@ getContainer(); + $this->export = self::$container->get('chill.activity.export.list_activity_linked_to_person'); - $this->export = $container->get('chill.activity.export.list_activity'); + $request = $this->prophesize() + ->willExtend(\Symfony\Component\HttpFoundation\Request::class); - // add a fake request with a default locale (used in translatable string) - $prophet = new \Prophecy\Prophet(); - $request = $prophet->prophesize(); - $request->willExtend(\Symfony\Component\HttpFoundation\Request::class); $request->getLocale()->willReturn('fr'); - $container->get('request_stack') + self::$container->get('request_stack') ->push($request->reveal()); } diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Export/StatActivityDurationSumTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToPerson/StatActivityDurationTest.php similarity index 55% rename from src/Bundle/ChillActivityBundle/Tests/Export/Export/StatActivityDurationSumTest.php rename to src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToPerson/StatActivityDurationTest.php index c69a31b5a..44d327ffd 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Export/StatActivityDurationSumTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Export/LinkedToPerson/StatActivityDurationTest.php @@ -1,16 +1,17 @@ getContainer(); - - $this->export = $container->get('chill.activity.export.sum_activity_duration'); + $this->export = self::$container->get('chill.activity.export.sum_activity_duration_linked_to_person'); } public function getExport() @@ -41,14 +36,14 @@ final class StatActivityDurationSumTest extends AbstractExportTest return $this->export; } - public function getFormData() + public function getFormData(): array { return [ [], ]; } - public function getModifiersCombination() + public function getModifiersCombination(): array { return [ ['activity'], diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/ActivityTypeFilterTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/ActivityTypeFilterTest.php new file mode 100644 index 000000000..72b99375b --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/ActivityTypeFilterTest.php @@ -0,0 +1,79 @@ +filter = self::$container->get('chill.activity.export.filter_activitytype'); + } + + public function getFilter() + { + return $this->filter; + } + + public function getFormData(): array + { + $em = self::$container->get(EntityManagerInterface::class); + + $array = $em->createQueryBuilder() + ->from(ActivityType::class, 'at') + ->select('at') + ->getQuery() + ->getResult(); + + $data = []; + + foreach ($array as $a) { + $data[] = [ + 'accepted_activitytypes' => $a, + ]; + } + + return $data; + } + + public function getQueryBuilders(): array + { + if (null === self::$kernel) { + self::bootKernel(); + } + + $em = self::$container->get(EntityManagerInterface::class); + + return [ + $em->createQueryBuilder() + ->select('count(activity.id)') + ->from(AccompanyingPeriod::class, 'acp') + ->join(Activity::class, 'activity', Expr\Join::WITH, 'activity.accompanyingPeriod = acp') + ->join('activity.activityType', 'acttype'), + ]; + } +} diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/BySocialActionFilterTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/BySocialActionFilterTest.php new file mode 100644 index 000000000..a707e1242 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/BySocialActionFilterTest.php @@ -0,0 +1,76 @@ +filter = self::$container->get('chill.activity.export.bysocialaction_filter'); + } + + public function getFilter() + { + return $this->filter; + } + + public function getFormData(): array + { + $em = self::$container->get(EntityManagerInterface::class); + + $array = $em->createQueryBuilder() + ->from(SocialAction::class, 'sa') + ->select('sa') + ->getQuery() + ->getResult(); + + $data = []; + + foreach ($array as $a) { + $data[] = [ + 'accepted_socialactions' => $a, + ]; + } + + return $data; + } + + public function getQueryBuilders(): array + { + if (null === self::$kernel) { + self::bootKernel(); + } + + $em = self::$container->get(EntityManagerInterface::class); + + return [ + $em->createQueryBuilder() + ->select('count(activity.id)') + ->from(Activity::class, 'activity') + ->join('activity.socialActions', 'actsocialaction'), + ]; + } +} diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/BySocialIssueFilterTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/BySocialIssueFilterTest.php new file mode 100644 index 000000000..5f34a8f3b --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/BySocialIssueFilterTest.php @@ -0,0 +1,76 @@ +filter = self::$container->get('chill.activity.export.bysocialissue_filter'); + } + + public function getFilter() + { + return $this->filter; + } + + public function getFormData(): array + { + $em = self::$container->get(EntityManagerInterface::class); + + $array = $em->createQueryBuilder() + ->from(SocialIssue::class, 'si') + ->select('si') + ->getQuery() + ->getResult(); + + $data = []; + + foreach ($array as $a) { + $data[] = [ + 'accepted_socialissues' => $a, + ]; + } + + return $data; + } + + public function getQueryBuilders(): array + { + if (null === self::$kernel) { + self::bootKernel(); + } + + $em = self::$container->get(EntityManagerInterface::class); + + return [ + $em->createQueryBuilder() + ->select('count(activity.id)') + ->from(Activity::class, 'activity') + ->join('activity.socialIssues', 'actsocialissue'), + ]; + } +} diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/ByUserFilterTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/ByUserFilterTest.php new file mode 100644 index 000000000..47e76e25c --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/ByUserFilterTest.php @@ -0,0 +1,76 @@ +filter = self::$container->get('chill.activity.export.byuser_filter'); + } + + public function getFilter() + { + return $this->filter; + } + + public function getFormData(): array + { + $em = self::$container->get(EntityManagerInterface::class); + + $array = $em->createQueryBuilder() + ->from(User::class, 'u') + ->select('u') + ->getQuery() + ->getResult(); + + $data = []; + + foreach ($array as $a) { + $data[] = [ + 'accepted_users' => $a, + ]; + } + + return $data; + } + + public function getQueryBuilders(): array + { + if (null === self::$kernel) { + 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/EmergencyFilterTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/EmergencyFilterTest.php new file mode 100644 index 000000000..5242f337a --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/EmergencyFilterTest.php @@ -0,0 +1,61 @@ +filter = self::$container->get('chill.activity.export.emergency_filter'); + } + + public function getFilter() + { + return $this->filter; + } + + public function getFormData(): array + { + return [ + ['accepted_emergency' => true], + ['accepted_emergency' => false], + ]; + } + + public function getQueryBuilders(): array + { + if (null === self::$kernel) { + self::bootKernel(); + } + + $em = self::$container->get(EntityManagerInterface::class); + + return [ + $em->createQueryBuilder() + ->select('count(activity.id)') + ->from(Activity::class, 'activity'), + ]; + } +} diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/LocationTypeFilterTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/LocationTypeFilterTest.php new file mode 100644 index 000000000..c037a28b1 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/LocationTypeFilterTest.php @@ -0,0 +1,76 @@ +filter = self::$container->get('chill.activity.export.locationtype_filter'); + } + + public function getFilter() + { + return $this->filter; + } + + public function getFormData(): array + { + $em = self::$container->get(EntityManagerInterface::class); + + $array = $em->createQueryBuilder() + ->from(LocationType::class, 'lt') + ->select('lt') + ->getQuery() + ->getResult(); + + $data = []; + + foreach ($array as $a) { + $data[] = [ + 'accepted_locationtype' => $a, + ]; + } + + return $data; + } + + public function getQueryBuilders(): array + { + if (null === self::$kernel) { + self::bootKernel(); + } + + $em = self::$container->get(EntityManagerInterface::class); + + return [ + $em->createQueryBuilder() + ->select('count(activity.id)') + ->from(Activity::class, 'activity') + ->join('activity.location', 'actloc'), + ]; + } +} diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/SentReceivedFilterTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/SentReceivedFilterTest.php new file mode 100644 index 000000000..6b16daa99 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/SentReceivedFilterTest.php @@ -0,0 +1,61 @@ +filter = self::$container->get('chill.activity.export.sentreceived_filter'); + } + + public function getFilter() + { + return $this->filter; + } + + public function getFormData(): array + { + return [ + ['accepted_sentreceived' => Activity::SENTRECEIVED_SENT], + ['accepted_sentreceived' => Activity::SENTRECEIVED_RECEIVED], + ]; + } + + public function getQueryBuilders(): array + { + if (null === self::$kernel) { + self::bootKernel(); + } + + $em = self::$container->get(EntityManagerInterface::class); + + return [ + $em->createQueryBuilder() + ->select('count(activity.id)') + ->from(Activity::class, 'activity'), + ]; + } +} diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/UserFilterTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/UserFilterTest.php new file mode 100644 index 000000000..fda613d2e --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/UserFilterTest.php @@ -0,0 +1,75 @@ +filter = self::$container->get('chill.activity.export.user_filter'); + } + + public function getFilter() + { + return $this->filter; + } + + public function getFormData(): array + { + $em = self::$container->get(EntityManagerInterface::class); + + $array = $em->createQueryBuilder() + ->from(User::class, 'u') + ->select('u') + ->getQuery() + ->getResult(); + + $data = []; + + foreach ($array as $a) { + $data[] = [ + 'accepted_users' => $a, + ]; + } + + return $data; + } + + public function getQueryBuilders(): array + { + if (null === self::$kernel) { + self::bootKernel(); + } + + $em = self::$container->get(EntityManagerInterface::class); + + return [ + $em->createQueryBuilder() + ->select('count(activity.id)') + ->from(Activity::class, 'activity'), + ]; + } +} diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/UserScopeFilterTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/UserScopeFilterTest.php new file mode 100644 index 000000000..5742662b2 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/UserScopeFilterTest.php @@ -0,0 +1,76 @@ +filter = self::$container->get('chill.activity.export.userscope_filter'); + } + + public function getFilter() + { + return $this->filter; + } + + public function getFormData(): array + { + $em = self::$container->get(EntityManagerInterface::class); + + $array = $em->createQueryBuilder() + ->from(Scope::class, 's') + ->select('s') + ->getQuery() + ->getResult(); + + $data = []; + + foreach ($array as $a) { + $data[] = [ + 'accepted_userscope' => $a, + ]; + } + + return $data; + } + + public function getQueryBuilders(): array + { + if (null === self::$kernel) { + self::bootKernel(); + } + + $em = self::$container->get(EntityManagerInterface::class); + + return [ + $em->createQueryBuilder() + ->select('count(activity.id)') + ->from(Activity::class, 'activity') + ->join('activity.user', 'actuser'), + ]; + } +} diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ActivityDateFilterTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ActivityDateFilterTest.php new file mode 100644 index 000000000..8d6d6b790 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ActivityDateFilterTest.php @@ -0,0 +1,64 @@ +filter = self::$container->get('chill.activity.export.date_filter'); + } + + public function getFilter() + { + return $this->filter; + } + + public function getFormData(): array + { + return [ + [ + 'date_from' => DateTime::createFromFormat('Y-m-d', '2020-01-01'), + 'date_to' => DateTime::createFromFormat('Y-m-d', '2021-01-01'), + ], + ]; + } + + public function getQueryBuilders(): array + { + if (null === self::$kernel) { + self::bootKernel(); + } + + $em = self::$container->get(EntityManagerInterface::class); + + return [ + $em->createQueryBuilder() + ->select('count(activity.id)') + ->from(Activity::class, 'activity'), + ]; + } +} diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ActivityReasonFilterTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ActivityReasonFilterTest.php index 5b8ae08c3..26d5248ae 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ActivityReasonFilterTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ActivityReasonFilterTest.php @@ -1,18 +1,20 @@ getContainer(); + $this->filter = self::$container->get('chill.activity.export.reason_filter'); - $this->filter = $container->get('chill.activity.export.reason_filter'); + $request = $this->prophesize() + ->willExtend(\Symfony\Component\HttpFoundation\Request::class); - // add a fake request with a default locale (used in translatable string) - $prophet = new \Prophecy\Prophet(); - $request = $prophet->prophesize(); - $request->willExtend(\Symfony\Component\HttpFoundation\Request::class); $request->getLocale()->willReturn('fr'); - $container->get('request_stack') + self::$container->get('request_stack') ->push($request->reveal()); } diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ActivityTypeFilterTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ActivityTypeFilterTest.php new file mode 100644 index 000000000..9fd1250ca --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ActivityTypeFilterTest.php @@ -0,0 +1,75 @@ +filter = self::$container->get('chill.activity.export.type_filter'); + } + + public function getFilter() + { + return $this->filter; + } + + public function getFormData(): array + { + $em = self::$container->get(EntityManagerInterface::class); + + $array = $em->createQueryBuilder() + ->from(ActivityType::class, 'at') + ->select('at') + ->getQuery() + ->getResult(); + + $data = []; + + foreach ($array as $a) { + $data[] = [ + 'types' => $a, + ]; + } + + return $data; + } + + public function getQueryBuilders(): array + { + if (null === self::$kernel) { + self::bootKernel(); + } + + $em = self::$container->get(EntityManagerInterface::class); + + return [ + $em->createQueryBuilder() + ->select('count(activity.id)') + ->from(Activity::class, 'activity'), + ]; + } +} diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/PersonFilters/ActivityReasonFilterTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/PersonFilters/ActivityReasonFilterTest.php new file mode 100644 index 000000000..b4e962ba9 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/PersonFilters/ActivityReasonFilterTest.php @@ -0,0 +1,76 @@ +filter = self::$container->get('chill.activity.export.reason_filter'); + } + + public function getFilter() + { + return $this->filter; + } + + public function getFormData(): array + { + $em = self::$container->get(EntityManagerInterface::class); + + $array = $em->createQueryBuilder() + ->from(ActivityReason::class, 'ar') + ->select('ar') + ->getQuery() + ->getResult(); + + $data = []; + + foreach ($array as $a) { + $data[] = [ + 'reasons' => $a, + ]; + } + + return $data; + } + + public function getQueryBuilders(): array + { + if (null === self::$kernel) { + self::bootKernel(); + } + + $em = self::$container->get(EntityManagerInterface::class); + + return [ + $em->createQueryBuilder() + ->select('count(activity.id)') + ->from(Activity::class, 'activity') + ->join('activity.reasons', 'actreasons'), + ]; + } +} diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/PersonFilters/PersonHavingActivityBetweenDateFilterTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/PersonFilters/PersonHavingActivityBetweenDateFilterTest.php new file mode 100644 index 000000000..eb296e9a4 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/PersonFilters/PersonHavingActivityBetweenDateFilterTest.php @@ -0,0 +1,78 @@ +filter = self::$container->get('chill.activity.export.person_having_an_activity_between_date_filter'); + } + + public function getFilter() + { + return $this->filter; + } + + public function getFormData(): array + { + $em = self::$container->get(EntityManagerInterface::class); + + $array = $em->createQueryBuilder() + ->from(ActivityReason::class, 'ar') + ->select('ar') + ->getQuery() + ->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, + ]; + } + + return $data; + } + + public function getQueryBuilders(): array + { + if (null === self::$kernel) { + self::bootKernel(); + } + + $em = self::$container->get(EntityManagerInterface::class); + + return [ + $em->createQueryBuilder() + ->select('count(activity.id)') + ->from(Activity::class, 'activity'), + ]; + } +} diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/PersonHavingActivityBetweenDateFilterTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/PersonHavingActivityBetweenDateFilterTest.php index 94d99c2a7..762de529e 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/PersonHavingActivityBetweenDateFilterTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/PersonHavingActivityBetweenDateFilterTest.php @@ -1,16 +1,17 @@ getContainer(); + $this->filter = self::$container->get('chill.activity.export.person_having_an_activity_between_date_filter'); - $this->filter = $container->get('chill.activity.export.' - . 'person_having_an_activity_between_date_filter'); + $request = $this->prophesize() + ->willExtend(\Symfony\Component\HttpFoundation\Request::class); - // add a fake request with a default locale (used in translatable string) - $prophet = new \Prophecy\Prophet(); - $request = $prophet->prophesize(); - $request->willExtend(\Symfony\Component\HttpFoundation\Request::class); $request->getLocale()->willReturn('fr'); - $container->get('request_stack') + self::$container->get('request_stack') ->push($request->reveal()); } diff --git a/src/Bundle/ChillActivityBundle/Tests/Form/ActivityTypeTest.php b/src/Bundle/ChillActivityBundle/Tests/Form/ActivityTypeTest.php index 0a6518018..3ed6b4409 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Form/ActivityTypeTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Form/ActivityTypeTest.php @@ -1,14 +1,14 @@ true, ]]); -// var_dump($form->getErrors()->count()); var_dump($form->isValid()); -// foreach($form->getErrors() as $e) { fwrite(STDOUT, var_dump($e->getMessage())); } -// var_dump($form->getErrors()); + // var_dump($form->getErrors()->count()); var_dump($form->isValid()); + // foreach($form->getErrors() as $e) { fwrite(STDOUT, var_dump($e->getMessage())); } + // var_dump($form->getErrors()); $this->assertTrue($form->isSynchronized(), 'Test the form is synchronized'); $this->assertTrue($form->isValid(), 'test the form is valid'); @@ -188,7 +188,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 76e8e438c..a8d85daa6 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Form/Type/TranslatableActivityReasonTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Form/Type/TranslatableActivityReasonTest.php @@ -1,17 +1,17 @@ getTranslatableStringHelper() ); @@ -79,15 +79,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 7493bc5f4..770b88cf4 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Form/Type/TranslatableActivityTypeTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Form/Type/TranslatableActivityTypeTest.php @@ -1,14 +1,14 @@ 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 d069c05f4..664196ad4 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Timeline/TimelineProviderTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Timeline/TimelineProviderTest.php @@ -1,14 +1,14 @@ addSql('ALTER TABLE activity DROP updatedAt'); + $this->addSql('ALTER TABLE activity DROP createdAt'); + $this->addSql('ALTER TABLE activity DROP updatedBy_id'); + $this->addSql('ALTER TABLE activity DROP createdBy_id'); + + // rename some indexes on activity + $this->addSql('ALTER INDEX idx_ac74095a217bbb47 RENAME TO idx_55026b0c217bbb47'); + $this->addSql('ALTER INDEX idx_ac74095a682b5931 RENAME TO idx_55026b0c682b5931'); + $this->addSql('ALTER INDEX idx_ac74095aa76ed395 RENAME TO idx_55026b0ca76ed395'); + $this->addSql('ALTER INDEX idx_ac74095ac54c8c93 RENAME TO idx_55026b0cc54c8c93'); + } + + public function getDescription(): string + { + return 'Track update and create on activity'; + } + + public function up(Schema $schema): void + { + $this->addSql('ALTER TABLE activity ADD updatedAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL'); + $this->addSql('ALTER TABLE activity ADD createdAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL'); + $this->addSql('ALTER TABLE activity ADD updatedBy_id INT DEFAULT NULL'); + $this->addSql('ALTER TABLE activity ADD createdBy_id INT DEFAULT NULL'); + $this->addSql('COMMENT ON COLUMN activity.updatedAt IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('COMMENT ON COLUMN activity.createdAt IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('ALTER TABLE activity ADD CONSTRAINT FK_AC74095A65FF1AEC FOREIGN KEY (updatedBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE activity ADD CONSTRAINT FK_AC74095A3174800F FOREIGN KEY (createdBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('CREATE INDEX IDX_AC74095A65FF1AEC ON activity (updatedBy_id)'); + $this->addSql('CREATE INDEX IDX_AC74095A3174800F ON activity (createdBy_id)'); + + // rename some indexes on activity + $this->addSql('ALTER INDEX idx_55026b0cc54c8c93 RENAME TO IDX_AC74095AC54C8C93'); + $this->addSql('ALTER INDEX idx_55026b0c217bbb47 RENAME TO IDX_AC74095A217BBB47'); + $this->addSql('ALTER INDEX idx_55026b0c682b5931 RENAME TO IDX_AC74095A682B5931'); + $this->addSql('ALTER INDEX idx_55026b0ca76ed395 RENAME TO IDX_AC74095AA76ED395'); + + $this->addSql('UPDATE activity SET updatedBy_id=user_id, createdBy_id=user_id, createdAt="date", updatedAt="date"'); + } +} diff --git a/src/Bundle/ChillActivityBundle/translations/messages.fr.yml b/src/Bundle/ChillActivityBundle/translations/messages.fr.yml index 601455279..e08e1fb62 100644 --- a/src/Bundle/ChillActivityBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillActivityBundle/translations/messages.fr.yml @@ -1,41 +1,41 @@ #general -Show the activity: Voir l'activité -Edit the activity: Modifier l'activité -Activity: Activité +Show the activity: Voir l'échange +Edit the activity: Modifier l'échange +Activity: Échange Duration time: Durée Duration Time: Durée durationTime: durée Travel time: Durée de déplacement -Attendee: Présence de la personne -attendee: présence de la personne +Attendee: Présence de l'usager +attendee: présence de l'usager list_reasons: liste des sujets user_username: nom de l'utilisateur circle_name: nom du cercle Remark: Commentaire No comments: Aucun commentaire -Add a new activity: Ajouter une nouvelle activité -Activity list: Liste des activités +Add a new activity: Ajouter une nouvel échange +Activity list: Liste des échanges present: présent not present: absent Delete: Supprimer Update: Mettre à jour -Update activity: Modifier l'activité +Update activity: Modifier l'échange Scope: Cercle -Activity data: Données de l'activité -Activity location: Localisation de l'activité +Activity data: Données de l'échange +Activity location: Localisation de l'échange No reason associated: Aucun sujet No social issues associated: Aucune problématique sociale No social actions associated: Aucune action d'accompagnement -There isn't any activities.: Aucune activité enregistrée. -type_name: type de l'activité +There isn't any activities.: Aucun échange enregistré. +type_name: type de l'échange person_firstname: prénom person_lastname: nom de famille -person_id: identifiant de la personne +person_id: identifiant de l'usager Type: Type Invisible: Invisible Optional: Optionnel Required: Obligatoire -Persons: Personnes +Persons: Usagers Users: Utilisateurs Emergency: Urgent Sent received: Entrant / Sortant @@ -45,13 +45,15 @@ by: 'Par ' location: Lieu Reasons: Sujets Private comment: Commentaire privé +sent: Envoyé +received: Reçu #forms -Activity creation: Nouvelle activité +Activity creation: Nouvel échange Create: Créer Back to the list: Retour à la liste -Save activity: Sauver l'activité +Save activity: Sauver l'échange Reset form: Remise à zéro du formulaire Choose the duration: Choisir la durée Choose a type: Choisir un type @@ -75,7 +77,7 @@ Choose a type: Choisir un type 4 hours: 4 heures 4 hours 30: 4 heures 30 5 hours: 5 heures -Concerned groups: Parties concernées +Concerned groups: Parties concernées par l'échange Persons in accompanying course: Usagers du parcours Third persons: Tiers non-pro. Others persons: Usagers @@ -88,46 +90,50 @@ activity: No documents: Aucun document #timeline -'%user% has done an %activity_type%': '%user% a effectué une activité de type "%activity_type%"' +'%user% has done an %activity_type%': '%user% a effectué un échange de type "%activity_type%"' #controller -'Success : activity created!': L'activité a été créée. -'The form is not valid. The activity has not been created !': Le formulaire est invalide. L'activité n'a pas été créée. -'Success : activity updated!': L'activité a été mise à jour. -'The form is not valid. The activity has not been updated !': Le formulaire est invalide. L'activité n'a pas été mise à jour. +'Success : activity created!': L'échange a été créé. +'The form is not valid. The activity has not been created !': Le formulaire est invalide. L'échange n'a pas été créé. +'Success : activity updated!': L'échange a été mis à jour. +'The form is not valid. The activity has not been updated !': Le formulaire est invalide. L'échange n'a pas été mise à jour. # ROLES -CHILL_ACTIVITY_CREATE: Créer une activité -CHILL_ACTIVITY_UPDATE: Modifier une activité -CHILL_ACTIVITY_SEE: Voir une activité -CHILL_ACTIVITY_SEE_DETAILS: Voir le détail des activités -CHILL_ACTIVITY_DELETE: Supprimer une activité -CHILL_ACTIVITY_STATS: Statistique des activités -CHILL_ACTIVITY_LIST: Liste des activités +CHILL_ACTIVITY_CREATE: Créer un échange +CHILL_ACTIVITY_UPDATE: Modifier un échange +CHILL_ACTIVITY_SEE: Voir un échange +CHILL_ACTIVITY_SEE_DETAILS: Voir le détail des échanges +CHILL_ACTIVITY_DELETE: Supprimer un échange +CHILL_ACTIVITY_STATS: Statistique des échanges +CHILL_ACTIVITY_LIST: Liste des échanges +CHILL_ACTIVITY_CREATE_PERSON: Créer un échange lié à un usager +CHILL_ACTIVITY_CREATE_ACCOMPANYING_COURSE: Créer un échange lié à un parcours +CHILL_ACTIVITY_FULL: Voir les détails, créer, supprimer et mettre à jour un échange # admin -Activities: Activités -Activity configuration: Configuration des activités -Activity configuration menu: Configuration des activités -Activity types: Types d'activité -Activity type configuration: Configuration des categories d'activités -Activity Reasons: Sujets d'une activité -Activity Reasons Category: Catégories de sujet d'activités -Activity Types Categories: Catégories des types d'activité -Activity Presences: Presences aux activités +Activities: Échanges +Activity configuration: Configuration des échanges +Activity configuration menu: Configuration des échanges +Activity types: Types d'échange +Activity type configuration: Configuration des catégories d'échanges +Activity Reasons: Sujets d'un échange +Activity Reasons Category: Catégories de sujet d'échanges +Activity Types Categories: Catégories des types d'échange +Activity Presences: Présences aux échanges +Associated activity reason category is inactive: La catégorie de sujet attachée est inactive # Crud crud: - activity_type: - title_new: Nouveau type d'activité - title_edit: Edition d'un type d'activité - activity_type_category: - title_new: Nouvelle catégorie de type d'activité - title_edit: Edition d'une catégorie de type d'activité - activity_presence: - title_new: Nouvelle Présence aux activités - title_edit: Edition d'une Présence aux activités + activity_type: + title_new: Nouveau type d'échange + title_edit: Édition d'un type d'échange + activity_type_category: + title_new: Nouvelle catégorie de type d'échange + title_edit: Édition d'une catégorie de type d'échange + activity_presence: + title_new: Nouvelle présence aux échanges + title_edit: Édition d'une présence aux échanges # activity reason admin ActivityReason list: Liste des sujets @@ -136,7 +142,7 @@ Active: Actif Category: Catégorie ActivityReason creation: Nouveau sujet ActivityReason edit: Modification d'un sujet -ActivityReason: Sujet d'activité +ActivityReason: Sujet d'échange The entity is inactive and won't be proposed: Le sujet est inactif et ne sera pas proposé The entity is active and will be proposed: Le sujet est actif et sera proposé @@ -145,19 +151,19 @@ ActivityReasonCategory list: Catégories de sujets Create a new activity category reason: Créer une nouvelle catégorie ActivityReasonCategory creation: Nouvelle catégorie de sujet ActivityReasonCategory edit: Modification d'une catégorie de sujet -ActivityReasonCategory: Catégorie de sujet d'activité +ActivityReasonCategory: Catégorie de sujet d'échange ActivityReasonCategory is active and will be proposed: La catégorie est active et sera proposée ActivityReasonCategory is inactive and won't be proposed: La catégorie est inactive et ne sera pas proposée #activity presence admin -ActivityPresence list: Liste des Présences aux activités -Create a new activity presence: Créer une nouvelle "Présence aux activités" +ActivityPresence list: Liste des présences aux échanges +Create a new activity presence: Créer une nouvelle "Présence aux échanges" # activity type type admin -ActivityType list: Types d'activités -Create a new activity type: Créer un nouveau type d'activité -Persons visible: Visibilité du champ Personnes -Persons label: Libellé du champ Personnes +ActivityType list: Types d'échanges +Create a new activity type: Créer un nouveau type d'échange +Persons visible: Visibilité du champ Usagers +Persons label: Libellé du champ Usagers User visible: Visibilité du champ Utilisateur User label: Libellé du champ Utilisateur Date visible: Visibilité du champ Date @@ -180,8 +186,8 @@ Private comment visible: Visibilité du champ Commentaire Privé Private comment label: Libellé du champ Commentaire Privé Emergency visible: Visibilité du champ Urgent Emergency label: Libellé du champ Urgent -Accompanying period visible: Visibilité du champ Période d'accompagnement -Accompanying period label: Libellé du champ Période d'accompagnement +Accompanying period visible: Visibilité du champ parcours d'accompagnement +Accompanying period label: Libellé du champ parcours d'accompagnement Social issues visible: Visibilité du champ Problématiques sociales Social issues label: Libellé du champ Problématiques sociales Social actions visible: Visibilité du champ Action sociale @@ -194,62 +200,176 @@ Documents visible: Visibilité du champ Documents Documents label: Libellé du champ Documents # activity type category admin -ActivityTypeCategory list: Liste des catégories des types d'activité -Create a new activity type category: Créer une nouvelle catégorie de type d'activité -Create a new activity in accompanying course: Créer une activité dans le parcours +ActivityTypeCategory list: Liste des catégories des types d'échange +Create a new activity type category: Créer une nouvelle catégorie de type d'échange +Create a new activity in accompanying course: Créer un échange dans le parcours # activity delete -Remove activity: Supprimer une activité -Are you sure you want to remove the activity about "%name%" ?: Êtes-vous sûr de vouloir supprimer une activité qui concerne "%name%" ? -The activity has been successfully removed.: L'activité a été supprimée. +Remove activity: Supprimer un échange +Are you sure you want to remove the activity about "%name%" ?: Êtes-vous sûr de vouloir supprimer un échange qui concerne "%name%" ? +The activity has been successfully removed.: L'échange a été supprimé. # exports -Count activities: Nombre d'activités -Count activities by various parameters.: Compte le nombre d'activités enregistrées en fonction de différents paramètres. -Sum activity duration: Total de la durée des activités -Sum activities duration by various parameters.: Additionne la durée des activités en fonction de différents paramètres. -List activities: Liste les activités -Number of activities: Nombre d'activités +Exports of activities linked to a person: Exports des échanges liés à un usager +Number of activities linked to a person: Nombre d'échanges liés à un usager +Count activities linked to a person: Nombre d'échanges +Count activities linked to a person by various parameters.: Compte le nombre d'échanges enregistrés et liés à un usager en fonction de différents paramètres. +Sum activity linked to a person duration: Durée des échanges +Sum activities linked to a person duration: Durée des échanges liés à un usager +Sum activities linked to a person duration by various parameters.: Additionne la durée des échanges en fonction de différents paramètres. +List activity linked to a person: Liste les échanges +List activities linked to a person: Liste des échanges liés à un usager +List activities linked to a person description: Crée la liste des échanges en fonction de différents paramètres. + +Exports of activities linked to an accompanying period: Exports des échanges liés à un parcours +Number of activities linked to an accompanying period: Nombre d'échanges liés à un parcours +Count activities linked to an accompanying period: Nombre d'échanges +Count activities linked to an accompanying period by various parameters.: Compte le nombre d'échanges enregistrés et liées à un parcours en fonction de différents paramètres. +Sum activity linked to an accompanying period duration: Somme de la durée des échanges +Sum activities linked to an accompanying period duration: Somme de la durée des échanges liés à un parcours +Sum activities linked to an accompanying period duration by various parameters.: Additionne la durée des échanges en fonction de différents paramètres. +Sum activity linked to an accompanying period visit duration: Somme de la durée de déplacement des échanges +Sum activities linked to an accompanying period visit duration: Somme de la durée de déplacement des échanges liés à un parcours +Sum activities linked to an accompanying period visit duration by various parameters.: Additionne la durée de déplacement des échanges en fonction de différents paramètres. +Average activity linked to an accompanying period duration: Moyenne de la durée des échanges +Average activities linked to an accompanying period duration: Moyenne de la durée des échanges liés à un parcours +Average activities linked to an accompanying period duration by various parameters.: Moyenne de la durée des échanges en fonction de différents paramètres. +Average activity linked to an accompanying period visit duration: Moyenne de la durée de déplacement des échanges +Average activities linked to an accompanying period visit duration: Moyenne de la durée de déplacement des échanges liés à un parcours +Average activities linked to an accompanying period visit duration by various parameters.: Moyenne de la durée de déplacement des échanges en fonction de différents paramètres. #filters -Filter by reason: Filtrer par sujet d'activité +Filter by reason: Filtrer les échanges par sujet 'Filtered by reasons: only %list%': 'Filtré par sujet: seulement %list%' -'Filtered by activity type: only %list%': "Filtré par type d'activity: seulement %list%" -Filtered by date activity: Filtrer par date d'activité -Activities after this date: Activités après cette date -Activities before this date: Activités avant cette date -"Filtered by date of activity: only between %date_from% and %date_to%": "Filtré par date de l'activité: 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 "activités après cette date" +'Filtered by activity type: only %list%': "Filtré par type d'échange: uniquement %list%" +Filtered by date activity: Filtrer les échanges par date +Activities after this date: Échanges après cette date +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 personnes ayant eu une activité dans la période donnée -Implied in an activity after this date: Impliqué dans une activité après cette date -Implied in an activity before this date: Impliqué dans une activité avant cette date -Filtered by person having an activity between %date_from% and %date_to% with reasons %reasons_name%: Filtré par personnes associées à une activité entre %date_from% et %date_to% avec les sujets %reasons_name% -Activity reasons for those activities: Sujets de ces activités +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 par type d'activité +Filter by activity type: Filtrer les échanges par type + +Filter activity by location: Filtrer les échanges par localisation +'Filtered activity by location: only %locations%': "Filtré par localisation: uniquement %locations%" +Filter activity by locationtype: Filtrer les échanges par type de localisation +'Filtered activity by locationtype: only %types%': "Filtré par type de localisation: uniquement %types%" +Accepted locationtype: Types de localisation +Accepted users: TMS(s) +Filter activity by emergency: Filtrer les échanges par urgence +'Filtered activity by emergency: only %emergency%': "Filtré par urgence: uniquement si %emergency%" +activity is emergency: l'échange est urgent +activity is not emergency: l'échange n'est pas urgent +Filter activity by sentreceived: Filtrer les échanges par envoyé/reçu +'Filtered activity by sentreceived: only %sentreceived%': "Filtré par envoyé/reçu: uniquement %sentreceived%" +Accepted sentreceived: '' +Filter activity by linked socialaction: Filtrer les échanges par action liée +'Filtered activity by linked socialaction: only %actions%': "Filtré par action liée: uniquement %actions%" +Filter activity by linked socialissue: Filtrer les échanges par problématique liée +'Filtered activity by linked socialissue: only %issues%': "Filtré par problématique liée: uniquement %issues%" +Filter activity by user: Filtrer les échanges par créateur +Filter activity by users: Filtrer les échanges par utilisateur participant +Filter activity by creator: Filtrer les échanges par créateur de l'échange +'Filtered activity by user: only %users%': "Filtré par référent: uniquement %users%" +'Filtered activity by users: only %users%': "Filtré par utilisateurs participants: uniquement %users%" +'Filtered activity by creator: only %users%': "Filtré par créateur: uniquement %users%" +Creators: Créateurs +Filter activity by userscope: Filtrer les échanges par service du créateur +'Filtered activity by userscope: only %scopes%': "Filtré par service du créateur: uniquement %scopes%" +Accepted userscope: Services + +Filter acp which has no activity: Filtrer les parcours qui n’ont pas d’échange +Filtered acp which has no activities: Filtrer les parcours sans échange associé +Group acp by activity number: Grouper les parcours par nombre d’échange #aggregators -Activity type: Type d'activité -Activity user: Utilisateur lié à l'activity +Activity type: Type d'échange +Activity user: Utilisateur lié à l'échange By reason: Par sujet By category of reason: Par catégorie de sujet Reason's level: Niveau du sujet -Group by reasons: Sujet d'activité -Aggregate by activity user: Aggréger par utilisateur lié à l'activité -Aggregate by activity type: Aggréger par type d'activité -Aggregate by activity reason: Aggréger par sujet de l'activité +Group by reasons: Sujet d'échange +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 -Last activities: Les dernières activités +Group activity by locationtype: Grouper les échanges par type de localisation +Group activity by date: Grouper les échanges par date +Frequency: Fréquence +by month: Par mois +by week: Par semaine +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 +Group activity by linked socialissue: Grouper les échanges par problématique liée +Group activity by userscope: Grouper les échanges par service du créateur -See activity in accompanying course context: Voir l'activité dans le contexte du parcours d'accompagnement +Last activities: Les derniers échanges -You get notified of an activity which does not exists any more: Cette notification ne correspond pas à une activité valide. -you are not allowed to see it details: La notification fait référence à une activité à laquelle vous n'avez pas accès. -This is the minimal activity data: Activité n° +See activity in accompanying course context: Voir l'échange dans le contexte du parcours d'accompagnement + +You get notified of an activity which does not exists any more: Cette notification ne correspond pas à un échange valide. +you are not allowed to see it details: La notification fait référence à un échange auquel vous n'avez pas accès. +This is the minimal activity data: Échange n° docgen: - Activity basic: Echange + Activity basic: Échange A basic context for activity: Contexte pour les échanges Accompanying period with a list of activities: Parcours d'accompagnement avec liste des échanges Accompanying period with a list of activities description: Ce contexte reprend les informations du parcours, et tous les échanges pour un parcours. Les échanges ne sont pas filtrés. + myActivitiesOnly: Prendre en compte uniquement les échanges dans lesquels je suis intervenu + myWorksOnly: Prendre en compte uniquement les actions d'accompagnement dont je suis référent + +export: + list: + activity: + users name: Nom des utilisateurs + users ids: Identifiant des utilisateurs + third parties ids: Identifiant des tiers + persons ids: Identifiant des usagers + persons name: Nom des usagers + thirds parties: Tiers + date: Date de l'échange + locationName: Localisation + sent received: Envoyé ou reçu + emergency: Urgence + accompanying course id: Identifiant du parcours + course circles: Cercles du parcours + travelTime: Durée de déplacement + durationTime: Durée + id: Identifiant + List activities linked to an accompanying course: Liste les échanges liés à un parcours en fonction de différents filtres. + List activity linked to a course: Liste des échanges liés à un parcours + + + filter: + activity: + by_usersjob: + Filter by users job: Filtrer les échanges par métier d'au moins un utilisateur participant + 'Filtered activity by users job: only %jobs%': 'Filtré par métier d''au moins un utilisateur participant: seulement %jobs%' + by_usersscope: + Filter by users scope: Filtrer les échanges par services d'au moins un utilisateur participant + 'Filtered activity by users scope: only %scopes%': 'Filtré par service d''au moins un utilisateur participant: seulement %scopes%' + aggregator: + activity: + by_sent_received: + Sent or received: Envoyé ou reçu + is sent: envoyé + is received: reçu + Group activity by sentreceived: Grouper les échanges par envoyé / reçu diff --git a/src/Bundle/ChillActivityBundle/translations/messages.nl.yaml b/src/Bundle/ChillActivityBundle/translations/messages.nl.yaml index 8d7cc293a..c7dc05f2b 100644 --- a/src/Bundle/ChillActivityBundle/translations/messages.nl.yaml +++ b/src/Bundle/ChillActivityBundle/translations/messages.nl.yaml @@ -47,10 +47,10 @@ Reasons: Onderwerpen #forms -Activity creation: Nouvelle activité +Activity creation: Nouvel échange Create: Créer Back to the list: Retour à la liste -Save activity: Sauver l'activité +Save activity: Sauver l'échange Reset form: Remise à zéro du formulaire Choose the duration: Choisir la durée Choose a type: Choisir un type @@ -79,43 +79,43 @@ activity: No documents: Aucun document #timeline -'%user% has done an %activity_type%': '%user% a effectué une activité de type "%activity_type%"' +'%user% has done an %activity_type%': '%user% a effectué un échange de type "%activity_type%"' #controller -'Success : activity created!': L'activité a été créée. -'The form is not valid. The activity has not been created !': Le formulaire est invalide. L'activité n'a pas été créée. -'Success : activity updated!': L'activité a été mise à jour. -'The form is not valid. The activity has not been updated !': Le formulaire est invalide. L'activité n'a pas été mise à jour. +'Success : activity created!': L'échange a été créé. +'The form is not valid. The activity has not been created !': Le formulaire est invalide. L'échange n'a pas été créé. +'Success : activity updated!': L'échange a été mis à jour. +'The form is not valid. The activity has not been updated !': Le formulaire est invalide. L'échange n'a pas été mis à jour. # ROLES -CHILL_ACTIVITY_CREATE: Créer une activité -CHILL_ACTIVITY_UPDATE: Modifier une activité -CHILL_ACTIVITY_SEE: Voir une activité -CHILL_ACTIVITY_SEE_DETAILS: Voir le détail des activités -CHILL_ACTIVITY_DELETE: Supprimer une activité -CHILL_ACTIVITY_STATS: Statistique des activités -CHILL_ACTIVITY_LIST: Liste des activités +CHILL_ACTIVITY_CREATE: Créer un échange +CHILL_ACTIVITY_UPDATE: Modifier un échange +CHILL_ACTIVITY_SEE: Voir un échange +CHILL_ACTIVITY_SEE_DETAILS: Voir le détail des échanges +CHILL_ACTIVITY_DELETE: Supprimer un échange +CHILL_ACTIVITY_STATS: Statistique des échanges +CHILL_ACTIVITY_LIST: Liste des échanges # admin -Activities: Activités -Activity configuration: Configuration des activités -Activity configuration menu: Configuration des activités -Activity types: Types d'activité -Activity type configuration: Configuration des categories d'activités -Activity Reasons: Sujets d'une activité -Activity Reasons Category: Catégories de sujet d'activités -Activity Types Categories: Catégories des types d'activité -Activity Presences: Presences des activités +Activities: Échanges +Activity configuration: Configuration des échanges +Activity configuration menu: Configuration des échanges +Activity types: Types d'échange +Activity type configuration: Configuration des categories d'échanges +Activity Reasons: Sujets d'un échange +Activity Reasons Category: Catégories de sujet d'échanges +Activity Types Categories: Catégories des types d'échanges +Activity Presences: Presences des échanges # Crud crud: activity_type: - title_new: Nouveau type d'activité - title_edit: Edition d'un type d'activité + title_new: Nouveau type d'échange + title_edit: Edition d'un type d'échange activity_type_category: - title_new: Nouvelle catégorie de type d'activité - title_edit: Edition d'une catégorie de type d'activité + title_new: Nouvelle catégorie de type d'échange + title_edit: Edition d'une catégorie de type d'échange # activity reason admin ActivityReason list: Liste des sujets @@ -124,7 +124,7 @@ Active: Actif Category: Catégorie ActivityReason creation: Nouveau sujet ActivityReason edit: Modification d'un sujet -ActivityReason: Sujet d'activité +ActivityReason: Sujet d'échange The entity is inactive and won't be proposed: Le sujet est inactif et ne sera pas proposé The entity is active and will be proposed: Le sujet est actif et sera proposé @@ -133,13 +133,13 @@ ActivityReasonCategory list: Catégories de sujets Create a new activity category reason: Créer une nouvelle catégorie ActivityReasonCategory creation: Nouvelle catégorie de sujet ActivityReasonCategory edit: Modification d'une catégorie de sujet -ActivityReasonCategory: Catégorie de sujet d'activité +ActivityReasonCategory: Catégorie de sujet d'échange ActivityReasonCategory is active and will be proposed: La catégorie est active et sera proposée ActivityReasonCategory is inactive and won't be proposed: La catégorie est inactive et ne sera pas proposée # activity type type admin -ActivityType list: Types d'activités -Create a new activity type: Créer un nouveau type d'activité +ActivityType list: Types d'échanges +Create a new activity type: Créer un nouveau type d'échange Persons visible: Visibilité du champ Personnes Persons label: Libellé du champ Personnes User visible: Visibilité du champ Utilisateur @@ -177,25 +177,25 @@ Documents label: Libellé du champ Documents # activity type category admin ActivityTypeCategory list: Liste des catégories des types d'activité -Create a new activity type category: Créer une nouvelle catégorie de type d'activité +Create a new activity type category: Créer une nouvelle catégorie de type d'échange # activity delete -Remove activity: Supprimer une activité -Are you sure you want to remove the activity about "%name%" ?: Êtes-vous sûr de vouloir supprimer une activité qui concerne "%name%" ? -The activity has been successfully removed.: L'activité a été supprimée. +Remove activity: Supprimer un échange +Are you sure you want to remove the activity about "%name%" ?: Êtes-vous sûr de vouloir supprimer un échange qui concerne "%name%" ? +The activity has been successfully removed.: L'échange a été supprimée. # exports -Count activities: Nombre d'activités -Count activities by various parameters.: Compte le nombre d'activités enregistrées en fonction de différents paramètres. -Sum activity duration: Total de la durée des activités -Sum activities duration by various parameters.: Additionne la durée des activités en fonction de différents paramètres. -List activities: Liste les activités -Number of activities: Nombre d'activités +Count activities: Nombre d'échanges +Count activities by various parameters.: Compte le nombre d'échanges enregistrées en fonction de différents paramètres. +Sum activity duration: Total de la durée des échanges +Sum activities duration by various parameters.: Additionne la durée des échanges en fonction de différents paramètres. +List activities: Liste les échanges +Number of activities: Nombre d'échanges #filters Filter by reason: Filtrer par sujet d'activité 'Filtered by reasons: only %list%': 'Filtré par sujet: seulement %list%' -'Filtered by activity type: only %list%': "Filtré par type d'activity: seulement %list%" +'Filtered by activity type: only %list%': "Filtré par type d'activity: uniquement %list%" Filtered by date activity: Filtrer par date d'activité Activities after this date: Activités après cette date Activities before this date: Activités avant cette date @@ -217,9 +217,9 @@ By reason: Par sujet By category of reason: Par catégorie de sujet Reason's level: Niveau du sujet Group by reasons: Sujet d'activité -Aggregate by activity user: Aggréger par utilisateur lié à l'activité -Aggregate by activity type: Aggréger par type d'activité -Aggregate by activity reason: Aggréger par sujet de l'activité +Aggregate by activity user: Grouper par utilisateur lié à l'activité +Aggregate by activity type: Grouper par type d'activité +Aggregate by activity reason: Grouper par sujet de l'activité Last activities: Les dernières activités @@ -231,4 +231,4 @@ This is the minimal activity data: Activité n° docgen: Activity basic: Echange - A basic context for activity: Contexte pour les échanges + A basic context for activity: Contexte pour les activités diff --git a/src/Bundle/ChillActivityBundle/translations/validators.fr.yml b/src/Bundle/ChillActivityBundle/translations/validators.fr.yml index 1f4f97a63..053752854 100644 --- a/src/Bundle/ChillActivityBundle/translations/validators.fr.yml +++ b/src/Bundle/ChillActivityBundle/translations/validators.fr.yml @@ -1,23 +1,23 @@ The reasons's level should not be empty: Le niveau du sujet ne peut pas être vide At least one reason must be choosen: Au moins un sujet doit être choisi -For this type of activity, you must add at least one person: Pour ce type d'activité, vous devez ajouter au moins un usager -For this type of activity, you must add at least one user: Pour ce type d'activité, vous devez ajouter au moins un utilisateur -For this type of activity, you must add at least one third party: Pour ce type d'activité, vous devez ajouter au moins un tiers -For this type of activity, user is required: Pour ce type d'activité, l'utilisateur est requis -For this type of activity, date is required: Pour ce type d'activité, la date est requise -For this type of activity, location is required: Pour ce type d'activité, la localisation est requise -For this type of activity, attendee is required: Pour ce type d'activité, le champ "Présence de la personne" est requis -For this type of activity, duration time is required: Pour ce type d'activité, la durée est requise -For this type of activity, travel time is required: Pour ce type d'activité, la durée du trajet est requise -For this type of activity, reasons is required: Pour ce type d'activité, le champ "sujet" est requis -For this type of activity, comment is required: Pour ce type d'activité, un commentaire est requis -For this type of activity, sent/received is required: Pour ce type d'activité, le champ Entrant/Sortant est requis -For this type of activity, document is required: Pour ce type d'activité, un document est requis -For this type of activity, emergency is required: Pour ce type d'activité, le champ "Urgent" est requis -For this type of activity, accompanying period is required: Pour ce type d'activité, le parcours d'accompagnement est requis -For this type of activity, you must add at least one social issue: Pour ce type d'activité, vous devez ajouter au moins une problématique sociale -For this type of activity, you must add at least one social action: Pour ce type d'activité, vous devez indiquez au moins une action sociale +For this type of activity, you must add at least one person: Pour ce type d'échange, vous devez ajouter au moins un usager +For this type of activity, you must add at least one user: Pour ce type d'échange, vous devez ajouter au moins un utilisateur +For this type of activity, you must add at least one third party: Pour ce type d'échange, vous devez ajouter au moins un tiers +For this type of activity, user is required: Pour ce type d'échange, l'utilisateur est requis +For this type of activity, date is required: Pour ce type d'échange, la date est requise +For this type of activity, location is required: Pour ce type d'échange, la localisation est requise +For this type of activity, attendee is required: Pour ce type d'échange, le champ "Présence de l'usager" est requis +For this type of activity, duration time is required: Pour ce type d'échange, la durée est requise +For this type of activity, travel time is required: Pour ce type d'échange, la durée du trajet est requise +For this type of activity, reasons is required: Pour ce type d'échange, le champ "sujet" est requis +For this type of activity, comment is required: Pour ce type d'échange, un commentaire est requis +For this type of activity, sent/received is required: Pour ce type d'échange, le champ Entrant/Sortant est requis +For this type of activity, document is required: Pour ce type d'échange, un document est requis +For this type of activity, emergency is required: Pour ce type d'échange, le champ "Urgent" est requis +For this type of activity, accompanying period is required: Pour ce type d'échange, le parcours d'accompagnement est requis +For this type of activity, you must add at least one social issue: Pour ce type d'échange, vous devez ajouter au moins une problématique sociale +For this type of activity, you must add at least one social action: Pour ce type d'échange, vous devez indiquer au moins une action sociale # admin This parameter must be equal to social issue parameter: Ce paramètre doit être égal au paramètre "Visibilité du champs Problématiques sociales" -The socialActionsVisible value is not compatible with the socialIssuesVisible value: Cette valeur du paramètre "Visibilité du champs Actions sociales" n'est pas compatible avec la valeur du paramètre "Visibilité du champs Problématiques sociales" \ No newline at end of file +The socialActionsVisible value is not compatible with the socialIssuesVisible value: Cette valeur du paramètre "Visibilité du champs Actions sociales" n'est pas compatible avec la valeur du paramètre "Visibilité du champs Problématiques sociales" diff --git a/src/Bundle/ChillAsideActivityBundle/src/ChillAsideActivityBundle.php b/src/Bundle/ChillAsideActivityBundle/src/ChillAsideActivityBundle.php index c87610fae..6917517b7 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/ChillAsideActivityBundle.php +++ b/src/Bundle/ChillAsideActivityBundle/src/ChillAsideActivityBundle.php @@ -1,14 +1,14 @@ getReference('aside_activity_category_0') ) ->setDate((new DateTimeImmutable('today')) - ->sub(new DateInterval('P' . random_int(1, 100) . 'D'))); + ->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 5c897e8ec..1d15adbc1 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/DataFixtures/ORM/LoadAsideActivityCategory.php +++ b/src/Bundle/ChillAsideActivityBundle/src/DataFixtures/ORM/LoadAsideActivityCategory.php @@ -1,14 +1,14 @@ load('services.yaml'); $loader->load('services/form.yaml'); $loader->load('services/menu.yaml'); + $loader->load('services/security.yaml'); + $loader->load('services/export.yaml'); } public function prepend(ContainerBuilder $container) diff --git a/src/Bundle/ChillAsideActivityBundle/src/DependencyInjection/Configuration.php b/src/Bundle/ChillAsideActivityBundle/src/DependencyInjection/Configuration.php index 76bcebca5..2e2398c3e 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/DependencyInjection/Configuration.php +++ b/src/Bundle/ChillAsideActivityBundle/src/DependencyInjection/Configuration.php @@ -1,14 +1,14 @@ getRootNode('chill_aside_activity') + $treeBuilder->getRootNode() ->children() ->arrayNode('form') ->canBeEnabled() @@ -132,9 +132,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 2f4efe580..3dc205f09 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Entity/AsideActivity.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Entity/AsideActivity.php @@ -1,14 +1,14 @@ asideActivityCategoryRepository = $asideActivityCategoryRepository; + $this->translatableStringHelper = $translatableStringHelper; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + $qb->addSelect('IDENTITY(aside.type) AS by_aside_activity_type_aggregator') + ->addGroupBy('by_aside_activity_type_aggregator'); + } + + public function applyOn(): string + { + return Declarations::ASIDE_ACTIVITY_TYPE; + } + + public function buildForm(FormBuilderInterface $builder) + { + // No form needed + } + + public function getLabels($key, array $values, $data) + { + return function ($value): string { + if ('_header' === $value) { + return 'export.aggregator.Aside activity type'; + } + + if (null === $value || null === $t = $this->asideActivityCategoryRepository->find($value)) { + return ''; + } + + return $this->translatableStringHelper->localize($t->getTitle()); + }; + } + + public function getQueryKeys($data): array + { + return ['by_aside_activity_type_aggregator']; + } + + public function getTitle(): string + { + return 'export.aggregator.Group by aside activity type'; + } +} diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserJobAggregator.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserJobAggregator.php new file mode 100644 index 000000000..d2fe7c2f2 --- /dev/null +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserJobAggregator.php @@ -0,0 +1,89 @@ +userJobRepository = $userJobRepository; + $this->translatableStringHelper = $translatableStringHelper; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + if (!in_array('aside_user', $qb->getAllAliases(), true)) { + $qb->leftJoin('aside.agent', 'aside_user'); + } + + $qb + ->addSelect('IDENTITY(aside_user.userJob) AS aside_activity_user_job_aggregator') + ->addGroupBy('aside_activity_user_job_aggregator'); + } + + public function applyOn() + { + return Declarations::ASIDE_ACTIVITY_TYPE; + } + + public function buildForm(FormBuilderInterface $builder) + { + // nothing to add in the form + } + + public function getLabels($key, array $values, $data) + { + return function ($value): string { + if ('_header' === $value) { + return 'Users \'s job'; + } + + if (null === $value || '' === $value) { + return ''; + } + + $j = $this->userJobRepository->find($value); + + return $this->translatableStringHelper->localize( + $j->getLabel() + ); + }; + } + + public function getQueryKeys($data): array + { + return ['aside_activity_user_job_aggregator']; + } + + public function getTitle() + { + return 'export.aggregator.Aggregate by user job'; + } +} diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserScopeAggregator.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserScopeAggregator.php new file mode 100644 index 000000000..6c06ee756 --- /dev/null +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserScopeAggregator.php @@ -0,0 +1,89 @@ +scopeRepository = $scopeRepository; + $this->translatableStringHelper = $translatableStringHelper; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + if (!in_array('aside_user', $qb->getAllAliases(), true)) { + $qb->leftJoin('aside.agent', 'aside_user'); + } + + $qb + ->addSelect('IDENTITY(aside_user.mainScope) AS aside_activity_user_scope_aggregator') + ->addGroupBy('aside_activity_user_scope_aggregator'); + } + + public function applyOn() + { + return Declarations::ASIDE_ACTIVITY_TYPE; + } + + public function buildForm(FormBuilderInterface $builder) + { + // nothing to add in the form + } + + public function getLabels($key, array $values, $data) + { + return function ($value): string { + if ('_header' === $value) { + return 'Users \'s scope'; + } + + if (null === $value || '' === $value) { + return ''; + } + + $s = $this->scopeRepository->find($value); + + return $this->translatableStringHelper->localize( + $s->getName() + ); + }; + } + + public function getQueryKeys($data): array + { + return ['aside_activity_user_scope_aggregator']; + } + + public function getTitle() + { + return 'export.aggregator.Aggregate by user scope'; + } +} diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Declarations.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Declarations.php new file mode 100644 index 000000000..8c808ea37 --- /dev/null +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Declarations.php @@ -0,0 +1,20 @@ +repository = $repository; + } + + public function buildForm(FormBuilderInterface $builder) + { + } + + public function getAllowedFormattersTypes(): array + { + return [FormatterInterface::TYPE_TABULAR]; + } + + public function getDescription(): string + { + return 'export.Average aside activities duration'; + } + + public function getGroup(): string + { + return 'export.Exports of aside activities'; + } + + 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"); + } + + return static fn ($value) => '_header' === $value ? 'Average duration aside activities' : $value; + } + + public function getQueryKeys($data): array + { + return ['export_avg_aside_activity_duration']; + } + + public function getResult($query, $data) + { + return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); + } + + public function getTitle(): string + { + return 'export.Average aside activities duration'; + } + + public function getType(): string + { + return Declarations::ASIDE_ACTIVITY_TYPE; + } + + public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + { + $qb = $this->repository->createQueryBuilder('aside'); + + $qb + ->select('AVG(aside.duration) as export_avg_aside_activity_duration') + ->andWhere($qb->expr()->isNotNull('aside.duration')); + + return $qb; + } + + public function requiredRole(): string + { + return AsideActivityVoter::STATS; + } + + public function supportsModifiers(): array + { + return [Declarations::ASIDE_ACTIVITY_TYPE]; + } +} diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Export/CountAsideActivity.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/CountAsideActivity.php new file mode 100644 index 000000000..9204fad4b --- /dev/null +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/CountAsideActivity.php @@ -0,0 +1,105 @@ +repository = $repository; + } + + public function buildForm(FormBuilderInterface $builder) + { + } + + public function getAllowedFormattersTypes(): array + { + return [FormatterInterface::TYPE_TABULAR]; + } + + public function getDescription(): string + { + return 'export.Count aside activities by various parameters.'; + } + + public function getGroup(): string + { + return 'export.Exports of aside activities'; + } + + public function getLabels($key, array $values, $data) + { + if ('export_result' !== $key) { + throw new LogicException("the key {$key} is not used by this export"); + } + + $labels = array_combine($values, $values); + $labels['_header'] = $this->getTitle(); + + return static fn ($value) => $labels[$value]; + } + + public function getQueryKeys($data): array + { + return ['export_result']; + } + + public function getResult($query, $data) + { + return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); + } + + public function getTitle(): string + { + return 'export.Count aside activities'; + } + + public function getType(): string + { + return Declarations::ASIDE_ACTIVITY_TYPE; + } + + public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + { + $qb = $this->repository->createQueryBuilder('aside'); + + $qb->select('COUNT(DISTINCT aside.id) AS export_result'); + + return $qb; + } + + public function requiredRole(): string + { + return AsideActivityVoter::STATS; + } + + public function supportsModifiers(): array + { + return [ + Declarations::ASIDE_ACTIVITY_TYPE, + ]; + } +} diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Export/ListAsideActivity.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/ListAsideActivity.php new file mode 100644 index 000000000..aee168174 --- /dev/null +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/ListAsideActivity.php @@ -0,0 +1,239 @@ +em = $em; + $this->dateTimeHelper = $dateTimeHelper; + $this->userHelper = $userHelper; + $this->scopeRepository = $scopeRepository; + $this->centerRepository = $centerRepository; + $this->asideActivityCategoryRepository = $asideActivityCategoryRepository; + $this->categoryRender = $categoryRender; + $this->translatableStringHelper = $translatableStringHelper; + } + + public function buildForm(FormBuilderInterface $builder) + { + } + + public function getAllowedFormattersTypes() + { + return [FormatterInterface::TYPE_LIST]; + } + + public function getDescription() + { + return 'export.aside_activity.List of aside activities'; + } + + public function getGroup(): string + { + return 'export.Exports of aside activities'; + } + + 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 $value ?? ''; + }; + + case 'duration': + return static function ($value) use ($key) { + if ('_header' === $value) { + return 'export.aside_activity.' . $key; + } + + if (null === $value) { + return ''; + } + + if ($value instanceof DateTimeInterface) { + return $value->format('H:i:s'); + } + + return $value; + }; + + case 'createdAt': + case 'updatedAt': + case 'date': + return $this->dateTimeHelper->getLabel('export.aside_activity.' . $key); + + case 'agent_id': + case 'creator_id': + return $this->userHelper->getLabel($key, $values, 'export.aside_activity.' . $key); + + case 'aside_activity_type': + return function ($value) { + if ('_header' === $value) { + return 'export.aside_activity.aside_activity_type'; + } + + if (null === $value || '' === $value || null === $c = $this->asideActivityCategoryRepository->find($value)) { + return ''; + } + + return $this->categoryRender->renderString($c, []); + }; + + case 'main_scope': + return function ($value) { + if ('_header' === $value) { + return 'export.aside_activity.main_scope'; + } + + 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); + } + } + + public function getQueryKeys($data) + { + return [ + 'id', + 'createdAt', + 'updatedAt', + 'agent_id', + 'creator_id', + 'main_scope', + 'main_center', + 'aside_activity_type', + 'date', + 'duration', + 'note', + ]; + } + + public function getResult($query, $data): array + { + return $query->getQuery()->getResult(AbstractQuery::HYDRATE_ARRAY); + } + + public function getTitle() + { + return 'export.aside_activity.List of aside activities'; + } + + public function getType(): string + { + return Declarations::ASIDE_ACTIVITY_TYPE; + } + + public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + { + $qb = $this->em->createQueryBuilder() + ->from(AsideActivity::class, 'aside') + ->leftJoin('aside.agent', 'agent'); + + $qb + ->addSelect('aside.id AS id') + ->addSelect('aside.createdAt AS createdAt') + ->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(agent.mainCenter) AS main_center') + ->addSelect('IDENTITY(aside.type) AS aside_activity_type') + ->addSelect('aside.date') + ->addSelect('aside.duration') + ->addSelect('aside.note'); + + return $qb; + } + + public function requiredRole(): string + { + return AsideActivityVoter::STATS; + } + + public function supportsModifiers() + { + return [Declarations::ASIDE_ACTIVITY_TYPE]; + } +} diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Export/SumAsideActivityDuration.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/SumAsideActivityDuration.php new file mode 100644 index 000000000..af17a2591 --- /dev/null +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/SumAsideActivityDuration.php @@ -0,0 +1,102 @@ +repository = $repository; + } + + public function buildForm(FormBuilderInterface $builder) + { + } + + public function getAllowedFormattersTypes(): array + { + return [FormatterInterface::TYPE_TABULAR]; + } + + public function getDescription(): string + { + return 'export.Sum aside activities duration'; + } + + public function getGroup(): string + { + return 'export.Exports of aside activities'; + } + + 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"); + } + + return static fn ($value) => '_header' === $value ? 'Sum duration aside activities' : $value; + } + + public function getQueryKeys($data): array + { + return ['export_sum_aside_activity_duration']; + } + + public function getResult($query, $data) + { + return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); + } + + public function getTitle(): string + { + return 'export.Sum aside activities duration'; + } + + public function getType(): string + { + return Declarations::ASIDE_ACTIVITY_TYPE; + } + + public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + { + $qb = $this->repository + ->createQueryBuilder('aside'); + + $qb->select('SUM(aside.duration) as export_sum_aside_activity_duration') + ->andWhere($qb->expr()->isNotNull('aside.duration')); + + return $qb; + } + + public function requiredRole(): string + { + return AsideActivityVoter::STATS; + } + + public function supportsModifiers(): array + { + return [Declarations::ASIDE_ACTIVITY_TYPE]; + } +} diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByActivityTypeFilter.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByActivityTypeFilter.php new file mode 100644 index 000000000..3ad8e0e93 --- /dev/null +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByActivityTypeFilter.php @@ -0,0 +1,96 @@ +categoryRender = $categoryRender; + $this->asideActivityTypeRepository = $asideActivityTypeRepository; + $this->translatableStringHelper = $translatableStringHelper; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + $clause = $qb->expr()->in('aside.type', ':types'); + + $qb->andWhere($clause); + $qb->setParameter('types', $data['types']); + } + + public function applyOn(): string + { + return Declarations::ASIDE_ACTIVITY_TYPE; + } + + public function buildForm(FormBuilderInterface $builder) + { + $builder + ->add('types', EntityType::class, [ + 'class' => AsideActivityCategory::class, + 'choices' => $this->asideActivityTypeRepository->findAllActive(), + 'required' => false, + 'multiple' => true, + 'expanded' => false, + 'attr' => [ + 'class' => 'select2', + ], + 'choice_label' => function (AsideActivityCategory $category) { + $options = []; + + return $this->categoryRender->renderString($category, $options); + }, + ]); + } + + public function describeAction($data, $format = 'string'): array + { + $types = array_map( + fn (AsideActivityCategory $t): string => $this->translatableStringHelper->localize($t->getTitle()), + $data['types']->toArray() + ); + + return ['export.filter.Filtered by aside activity type: only %type%', [ + '%type%' => implode(', ', $types), + ]]; + } + + public function getTitle(): string + { + return 'export.filter.Filter by aside activity type'; + } +} diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByDateFilter.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByDateFilter.php new file mode 100644 index 000000000..7a1b6f4dc --- /dev/null +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByDateFilter.php @@ -0,0 +1,135 @@ +translator = $translator; + $this->rollingDateConverter = $rollingDateConverter; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + $clause = $qb->expr()->between( + 'aside.date', + ':date_from', + ':date_to' + ); + + $qb->andWhere($clause); + + $qb->setParameter( + 'date_from', + $this->rollingDateConverter->convert($data['date_from']) + )->setParameter( + 'date_to', + $this->rollingDateConverter->convert($data['date_to']) + ); + } + + public function applyOn(): string + { + return Declarations::ASIDE_ACTIVITY_TYPE; + } + + public function buildForm(FormBuilderInterface $builder) + { + $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) { + /** @var \Symfony\Component\Form\FormInterface $filterForm */ + $filterForm = $event->getForm()->getParent(); + $enabled = $filterForm->get(FilterType::ENABLED_FIELD)->getData(); + + if (true === $enabled) { + // if the filter is enabled, add some validation + $form = $event->getForm(); + $date_from = $form->get('date_from')->getData(); + $date_to = $form->get('date_to')->getData(); + + // check that fields are not empty + if (null === $date_from) { + $form->get('date_from')->addError(new FormError( + $this->translator->trans('This field ' + . 'should not be empty') + )); + } + + if (null === $date_to) { + $form->get('date_to')->addError(new FormError( + $this->translator->trans('This field ' + . 'should not be empty') + )); + } + + // check that date_from is before date_to + if ( + (null !== $date_from && null !== $date_to) + && $date_from >= $date_to + ) { + $form->get('date_to')->addError(new FormError( + $this->translator->trans('export.filter.This date should be after ' + . 'the date given in "Implied in an aside activity after ' + . 'this date" field') + )); + } + } + }); + } + + public function describeAction($data, $format = 'string'): array + { + return ['export.filter.Filtered by aside activities between %dateFrom% and %dateTo%', [ + '%dateFrom%' => $this->rollingDateConverter->convert($data['date_from'])->format('d-m-Y'), + '%dateTo%' => $this->rollingDateConverter->convert($data['date_to'])->format('d-m-Y'), + ]]; + } + + public function getTitle(): string + { + return 'export.filter.Filter by aside activity date'; + } +} diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserFilter.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserFilter.php new file mode 100644 index 000000000..795c813cd --- /dev/null +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserFilter.php @@ -0,0 +1,74 @@ +userRender = $userRender; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + $clause = $qb->expr()->in('aside.agent', ':users'); + + $qb + ->andWhere($clause) + ->setParameter('users', $data['accepted_users']); + } + + public function applyOn(): string + { + return Declarations::ASIDE_ACTIVITY_TYPE; + } + + public function buildForm(FormBuilderInterface $builder) + { + $builder->add('accepted_users', PickUserDynamicType::class, [ + 'multiple' => true, + 'label' => 'Creators', + ]); + } + + public function describeAction($data, $format = 'string'): array + { + $users = []; + + foreach ($data['accepted_users'] as $u) { + $users[] = $this->userRender->renderString($u, []); + } + + return ['export.filter.Filtered aside activity by user: only %users%', [ + '%users%' => implode(', ', $users), + ]]; + } + + public function getTitle(): string + { + return 'export.filter.Filter aside activity by user'; + } +} diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserJobFilter.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserJobFilter.php new file mode 100644 index 000000000..86194b123 --- /dev/null +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserJobFilter.php @@ -0,0 +1,81 @@ +translatableStringHelper = $translatableStringHelper; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + $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' + ) + ) + ->setParameter('aside_activity_user_job_filter_jobs', $data['jobs']); + } + + public function applyOn() + { + return Declarations::ASIDE_ACTIVITY_TYPE; + } + + public function buildForm(FormBuilderInterface $builder) + { + $builder->add('jobs', EntityType::class, [ + 'class' => UserJob::class, + 'choice_label' => fn (UserJob $j) => $this->translatableStringHelper->localize($j->getLabel()), + 'multiple' => true, + 'expanded' => true, + ]); + } + + public function describeAction($data, $format = 'string') + { + return ['export.filter.Filtered aside activities by user jobs: only %jobs%', [ + '%jobs%' => implode( + ', ', + array_map( + fn (UserJob $job) => $this->translatableStringHelper->localize($job->getLabel()), + $data['jobs']->toArray() + ) + ), + ]]; + } + + public function getTitle() + { + return 'export.filter.Filter by user jobs'; + } +} diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserScopeFilter.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserScopeFilter.php new file mode 100644 index 000000000..4342e11eb --- /dev/null +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserScopeFilter.php @@ -0,0 +1,88 @@ +scopeRepository = $scopeRepository; + $this->translatableStringHelper = $translatableStringHelper; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + $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 ' + ) + ) + ->setParameter('aside_activity_user_scope_filter_scopes', $data['scopes']); + } + + public function applyOn() + { + return Declarations::ASIDE_ACTIVITY_TYPE; + } + + public function buildForm(FormBuilderInterface $builder) + { + $builder->add('scopes', EntityType::class, [ + 'class' => Scope::class, + 'choices' => $this->scopeRepository->findAllActive(), + 'choice_label' => fn (Scope $s) => $this->translatableStringHelper->localize($s->getName()), + 'multiple' => true, + 'expanded' => true, + ]); + } + + public function describeAction($data, $format = 'string') + { + return ['export.filter.Filtered aside activities by user scope: only %scopes%', [ + '%scopes%' => implode( + ', ', + array_map( + fn (Scope $s) => $this->translatableStringHelper->localize($s->getName()), + $data['scopes']->toArray() + ) + ), + ]]; + } + + public function getTitle() + { + return 'export.filter.Filter by user scope'; + } +} diff --git a/src/Bundle/ChillAsideActivityBundle/src/Form/AsideActivityCategoryType.php b/src/Bundle/ChillAsideActivityBundle/src/Form/AsideActivityCategoryType.php index 314dbe551..c183fcea6 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Form/AsideActivityCategoryType.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Form/AsideActivityCategoryType.php @@ -1,14 +1,14 @@ timeChoices = $parameterBag->get('chill_aside_activity.form.time_duration'); - $this->storage = $storage; - $this->categoryRender = $categoryRender; } public function buildForm(FormBuilderInterface $builder, array $options) @@ -81,28 +69,10 @@ final class AsideActivityFormType extends AbstractType 'required' => true, ] ) - ->add( - 'type', - EntityType::class, - [ - 'label' => 'Type', - 'required' => true, - 'class' => AsideActivityCategory::class, - 'placeholder' => 'Choose the activity category', - 'query_builder' => static function (EntityRepository $er) { - $qb = $er->createQueryBuilder('ac'); - $qb->where($qb->expr()->eq('ac.isActive', 'TRUE')) - ->addOrderBy('ac.ordering', 'ASC'); - - return $qb; - }, - 'choice_label' => function (AsideActivityCategory $asideActivityCategory) { - $options = []; - - return $this->categoryRender->renderString($asideActivityCategory, $options); - }, - ] - ) + ->add('type', PickAsideActivityCategoryType::class, [ + 'label' => 'Type', + 'required' => true, + ]) ->add('duration', ChoiceType::class, $durationTimeOptions) ->add('note', ChillTextareaType::class, [ 'label' => 'Note', @@ -125,9 +95,7 @@ final class AsideActivityFormType extends AbstractType // 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(); + $data = $formEvent->getData() ?? DateTime::createFromFormat('U', '300'); $seconds = $data->getTimezone()->getOffset($data); $data->setTimeZone($timezoneUTC); $data->add(new DateInterval('PT' . $seconds . 'S')); diff --git a/src/Bundle/ChillAsideActivityBundle/src/Form/Type/PickAsideActivityCategoryType.php b/src/Bundle/ChillAsideActivityBundle/src/Form/Type/PickAsideActivityCategoryType.php new file mode 100644 index 000000000..3ee392517 --- /dev/null +++ b/src/Bundle/ChillAsideActivityBundle/src/Form/Type/PickAsideActivityCategoryType.php @@ -0,0 +1,57 @@ +categoryRender = $categoryRender; + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver + ->setDefaults([ + 'class' => AsideActivityCategory::class, + 'placeholder' => 'Choose the activity category', + 'query_builder' => static function (EntityRepository $er) { + $qb = $er->createQueryBuilder('ac'); + $qb->where($qb->expr()->eq('ac.isActive', 'TRUE')) + ->addOrderBy('ac.ordering', 'ASC'); + + return $qb; + }, + 'choice_label' => function (AsideActivityCategory $asideActivityCategory) { + $options = []; + + return $this->categoryRender->renderString($asideActivityCategory, $options); + }, + 'attr' => ['class' => 'select2'], + ]); + } + + public function getParent(): string + { + return EntityType::class; + } +} diff --git a/src/Bundle/ChillAsideActivityBundle/src/Menu/AdminMenuBuilder.php b/src/Bundle/ChillAsideActivityBundle/src/Menu/AdminMenuBuilder.php index 30b587a7e..8cc076a6e 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Menu/AdminMenuBuilder.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Menu/AdminMenuBuilder.php @@ -1,14 +1,14 @@ 11, 'icons' => ['plus'], ]); - $menu->addChild($this->translator->trans('Phonecall'), [ - 'route' => 'chill_crud_aside_activity_new', - 'routeParameters' => [ - 'type' => 1, - 'duration' => 900, - ], - ]) - ->setExtras([ - 'order' => 12, - 'icons' => ['plus'], - ]); } } diff --git a/src/Bundle/ChillAsideActivityBundle/src/Menu/UserMenuBuilder.php b/src/Bundle/ChillAsideActivityBundle/src/Menu/UserMenuBuilder.php index ceca055e4..154556bca 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Menu/UserMenuBuilder.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Menu/UserMenuBuilder.php @@ -1,14 +1,14 @@ repository->findAll(); } + public function findAllActive(): array + { + return $this->repository->findBy(['isActive' => true]); + } + /** * @param mixed|null $limit * @param mixed|null $offset diff --git a/src/Bundle/ChillAsideActivityBundle/src/Repository/AsideActivityRepository.php b/src/Bundle/ChillAsideActivityBundle/src/Repository/AsideActivityRepository.php index a318837f7..b3dc46f87 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Repository/AsideActivityRepository.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Repository/AsideActivityRepository.php @@ -1,57 +1,31 @@ repository = $entityManager->getRepository(AsideActivity::class); - } - - public function find($id): ?AsideActivity - { - return $this->repository->find($id); - } - - /** - * @return AsideActivity[] - */ - public function findAll(): array - { - return $this->repository->findAll(); - } - - /** - * @param mixed|null $limit - * @param mixed|null $offset - * - * @return AsideActivity[] - */ - public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array - { - return $this->repository->findBy($criteria, $orderBy, $limit, $offset); - } - - public function findOneBy(array $criteria): ?AsideActivity - { - return $this->repository->findOneBy($criteria); + parent::__construct($registry, AsideActivity::class); } public function getClassName(): string diff --git a/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/_delete.html.twig b/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/_delete.html.twig index 2f30b14cf..79c2e6919 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/_delete.html.twig +++ b/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/_delete.html.twig @@ -1,4 +1,4 @@ -
    + {% block crud_content_header %}

    {{ ('crud.'~crud_name~'.title_delete')|trans({ '%as_string%': 'Aside Activity' }) }}

    {% endblock crud_content_header %} @@ -27,4 +27,4 @@
{{ form_end(form) }} -
+ 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 4dd12e042..5ffc73684 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/index.html.twig +++ b/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/index.html.twig @@ -87,5 +87,5 @@ {% endif %} - + {% endblock %} diff --git a/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/new.html.twig b/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/new.html.twig index 09acf9859..99ff217b1 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/new.html.twig +++ b/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/new.html.twig @@ -1,4 +1,4 @@ -{% extends '@ChillMain/Admin/layout.html.twig' %} +{% extends '@ChillMain/layout.html.twig' %} {% block js %} {{ parent() }} @@ -14,7 +14,7 @@ {% include('@ChillMain/CRUD/_new_title.html.twig') %} {% endblock %} -{% block admin_content %} +{% block content %} {% embed '@ChillMain/CRUD/_new_content.html.twig' %} {% block content_form_actions_save_and_show %}{% endblock %} {% endembed %} diff --git a/src/Bundle/ChillAsideActivityBundle/src/Security/AsideActivityVoter.php b/src/Bundle/ChillAsideActivityBundle/src/Security/AsideActivityVoter.php new file mode 100644 index 000000000..6b308b5f2 --- /dev/null +++ b/src/Bundle/ChillAsideActivityBundle/src/Security/AsideActivityVoter.php @@ -0,0 +1,79 @@ +voterHelper = $voterHelperFactory + ->generate(self::class) + ->addCheckFor(Center::class, [self::STATS]) + ->build(); + } + + /** + * @return string[] + */ + public function getRoles(): array + { + return $this->getAttributes(); + } + + /** + * @return string[][] + */ + public function getRolesWithHierarchy(): array + { + return ['Aside activity' => $this->getRoles()]; + } + + /** + * @return string[] + */ + public function getRolesWithoutScope(): array + { + return $this->getAttributes(); + } + + protected function supports($attribute, $subject) + { + return $this->voterHelper->supports($attribute, $subject); + } + + protected function voteOnAttribute($attribute, $subject, TokenInterface $token): bool + { + if (!$token->getUser() instanceof User) { + return false; + } + + return $this->voterHelper->voteOnAttribute($attribute, $subject, $token); + } + + private function getAttributes(): array + { + return [self::STATS]; + } +} diff --git a/src/Bundle/ChillAsideActivityBundle/src/Templating/Entity/CategoryRender.php b/src/Bundle/ChillAsideActivityBundle/src/Templating/Entity/CategoryRender.php index 8b49098e4..066a0ca52 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Templating/Entity/CategoryRender.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Templating/Entity/CategoryRender.php @@ -1,14 +1,14 @@ + */ final class CategoryRender implements ChillEntityRenderInterface { public const DEFAULT_ARGS = [ @@ -45,9 +48,6 @@ final class CategoryRender implements ChillEntityRenderInterface return $parents; } - /** - * @param AsideActivityCategory $asideActivityCategory - */ public function renderBox($asideActivityCategory, array $options): string { $options = array_merge(self::DEFAULT_ARGS, $options); @@ -63,9 +63,6 @@ final class CategoryRender implements ChillEntityRenderInterface ); } - /** - * @param AsideActivityCategory $asideActivityCategory - */ public function renderString($asideActivityCategory, array $options): string { $options = array_merge(self::DEFAULT_ARGS, $options); @@ -84,9 +81,6 @@ final class CategoryRender implements ChillEntityRenderInterface return implode($options[self::SEPERATOR_KEY], $titles); } - /** - * @param AsideActivityCategory $asideActivityCategory - */ public function supports($asideActivityCategory, array $options): bool { return $asideActivityCategory instanceof AsideActivityCategory; diff --git a/src/Bundle/ChillAsideActivityBundle/src/Tests/Controller/AsideActivityControllerTest.php b/src/Bundle/ChillAsideActivityBundle/src/Tests/Controller/AsideActivityControllerTest.php index 1dea9bc0c..196dd5bc3 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Tests/Controller/AsideActivityControllerTest.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Tests/Controller/AsideActivityControllerTest.php @@ -1,14 +1,14 @@ $elements */ public function calculate(array $elements): ?CalculatorResult; diff --git a/src/Bundle/ChillBudgetBundle/Calculator/CalculatorManager.php b/src/Bundle/ChillBudgetBundle/Calculator/CalculatorManager.php index 21c9e18ca..7987bdfdf 100644 --- a/src/Bundle/ChillBudgetBundle/Calculator/CalculatorManager.php +++ b/src/Bundle/ChillBudgetBundle/Calculator/CalculatorManager.php @@ -1,17 +1,19 @@ */ - protected $calculators = []; + private array $calculators = []; - protected $defaultCalculator = []; + /** + * @var string[] + */ + private array $defaultCalculator = []; public function addCalculator(CalculatorInterface $calculator, bool $default) { - $this->calculators[$calculator::getAlias()] = $calculator; + $this->calculators[$calculator->getAlias()] = $calculator; if ($default) { - $this->defaultCalculator[] = $calculator::getAlias(); + $this->defaultCalculator[] = $calculator->getAlias(); } } /** - * @param AbstractElement[] $elements + * @param array $elements * * @return CalculatorResult[] */ @@ -46,23 +51,17 @@ class CalculatorManager $results = []; foreach ($this->defaultCalculator as $alias) { - $calculator = $this->calculators[$alias]; - $result = $calculator->calculate($elements); + $result = $this->getCalculator($alias)->calculate($elements); if (null !== $result) { - $results[$calculator::getAlias()] = $result; + $results[$alias] = $result; } } return $results; } - /** - * @param string $alias - * - * @return CalculatorInterface - */ - public function getCalculator($alias) + public function getCalculator(string $alias): CalculatorInterface { if (false === array_key_exists($alias, $this->calculators)) { throw new OutOfBoundsException("The calculator with alias '{$alias}' does " diff --git a/src/Bundle/ChillBudgetBundle/Calculator/CalculatorResult.php b/src/Bundle/ChillBudgetBundle/Calculator/CalculatorResult.php index c3cc40008..380e641a6 100644 --- a/src/Bundle/ChillBudgetBundle/Calculator/CalculatorResult.php +++ b/src/Bundle/ChillBudgetBundle/Calculator/CalculatorResult.php @@ -1,14 +1,14 @@ resources = $resources; - $this->charges = $charges; - } - - public function getChargesKeys(): array - { - return array_map(static function ($element) { return $element['key']; }, $this->charges); - } - - /** - * @return array where keys are the resource'key and label the ressource label - */ - public function getChargesLabels() - { - $charges = []; - - foreach ($this->charges as $definition) { - $charges[$definition['key']] = $this->normalizeLabel($definition['labels']); - } - - return $charges; - } - - public function getResourcesKeys(): array - { - return array_map(static function ($element) { return $element['key']; }, $this->resources); - } - - /** - * @return array where keys are the resource'key and label the ressource label - */ - public function getResourcesLabels() - { - $resources = []; - - foreach ($this->resources as $definition) { - $resources[$definition['key']] = $this->normalizeLabel($definition['labels']); - } - - return $resources; - } - - private function normalizeLabel($labels) - { - $normalizedLabels = []; - - foreach ($labels as $labelDefinition) { - $normalizedLabels[$labelDefinition['lang']] = $labelDefinition['label']; - } - - return $normalizedLabels; - } -} diff --git a/src/Bundle/ChillBudgetBundle/Controller/AbstractElementController.php b/src/Bundle/ChillBudgetBundle/Controller/AbstractElementController.php index 94cb9255a..125c72ce4 100644 --- a/src/Bundle/ChillBudgetBundle/Controller/AbstractElementController.php +++ b/src/Bundle/ChillBudgetBundle/Controller/AbstractElementController.php @@ -1,14 +1,14 @@ getPerson() ? $element->getPerson() : $element->getHousehold(); + $entity = $element->getPerson() ?? $element->getHousehold(); $form = $this->createForm($this->getType(), $element); $form->add('submit', SubmitType::class); diff --git a/src/Bundle/ChillBudgetBundle/Controller/Admin/AdminController.php b/src/Bundle/ChillBudgetBundle/Controller/Admin/AdminController.php new file mode 100644 index 000000000..998775fbe --- /dev/null +++ b/src/Bundle/ChillBudgetBundle/Controller/Admin/AdminController.php @@ -0,0 +1,26 @@ +render('@ChillBudget/Admin/index.html.twig'); + } +} diff --git a/src/Bundle/ChillBudgetBundle/Controller/Admin/ChargeKindController.php b/src/Bundle/ChillBudgetBundle/Controller/Admin/ChargeKindController.php new file mode 100644 index 000000000..e1989176d --- /dev/null +++ b/src/Bundle/ChillBudgetBundle/Controller/Admin/ChargeKindController.php @@ -0,0 +1,28 @@ +addOrderBy('e.ordering', 'ASC'); + + return $query; + } +} diff --git a/src/Bundle/ChillBudgetBundle/Controller/Admin/ResourceKindController.php b/src/Bundle/ChillBudgetBundle/Controller/Admin/ResourceKindController.php new file mode 100644 index 000000000..90b8e750a --- /dev/null +++ b/src/Bundle/ChillBudgetBundle/Controller/Admin/ResourceKindController.php @@ -0,0 +1,28 @@ +addOrderBy('e.ordering', 'ASC'); + + return $query; + } +} diff --git a/src/Bundle/ChillBudgetBundle/Controller/ChargeController.php b/src/Bundle/ChillBudgetBundle/Controller/ChargeController.php index ec556ffed..5d4a8b3a4 100644 --- a/src/Bundle/ChillBudgetBundle/Controller/ChargeController.php +++ b/src/Bundle/ChillBudgetBundle/Controller/ChargeController.php @@ -1,14 +1,14 @@ em = $em; - $this->translator = $translator; - $this->chillMainLogger = $chillMainLogger; $this->calculator = $calculator; + $this->resourceRepository = $resourceRepository; + $this->chargeRepository = $chargeRepository; } /** @@ -59,24 +57,10 @@ class ElementController extends AbstractController { $this->denyAccessUnlessGranted(BudgetElementVoter::SEE, $person); - $charges = $this->em - ->getRepository(Charge::class) - ->findByPerson($person); + $charges = $this->chargeRepository->findAllByEntity($person); + $resources = $this->resourceRepository->findAllByEntity($person); - $ressources = $this->em - ->getRepository(Resource::class) - ->findByPerson($person); - - $now = new DateTime('now'); - - $actualCharges = $this->em - ->getRepository(Charge::class) - ->findByEntityAndDate($person, $now); - $actualResources = $this->em - ->getRepository(Resource::class) - ->findByEntityAndDate($person, $now); - - $elements = array_merge($actualCharges, $actualResources); + $elements = array_merge($charges, $resources); if (count($elements) > 0) { $results = $this->calculator->calculateDefault($elements); @@ -85,7 +69,7 @@ class ElementController extends AbstractController return $this->render('ChillBudgetBundle:Person:index.html.twig', [ 'person' => $person, 'charges' => $charges, - 'resources' => $ressources, + 'resources' => $resources, 'results' => $results ?? [], ]); } @@ -100,60 +84,19 @@ class ElementController extends AbstractController { $this->denyAccessUnlessGranted(BudgetElementVoter::SEE, $household); - $charges = $this->em - ->getRepository(Charge::class) - ->findByHousehold($household); + $charges = $this->chargeRepository->findAllByEntity($household); + $resources = $this->resourceRepository->findAllByEntity($household); - $ressources = $this->em - ->getRepository(Resource::class) - ->findByHousehold($household); - - $now = new DateTime('now'); - - $actualCharges = $this->em - ->getRepository(Charge::class) - ->findByEntityAndDate($household, $now); - $actualResources = $this->em - ->getRepository(Resource::class) - ->findByEntityAndDate($household, $now); - - $elements = array_merge($actualCharges, $actualResources); + $elements = array_merge($charges, $resources); if (count($elements) > 0) { $results = $this->calculator->calculateDefault($elements); } - // quick solution to calculate the sum, difference and amount from - // controller. This should be done from the calculators - // TODO replace this by calculators - $wholeCharges = $actualCharges; - $wholeResources = $actualResources; - - foreach ($household->getCurrentPersons() as $person) { - $wholeCharges = array_merge( - $wholeCharges, - $this->em - ->getRepository(Charge::class) - ->findByEntityAndDate($person, $now) - ); - $wholeResources = array_merge( - $wholeResources, - $this->em - ->getRepository(Resource::class) - ->findByEntityAndDate($person, $now) - ); - } - return $this->render('ChillBudgetBundle:Household:index.html.twig', [ 'household' => $household, 'charges' => $charges, - 'resources' => $ressources, - 'wholeResources' => array_filter($wholeResources, static function (Resource $r) use ($now) { - return $r->getStartDate() <= $now && ($r->getEndDate() === null || $r->getEndDate() >= $now); - }), - 'wholeCharges' => array_filter($wholeCharges, static function (Charge $c) use ($now) { - return $c->getStartDate() <= $now && ($c->getEndDate() === null || $c->getEndDate() >= $now); - }), + 'resources' => $resources, 'results' => $results ?? [], ]); } diff --git a/src/Bundle/ChillBudgetBundle/Controller/ResourceController.php b/src/Bundle/ChillBudgetBundle/Controller/ResourceController.php index 3ecd53097..e7f1e9ff3 100644 --- a/src/Bundle/ChillBudgetBundle/Controller/ResourceController.php +++ b/src/Bundle/ChillBudgetBundle/Controller/ResourceController.php @@ -1,14 +1,14 @@ processConfiguration($configuration, $configs); $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__ . '/../config')); - $loader->load('services/config.yaml'); $loader->load('services/form.yaml'); + $loader->load('services/repository.yaml'); $loader->load('services/security.yaml'); $loader->load('services/controller.yaml'); $loader->load('services/templating.yaml'); @@ -48,6 +52,7 @@ class ChillBudgetExtension extends Extension implements PrependExtensionInterfac { $this->prependAuthorization($container); $this->prependRoutes($container); + $this->prependCruds($container); } /** (non-PHPdoc). @@ -75,6 +80,56 @@ class ChillBudgetExtension extends Extension implements PrependExtensionInterfac ]); } + protected function prependCruds(ContainerBuilder $container) + { + $container->prependExtensionConfig('chill_main', [ + 'cruds' => [ + [ + 'class' => ChargeKind::class, + 'name' => 'charge_kind', + 'base_path' => '/admin/budget/charge-kind', + 'form_class' => \Chill\BudgetBundle\Form\Admin\ChargeKindType::class, + 'controller' => ChargeKindController::class, + 'actions' => [ + 'index' => [ + 'role' => 'ROLE_ADMIN', + 'template' => '@ChillBudget/Admin/Charge/index.html.twig', + ], + 'new' => [ + 'role' => 'ROLE_ADMIN', + 'template' => '@ChillBudget/Admin/Charge/new.html.twig', + ], + 'edit' => [ + 'role' => 'ROLE_ADMIN', + 'template' => '@ChillBudget/Admin/Charge/edit.html.twig', + ], + ], + ], + [ + 'class' => ResourceKind::class, + 'name' => 'resource_kind', + 'base_path' => '/admin/budget/resource-kind', + 'form_class' => \Chill\BudgetBundle\Form\Admin\ResourceKindType::class, + 'controller' => ResourceKindController::class, + 'actions' => [ + 'index' => [ + 'role' => 'ROLE_ADMIN', + 'template' => '@ChillBudget/Admin/Resource/index.html.twig', + ], + 'new' => [ + 'role' => 'ROLE_ADMIN', + 'template' => '@ChillBudget/Admin/Resource/new.html.twig', + ], + 'edit' => [ + 'role' => 'ROLE_ADMIN', + 'template' => '@ChillBudget/Admin/Resource/edit.html.twig', + ], + ], + ], + ], + ]); + } + protected function storeConfig($position, array $config, ContainerBuilder $container) { $container diff --git a/src/Bundle/ChillBudgetBundle/DependencyInjection/Compiler/CalculatorCompilerPass.php b/src/Bundle/ChillBudgetBundle/DependencyInjection/Compiler/CalculatorCompilerPass.php index ec47d5faa..012c4eab5 100644 --- a/src/Bundle/ChillBudgetBundle/DependencyInjection/Compiler/CalculatorCompilerPass.php +++ b/src/Bundle/ChillBudgetBundle/DependencyInjection/Compiler/CalculatorCompilerPass.php @@ -1,14 +1,14 @@ 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 a18e17ae6..ff9931f2d 100644 --- a/src/Bundle/ChillBudgetBundle/DependencyInjection/Configuration.php +++ b/src/Bundle/ChillBudgetBundle/DependencyInjection/Configuration.php @@ -1,42 +1,39 @@ getRootNode('chill_budget'); + $rootNode = $treeBuilder->getRootNode(); $rootNode ->children() // ressources ->arrayNode('resources')->defaultValue([]) + ->setDeprecated('Chill', '2.0', 'Since the introduction of budget admin entities, config is no longer used') ->arrayPrototype() ->children() ->scalarNode('key')->isRequired()->cannotBeEmpty() ->info('the key stored in database') ->example('salary') ->end() + ->booleanNode('active')->defaultTrue()->end() ->arrayNode('labels')->isRequired()->requiresAtLeastOneElement() ->arrayPrototype() ->children() @@ -53,12 +50,14 @@ class Configuration implements ConfigurationInterface ->end() ->end() ->arrayNode('charges')->defaultValue([]) + ->setDeprecated('Chill', '2.0', 'Since the introduction of budget admin entities, config is no longer used') ->arrayPrototype() ->children() ->scalarNode('key')->isRequired()->cannotBeEmpty() ->info('the key stored in database') ->example('salary') ->end() + ->booleanNode('active')->defaultTrue()->end() ->arrayNode('labels')->isRequired()->requiresAtLeastOneElement() ->arrayPrototype() ->children() diff --git a/src/Bundle/ChillBudgetBundle/Entity/AbstractElement.php b/src/Bundle/ChillBudgetBundle/Entity/AbstractElement.php index 747e6f4b4..619a6cdd7 100644 --- a/src/Bundle/ChillBudgetBundle/Entity/AbstractElement.php +++ b/src/Bundle/ChillBudgetBundle/Entity/AbstractElement.php @@ -1,14 +1,14 @@ household = $household; diff --git a/src/Bundle/ChillBudgetBundle/Entity/Charge.php b/src/Bundle/ChillBudgetBundle/Entity/Charge.php index 4acc6e855..056e4abff 100644 --- a/src/Bundle/ChillBudgetBundle/Entity/Charge.php +++ b/src/Bundle/ChillBudgetBundle/Entity/Charge.php @@ -1,18 +1,18 @@ setStartDate(new DateTimeImmutable('today')); } - public function getCenter(): ?Center + public function getCenters(): array { - return $this->getPerson()->getCenter(); + if (null !== $this->getPerson()) { + return [$this->getPerson()->getCenter()]; + } + + return $this->getHousehold()->getCurrentPersons()->map(static fn (Person $p) => $p->getCenter())->toArray(); + } + + public function getCharge(): ?ChargeKind + { + return $this->charge; } public function getHelp() @@ -89,6 +102,13 @@ class Charge extends AbstractElement implements HasCenterInterface return false; } + public function setCharge(?ChargeKind $charge): self + { + $this->charge = $charge; + + return $this; + } + public function setHelp($help) { $this->help = $help; diff --git a/src/Bundle/ChillBudgetBundle/Entity/ChargeKind.php b/src/Bundle/ChillBudgetBundle/Entity/ChargeKind.php new file mode 100644 index 000000000..b6ebcf347 --- /dev/null +++ b/src/Bundle/ChillBudgetBundle/Entity/ChargeKind.php @@ -0,0 +1,115 @@ +id; + } + + public function getIsActive(): bool + { + return $this->isActive; + } + + public function getKind(): ?string + { + return $this->kind; + } + + public function getName(): ?array + { + return $this->name; + } + + public function getOrdering(): float + { + return $this->ordering; + } + + public function setIsActive(bool $isActive): self + { + $this->isActive = $isActive; + + return $this; + } + + public function setKind(?string $kind): self + { + $this->kind = $kind; + + return $this; + } + + public function setName(array $name): self + { + $this->name = $name; + + return $this; + } + + public function setOrdering(float $ordering): ChargeKind + { + $this->ordering = $ordering; + + return $this; + } +} diff --git a/src/Bundle/ChillBudgetBundle/Entity/Resource.php b/src/Bundle/ChillBudgetBundle/Entity/Resource.php index 6100e90a0..28b8645e0 100644 --- a/src/Bundle/ChillBudgetBundle/Entity/Resource.php +++ b/src/Bundle/ChillBudgetBundle/Entity/Resource.php @@ -1,18 +1,18 @@ setStartDate(new DateTimeImmutable('today')); } - public function getCenter(): ?Center + public function getCenters(): array { - return $this->getPerson()->getCenter(); + if (null !== $this->getPerson()) { + return [$this->getPerson()->getCenter()]; + } + + return $this->getHousehold()->getCurrentPersons()->map(static fn (Person $p) => $p->getCenter())->toArray(); } /** @@ -53,6 +61,11 @@ class Resource extends AbstractElement implements HasCenterInterface return $this->id; } + public function getResource(): ?ResourceKind + { + return $this->resource; + } + public function isCharge(): bool { return false; @@ -62,4 +75,11 @@ class Resource extends AbstractElement implements HasCenterInterface { return true; } + + public function setResource(?ResourceKind $resource): self + { + $this->resource = $resource; + + return $this; + } } diff --git a/src/Bundle/ChillBudgetBundle/Entity/ResourceKind.php b/src/Bundle/ChillBudgetBundle/Entity/ResourceKind.php new file mode 100644 index 000000000..4b90be5e1 --- /dev/null +++ b/src/Bundle/ChillBudgetBundle/Entity/ResourceKind.php @@ -0,0 +1,115 @@ +id; + } + + public function getIsActive(): bool + { + return $this->isActive; + } + + public function getKind(): ?string + { + return $this->kind; + } + + public function getName(): ?array + { + return $this->name; + } + + public function getOrdering(): float + { + return $this->ordering; + } + + public function setIsActive(bool $isActive): self + { + $this->isActive = $isActive; + + return $this; + } + + public function setKind(?string $kind): self + { + $this->kind = $kind; + + return $this; + } + + public function setName(array $name): self + { + $this->name = $name; + + return $this; + } + + public function setOrdering(float $ordering): self + { + $this->ordering = $ordering; + + return $this; + } +} diff --git a/src/Bundle/ChillBudgetBundle/Form/Admin/ChargeKindType.php b/src/Bundle/ChillBudgetBundle/Form/Admin/ChargeKindType.php new file mode 100644 index 000000000..c3c2a66bf --- /dev/null +++ b/src/Bundle/ChillBudgetBundle/Form/Admin/ChargeKindType.php @@ -0,0 +1,47 @@ +add('name', TranslatableStringFormType::class, [ + 'label' => 'Title', + ]) + ->add('kind', TextType::class, [ + 'label' => 'budget.admin.form.Charge_kind_key', + 'help' => 'budget.admin.form.This kind must contains only alphabeticals characters, and dashes. This string is in use during document generation. Changes may have side effect on document', + ]) + ->add('ordering', NumberType::class) + ->add('isActive', CheckboxType::class, [ + 'label' => 'Actif ?', + 'required' => false, + ]); + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver + ->setDefault('class', ChargeKind::class); + } +} diff --git a/src/Bundle/ChillBudgetBundle/Form/Admin/ResourceKindType.php b/src/Bundle/ChillBudgetBundle/Form/Admin/ResourceKindType.php new file mode 100644 index 000000000..41d3a8b53 --- /dev/null +++ b/src/Bundle/ChillBudgetBundle/Form/Admin/ResourceKindType.php @@ -0,0 +1,47 @@ +add('name', TranslatableStringFormType::class, [ + 'label' => 'Title', + ]) + ->add('kind', TextType::class, [ + 'label' => 'budget.admin.form.Resource_kind_key', + 'help' => 'budget.admin.form.This kind must contains only alphabeticals characters, and dashes. This string is in use during document generation. Changes may have side effect on document', + ]) + ->add('ordering', NumberType::class) + ->add('isActive', CheckboxType::class, [ + 'label' => 'Actif ?', + 'required' => false, + ]); + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver + ->setDefault('class', ResourceKind::class); + } +} diff --git a/src/Bundle/ChillBudgetBundle/Form/ChargeType.php b/src/Bundle/ChillBudgetBundle/Form/ChargeType.php index 1e054d86b..72863163f 100644 --- a/src/Bundle/ChillBudgetBundle/Form/ChargeType.php +++ b/src/Bundle/ChillBudgetBundle/Form/ChargeType.php @@ -1,51 +1,59 @@ configRepository = $configRepository; $this->translatableStringHelper = $translatableStringHelper; + $this->repository = $repository; + $this->translator = $translator; } public function buildForm(FormBuilderInterface $builder, array $options) { $builder - ->add('type', ChoiceType::class, [ - 'choices' => $this->getTypes(), - 'placeholder' => 'Choose a charge type', - 'attr' => ['class' => ' select2 '], + ->add('charge', EntityType::class, [ + 'class' => ChargeKind::class, + 'choices' => $this->repository->findAllActive(), + 'label' => 'Charge type', + 'required' => true, + 'placeholder' => $this->translator->trans('admin.form.Choose the type of charge'), + 'choice_label' => fn (ChargeKind $resource) => $this->translatableStringHelper->localize($resource->getName()), + 'attr' => ['class' => 'select2'], ]) ->add('amount', MoneyType::class) ->add('comment', TextareaType::class, [ @@ -99,19 +107,4 @@ class ChargeType extends AbstractType { return 'chill_budgetbundle_charge'; } - - private function getTypes() - { - $charges = $this->configRepository - ->getChargesLabels(); - - // rewrite labels to filter in language - foreach ($charges as $key => $labels) { - $charges[$key] = $this->translatableStringHelper->localize($labels); - } - - asort($charges); - - return array_flip($charges); - } } diff --git a/src/Bundle/ChillBudgetBundle/Form/ResourceType.php b/src/Bundle/ChillBudgetBundle/Form/ResourceType.php index 51e3f6799..09dfd16f3 100644 --- a/src/Bundle/ChillBudgetBundle/Form/ResourceType.php +++ b/src/Bundle/ChillBudgetBundle/Form/ResourceType.php @@ -1,51 +1,58 @@ configRepository = $configRepository; $this->translatableStringHelper = $translatableStringHelper; + $this->repository = $repository; + $this->translator = $translator; } public function buildForm(FormBuilderInterface $builder, array $options) { $builder - ->add('type', ChoiceType::class, [ - 'choices' => $this->getTypes(), - 'placeholder' => 'Choose a resource type', - 'label' => 'Resource element type', - 'attr' => ['class' => ' select2 '], + ->add('resource', EntityType::class, [ + 'class' => ResourceKind::class, + 'choices' => $this->repository->findAllActive(), + 'label' => 'Resource type', + 'required' => true, + 'placeholder' => $this->translator->trans('admin.form.Choose the type of resource'), + 'choice_label' => fn (ResourceKind $resource) => $this->translatableStringHelper->localize($resource->getName()), + 'attr' => ['class' => 'select2'], ]) ->add('amount', MoneyType::class) ->add('comment', TextareaType::class, [ @@ -83,19 +90,4 @@ class ResourceType extends AbstractType { return 'chill_budgetbundle_resource'; } - - private function getTypes() - { - $resources = $this->configRepository - ->getResourcesLabels(); - - // rewrite labels to filter in language - foreach ($resources as $key => $labels) { - $resources[$key] = $this->translatableStringHelper->localize($labels); - } - - asort($resources); - - return array_flip($resources); - } } diff --git a/src/Bundle/ChillBudgetBundle/Menu/AdminMenuBuilder.php b/src/Bundle/ChillBudgetBundle/Menu/AdminMenuBuilder.php new file mode 100644 index 000000000..894230ad9 --- /dev/null +++ b/src/Bundle/ChillBudgetBundle/Menu/AdminMenuBuilder.php @@ -0,0 +1,62 @@ +security = $security; + } + + public function buildMenu($menuId, MenuItem $menu, array $parameters) + { + // all the entries below must have ROLE_ADMIN permissions + if (!$this->security->isGranted('ROLE_ADMIN')) { + return; + } + + $menu->addChild('Budget', [ + 'route' => 'chill_admin_budget', + ]) + ->setAttribute('class', 'list-group-item-header') + ->setExtras([ + 'order' => 7050, + 'explain' => 'Budget resource and charge type configuration', + ]); + $menu + ->addChild('admin.menu.Resource types', [ + 'route' => 'chill_crud_resource_kind_index', + ]) + ->setExtras([ + 'order' => 7060, + ]); + $menu + ->addChild('admin.menu.Charge types', [ + 'route' => 'chill_crud_charge_kind_index', + ]) + ->setExtras([ + 'order' => 7070, + ]); + } + + public static function getMenuIds(): array + { + return ['admin_section', 'admin_budget']; + } +} diff --git a/src/Bundle/ChillBudgetBundle/Menu/HouseholdMenuBuilder.php b/src/Bundle/ChillBudgetBundle/Menu/HouseholdMenuBuilder.php index f9f295e17..1224c0bfe 100644 --- a/src/Bundle/ChillBudgetBundle/Menu/HouseholdMenuBuilder.php +++ b/src/Bundle/ChillBudgetBundle/Menu/HouseholdMenuBuilder.php @@ -1,14 +1,14 @@ repository = $entityManager->getRepository(ChargeKind::class); + } + + public function find($id): ?ChargeKind + { + return $this->repository->find($id); + } + + /** + * @return array + */ + public function findAll(): array + { + return $this->repository->findAll(); + } + + /** + * @return array + */ + public function findAllActive(): array + { + $qb = $this->repository->createQueryBuilder('c'); + + return $qb + ->select('c') + ->where($qb->expr()->eq('c.isActive', 'true')) + ->orderBy('c.ordering', 'ASC') + ->getQuery() + ->getResult(); + } + + /** + * @return array + */ + public function findAllByType(string $type): array + { + return $this->findBy(['elementType' => $type]); + } + + /** + * @param mixed|null $limit + * @param mixed|null $offset + * + * @return array + */ + public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array + { + return $this->repository->findBy($criteria, $orderBy, $limit, $offset); + } + + public function findOneBy(array $criteria): ?ChargeKind + { + return $this->repository->findOneBy($criteria); + } + + public function findOneByKind(string $kind): ?ChargeKind + { + return $this->repository->findOneBy(['kind' => $kind]); + } + + public function getClassName(): string + { + return ChargeKind::class; + } +} diff --git a/src/Bundle/ChillBudgetBundle/Repository/ChargeKindRepositoryInterface.php b/src/Bundle/ChillBudgetBundle/Repository/ChargeKindRepositoryInterface.php new file mode 100644 index 000000000..f92851eba --- /dev/null +++ b/src/Bundle/ChillBudgetBundle/Repository/ChargeKindRepositoryInterface.php @@ -0,0 +1,49 @@ + + */ + public function findAll(): array; + + /** + * @return array + */ + public function findAllActive(): array; + + /** + * @return array + */ + 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 findOneBy(array $criteria): ?ChargeKind; + + public function findOneByKind(string $kind): ?ChargeKind; + + public function getClassName(): string; +} diff --git a/src/Bundle/ChillBudgetBundle/Repository/ChargeRepository.php b/src/Bundle/ChillBudgetBundle/Repository/ChargeRepository.php index 62aa9eea6..9b89010dc 100644 --- a/src/Bundle/ChillBudgetBundle/Repository/ChargeRepository.php +++ b/src/Bundle/ChillBudgetBundle/Repository/ChargeRepository.php @@ -1,19 +1,22 @@ createQueryBuilder('c'); + + $property = $entity instanceof Person ? 'person' : 'household'; + + $qb->where("c.{$property} = :entity") + ->setParameter('entity', $entity); + + return $qb->getQuery()->getResult(); + } + public function findByEntityAndDate($entity, DateTime $date, $sort = null) { $qb = $this->createQueryBuilder('c'); $entityStr = $entity instanceof Person ? 'person' : 'household'; - $qb->where("c.{$entityStr} = :{$entityStr}") - ->andWhere('c.startDate < :date') - ->andWhere('c.startDate < :date OR c.startDate IS NULL'); + $qb->where("c.{$entityStr} = :entity") + ->andWhere('c.startDate <= :date') + ->andWhere('c.endDate > :date OR c.endDate IS NULL'); if (null !== $sort) { $qb->orderBy($sort); } $qb->setParameters([ - $entityStr => $entity, + 'entity' => $entity, 'date' => $date, ]); diff --git a/src/Bundle/ChillBudgetBundle/Repository/ResourceKindRepository.php b/src/Bundle/ChillBudgetBundle/Repository/ResourceKindRepository.php new file mode 100644 index 000000000..1f9f99753 --- /dev/null +++ b/src/Bundle/ChillBudgetBundle/Repository/ResourceKindRepository.php @@ -0,0 +1,88 @@ +repository = $entityManager->getRepository(ResourceKind::class); + } + + public function find($id): ?ResourceKind + { + return $this->repository->find($id); + } + + /** + * @return list + */ + public function findAll(): array + { + return $this->repository->findAll(); + } + + /** + * @return list + */ + public function findAllActive(): array + { + $qb = $this->repository->createQueryBuilder('r'); + + return $qb + ->select('r') + ->where($qb->expr()->eq('r.isActive', 'true')) + ->orderBy('r.ordering', 'ASC') + ->getQuery() + ->getResult(); + } + + public function findOneByKind(string $kind): ?ResourceKind + { + return $this->repository->findOneBy(['kind' => $kind]) ; + } + + /** + * @return list + */ + public function findAllByType(string $type): array + { + return $this->findBy(['elementType' => $type]); + } + + /** + * @param mixed|null $limit + * @param mixed|null $offset + * + * @return list + */ + public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array + { + return $this->repository->findBy($criteria, $orderBy, $limit, $offset); + } + + public function findOneBy(array $criteria): ?ResourceKind + { + return $this->repository->findOneBy($criteria); + } + + public function getClassName(): string + { + return ResourceKind::class; + } +} diff --git a/src/Bundle/ChillBudgetBundle/Repository/ResourceKindRepositoryInterface.php b/src/Bundle/ChillBudgetBundle/Repository/ResourceKindRepositoryInterface.php new file mode 100644 index 000000000..dac46ff35 --- /dev/null +++ b/src/Bundle/ChillBudgetBundle/Repository/ResourceKindRepositoryInterface.php @@ -0,0 +1,49 @@ + + */ + public function findAll(): array; + + /** + * @return list + */ + public function findAllActive(): array; + + /** + * @return list + */ + 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 findOneBy(array $criteria): ?ResourceKind; + + public function findOneByKind(string $kind): ?ResourceKind; + + public function getClassName(): string; +} diff --git a/src/Bundle/ChillBudgetBundle/Repository/ResourceRepository.php b/src/Bundle/ChillBudgetBundle/Repository/ResourceRepository.php index 23cc5ca54..836f13e4b 100644 --- a/src/Bundle/ChillBudgetBundle/Repository/ResourceRepository.php +++ b/src/Bundle/ChillBudgetBundle/Repository/ResourceRepository.php @@ -1,19 +1,23 @@ createQueryBuilder('r'); + + $property = $entity instanceof Person ? 'person' : 'household'; + + $qb->where("r.{$property} = :entity") + ->setParameter('entity', $entity); + + return $qb->getQuery()->getResult(); + } + + public function findByEntityAndDate(Person|Household $entity, DateTime $date, $sort = null) { $qb = $this->createQueryBuilder('c'); $entityStr = $entity instanceof Person ? 'person' : 'household'; - $qb->where("c.{$entityStr} = :{$entityStr}") - // TODO: in controller, the budget and charges asked are also for future and actual - //->andWhere('c.startDate < :date') - // TODO: there is a misconception here, the end date must be lower or null. startDate are never null - //->andWhere('c.startDate < :date OR c.startDate IS NULL'); -; + $qb->where("c.{$entityStr} = :entity") + ->andWhere('c.startDate <= :date') + ->andWhere('c.endDate > :date OR c.endDate IS NULL'); if (null !== $sort) { $qb->orderBy($sort); } $qb->setParameters([ - $entityStr => $entity, - //'date' => $date, + 'entity' => $entity, + 'date' => $date, ]); return $qb->getQuery()->getResult(); diff --git a/src/Bundle/ChillBudgetBundle/Resources/public/page/chillbudget.scss b/src/Bundle/ChillBudgetBundle/Resources/public/page/chillbudget.scss index 20a87cce2..79f40dc76 100644 --- a/src/Bundle/ChillBudgetBundle/Resources/public/page/chillbudget.scss +++ b/src/Bundle/ChillBudgetBundle/Resources/public/page/chillbudget.scss @@ -1,39 +1,42 @@ -.subtitle { +h3.subtitle { margin-top: 1rem; margin-bottom: 1rem; padding: 1rem; + &::before { + font: normal normal normal 20px/1 ForkAwesome; + margin-right: 0.5em; + content: "\f061"; + } } -.family-title { + +$col_charge: #e03851d7; +$col_resource: #6d9e63d8; + +h4.family-title { + margin-top: 1.5rem; margin-bottom: 1rem !important; + padding-left: 0.7em; + i { + margin-right: 0.4em; + } + &.charge i { color: $col_charge; } + &.resource i { color: $col_resource; } } .budget-table th { th { color: white; } } -.budget-table { - th.charge { - background-color: #e03851d7; - } -} -.budget-table { - th.resource { - background-color: #6d9e63d8; - } -} -.budget-table { + +table.budget-table { th, td { padding: 10px; text-align: right; } - td.column-wide { - width: 20%; - } - td.column-small { - width: 15%; - &.right { - align-items: right; - } + th.charge { background-color: $col_charge; } + th.resource { background-color: $col_resource; } + td.column-fixed { + width: 9.5em; } } @@ -58,4 +61,4 @@ button[aria-expanded="true"] > span.folded, button[aria-expanded="false"] > span.unfolded { display: none; } button[aria-expanded="false"] > span.folded, -button[aria-expanded="true"] > span.unfolded { display: inline; } \ No newline at end of file +button[aria-expanded="true"] > span.unfolded { display: inline; } diff --git a/src/Bundle/ChillBudgetBundle/Resources/views/Admin/Charge/edit.html.twig b/src/Bundle/ChillBudgetBundle/Resources/views/Admin/Charge/edit.html.twig new file mode 100644 index 000000000..3a60d3e9c --- /dev/null +++ b/src/Bundle/ChillBudgetBundle/Resources/views/Admin/Charge/edit.html.twig @@ -0,0 +1,12 @@ +{% extends '@ChillMain/CRUD/Admin/index.html.twig' %} + +{% block title %} + {% include('@ChillMain/CRUD/_edit_title.html.twig') %} +{% endblock %} + +{% block admin_content %} + {% embed '@ChillMain/CRUD/_edit_content.html.twig' %} + {% block content_form_actions_view %}{% endblock %} + {% block content_form_actions_save_and_show %}{% endblock %} + {% endembed %} +{% endblock %} diff --git a/src/Bundle/ChillBudgetBundle/Resources/views/Admin/Charge/index.html.twig b/src/Bundle/ChillBudgetBundle/Resources/views/Admin/Charge/index.html.twig new file mode 100644 index 000000000..4f56c92cb --- /dev/null +++ b/src/Bundle/ChillBudgetBundle/Resources/views/Admin/Charge/index.html.twig @@ -0,0 +1,54 @@ +{% extends '@ChillMain/Admin/layoutWithVerticalMenu.html.twig' %} + +{% block title %}{{ 'admin.title.Charge Type List'|trans }}{% endblock title %} + +{% block admin_content %} + +

{{ 'admin.title.Charge Type List'|trans }}

+ + + + + + + + + + + + {% for entity in entities %} + + + + + + + {% endfor %} + +
{{ 'Ordering'|trans }}{{ 'Name'|trans }}{{ 'Active'|trans }} 
{{ entity.ordering }} + {{ entity|chill_entity_render_box }}
+ {{ 'budget.admin.form.Charge_kind_key'|trans }} : {{ entity.kind }} +
+ {%- if entity.isActive -%} + + {%- else -%} + + {%- endif -%} + +
    +
  • + +
  • +
+
+ + {{ chill_pagination(paginator) }} + + +{% endblock %} diff --git a/src/Bundle/ChillBudgetBundle/Resources/views/Admin/Charge/new.html.twig b/src/Bundle/ChillBudgetBundle/Resources/views/Admin/Charge/new.html.twig new file mode 100644 index 000000000..00ebd2938 --- /dev/null +++ b/src/Bundle/ChillBudgetBundle/Resources/views/Admin/Charge/new.html.twig @@ -0,0 +1,11 @@ +{% extends '@ChillMain/CRUD/Admin/index.html.twig' %} + +{% block title %} + {% include('@ChillMain/CRUD/_new_title.html.twig') %} +{% endblock %} + +{% block admin_content %} + {% embed '@ChillMain/CRUD/_new_content.html.twig' %} + {% block content_form_actions_save_and_show %}{% endblock %} + {% endembed %} +{% endblock %} diff --git a/src/Bundle/ChillBudgetBundle/Resources/views/Admin/Resource/edit.html.twig b/src/Bundle/ChillBudgetBundle/Resources/views/Admin/Resource/edit.html.twig new file mode 100644 index 000000000..3a60d3e9c --- /dev/null +++ b/src/Bundle/ChillBudgetBundle/Resources/views/Admin/Resource/edit.html.twig @@ -0,0 +1,12 @@ +{% extends '@ChillMain/CRUD/Admin/index.html.twig' %} + +{% block title %} + {% include('@ChillMain/CRUD/_edit_title.html.twig') %} +{% endblock %} + +{% block admin_content %} + {% embed '@ChillMain/CRUD/_edit_content.html.twig' %} + {% block content_form_actions_view %}{% endblock %} + {% block content_form_actions_save_and_show %}{% endblock %} + {% endembed %} +{% endblock %} diff --git a/src/Bundle/ChillBudgetBundle/Resources/views/Admin/Resource/index.html.twig b/src/Bundle/ChillBudgetBundle/Resources/views/Admin/Resource/index.html.twig new file mode 100644 index 000000000..62d25c0ca --- /dev/null +++ b/src/Bundle/ChillBudgetBundle/Resources/views/Admin/Resource/index.html.twig @@ -0,0 +1,54 @@ +{% extends '@ChillMain/Admin/layoutWithVerticalMenu.html.twig' %} + +{% block title %}{{ 'admin.title.Resource Type List'|trans }}{% endblock title %} + +{% block admin_content %} + +

{{ 'admin.title.Resource Type List'|trans }}

+ + + + + + + + + + + + {% for entity in entities %} + + + + + + + {% endfor %} + +
{{ 'Ordering'|trans }}{{ 'Name'|trans }}{{ 'Active'|trans }} 
{{ entity.ordering }} + {{ entity|chill_entity_render_box }}
+ {{ 'budget.admin.form.Resource_kind_key'|trans }} : {{ entity.kind }} +
+ {%- if entity.isActive -%} + + {%- else -%} + + {%- endif -%} + +
    +
  • + +
  • +
+
+ + {{ chill_pagination(paginator) }} + + +{% endblock %} diff --git a/src/Bundle/ChillBudgetBundle/Resources/views/Admin/Resource/new.html.twig b/src/Bundle/ChillBudgetBundle/Resources/views/Admin/Resource/new.html.twig new file mode 100644 index 000000000..00ebd2938 --- /dev/null +++ b/src/Bundle/ChillBudgetBundle/Resources/views/Admin/Resource/new.html.twig @@ -0,0 +1,11 @@ +{% extends '@ChillMain/CRUD/Admin/index.html.twig' %} + +{% block title %} + {% include('@ChillMain/CRUD/_new_title.html.twig') %} +{% endblock %} + +{% block admin_content %} + {% embed '@ChillMain/CRUD/_new_content.html.twig' %} + {% block content_form_actions_save_and_show %}{% endblock %} + {% endembed %} +{% endblock %} diff --git a/src/Bundle/ChillBudgetBundle/Resources/views/Admin/index.html.twig b/src/Bundle/ChillBudgetBundle/Resources/views/Admin/index.html.twig new file mode 100644 index 000000000..0e3beca04 --- /dev/null +++ b/src/Bundle/ChillBudgetBundle/Resources/views/Admin/index.html.twig @@ -0,0 +1,14 @@ +{% extends "@ChillMain/Admin/layoutWithVerticalMenu.html.twig" %} + +{% block vertical_menu_content %} + {{ chill_menu('admin_budget', { + 'layout': '@ChillMain/Admin/menu_admin_section.html.twig', + }) }} +{% endblock %} + +{% block layout_wvm_content %} + {% block admin_content %} + +

{{ 'admin.title.Budget configuration'|trans }}

+ {% endblock %} +{% endblock %} diff --git a/src/Bundle/ChillBudgetBundle/Resources/views/Budget/_budget.html.twig b/src/Bundle/ChillBudgetBundle/Resources/views/Budget/_budget.html.twig index 6e248bf0c..e75b83ee8 100644 --- a/src/Bundle/ChillBudgetBundle/Resources/views/Budget/_budget.html.twig +++ b/src/Bundle/ChillBudgetBundle/Resources/views/Budget/_budget.html.twig @@ -32,28 +32,21 @@ {% endif %} {% endfor %} -

{{ 'Actual budget'|trans }}

- {% if actualCharges|length > 0 or actualResources|length > 0 %} - {% include 'ChillBudgetBundle:Budget:_current_budget.html.twig' with { + {% include '@ChillBudget/Budget/_current_budget.html.twig' with { 'actualResources': actualResources, 'actualCharges': actualCharges, 'results': results, 'entity': entity } %} {% else %} -
-
-

{{ "There isn't any element recorded"|trans }}

-
-
+

{{ "There isn't any element recorded"|trans }}

{% endif %} {% if pastCharges|length > 0 or pastResources|length > 0 %} -

{{ 'Past budget'|trans }}

- - {% include 'ChillBudgetBundle:Budget:_past_budget.html.twig' with { +

{{ 'Past budget'|trans }}

+ {% include '@ChillBudget/Budget/_past_budget.html.twig' with { 'pastCharges': pastCharges, 'pastResources': pastResources, 'entity': entity @@ -61,9 +54,8 @@ {% endif %} {% if futureCharges|length > 0 or futureResources|length > 0 %} -

{{ 'Future budget'|trans }}

- - {% include 'ChillBudgetBundle:Budget:_future_budget.html.twig' with { +

{{ 'Future budget'|trans }}

+ {% include '@ChillBudget/Budget/_future_budget.html.twig' with { 'futureResources': futureResources, 'futureCharges': futureCharges, 'entity': entity diff --git a/src/Bundle/ChillBudgetBundle/Resources/views/Budget/_current_budget.html.twig b/src/Bundle/ChillBudgetBundle/Resources/views/Budget/_current_budget.html.twig index b996da211..d8626aee2 100644 --- a/src/Bundle/ChillBudgetBundle/Resources/views/Budget/_current_budget.html.twig +++ b/src/Bundle/ChillBudgetBundle/Resources/views/Budget/_current_budget.html.twig @@ -1,30 +1,17 @@ -{% from 'ChillBudgetBundle:Budget:_macros.html.twig' import table_elements, table_results %} - -{#

{{ 'Actual budget'|trans }}

#} - -
-

{{ 'Actual resources'|trans }}

+{% from '@ChillBudget/Budget/_macros.html.twig' import table_elements, table_results %} +
+

{{ 'Actual resources'|trans }}

{% if actualResources|length > 0 %} -
{{ table_elements(actualResources, 'resource') }} -
{% else %} -
{{ 'No resources registered'|trans }} -
{% endif %} -
-
-

{{ 'Actual charges'|trans }}

+

{{ 'Actual charges'|trans }}

{% if actualCharges|length > 0 %} -
{{ table_elements(actualCharges, 'charge') }} -
{% else %} -
{{ 'No charges registered'|trans }} -
{% endif %}
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 1547d8749..62f4e3e96 100644 --- a/src/Bundle/ChillBudgetBundle/Resources/views/Budget/_future_budget.html.twig +++ b/src/Bundle/ChillBudgetBundle/Resources/views/Budget/_future_budget.html.twig @@ -20,32 +20,23 @@ aria-labelledby="heading_future_{{ entity.id }}" data-bs-parent="#future_{{ entity.id }}"> -
-

{{ 'Future resources'|trans }}

+
+

{{ 'Future resources'|trans }}

{% if futureResources|length > 0 %} -
- {{ table_elements(futureResources, 'resource') }} -
+ {{ table_elements(futureResources, 'resource') }} {% else %} -
- {{ 'No future resources registered'|trans }} -
+ {{ 'No future resources registered'|trans }} {% endif %} -
-
-

{{ 'Future charges'|trans }}

+ +

{{ 'Future charges'|trans }}

{% if futureCharges|length > 0 %} -
- {{ table_elements(futureCharges, 'charge') }} -
+ {{ table_elements(futureCharges, 'charge') }} {% else %} -
- {{ 'No future charges registered'|trans }} -
+ {{ 'No future charges registered'|trans }} {% endif %}
- \ No newline at end of file + diff --git a/src/Bundle/ChillBudgetBundle/Resources/views/Budget/_macros.html.twig b/src/Bundle/ChillBudgetBundle/Resources/views/Budget/_macros.html.twig index 41b6f36df..dfa286af4 100644 --- a/src/Bundle/ChillBudgetBundle/Resources/views/Budget/_macros.html.twig +++ b/src/Bundle/ChillBudgetBundle/Resources/views/Budget/_macros.html.twig @@ -1,5 +1,5 @@ {% macro table_elements(elements, family) %} - +
@@ -13,21 +13,28 @@ {% for f in elements %} {% set total = total + f.amount %} - - - + - {% endfor %} diff --git a/src/Bundle/ChillDocGeneratorBundle/Resources/views/Email/on_generation_failed_email.txt.twig b/src/Bundle/ChillDocGeneratorBundle/Resources/views/Email/on_generation_failed_email.txt.twig new file mode 100644 index 000000000..c4ca7079d --- /dev/null +++ b/src/Bundle/ChillDocGeneratorBundle/Resources/views/Email/on_generation_failed_email.txt.twig @@ -0,0 +1,16 @@ +{{ creator.label }}, + +{{ 'docgen.failure_email.The generation of the document {template_name} failed'|trans({'{template_name}': template.name|localize_translatable_string}) }} + +{{ 'docgen.failure_email.Forward this email to your administrator for solving'|trans }} + +{{ 'docgen.failure_email.References'|trans }}: +{% if errors|length > 0 %} +{{ 'docgen.failure_email.The following errors were encoutered'|trans }}: + +{% for error in errors %} +- {{ error }} +{% endfor %} +{% endif %} +- template_id: {{ template.id }} +- stored_object_destination_id: {{ stored_object_id }} diff --git a/src/Bundle/ChillDocGeneratorBundle/Resources/views/Generator/basic_form.html.twig b/src/Bundle/ChillDocGeneratorBundle/Resources/views/Generator/basic_form.html.twig index 7b24eae0d..35fa6e319 100644 --- a/src/Bundle/ChillDocGeneratorBundle/Resources/views/Generator/basic_form.html.twig +++ b/src/Bundle/ChillDocGeneratorBundle/Resources/views/Generator/basic_form.html.twig @@ -2,6 +2,14 @@ {% block title 'docgen.Generate a document'|trans %} +{% block js %} + {{ encore_entry_script_tags('mod_pickentity_type') }} +{% endblock %} + +{% block css %} + {{ encore_entry_link_tags('mod_pickentity_type') }} +{% endblock %} + {% block content %}

{{ block('title') }}

diff --git a/src/Bundle/ChillDocGeneratorBundle/Resources/views/Generator/debug_value.html.twig b/src/Bundle/ChillDocGeneratorBundle/Resources/views/Generator/debug_value.html.twig new file mode 100644 index 000000000..c08a05c96 --- /dev/null +++ b/src/Bundle/ChillDocGeneratorBundle/Resources/views/Generator/debug_value.html.twig @@ -0,0 +1,8 @@ + + + {{ 'Doc generator debug'|trans }} + + +
{{ datas }}
+ + diff --git a/src/Bundle/ChillDocGeneratorBundle/Serializer/Encoder/DocGenEncoder.php b/src/Bundle/ChillDocGeneratorBundle/Serializer/Encoder/DocGenEncoder.php index b8fca5119..51527bbef 100644 --- a/src/Bundle/ChillDocGeneratorBundle/Serializer/Encoder/DocGenEncoder.php +++ b/src/Bundle/ChillDocGeneratorBundle/Serializer/Encoder/DocGenEncoder.php @@ -1,14 +1,14 @@ translatableStringHelper ->localize($value); + } elseif ($value instanceof ReadableCollection) { + // when normalizing collection, we should not preserve keys (to ensure that the result is a list) + // this is why we make call to the normalizer again to use the CollectionDocGenNormalizer + $data[$key] = + $this->normalizer->normalize($value, $format, array_merge( + $objectContext, + $attribute->getNormalizationContextForGroups($expectedGroups) + )); } elseif (is_iterable($value)) { $arr = []; diff --git a/src/Bundle/ChillDocGeneratorBundle/Service/Context/BaseContextData.php b/src/Bundle/ChillDocGeneratorBundle/Service/Context/BaseContextData.php index 0e2da164f..5aed8554b 100644 --- a/src/Bundle/ChillDocGeneratorBundle/Service/Context/BaseContextData.php +++ b/src/Bundle/ChillDocGeneratorBundle/Service/Context/BaseContextData.php @@ -1,14 +1,14 @@ security = $security; $this->normalizer = $normalizer; } - public function getData(): array + public function getData(?User $user = null): array { $data = []; - $user = $this->security->getUser(); $data['creator'] = $this->normalizer->normalize( $user instanceof User ? $user : null, @@ -42,6 +38,9 @@ class BaseContextData $data['createdAt'] = $this->normalizer->normalize(new DateTimeImmutable(), 'docgen', [ 'docgen:expects' => DateTimeImmutable::class, 'groups' => ['docgen:read'], ]); + $data['createdAtDate'] = $this->normalizer->normalize(new DateTimeImmutable('today'), 'docgen', [ + 'docgen:expects' => DateTimeImmutable::class, 'groups' => ['docgen:read'], + ]); $data['location'] = $this->normalizer->normalize( $user instanceof User ? $user->getCurrentLocation() : null, 'docgen', diff --git a/src/Bundle/ChillDocGeneratorBundle/Service/Generator/Generator.php b/src/Bundle/ChillDocGeneratorBundle/Service/Generator/Generator.php new file mode 100644 index 000000000..e2e09f349 --- /dev/null +++ b/src/Bundle/ChillDocGeneratorBundle/Service/Generator/Generator.php @@ -0,0 +1,155 @@ +contextManager = $contextManager; + $this->driver = $driver; + $this->entityManager = $entityManager; + $this->logger = $logger; + $this->storedObjectManager = $storedObjectManager; + } + + /** + * @template T of File|null + * @template B of bool + * @param B $isTest + * @param (B is true ? T : null) $testFile + * @psalm-return (B is true ? string : null) + * @throws \Symfony\Component\Serializer\Exception\ExceptionInterface|\Throwable + */ + public function generateDocFromTemplate( + DocGeneratorTemplate $template, + int $entityId, + array $contextGenerationDataNormalized, + ?StoredObject $destinationStoredObject = null, + bool $isTest = false, + ?File $testFile = null, + ?User $creator = null + ): ?string { + if ($destinationStoredObject instanceof StoredObject && StoredObject::STATUS_PENDING !== $destinationStoredObject->getStatus()) { + $this->logger->info(self::LOG_PREFIX.'Aborting generation of an already generated document'); + throw new ObjectReadyException(); + } + + $this->logger->info(self::LOG_PREFIX.'Starting generation of a document', [ + 'entity_id' => $entityId, + 'destination_stored_object' => $destinationStoredObject === null ? null : $destinationStoredObject->getId() + ]); + + $context = $this->contextManager->getContextByDocGeneratorTemplate($template); + + $entity = $this + ->entityManager + ->find($context->getEntityClass(), $entityId) + ; + + if (null === $entity) { + throw new RelatedEntityNotFoundException($template->getEntity(), $entityId); + } + + $contextGenerationDataNormalized = array_merge( + $contextGenerationDataNormalized, + ['creator' => $creator], + $context instanceof DocGeneratorContextWithPublicFormInterface ? + $context->contextGenerationDataDenormalize($template, $entity, $contextGenerationDataNormalized) + : [] + ); + + $data = $context->getData($template, $entity, $contextGenerationDataNormalized); + + $destinationStoredObjectId = $destinationStoredObject instanceof StoredObject ? $destinationStoredObject->getId() : null; + $this->entityManager->clear(); + gc_collect_cycles(); + if (null !== $destinationStoredObjectId) { + $destinationStoredObject = $this->entityManager->find(StoredObject::class, $destinationStoredObjectId); + } + + if ($isTest && ($testFile instanceof File)) { + $templateDecrypted = file_get_contents($testFile->getPathname()); + } else { + $templateDecrypted = $this->storedObjectManager->read($template->getFile()); + } + + try { + $generatedResource = $this + ->driver + ->generateFromString( + $templateDecrypted, + $template->getFile()->getType(), + $data, + $template->getFile()->getFilename() + ); + } catch (TemplateException $e) { + throw new GeneratorException($e->getErrors(), $e); + } + + if (true === $isTest) { + $this->logger->info(self::LOG_PREFIX.'Finished generation of a document', [ + 'is_test' => true, + 'entity_id' => $entityId, + 'destination_stored_object' => $destinationStoredObject === null ? null : $destinationStoredObject->getId() + ]); + return $generatedResource; + } + + /** @var StoredObject $destinationStoredObject */ + $destinationStoredObject + ->setType($template->getFile()->getType()) + ->setFilename(sprintf('%s_odt', uniqid('doc_', true))) + ->setStatus(StoredObject::STATUS_READY) + ; + + $this->storedObjectManager->write($destinationStoredObject, $generatedResource); + + $this->entityManager->flush(); + + $this->logger->info(self::LOG_PREFIX.'Finished generation of a document', [ + 'entity_id' => $entityId, + 'destination_stored_object' => $destinationStoredObject->getId(), + ]); + + return null; + } +} diff --git a/src/Bundle/ChillDocGeneratorBundle/Service/Generator/GeneratorException.php b/src/Bundle/ChillDocGeneratorBundle/Service/Generator/GeneratorException.php new file mode 100644 index 000000000..4360413b4 --- /dev/null +++ b/src/Bundle/ChillDocGeneratorBundle/Service/Generator/GeneratorException.php @@ -0,0 +1,41 @@ + + */ + private array $errors; + + public function __construct(array $errors = [], ?Throwable $previous = null) + { + $this->errors = $errors; + parent::__construct( + 'Could not generate the document', + 15252, + $previous + ); + } + + /** + * @return array + */ + public function getErrors(): array + { + return $this->errors; + } +} diff --git a/src/Bundle/ChillDocGeneratorBundle/Service/Generator/GeneratorInterface.php b/src/Bundle/ChillDocGeneratorBundle/Service/Generator/GeneratorInterface.php new file mode 100644 index 000000000..dcef8f99d --- /dev/null +++ b/src/Bundle/ChillDocGeneratorBundle/Service/Generator/GeneratorInterface.php @@ -0,0 +1,38 @@ +docGeneratorTemplateRepository = $docGeneratorTemplateRepository; + $this->entityManager = $entityManager; + $this->logger = $logger; + $this->mailer = $mailer; + $this->storedObjectRepository = $storedObjectRepository; + $this->translator = $translator; + $this->userRepository = $userRepository; + } + + + public static function getSubscribedEvents() + { + return [ + WorkerMessageFailedEvent::class => 'onMessageFailed' + ]; + } + + public function onMessageFailed(WorkerMessageFailedEvent $event): void + { + if ($event->willRetry()) { + return; + } + + if (!$event->getEnvelope()->getMessage() instanceof RequestGenerationMessage) { + return; + } + + /** @var \Chill\DocGeneratorBundle\Service\Messenger\RequestGenerationMessage $message */ + $message = $event->getEnvelope()->getMessage(); + + $this->logger->error(self::LOG_PREFIX.'Docgen failed', [ + 'stored_object_id' => $message->getDestinationStoredObjectId(), + 'entity_id' => $message->getEntityId(), + 'template_id' => $message->getTemplateId(), + 'creator_id' => $message->getCreatorId(), + 'throwable_class' => get_class($event->getThrowable()), + ]); + + $this->markObjectAsFailed($message); + $this->warnCreator($message, $event); + } + + private function markObjectAsFailed(RequestGenerationMessage $message): void + { + $object = $this->storedObjectRepository->find($message->getDestinationStoredObjectId()); + + if (null === $object) { + $this->logger->error(self::LOG_PREFIX.'Stored object not found', ['stored_object_id', $message->getDestinationStoredObjectId()]); + } + + $object->setStatus(StoredObject::STATUS_FAILURE); + + $this->entityManager->flush(); + } + + private function warnCreator(RequestGenerationMessage $message, WorkerMessageFailedEvent $event): void + { + $creatorId = $message->getCreatorId(); + + if (null === $creator = $this->userRepository->find($creatorId)) { + $this->logger->error(self::LOG_PREFIX.'Creator not found with given id', ['creator_id', $creatorId]); + return; + } + + if (null === $creator->getEmail() || '' === $creator->getEmail()) { + $this->logger->info(self::LOG_PREFIX.'Creator does not have any email', ['user' => $creator->getUsernameCanonical()]); + return; + } + + // if the exception is not a GeneratorException, we try the previous one... + $throwable = $event->getThrowable(); + if (!$throwable instanceof GeneratorException) { + $throwable = $throwable->getPrevious(); + } + + if ($throwable instanceof GeneratorException) { + $errors = $throwable->getErrors(); + } else { + $errors = [$throwable->getTraceAsString()]; + } + + if (null === $template = $this->docGeneratorTemplateRepository->find($message->getTemplateId())) { + $this->logger->info(self::LOG_PREFIX.'Template not found', ['template_id' => $message->getTemplateId()]); + return; + } + + $email = (new TemplatedEmail()) + ->to($creator->getEmail()) + ->subject($this->translator->trans('docgen.failure_email.The generation of a document failed')) + ->textTemplate('@ChillDocGenerator/Email/on_generation_failed_email.txt.twig') + ->context([ + 'errors' => $errors, + 'template' => $template, + 'creator' => $creator, + 'stored_object_id' => $message->getDestinationStoredObjectId(), + ]); + + $this->mailer->send($email); + } +} diff --git a/src/Bundle/ChillDocGeneratorBundle/Service/Messenger/RequestGenerationHandler.php b/src/Bundle/ChillDocGeneratorBundle/Service/Messenger/RequestGenerationHandler.php new file mode 100644 index 000000000..49c3430ad --- /dev/null +++ b/src/Bundle/ChillDocGeneratorBundle/Service/Messenger/RequestGenerationHandler.php @@ -0,0 +1,98 @@ +docGeneratorTemplateRepository = $docGeneratorTemplateRepository; + $this->entityManager = $entityManager; + $this->generator = $generator; + $this->logger = $logger; + $this->storedObjectRepository = $storedObjectRepository; + $this->userRepository = $userRepository; + } + + public function __invoke(RequestGenerationMessage $message) + { + if (null === $template = $this->docGeneratorTemplateRepository->find($message->getTemplateId())) { + throw new \RuntimeException('template not found: ' . $message->getTemplateId()); + } + + if (null === $destinationStoredObject = $this->storedObjectRepository->find($message->getDestinationStoredObjectId())) { + throw new \RuntimeException('destination stored object not found : ' . $message->getDestinationStoredObjectId()); + } + + if ($destinationStoredObject->getGenerationTrialsCounter() >= self::AUTHORIZED_TRIALS) { + throw new UnrecoverableMessageHandlingException('maximum number of retry reached'); + } + + $creator = $this->userRepository->find($message->getCreatorId()); + + $destinationStoredObject->addGenerationTrial(); + $this->entityManager->createQuery('UPDATE '.StoredObject::class.' s SET s.generationTrialsCounter = s.generationTrialsCounter + 1 WHERE s.id = :id') + ->setParameter('id', $destinationStoredObject->getId()) + ->execute(); + + $this->generator->generateDocFromTemplate( + $template, + $message->getEntityId(), + $message->getContextGenerationData(), + $destinationStoredObject, + false, + null, + $creator + ); + + $this->logger->info(self::LOG_PREFIX.'Request generation finished', [ + 'template_id' => $message->getTemplateId(), + 'destination_stored_object' => $message->getDestinationStoredObjectId(), + 'duration_int' => (new \DateTimeImmutable('now'))->getTimestamp() - $message->getCreatedAt()->getTimestamp(), + ]); + } +} diff --git a/src/Bundle/ChillDocGeneratorBundle/Service/Messenger/RequestGenerationMessage.php b/src/Bundle/ChillDocGeneratorBundle/Service/Messenger/RequestGenerationMessage.php new file mode 100644 index 000000000..e8734e62b --- /dev/null +++ b/src/Bundle/ChillDocGeneratorBundle/Service/Messenger/RequestGenerationMessage.php @@ -0,0 +1,76 @@ +creatorId = $creator->getId(); + $this->templateId = $template->getId(); + $this->entityId = $entityId; + $this->destinationStoredObjectId = $destinationStoredObject->getId(); + $this->contextGenerationData = $contextGenerationData; + $this->createdAt = new \DateTimeImmutable('now'); + } + + public function getCreatorId(): int + { + return $this->creatorId; + } + + public function getDestinationStoredObjectId(): int + { + return $this->destinationStoredObjectId; + } + + public function getTemplateId(): int + { + return $this->templateId; + } + + public function getEntityId(): int + { + return $this->entityId; + } + + public function getContextGenerationData(): array + { + return $this->contextGenerationData; + } + + public function getCreatedAt(): \DateTimeImmutable + { + return $this->createdAt; + } +} diff --git a/src/Bundle/ChillDocGeneratorBundle/config/services.yaml b/src/Bundle/ChillDocGeneratorBundle/config/services.yaml index 5bdfe2a11..5fef6fb22 100644 --- a/src/Bundle/ChillDocGeneratorBundle/config/services.yaml +++ b/src/Bundle/ChillDocGeneratorBundle/config/services.yaml @@ -20,10 +20,14 @@ services: resource: '../Serializer/Normalizer/' tags: - { name: 'serializer.normalizer', priority: -152 } + Chill\DocGeneratorBundle\Serializer\Normalizer\CollectionDocGenNormalizer: tags: - { name: 'serializer.normalizer', priority: -126 } + Chill\DocGeneratorBundle\Service\Context\: + resource: "../Service/Context" + Chill\DocGeneratorBundle\Controller\: resource: "../Controller" autowire: true @@ -34,18 +38,20 @@ services: autowire: true autoconfigure: true - Chill\DocGeneratorBundle\Service\Context\: - resource: "../Service/Context/" - autowire: true - autoconfigure: true - Chill\DocGeneratorBundle\GeneratorDriver\: resource: "../GeneratorDriver/" autowire: true autoconfigure: true + Chill\DocGeneratorBundle\Service\Messenger\: + resource: "../Service/Messenger/" + + Chill\DocGeneratorBundle\Service\Generator\Generator: ~ + Chill\DocGeneratorBundle\Service\Generator\GeneratorInterface: '@Chill\DocGeneratorBundle\Service\Generator\Generator' + Chill\DocGeneratorBundle\Driver\RelatorioDriver: '@Chill\DocGeneratorBundle\Driver\DriverInterface' Chill\DocGeneratorBundle\Context\ContextManager: arguments: $contexts: !tagged_iterator { tag: chill_docgen.context, default_index_method: getKey } + Chill\DocGeneratorBundle\Context\ContextManagerInterface: '@Chill\DocGeneratorBundle\Context\ContextManager' diff --git a/src/Bundle/ChillDocGeneratorBundle/migrations/Version20210805162522.php b/src/Bundle/ChillDocGeneratorBundle/migrations/Version20210805162522.php index b11112bad..94337386b 100644 --- a/src/Bundle/ChillDocGeneratorBundle/migrations/Version20210805162522.php +++ b/src/Bundle/ChillDocGeneratorBundle/migrations/Version20210805162522.php @@ -1,14 +1,14 @@ addSql('ALTER TABLE chill_doc.stored_object ADD template_id INT DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_doc.stored_object ADD status TEXT DEFAULT \'ready\' NOT NULL'); + $this->addSql('ALTER TABLE chill_doc.stored_object ADD createdAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL'); + $this->addSql('UPDATE chill_doc.stored_object SET createdAt = creation_date'); + $this->addSql('ALTER TABLE chill_doc.stored_object ADD createdBy_id INT DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_doc.stored_object DROP creation_date;'); + $this->addSql('ALTER TABLE chill_doc.stored_object ALTER type SET DEFAULT \'\''); + $this->addSql('ALTER TABLE chill_doc.stored_object ALTER title DROP DEFAULT'); + $this->addSql('COMMENT ON COLUMN chill_doc.stored_object.createdAt IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('ALTER TABLE chill_doc.stored_object ADD CONSTRAINT FK_49604E365DA0FB8 FOREIGN KEY (template_id) REFERENCES chill_docgen_template (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_doc.stored_object ADD CONSTRAINT FK_49604E363174800F FOREIGN KEY (createdBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('CREATE INDEX IDX_49604E365DA0FB8 ON chill_doc.stored_object (template_id)'); + $this->addSql('CREATE INDEX IDX_49604E363174800F ON chill_doc.stored_object (createdBy_id)'); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_doc.stored_object DROP CONSTRAINT FK_49604E365DA0FB8'); + $this->addSql('ALTER TABLE chill_doc.stored_object DROP CONSTRAINT FK_49604E363174800F'); + $this->addSql('ALTER TABLE chill_doc.stored_object DROP template_id'); + $this->addSql('ALTER TABLE chill_doc.stored_object DROP status'); + $this->addSql('ALTER TABLE chill_doc.stored_object ADD creation_date TIMESTAMP(0) DEFAULT NOW()'); + $this->addSql('UPDATE chill_doc.stored_object SET creation_date = createdAt'); + $this->addSql('ALTER TABLE chill_doc.stored_object DROP createdAt'); + $this->addSql('ALTER TABLE chill_doc.stored_object DROP createdBy_id'); + $this->addSql('ALTER TABLE chill_doc.stored_object ALTER title SET DEFAULT \'\''); + $this->addSql('ALTER TABLE chill_doc.stored_object ALTER type DROP DEFAULT'); + } +} diff --git a/src/Bundle/ChillDocGeneratorBundle/tests/Serializer/Encoder/DocGenEncoderTest.php b/src/Bundle/ChillDocGeneratorBundle/tests/Serializer/Encoder/DocGenEncoderTest.php index 1e529712f..58208b045 100644 --- a/src/Bundle/ChillDocGeneratorBundle/tests/Serializer/Encoder/DocGenEncoderTest.php +++ b/src/Bundle/ChillDocGeneratorBundle/tests/Serializer/Encoder/DocGenEncoderTest.php @@ -1,14 +1,14 @@ normalizer = self::$container->get(NormalizerInterface::class); + } + + public function testNormalizeFilteredArray(): void + { + $coll = new ArrayCollection([ + (object) ['v' => 'foo'], + (object) ['v' => 'bar'], + (object) ['v' => 'baz'], + ]); + + //filter to get non continuous indexes + $criteria = new Criteria(); + $criteria->where(Criteria::expr()->neq('v', 'bar')); + + $filtered = $coll->matching($criteria); + $normalized = $this->normalizer->normalize($filtered, 'docgen', []); + + self::assertIsArray($normalized); + self::assertArrayHasKey(0, $normalized); + self::assertArrayHasKey(1, $normalized); + } +} diff --git a/src/Bundle/ChillDocGeneratorBundle/tests/Serializer/Normalizer/DocGenObjectNormalizerTest.php b/src/Bundle/ChillDocGeneratorBundle/tests/Serializer/Normalizer/DocGenObjectNormalizerTest.php index 178e9b183..3250d4d5a 100644 --- a/src/Bundle/ChillDocGeneratorBundle/tests/Serializer/Normalizer/DocGenObjectNormalizerTest.php +++ b/src/Bundle/ChillDocGeneratorBundle/tests/Serializer/Normalizer/DocGenObjectNormalizerTest.php @@ -1,14 +1,14 @@ assertArrayNotHasKey('baz', $actual['child']); } + public function testNormalizableBooleanPropertyOrMethodOnNull() + { + $actual = $this->normalizer->normalize(null, 'docgen', [AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => TestableClassWithBool::class]); + + $expected = [ + 'foo' => null, + 'thing' => null, + 'isNull' => true, + ]; + + $this->assertEquals($expected, $actual); + } + public function testNormalizationBasic() { $scope = new Scope(); @@ -93,6 +106,22 @@ final class DocGenObjectNormalizerTest extends KernelTestCase $this->assertEquals($expected, $normalized, 'test normalization fo a scope'); } + public function testNormalizeBooleanPropertyOrMethod() + { + $testable = new TestableClassWithBool(); + $testable->foo = false; + + $actual = $this->normalizer->normalize($testable, 'docgen', [AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => TestableClassWithBool::class]); + + $expected = [ + 'foo' => false, + 'thing' => true, + 'isNull' => false, + ]; + + $this->assertEquals($expected, $actual); + } + public function testNormalizeNull() { $actual = $this->normalizer->normalize(null, 'docgen', [AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => Scope::class]); @@ -170,3 +199,19 @@ class TestableChildClass */ public string $foo = 'bar'; } + +class TestableClassWithBool +{ + /** + * @Serializer\Groups("docgen:read") + */ + public bool $foo; + + /** + * @Serializer\Groups("docgen:read") + */ + public function getThing(): bool + { + return true; + } +} diff --git a/src/Bundle/ChillDocGeneratorBundle/tests/Service/Context/BaseContextDataTest.php b/src/Bundle/ChillDocGeneratorBundle/tests/Service/Context/BaseContextDataTest.php index feb9afd35..3734df03a 100644 --- a/src/Bundle/ChillDocGeneratorBundle/tests/Service/Context/BaseContextDataTest.php +++ b/src/Bundle/ChillDocGeneratorBundle/tests/Service/Context/BaseContextDataTest.php @@ -1,14 +1,14 @@ setFile($templateStoredObject = (new StoredObject()) + ->setType('application/test')); + $destinationStoredObject = (new StoredObject())->setStatus(StoredObject::STATUS_PENDING); + $reflection = new \ReflectionClass($destinationStoredObject); + $reflection->getProperty('id')->setAccessible(true); + $reflection->getProperty('id')->setValue($destinationStoredObject, 1); + $entity = new class () {}; + $data = []; + + $context = $this->prophesize(DocGeneratorContextInterface::class); + $context->getData($template, $entity, Argument::type('array'))->willReturn($data); + $context->getName()->willReturn('dummy_context'); + $context->getEntityClass()->willReturn('DummyClass'); + $context = $context->reveal(); + + $contextManagerInterface = $this->prophesize(ContextManagerInterface::class); + $contextManagerInterface->getContextByDocGeneratorTemplate($template) + ->willReturn($context); + + $driver = $this->prophesize(DriverInterface::class); + $driver->generateFromString('template', 'application/test', $data, Argument::any()) + ->willReturn('generated'); + + $entityManager = $this->prophesize(EntityManagerInterface::class); + $entityManager->find(StoredObject::class, 1) + ->willReturn($destinationStoredObject); + $entityManager->find('DummyClass', Argument::type('int')) + ->willReturn($entity); + $entityManager->clear()->shouldBeCalled(); + $entityManager->flush()->shouldBeCalled(); + + $storedObjectManager = $this->prophesize(StoredObjectManagerInterface::class); + $storedObjectManager->read($templateStoredObject)->willReturn('template'); + $storedObjectManager->write($destinationStoredObject, 'generated')->shouldBeCalled(); + + + $generator = new Generator( + $contextManagerInterface->reveal(), + $driver->reveal(), + $entityManager->reveal(), + new NullLogger(), + $storedObjectManager->reveal() + ); + + $generator->generateDocFromTemplate( + $template, + 1, + [], + $destinationStoredObject + ); + } + + public function testPreventRegenerateDocument(): void + { + $this->expectException(ObjectReadyException::class); + + $generator = new Generator( + $this->prophesize(ContextManagerInterface::class)->reveal(), + $this->prophesize(DriverInterface::class)->reveal(), + $this->prophesize(EntityManagerInterface::class)->reveal(), + new NullLogger(), + $this->prophesize(StoredObjectManagerInterface::class)->reveal() + ); + + $template = (new DocGeneratorTemplate())->setFile($templateStoredObject = (new StoredObject()) + ->setType('application/test')); + $destinationStoredObject = (new StoredObject())->setStatus(StoredObject::STATUS_READY); + + $generator->generateDocFromTemplate( + $template, + 1, + [], + $destinationStoredObject + ); + } + + public function testRelatedEntityNotFound(): void + { + $this->expectException(RelatedEntityNotFoundException::class); + + $template = (new DocGeneratorTemplate())->setFile($templateStoredObject = (new StoredObject()) + ->setType('application/test')); + $destinationStoredObject = (new StoredObject())->setStatus(StoredObject::STATUS_PENDING); + $reflection = new \ReflectionClass($destinationStoredObject); + $reflection->getProperty('id')->setAccessible(true); + $reflection->getProperty('id')->setValue($destinationStoredObject, 1); + + $context = $this->prophesize(DocGeneratorContextInterface::class); + $context->getName()->willReturn('dummy_context'); + $context->getEntityClass()->willReturn('DummyClass'); + $context = $context->reveal(); + + $contextManagerInterface = $this->prophesize(ContextManagerInterface::class); + $contextManagerInterface->getContextByDocGeneratorTemplate($template) + ->willReturn($context); + + $entityManager = $this->prophesize(EntityManagerInterface::class); + $entityManager->find(Argument::type('string'), Argument::type('int')) + ->willReturn(null); + + $generator = new Generator( + $contextManagerInterface->reveal(), + $this->prophesize(DriverInterface::class)->reveal(), + $entityManager->reveal(), + new NullLogger(), + $this->prophesize(StoredObjectManagerInterface::class)->reveal() + ); + + $generator->generateDocFromTemplate( + $template, + 1, + [], + $destinationStoredObject + ); + } +} diff --git a/src/Bundle/ChillDocGeneratorBundle/translations/messages.fr.yml b/src/Bundle/ChillDocGeneratorBundle/translations/messages.fr.yml index bf37ff838..5e55d6df8 100644 --- a/src/Bundle/ChillDocGeneratorBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillDocGeneratorBundle/translations/messages.fr.yml @@ -1,6 +1,6 @@ docgen: - Generate a document: Génerer un document - Generate: Génerer + Generate a document: Générer un document + Generate: Générer Document generation: Génération de documents Manage templates and document generation: Gestion des documents générés et de leurs modèles Pick template context: Choisir un contexte @@ -10,6 +10,16 @@ docgen: test generate: Tester la génération With context %name%: 'Avec le contexte "%name%"' + Doc generation failed: La génération de ce document a échoué + Doc generation is pending: La génération de ce document est en cours + Come back later: Revenir plus tard + + failure_email: + The generation of a document failed: La génération d'un document a échoué + The generation of the document {template_name} failed: La génération d'un document à partir du modèle {{ template_name }} a échoué. + The following errors were encoutered: Les erreurs suivantes ont été rencontrées + Forward this email to your administrator for solving: Faites suivre ce message vers votre administrateur pour la résolution du problème. + References: Références crud: docgen_template: @@ -19,4 +29,4 @@ crud: Show data instead of generating: Montrer les données au lieu de générer le document -Template file: Fichier modèle \ No newline at end of file +Template file: Fichier modèle diff --git a/src/Bundle/ChillDocStoreBundle/ChillDocStoreBundle.php b/src/Bundle/ChillDocStoreBundle/ChillDocStoreBundle.php index c5536d0f1..81c71f45f 100644 --- a/src/Bundle/ChillDocStoreBundle/ChillDocStoreBundle.php +++ b/src/Bundle/ChillDocStoreBundle/ChillDocStoreBundle.php @@ -1,14 +1,14 @@ security = $security; + } + + /** + * @Route("/api/1.0/doc-store/stored-object/{uuid}/is-ready") + */ + public function isDocumentReady(StoredObject $storedObject): Response + { + if (!$this->security->isGranted('ROLE_USER')) { + throw new AccessDeniedHttpException(); + } + + return new JsonResponse( + [ + 'id' => $storedObject->getId(), + 'filename' => $storedObject->getFilename(), + 'status' => $storedObject->getStatus(), + 'type' => $storedObject->getType(), + ] + ); + } +} diff --git a/src/Bundle/ChillDocStoreBundle/DataFixtures/ORM/LoadDocumentACL.php b/src/Bundle/ChillDocStoreBundle/DataFixtures/ORM/LoadDocumentACL.php index cab5895a9..d52926626 100644 --- a/src/Bundle/ChillDocStoreBundle/DataFixtures/ORM/LoadDocumentACL.php +++ b/src/Bundle/ChillDocStoreBundle/DataFixtures/ORM/LoadDocumentACL.php @@ -1,14 +1,14 @@ getRootNode('chill_doc_store'); + $rootNode = $treeBuilder->getRootNode(); // Here you should define the parameters that are allowed to // configure your bundle. See the documentation linked above for diff --git a/src/Bundle/ChillDocStoreBundle/Entity/AccompanyingCourseDocument.php b/src/Bundle/ChillDocStoreBundle/Entity/AccompanyingCourseDocument.php index b013f1a44..d0613f9a3 100644 --- a/src/Bundle/ChillDocStoreBundle/Entity/AccompanyingCourseDocument.php +++ b/src/Bundle/ChillDocStoreBundle/Entity/AccompanyingCourseDocument.php @@ -1,16 +1,18 @@ course->getCenters(); + } + public function getCourse(): ?AccompanyingPeriod { return $this->course; } + public function getScopes(): iterable + { + if (null === $this->course) { + return []; + } + + return $this->course->getScopes(); + } + public function setCourse(?AccompanyingPeriod $course): self { $this->course = $course; diff --git a/src/Bundle/ChillDocStoreBundle/Entity/Document.php b/src/Bundle/ChillDocStoreBundle/Entity/Document.php index e26469573..2345aef61 100644 --- a/src/Bundle/ChillDocStoreBundle/Entity/Document.php +++ b/src/Bundle/ChillDocStoreBundle/Entity/Document.php @@ -1,14 +1,14 @@ object; } - /** - * Get scope. - * - * @return \Chill\MainBundle\Entity\Scope - */ - public function getScope(): ?Scope - { - return $this->scope; - } - public function getTemplate(): ?DocGeneratorTemplate { return $this->template; @@ -175,13 +156,6 @@ class Document implements HasScopeInterface, TrackCreationInterface, TrackUpdate return $this; } - public function setScope($scope): self - { - $this->scope = $scope; - - return $this; - } - public function setTemplate(?DocGeneratorTemplate $template): self { $this->template = $template; diff --git a/src/Bundle/ChillDocStoreBundle/Entity/DocumentCategory.php b/src/Bundle/ChillDocStoreBundle/Entity/DocumentCategory.php index 69af75f42..3d9cc3220 100644 --- a/src/Bundle/ChillDocStoreBundle/Entity/DocumentCategory.php +++ b/src/Bundle/ChillDocStoreBundle/Entity/DocumentCategory.php @@ -1,14 +1,14 @@ getPerson()->getCenter(); @@ -37,10 +45,22 @@ class PersonDocument extends Document implements HasCenterInterface, HasScopeInt return $this->person; } + public function getScope(): ?Scope + { + return $this->scope; + } + public function setPerson($person): self { $this->person = $person; return $this; } + + public function setScope($scope): self + { + $this->scope = $scope; + + return $this; + } } diff --git a/src/Bundle/ChillDocStoreBundle/Entity/StoredObject.php b/src/Bundle/ChillDocStoreBundle/Entity/StoredObject.php index f852e9f14..0583e26b2 100644 --- a/src/Bundle/ChillDocStoreBundle/Entity/StoredObject.php +++ b/src/Bundle/ChillDocStoreBundle/Entity/StoredObject.php @@ -1,19 +1,22 @@ creationDate = new DateTime(); $this->uuid = Uuid::uuid4(); + $this->status = $status; } + public function addGenerationTrial(): self + { + $this->generationTrialsCounter++; + + return $this; + } + + /** + * @Serializer\Groups({"read", "write"}) + * @deprecated + */ public function getCreationDate(): DateTime { - return $this->creationDate; + return DateTime::createFromImmutable($this->createdAt); } - public function getDatas() + public function getDatas(): array { return $this->datas; } - public function getFilename() + public function getFilename(): string { return $this->filename; } - public function getId() + public function getGenerationTrialsCounter(): int + { + return $this->generationTrialsCounter; + } + + public function getId(): ?int { return $this->id; } @@ -133,6 +175,14 @@ class StoredObject implements AsyncFileInterface, Document return $this->getFilename(); } + /** + * @return StoredObject::STATUS_* + */ + public function getStatus(): string + { + return $this->status; + } + public function getTitle() { return $this->title; @@ -153,52 +203,92 @@ class StoredObject implements AsyncFileInterface, Document return (string) $this->uuid; } - public function setCreationDate(DateTime $creationDate) + /** + * @Serializer\Groups({"write"}) + * @deprecated + */ + public function setCreationDate(DateTime $creationDate): self { - $this->creationDate = $creationDate; + $this->createdAt = \DateTimeImmutable::createFromMutable($creationDate); return $this; } - public function setDatas(?array $datas) + public function setDatas(?array $datas): self { $this->datas = (array) $datas; return $this; } - public function setFilename(?string $filename) + public function setFilename(?string $filename): self { $this->filename = (string) $filename; return $this; } - public function setIv(?array $iv) + public function setIv(?array $iv): self { $this->iv = (array) $iv; return $this; } - public function setKeyInfos(?array $keyInfos) + public function setKeyInfos(?array $keyInfos): self { $this->keyInfos = (array) $keyInfos; return $this; } - public function setTitle(?string $title) + /** + * @param StoredObject::STATUS_* $status + */ + public function setStatus(string $status): self + { + $this->status = $status; + + return $this; + } + + public function setTitle(?string $title): self { $this->title = (string) $title; return $this; } - public function setType(?string $type) + public function setType(?string $type): self { $this->type = (string) $type; return $this; } + + public function getTemplate(): ?DocGeneratorTemplate + { + return $this->template; + } + + public function hasTemplate(): bool + { + return null !== $this->template; + } + + public function setTemplate(?DocGeneratorTemplate $template): StoredObject + { + $this->template = $template; + return $this; + } + + public function isPending(): bool + { + return self::STATUS_PENDING === $this->getStatus(); + } + + public function isFailure(): bool + { + return self::STATUS_FAILURE === $this->getStatus(); + } } diff --git a/src/Bundle/ChillDocStoreBundle/EntityRepository/AccompanyingCourseDocumentRepository.php b/src/Bundle/ChillDocStoreBundle/EntityRepository/AccompanyingCourseDocumentRepository.php index dfbd533d9..cb45d7d45 100644 --- a/src/Bundle/ChillDocStoreBundle/EntityRepository/AccompanyingCourseDocumentRepository.php +++ b/src/Bundle/ChillDocStoreBundle/EntityRepository/AccompanyingCourseDocumentRepository.php @@ -1,14 +1,14 @@ add('category', EntityType::class, [ 'placeholder' => 'Choose a document category', 'class' => DocumentCategory::class, - 'query_builder' => static function (EntityRepository $er) { - return $er->createQueryBuilder('c') - ->where('c.documentClass = :docClass') - ->setParameter('docClass', AccompanyingCourseDocument::class); - }, - 'choice_label' => function ($entity = null) { - return $entity ? $this->translatableStringHelper->localize($entity->getName()) : ''; - }, + 'query_builder' => static fn (EntityRepository $er) => $er->createQueryBuilder('c') + ->where('c.documentClass = :docClass') + ->setParameter('docClass', AccompanyingCourseDocument::class), + 'choice_label' => fn ($entity = null) => $entity ? $this->translatableStringHelper->localize($entity->getName()) : '', ]); } @@ -88,10 +84,5 @@ class AccompanyingCourseDocumentType extends AbstractType $resolver->setDefaults([ 'data_class' => Document::class, ]); - - // $resolver->setRequired(['role', 'center']) - // ->setAllowedTypes('role', [ \Symfony\Component\Security\Core\Role\Role::class ]) - // ->setAllowedTypes('center', [ \Chill\MainBundle\Entity\Center::class ]) - // ; } } diff --git a/src/Bundle/ChillDocStoreBundle/Form/DocumentCategoryType.php b/src/Bundle/ChillDocStoreBundle/Form/DocumentCategoryType.php index 62c63fe1f..d42bb90d5 100644 --- a/src/Bundle/ChillDocStoreBundle/Form/DocumentCategoryType.php +++ b/src/Bundle/ChillDocStoreBundle/Form/DocumentCategoryType.php @@ -1,14 +1,14 @@ translatableStringHelper = $translatableStringHelper; $this->scopeResolverDispatcher = $scopeResolverDispatcher; $this->parameterBag = $parameterBag; + $this->centerResolverDispatcher = $centerResolverDispatcher; } public function buildForm(FormBuilderInterface $builder, array $options) @@ -65,14 +67,10 @@ class PersonDocumentType extends AbstractType ->add('category', EntityType::class, [ 'placeholder' => 'Choose a document category', 'class' => DocumentCategory::class, - 'query_builder' => static function (EntityRepository $er) { - return $er->createQueryBuilder('c') - ->where('c.documentClass = :docClass') - ->setParameter('docClass', PersonDocument::class); - }, - 'choice_label' => function ($entity = null) { - return $entity ? $this->translatableStringHelper->localize($entity->getName()) : ''; - }, + 'query_builder' => static fn (EntityRepository $er) => $er->createQueryBuilder('c') + ->where('c.documentClass = :docClass') + ->setParameter('docClass', PersonDocument::class), + 'choice_label' => fn ($entity = null) => $entity ? $this->translatableStringHelper->localize($entity->getName()) : '', ]); if ($isScopeConcerned && $this->parameterBag->get('chill_main')['acl']['form_show_scopes']) { diff --git a/src/Bundle/ChillDocStoreBundle/Form/StoredObjectType.php b/src/Bundle/ChillDocStoreBundle/Form/StoredObjectType.php index aaf1e80da..4f3cd315f 100644 --- a/src/Bundle/ChillDocStoreBundle/Form/StoredObjectType.php +++ b/src/Bundle/ChillDocStoreBundle/Form/StoredObjectType.php @@ -1,14 +1,14 @@ setAttribute('class', 'list-group-item-header') ->setExtras([ 'order' => 4000, - 'icons' => ['file-pdf-o'], ]); $menu->addChild('Document category list', [ diff --git a/src/Bundle/ChillDocStoreBundle/Menu/MenuBuilder.php b/src/Bundle/ChillDocStoreBundle/Menu/MenuBuilder.php index d579dc162..41d31ced3 100644 --- a/src/Bundle/ChillDocStoreBundle/Menu/MenuBuilder.php +++ b/src/Bundle/ChillDocStoreBundle/Menu/MenuBuilder.php @@ -1,14 +1,14 @@ addACL($qb, $person); foreach ($orderBy as $field => $order) { - $qb->addOrderBy($field, $order); + $qb->addOrderBy('d.' . $field, $order); } $qb->setFirstResult($offset)->setMaxResults($limit); diff --git a/src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentACLAwareRepositoryInterface.php b/src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentACLAwareRepositoryInterface.php index 85b0288c0..6c4bd2e9a 100644 --- a/src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentACLAwareRepositoryInterface.php +++ b/src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentACLAwareRepositoryInterface.php @@ -1,14 +1,14 @@ { labelPreparing = button.dataset.labelPreparing, labelReady = button.dataset.labelReady, mimeType = button.dataset.mimeType, - extension = mime.extension(mimeType), + extension = mime.getExtension(mimeType), decryptError = "Error while decrypting file", fetchError = "Error while fetching file", key, url @@ -93,4 +93,4 @@ window.addEventListener('load', function(e) { initializeButtons(e.target); }); -export { initializeButtons, download }; \ No newline at end of file +export { initializeButtons, download }; diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/module/document_action_buttons_group/index.ts b/src/Bundle/ChillDocStoreBundle/Resources/public/module/document_action_buttons_group/index.ts new file mode 100644 index 000000000..4180808dd --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/module/document_action_buttons_group/index.ts @@ -0,0 +1,49 @@ +import {_createI18n} from "../../../../../ChillMainBundle/Resources/public/vuejs/_js/i18n"; +import DocumentActionButtonsGroup from "../../vuejs/DocumentActionButtonsGroup.vue"; +import {createApp} from "vue"; +import {StoredObject, StoredObjectStatusChange} from "../../types"; +import {is_object_ready} from "../../vuejs/StoredObjectButton/helpers"; + +const i18n = _createI18n({}); + +window.addEventListener('DOMContentLoaded', function (e) { + document.querySelectorAll('div[data-download-buttons]').forEach((el) => { + const app = createApp({ + components: {DocumentActionButtonsGroup}, + data() { + + const datasets = el.dataset as { + filename: string, + canEdit: string, + storedObject: string, + buttonSmall: string, + }; + + const + storedObject = JSON.parse(datasets.storedObject) as StoredObject, + filename = datasets.filename, + canEdit = datasets.canEdit === '1', + small = datasets.buttonSmall === '1' + ; + + return { storedObject, filename, canEdit, small }; + }, + template: '', + methods: { + onStoredObjectStatusChange: function(newStatus: StoredObjectStatusChange): void { + this.$data.storedObject.status = newStatus.status; + this.$data.storedObject.filename = newStatus.filename; + this.$data.storedObject.type = newStatus.type; + + // remove eventual div which inform pending status + document.querySelectorAll(`[data-docgen-is-pending="${this.$data.storedObject.id}"]`) + .forEach(function(el) { + el.remove(); + }); + } + } + }); + + app.use(i18n).mount(el); + }) +}); diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/types.ts b/src/Bundle/ChillDocStoreBundle/Resources/public/types.ts new file mode 100644 index 000000000..825055973 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/types.ts @@ -0,0 +1,35 @@ +import {DateTime} from "../../../ChillMainBundle/Resources/public/types"; + +export type StoredObjectStatus = "ready"|"failure"|"pending"; + +export interface StoredObject { + id: number, + + /** + * filename of the object in the object storage + */ + filename: string, + creationDate: DateTime, + datas: object, + iv: number[], + keyInfos: object, + title: string, + type: string, + uuid: string, + status: StoredObjectStatus, +} + +export interface StoredObjectStatusChange { + id: number, + filename: string, + status: StoredObjectStatus, + type: string, +} + +/** + * Function executed by the WopiEditButton component. + */ +export type WopiEditButtonExecutableBeforeLeaveFunction = { + (): Promise +} + diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/DocumentActionButtonsGroup.vue b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/DocumentActionButtonsGroup.vue new file mode 100644 index 000000000..88587e90f --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/DocumentActionButtonsGroup.vue @@ -0,0 +1,123 @@ + + + + + diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/README.md b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/README.md new file mode 100644 index 000000000..2d10dace8 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/README.md @@ -0,0 +1,5 @@ +# About buttons and components available + +## DocumentActionButtonsGroup + +This is an component to use to render a group of button with actions linked to a document. diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/ConvertButton.vue b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/ConvertButton.vue new file mode 100644 index 000000000..aa99a223f --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/ConvertButton.vue @@ -0,0 +1,46 @@ + + + diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/DownloadButton.vue b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/DownloadButton.vue new file mode 100644 index 000000000..98645873b --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/DownloadButton.vue @@ -0,0 +1,54 @@ + + + diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/WopiEditButton.vue b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/WopiEditButton.vue new file mode 100644 index 000000000..d68f60f86 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/WopiEditButton.vue @@ -0,0 +1,44 @@ + + + + + + diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/helpers.ts b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/helpers.ts new file mode 100644 index 000000000..5b61a108d --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/helpers.ts @@ -0,0 +1,193 @@ +import {StoredObject, StoredObjectStatus, StoredObjectStatusChange} from "../../types"; + +const MIMES_EDIT = new Set([ + 'application/vnd.ms-powerpoint', + 'application/vnd.ms-excel', + 'application/vnd.oasis.opendocument.text', + 'application/vnd.oasis.opendocument.text-flat-xml', + 'application/vnd.oasis.opendocument.spreadsheet', + 'application/vnd.oasis.opendocument.spreadsheet-flat-xml', + 'application/vnd.oasis.opendocument.presentation', + 'application/vnd.oasis.opendocument.presentation-flat-xml', + 'application/vnd.oasis.opendocument.graphics', + 'application/vnd.oasis.opendocument.graphics-flat-xml', + 'application/vnd.oasis.opendocument.chart', + 'application/msword', + 'application/vnd.ms-excel', + 'application/vnd.ms-powerpoint', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'application/vnd.ms-word.document.macroEnabled.12', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'application/vnd.ms-excel.sheet.binary.macroEnabled.12', + 'application/vnd.ms-excel.sheet.macroEnabled.12', + 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'application/vnd.ms-powerpoint.presentation.macroEnabled.12', + 'application/x-dif-document', + 'text/spreadsheet', + 'text/csv', + 'application/x-dbase', + 'text/rtf', + 'text/plain', + 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', +]); + + + +const MIMES_VIEW = new Set([ + ...MIMES_EDIT, + [ + 'image/svg+xml', + 'application/vnd.sun.xml.writer', + 'application/vnd.sun.xml.calc', + 'application/vnd.sun.xml.impress', + 'application/vnd.sun.xml.draw', + 'application/vnd.sun.xml.writer.global', + 'application/vnd.sun.xml.writer.template', + 'application/vnd.sun.xml.calc.template', + 'application/vnd.sun.xml.impress.template', + 'application/vnd.sun.xml.draw.template', + 'application/vnd.oasis.opendocument.text-master', + 'application/vnd.oasis.opendocument.text-template', + 'application/vnd.oasis.opendocument.text-master-template', + 'application/vnd.oasis.opendocument.spreadsheet-template', + 'application/vnd.oasis.opendocument.presentation-template', + 'application/vnd.oasis.opendocument.graphics-template', + 'application/vnd.ms-word.template.macroEnabled.12', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', + 'application/vnd.ms-excel.template.macroEnabled.12', + 'application/vnd.openxmlformats-officedocument.presentationml.template', + 'application/vnd.ms-powerpoint.template.macroEnabled.12', + 'application/vnd.wordperfect', + 'application/x-aportisdoc', + 'application/x-hwp', + 'application/vnd.ms-works', + 'application/x-mswrite', + 'application/vnd.lotus-1-2-3', + 'image/cgm', + 'image/vnd.dxf', + 'image/x-emf', + 'image/x-wmf', + 'application/coreldraw', + 'application/vnd.visio2013', + 'application/vnd.visio', + 'application/vnd.ms-visio.drawing', + 'application/x-mspublisher', + 'application/x-sony-bbeb', + 'application/x-gnumeric', + 'application/macwriteii', + 'application/x-iwork-numbers-sffnumbers', + 'application/vnd.oasis.opendocument.text-web', + 'application/x-pagemaker', + 'application/x-fictionbook+xml', + 'application/clarisworks', + 'image/x-wpg', + 'application/x-iwork-pages-sffpages', + 'application/x-iwork-keynote-sffkey', + 'application/x-abiword', + 'image/x-freehand', + 'application/vnd.sun.xml.chart', + 'application/x-t602', + 'image/bmp', + 'image/png', + 'image/gif', + 'image/tiff', + 'image/jpg', + 'image/jpeg', + 'application/pdf', + ] +]) + +function is_extension_editable(mimeType: string): boolean { + return MIMES_EDIT.has(mimeType); +} + +function is_extension_viewable(mimeType: string): boolean { + return MIMES_VIEW.has(mimeType); +} + +function build_convert_link(uuid: string) { + return `/chill/wopi/convert/${uuid}`; +} + +function build_download_info_link(object_name: string) { + return `/asyncupload/temp_url/generate/GET?object_name=${object_name}`; +} + +function build_wopi_editor_link(uuid: string, returnPath?: string) { + if (returnPath === undefined) { + returnPath = window.location.pathname + window.location.search + window.location.hash; + } + + return `/chill/wopi/edit/${uuid}?returnPath=` + encodeURIComponent(returnPath); +} + +function download_doc(url: string): Promise { + return window.fetch(url).then(r => { + if (r.ok) { + return r.blob() + } + + throw new Error('Could not download document'); + }); +} + +async function download_and_decrypt_doc(urlGenerator: string, keyData: JsonWebKey, iv: Uint8Array): Promise +{ + const algo = 'AES-CBC'; + // get an url to download the object + const downloadInfoResponse = await window.fetch(urlGenerator); + + if (!downloadInfoResponse.ok) { + throw new Error("error while downloading url " + downloadInfoResponse.status + " " + downloadInfoResponse.statusText); + } + + const downloadInfo = await downloadInfoResponse.json() as {url: string}; + const rawResponse = await window.fetch(downloadInfo.url); + + if (!rawResponse.ok) { + throw new Error("error while downloading raw file " + rawResponse.status + " " + rawResponse.statusText); + } + + if (iv.length === 0) { + return rawResponse.blob(); + } + + const rawBuffer = await rawResponse.arrayBuffer(); + + try { + const key = await window.crypto.subtle + .importKey('jwk', keyData, { name: algo }, false, ['decrypt']); + const decrypted = await window.crypto.subtle + .decrypt({ name: algo, iv: iv }, key, rawBuffer); + + return Promise.resolve(new Blob([decrypted])); + } catch (e) { + console.error('get error while keys and decrypt operations'); + console.error(e); + + throw e; + } +} + +async function is_object_ready(storedObject: StoredObject): Promise +{ + const new_status_response = await window + .fetch( `/api/1.0/doc-store/stored-object/${storedObject.uuid}/is-ready`); + + if (!new_status_response.ok) { + throw new Error("could not fetch the new status"); + } + + return await new_status_response.json(); +} + +export { + build_convert_link, + build_download_info_link, + build_wopi_editor_link, + download_and_decrypt_doc, + download_doc, + is_extension_editable, + is_extension_viewable, + is_object_ready, +}; diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/_workflow.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/_workflow.html.twig index e9147ed4a..58d4903b4 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/_workflow.html.twig +++ b/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/_workflow.html.twig @@ -1,64 +1,58 @@ {% import "@ChillDocStore/Macro/macro.html.twig" as m %} {% import "@ChillDocStore/Macro/macro_mimeicon.html.twig" as mm %} -
-
-
-
- -
-
-

{{ document.title }}

+{% if document is null %} +
+ {{ 'workflow.Document deleted'|trans }} +
+{% else %} +
+
+
+
+ +
+
+

{{ document.title }}

- {{ mm.mimeIcon(document.object.type) }} - - {% if document.description is not empty %} -
- {{ document.description }} -
- {% endif %} + {{ mm.mimeIcon(document.object.type) }} + {% if document.description is not empty %} +
+ {{ document.description }} +
+ {% endif %} +
-
-{% set freezed = false %} -{% for step in entity_workflow.stepsChained %} - {% if loop.last %} - {% if step.previous is not null and step.previous.freezeAfter == true %} - {% set freezed = true %} - {% endif %} - {% endif %} -{% endfor %} - -{% if display_action is defined and display_action == true %} -
    - {% if document.course != null and is_granted('CHILL_PERSON_ACCOMPANYING_PERIOD_SEE', document.course) %} -
  • - - {{ 'Course number'|trans }} {{ document.course.id }} - -
  • - {% endif %} -
  • - {{ m.download_button(document.object, document.title) }} -
  • -
  • - {% if chill_document_is_editable(document.object) %} - {% if not freezed %} - {{ document.object|chill_document_edit_button({'title': document.title|e('html') }) }} - {% else %} - - {{ 'Update document'|trans }} - + {% set freezed = false %} + {% for step in entity_workflow.stepsChained %} + {% if loop.last %} + {% if step.previous is not null and step.previous.freezeAfter == true %} + {% set freezed = true %} {% endif %} {% endif %} - {% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_SEE', document) and document.course != null %} + {% endfor %} + + {% if display_action is defined and display_action == true %} + + {% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_SEE', document) and document.course != null %} +
  • + +
  • + {% endif %} +
+ {% endif %} {% endif %} diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/delete.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/delete.html.twig index a6679829b..c9bb608cf 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/delete.html.twig +++ b/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/delete.html.twig @@ -9,11 +9,6 @@
{{ 'Title'|trans }}
{{ document.title }}
- {% if document.scope is not null %} -
{{ 'Scope' | trans }}
-
{{ document.scope.name | localize_translatable_string }}
- {% endif %} -
{{ 'Category'|trans }}
{{ document.category.name|localize_translatable_string }}
diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/index.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/index.html.twig index df18efa71..7a013260c 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/index.html.twig +++ b/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/index.html.twig @@ -8,16 +8,16 @@ {% block js %} {{ parent() }} - {{ encore_entry_script_tags('mod_async_upload') }} {{ encore_entry_script_tags('mod_docgen_picktemplate') }} {{ encore_entry_script_tags('mod_entity_workflow_pick') }} + {{ encore_entry_script_tags('mod_document_action_buttons_group') }} {% endblock %} {% block css %} {{ parent() }} - {{ encore_entry_link_tags('mod_async_upload') }} {{ encore_entry_link_tags('mod_docgen_picktemplate') }} {{ encore_entry_link_tags('mod_entity_workflow_pick') }} + {{ encore_entry_link_tags('mod_document_action_buttons_group') }} {% endblock %} {% block content %} diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/show.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/show.html.twig index 45ed3988b..3c62451a9 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/show.html.twig +++ b/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/show.html.twig @@ -14,6 +14,7 @@ {{ parent() }} {{ encore_entry_link_tags('mod_async_upload') }} {{ encore_entry_link_tags('mod_entity_workflow_pick') }} + {{ encore_entry_link_tags('mod_document_action_buttons_group') }} {% endblock %} {% block content %} @@ -61,13 +62,8 @@ {% endif %}
  • - {{ m.download_button(document.object, document.title) }} + {{ document.object|chill_document_button_group(document.title) }}
  • - {% if chill_document_is_editable(document.object) %} -
  • - {{ document.object|chill_document_edit_button }} -
  • - {% endif %} {% set workflows_frame = chill_entity_workflow_list('Chill\\DocStoreBundle\\Entity\\AccompanyingCourseDocument', document.id) %} {% if workflows_frame is not empty %}
  • @@ -86,4 +82,5 @@ {{ parent() }} {{ encore_entry_script_tags('mod_async_upload') }} {{ encore_entry_script_tags('mod_entity_workflow_pick') }} + {{ encore_entry_script_tags('mod_document_action_buttons_group') }} {% endblock %} diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/Button/button_group.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/Button/button_group.html.twig new file mode 100644 index 000000000..f83cafd51 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Resources/views/Button/button_group.html.twig @@ -0,0 +1,7 @@ +{%- import "@ChillDocStore/Macro/macro.html.twig" as m -%} +
    diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/DocumentCategory/index.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/DocumentCategory/index.html.twig index 5731f57f5..ff20257a5 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/views/DocumentCategory/index.html.twig +++ b/src/Bundle/ChillDocStoreBundle/Resources/views/DocumentCategory/index.html.twig @@ -5,14 +5,14 @@ {% block admin_content %}

    {{ 'Document category list' | trans }}

    -
  • {{ 'Budget element type'|trans }}
    - - - {{ f.type|budget_element_type_display(family) }} - + + {% 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') }} + {{ f.amount|format_currency('EUR') }} {% if f.endDate is not null %} {{ f.startDate|format_date('short') ~ ' - ' ~ f.endDate|format_date('short') }} {% else %} - {{ f.startDate|format_date('short') ~ ' - ...' }} + {{ 'depuis le ' ~ f.startDate|format_date('short') }} {% endif %} +
      {% if is_granted('CHILL_BUDGET_ELEMENT_SEE', f) %}
    • @@ -76,10 +83,9 @@ {% set result = (totalResources - totalCharges) %} - +
      - @@ -87,7 +93,6 @@ - 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 14573048e..ad0a12755 100644 --- a/src/Bundle/ChillBudgetBundle/Resources/views/Budget/_past_budget.html.twig +++ b/src/Bundle/ChillBudgetBundle/Resources/views/Budget/_past_budget.html.twig @@ -20,34 +20,24 @@ aria-labelledby="heading_past_{{ entity.id }}" data-bs-parent="#past_{{ entity.id }}"> -
      -

      {{ 'Past resources'|trans }}

      +
      +

      {{ 'Past resources'|trans }}

      {% if pastResources|length > 0 %} -
      - {{ table_elements(pastResources, 'resource') }} -
      + {{ table_elements(pastResources, 'resource') }} {% else %} -
      - {{ 'No past resources registered'|trans }} -
      + {{ 'No past resources registered'|trans }} {% endif %} -
      -
      -

      {{ 'Past charges'|trans }}

      +

      {{ 'Past charges'|trans }}

      {% if pastCharges|length > 0 %} -
      - {{ table_elements(pastCharges, 'charge') }} -
      + {{ table_elements(pastCharges, 'charge') }} {% else %} -
      - {{ 'No past charges registered'|trans }} -
      + {{ 'No past charges registered'|trans }} {% endif %}
      - \ No newline at end of file + 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 f64184d6f..7a97731da 100644 --- a/src/Bundle/ChillBudgetBundle/Resources/views/Charge/confirm_delete.html.twig +++ b/src/Bundle/ChillBudgetBundle/Resources/views/Charge/confirm_delete.html.twig @@ -3,13 +3,13 @@ {% set indexPage = 'chill_budget_elements_index' %} {% set activeRouteKey = '' %} {% set person = element.person %} - {% set confirm_question = 'Are you sure you want to remove the charge "%type%" associated to "%name%" ?'|trans({ '%name%' : person.firstname ~ ' ' ~ person.lastname, '%type%': element.type|budget_element_type_display('charge') } ) %} + {% set confirm_question = 'Are you sure you want to remove the charge "%type%" associated to "%name%" ?'|trans({ '%name%' : person.firstname ~ ' ' ~ person.lastname, '%type%': element.charge.getName | localize_translatable_string } ) %} {% else %} {% set template = '@ChillPerson/Household/layout.html.twig' %} {% set indexPage = 'chill_budget_elements_household_index' %} {% set activeRouteKey = '' %} {% set household = element.household %} - {% set confirm_question = 'Are you sure you want to remove the charge "%type%" associated to household "%household%" ?'|trans({ '%household%' : household.id, '%type%': element.type|budget_element_type_display('charge') } ) %} + {% set confirm_question = 'Are you sure you want to remove the charge "%type%" associated to household "%household%" ?'|trans({ '%household%' : household.id, '%type%': element.charge.getName | localize_translatable_string } ) %} {% endif %} {% extends template %} diff --git a/src/Bundle/ChillBudgetBundle/Resources/views/Charge/edit.html.twig b/src/Bundle/ChillBudgetBundle/Resources/views/Charge/edit.html.twig index fed80a5c0..ab68314d6 100644 --- a/src/Bundle/ChillBudgetBundle/Resources/views/Charge/edit.html.twig +++ b/src/Bundle/ChillBudgetBundle/Resources/views/Charge/edit.html.twig @@ -21,7 +21,7 @@ {{ form_start(form) }} -{{ form_row(form.type) }} +{{ form_row(form.charge) }} {{ form_row(form.amount) }} {{ form_row(form.help) }} {{ form_row(form.comment) }} diff --git a/src/Bundle/ChillBudgetBundle/Resources/views/Charge/new.html.twig b/src/Bundle/ChillBudgetBundle/Resources/views/Charge/new.html.twig index 817d501ca..bdd7334bb 100644 --- a/src/Bundle/ChillBudgetBundle/Resources/views/Charge/new.html.twig +++ b/src/Bundle/ChillBudgetBundle/Resources/views/Charge/new.html.twig @@ -20,7 +20,7 @@ {{ form_start(form) }} -{{ form_row(form.type) }} +{{ form_row(form.charge) }} {{ form_row(form.amount) }} {{ form_row(form.help) }} {{ form_row(form.comment) }} diff --git a/src/Bundle/ChillBudgetBundle/Resources/views/Charge/view.html.twig b/src/Bundle/ChillBudgetBundle/Resources/views/Charge/view.html.twig index d3b9ef71b..509c421b0 100644 --- a/src/Bundle/ChillBudgetBundle/Resources/views/Charge/view.html.twig +++ b/src/Bundle/ChillBudgetBundle/Resources/views/Charge/view.html.twig @@ -25,7 +25,7 @@

      - {{ element.type|budget_element_type_display('charge') }} + {{ element.charge.getName | localize_translatable_string }}

      diff --git a/src/Bundle/ChillBudgetBundle/Resources/views/Entity/budget_element_type.html.twig b/src/Bundle/ChillBudgetBundle/Resources/views/Entity/budget_element_type.html.twig new file mode 100644 index 000000000..f2187a8b8 --- /dev/null +++ b/src/Bundle/ChillBudgetBundle/Resources/views/Entity/budget_element_type.html.twig @@ -0,0 +1 @@ +{{ entity.name|localize_translatable_string }} \ No newline at end of file diff --git a/src/Bundle/ChillBudgetBundle/Resources/views/Household/index.html.twig b/src/Bundle/ChillBudgetBundle/Resources/views/Household/index.html.twig index c2533e4c0..1df92b297 100644 --- a/src/Bundle/ChillBudgetBundle/Resources/views/Household/index.html.twig +++ b/src/Bundle/ChillBudgetBundle/Resources/views/Household/index.html.twig @@ -24,16 +24,14 @@ } %} {# -
      -

      {{ 'Budget calculator'|trans }}

      -
      - {{ table_results(wholeCharges, wholeResources) }} -
      +
      +

      {{ 'Budget calculator'|trans }}

      + {{ table_results(wholeCharges, wholeResources) }}
      #} {% if household.getCurrentMembers|length > 0 %} -

      {{ 'Current budget household members'|trans }}

      +

      {{ 'Budget household members'|trans }}

      {% for hm in household.getCurrentMembers %} {% set member = hm.person %} @@ -57,6 +55,8 @@ aria-labelledby="heading_{{ member.id }}" data-bs-parent="#nonCurrent"> +

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

      + {% include 'ChillBudgetBundle:Budget:_budget.html.twig' with { 'resources': member.getBudgetResources, 'charges': member.getBudgetCharges, diff --git a/src/Bundle/ChillBudgetBundle/Resources/views/Person/index.html.twig b/src/Bundle/ChillBudgetBundle/Resources/views/Person/index.html.twig index 959df3d62..18d04b889 100644 --- a/src/Bundle/ChillBudgetBundle/Resources/views/Person/index.html.twig +++ b/src/Bundle/ChillBudgetBundle/Resources/views/Person/index.html.twig @@ -17,17 +17,15 @@ {% block content %}

      {{ title }}

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

      {{ 'Budget calculator'|trans }}

      -
      - {{ table_results(charges, resources) }} -
      +
      +

      {{ 'Budget calculator'|trans }}

      + {{ table_results(charges, resources) }}
      {% 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 73ae173ff..5c2e80e10 100644 --- a/src/Bundle/ChillBudgetBundle/Resources/views/Resource/confirm_delete.html.twig +++ b/src/Bundle/ChillBudgetBundle/Resources/views/Resource/confirm_delete.html.twig @@ -3,13 +3,13 @@ {% set indexPage = 'chill_budget_elements_index' %} {% set activeRouteKey = '' %} {% set person = element.person %} - {% set confirm_question = 'Are you sure you want to remove the ressource "%type%" associated to "%name%" ?'|trans({ '%name%' : person.firstname ~ ' ' ~ person.lastname, '%type%': element.type|budget_element_type_display('resource') } ) %} + {% set confirm_question = 'Are you sure you want to remove the ressource "%type%" associated to "%name%" ?'|trans({ '%name%' : person.firstname ~ ' ' ~ person.lastname, '%type%': element.resource.getName | localize_translatable_string } ) %} {% else %} {% set template = '@ChillPerson/Household/layout.html.twig' %} {% set indexPage = 'chill_budget_elements_household_index' %} {% set activeRouteKey = '' %} {% set household = element.household %} - {% set confirm_question = 'Are you sure you want to remove the ressource "%type%" associated to household "%household%" ?'|trans({ '%household%' : household.id, '%type%': element.type|budget_element_type_display('resource') } ) %} + {% set confirm_question = 'Are you sure you want to remove the resource "%type%" associated to household "%household%" ?'|trans({ '%household%' : household.id, '%type%': element.resource.getName | localize_translatable_string} ) %} {% endif %} {% extends template %} diff --git a/src/Bundle/ChillBudgetBundle/Resources/views/Resource/edit.html.twig b/src/Bundle/ChillBudgetBundle/Resources/views/Resource/edit.html.twig index 8b051eeaf..85cf7e8e4 100644 --- a/src/Bundle/ChillBudgetBundle/Resources/views/Resource/edit.html.twig +++ b/src/Bundle/ChillBudgetBundle/Resources/views/Resource/edit.html.twig @@ -23,7 +23,7 @@ {{ form_start(form) }} -{{ form_row(form.type) }} +{{ form_row(form.resource) }} {{ form_row(form.amount) }} {{ form_row(form.comment) }} {{ form_row(form.startDate) }} diff --git a/src/Bundle/ChillBudgetBundle/Resources/views/Resource/new.html.twig b/src/Bundle/ChillBudgetBundle/Resources/views/Resource/new.html.twig index 4ea71f4de..5c1b63d12 100644 --- a/src/Bundle/ChillBudgetBundle/Resources/views/Resource/new.html.twig +++ b/src/Bundle/ChillBudgetBundle/Resources/views/Resource/new.html.twig @@ -22,8 +22,7 @@

      {{ title }}

      {{ form_start(form) }} - -{{ form_row(form.type) }} +{{ form_row(form.resource) }} {{ form_row(form.amount) }} {{ form_row(form.comment) }} {{ form_row(form.startDate) }} diff --git a/src/Bundle/ChillBudgetBundle/Resources/views/Resource/view.html.twig b/src/Bundle/ChillBudgetBundle/Resources/views/Resource/view.html.twig index 110aed9fe..c3618b498 100644 --- a/src/Bundle/ChillBudgetBundle/Resources/views/Resource/view.html.twig +++ b/src/Bundle/ChillBudgetBundle/Resources/views/Resource/view.html.twig @@ -25,7 +25,7 @@

      - {{ element.type|budget_element_type_display('resource') }} + {{ element.resource.getName | localize_translatable_string }}

      diff --git a/src/Bundle/ChillBudgetBundle/Security/Authorization/BudgetElementVoter.php b/src/Bundle/ChillBudgetBundle/Security/Authorization/BudgetElementVoter.php index 18232f246..5203f9092 100644 --- a/src/Bundle/ChillBudgetBundle/Security/Authorization/BudgetElementVoter.php +++ b/src/Bundle/ChillBudgetBundle/Security/Authorization/BudgetElementVoter.php @@ -1,14 +1,14 @@ voter->supports($attribute, $subject); } protected function voteOnAttribute($attribute, $subject, TokenInterface $token) { - return $this->voter->voteOnAttribute($attribute, $subject, $token); + if ( + $subject instanceof Person + || ($subject instanceof AbstractElement && null !== $person = $subject->getPerson())) { + return $this->voter->voteOnAttribute($attribute, $person ?? $subject, $token); + } + + if ( + $subject instanceof Household + || ($subject instanceof AbstractElement && null !== $household = $subject->getHousehold())) { + foreach (($household ?? $subject)->getCurrentPersons() as $person) { + if ($this->voter->voteOnAttribute($attribute, $person, $token)) { + return true; + } + } + + return false; + } + + 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 243cd4749..1c66d4c1c 100644 --- a/src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudget.php +++ b/src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudget.php @@ -1,55 +1,60 @@ em = $em; - $this->configRepository = $configRepository; - $this->chargeLabels = $configRepository->getChargesLabels(); - $this->resourcesLabels = $configRepository->getResourcesLabels(); $this->translatableStringHelper = $translatableStringHelper; + $this->resourceKindRepository = $resourceKindRepository; + $this->chargeKindRepository = $chargeKindRepository; } public function getSummaryForHousehold(?Household $household): array @@ -61,7 +66,7 @@ class SummaryBudget implements SummaryBudgetInterface ]; } - $personIds = $household->getCurrentPersons()->map(static function (Person $p) { return $p->getId(); }); + $personIds = $household->getCurrentPersons()->map(static fn (Person $p) => $p->getId()); $ids = implode(', ', array_fill(0, count($personIds), '?')); $parameters = [...$personIds, $household->getId()]; @@ -110,7 +115,7 @@ class SummaryBudget implements SummaryBudgetInterface $rsm = new ResultSetMapping(); $rsm ->addScalarResult('sum', 'sum') - ->addScalarResult('type', 'type') + ->addScalarResult('kind_id', 'kind_id') ->addScalarResult('comment', 'comment'); return $rsm; @@ -118,51 +123,58 @@ class SummaryBudget implements SummaryBudgetInterface private function getEmptyChargeArray(): array { - $keys = $this->configRepository->getChargesKeys(); - $labels = $this->chargeLabels; + $keys = array_map(static fn (ChargeKind $kind) => $kind->getKind(), $this->chargeKindRepository->findAll()); - return array_combine($keys, array_map(function ($i) use ($labels) { - return ['sum' => 0.0, 'label' => $this->translatableStringHelper->localize($labels[$i]), '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 = $this->configRepository->getResourcesKeys(); - $labels = $this->resourcesLabels; + $keys = array_map(static fn (ResourceKind $kind) => $kind->getKind(), $this->resourceKindRepository->findAll()); - return array_combine($keys, array_map(function ($i) use ($labels) { - return ['sum' => 0.0, 'label' => $this->translatableStringHelper->localize($labels[$i]), '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 { + $result = []; + switch ($kind) { case 'charge': - $label = $this->chargeLabels; + foreach ($rows as $row) { + $chargeKind = $this->chargeKindRepository->find($row['kind_id']); - break; + if (null === $chargeKind) { + throw new RuntimeException('charge kind not found: ' . $row['kind_id']); + } + $result[$chargeKind->getKind()] = [ + 'sum' => (float) $row['sum'], + 'label' => $this->translatableStringHelper->localize($chargeKind->getName()), + 'comment' => (string) $row['comment'], + ]; + } + + return $result; case 'resource': - $label = $this->resourcesLabels; + foreach ($rows as $row) { + $resourceKind = $this->resourceKindRepository->find($row['kind_id']); - break; + if (null === $resourceKind) { + throw new RuntimeException('charge kind not found: ' . $row['kind_id']); + } + + $result[$resourceKind->getKind()] = [ + 'sum' => (float) $row['sum'], + 'label' => $this->translatableStringHelper->localize($resourceKind->getName()), + 'comment' => (string) $row['comment'], + ]; + } + + return $result; default: throw new LogicException(); } - - $result = []; - - foreach ($rows as $row) { - $result[$row['type']] = [ - 'sum' => (float) $row['sum'], - 'label' => $this->translatableStringHelper->localize($label[$row['type']]), - 'comment' => (string) $row['comment'], - ]; - } - - return $result; } } diff --git a/src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudgetInterface.php b/src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudgetInterface.php index 528c4626e..bcfb60b3e 100644 --- a/src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudgetInterface.php +++ b/src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudgetInterface.php @@ -1,14 +1,14 @@ + */ +final 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 renderBox($entity, array $options): string + { + return $this->engine->render('@ChillBudget/Entity/budget_element_type.html.twig', [ + 'entity' => $entity, + 'options' => $options, + ]); + } + + public function renderString($entity, array $options): string + { + $title = ''; + + if (null !== $entity->getName()) { + return $this->translatableStringHelper->localize($entity->getName()); + } + + return $title; + } + + public function supports($entity, array $options): bool + { + return $entity instanceof ChargeKind || $entity instanceof ResourceKind; + } +} diff --git a/src/Bundle/ChillBudgetBundle/Templating/Twig.php b/src/Bundle/ChillBudgetBundle/Templating/Twig.php deleted file mode 100644 index 1f4e4098b..000000000 --- a/src/Bundle/ChillBudgetBundle/Templating/Twig.php +++ /dev/null @@ -1,65 +0,0 @@ -configRepository = $configRepository; - $this->translatableStringHelper = $translatableStringHelper; - } - - public function displayLink($link, $family) - { - switch ($family) { - case 'resource': - return $this->translatableStringHelper->localize( - $this->configRepository->getResourcesLabels()[$link] - ); - - case 'charge': - return $this->translatableStringHelper->localize( - $this->configRepository->getChargesLabels()[$link] - ); - - default: - throw new UnexpectedValueException("This family of element: {$family} is not " - . "supported. Supported families are 'resource', 'charge'"); - } - } - - public function getFilters() - { - return [ - new TwigFilter('budget_element_type_display', [$this, 'displayLink'], ['is_safe' => ['html']]), - ]; - } -} diff --git a/src/Bundle/ChillBudgetBundle/Tests/Controller/ElementControllerTest.php b/src/Bundle/ChillBudgetBundle/Tests/Controller/ElementControllerTest.php index 92784909b..925506a99 100644 --- a/src/Bundle/ChillBudgetBundle/Tests/Controller/ElementControllerTest.php +++ b/src/Bundle/ChillBudgetBundle/Tests/Controller/ElementControllerTest.php @@ -1,14 +1,14 @@ prophesize(AbstractQuery::class); + $queryCharges->getResult()->willReturn([ + [ + 'sum' => 250.0, + 'comment' => '', + 'kind_id' => 1, // kind: rental + ], + ]); + $queryCharges->setParameters(Argument::type('array')) + ->will(static fn ($args, $query) => $query); + + $queryResources = $this->prophesize(AbstractQuery::class); + $queryResources->getResult()->willReturn([ + [ + 'sum' => 1500.0, + 'comment' => '', + 'kind_id' => 2, // kind: 'salary', + ], + ]); + $queryResources->setParameters(Argument::type('array')) + ->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')) { + return $queryResources->reveal(); + } + + if (false !== strpos($args[0], 'chill_budget.charge')) { + return $queryCharges->reveal(); + } + + throw new RuntimeException('this query does not have a stub counterpart: ' . $args[0]); + }); + + $chargeRepository = $this->prophesize(ChargeKindRepositoryInterface::class); + $chargeRepository->findAll()->willReturn([ + $rental = (new ChargeKind())->setKind('rental')->setName(['fr' => 'Rental']), + $other = (new ChargeKind())->setKind('other')->setName(['fr' => 'Other']), + ]); + $chargeRepository->find(1)->willReturn($rental); + $chargeRepository->findOneByKind('rental')->willReturn($rental); + $chargeRepository->findOneByKind('other')->willReturn($other); + + $resourceRepository = $this->prophesize(ResourceKindRepositoryInterface::class); + $resourceRepository->findAll()->willReturn([ + $salary = (new ResourceKind())->setKind('salary')->setName(['fr' => 'Salary']), + $misc = (new ResourceKind())->setKind('misc')->setName(['fr' => 'Misc']), + ]); + $resourceRepository->find(2)->willReturn($salary); + $resourceRepository->findOneByKind('salary')->willReturn($salary); + $resourceRepository->findOneByKind('misc')->willReturn($misc); + + $translatableStringHelper = $this->prophesize(TranslatableStringHelperInterface::class); + $translatableStringHelper->localize(Argument::type('array'))->will(static fn ($arg) => $arg[0]['fr']); + + $person = new Person(); + $personReflection = new ReflectionClass($person); + $personIdReflection = $personReflection->getProperty('id'); + $personIdReflection->setAccessible(true); + $personIdReflection->setValue($person, 1); + + $household = new 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')); + $household->addMember($householdMember); + + $summaryBudget = new SummaryBudget( + $em->reveal(), + $translatableStringHelper->reveal(), + $resourceRepository->reveal(), + $chargeRepository->reveal() + ); + + $summary = $summaryBudget->getSummaryForPerson($person); + $summaryForHousehold = $summaryBudget->getSummaryForHousehold($household); + + // we check the structure for the summary. The structure is the same for household + // and persons + + $expected = [ + 'charges' => [ + 'rental' => ['sum' => 250.0, 'comment' => '', 'label' => 'Rental'], + 'other' => ['sum' => 0.0, 'comment' => '', 'label' => 'Other'], + ], + 'resources' => [ + 'salary' => ['sum' => 1500.0, 'comment' => '', 'label' => 'Salary'], + 'misc' => ['sum' => 0.0, 'comment' => '', 'label' => 'Misc'], + ], + ]; + + foreach ([$summaryForHousehold, $summary] as $summary) { + $this->assertIsArray($summary); + $this->assertEqualsCanonicalizing(['charges', 'resources'], array_keys($summary)); + $this->assertEqualsCanonicalizing(['rental', 'other'], array_keys($summary['charges'])); + $this->assertEqualsCanonicalizing(['salary', 'misc'], array_keys($summary['resources'])); + + foreach ($expected as $resCha => $contains) { + foreach ($contains as $kind => $row) { + $this->assertEqualsCanonicalizing($row, $summary[$resCha][$kind]); + } + } + } + } +} diff --git a/src/Bundle/ChillBudgetBundle/config/services/config.yaml b/src/Bundle/ChillBudgetBundle/config/services/config.yaml deleted file mode 100644 index 21136db2f..000000000 --- a/src/Bundle/ChillBudgetBundle/config/services/config.yaml +++ /dev/null @@ -1,5 +0,0 @@ -services: - Chill\BudgetBundle\Config\ConfigRepository: - arguments: - $resources: '%chill_budget.resources%' - $charges: '%chill_budget.charges%' diff --git a/src/Bundle/ChillBudgetBundle/config/services/controller.yaml b/src/Bundle/ChillBudgetBundle/config/services/controller.yaml index 82c683536..ae6fb84d6 100644 --- a/src/Bundle/ChillBudgetBundle/config/services/controller.yaml +++ b/src/Bundle/ChillBudgetBundle/config/services/controller.yaml @@ -3,3 +3,7 @@ services: autowire: true resource: '../../Controller' tags: ['controller.service_arguments'] + Chill\BudgetBundle\Controller\Admin\: + autowire: true + autoconfigure: true + resource: '../../Controller/Admin' diff --git a/src/Bundle/ChillBudgetBundle/config/services/form.yaml b/src/Bundle/ChillBudgetBundle/config/services/form.yaml index 63862ce94..a033413d0 100644 --- a/src/Bundle/ChillBudgetBundle/config/services/form.yaml +++ b/src/Bundle/ChillBudgetBundle/config/services/form.yaml @@ -1,14 +1,9 @@ services: - Chill\BudgetBundle\Form\ResourceType: - arguments: - $configRepository: '@Chill\BudgetBundle\Config\ConfigRepository' - $translatableStringHelper: '@Chill\MainBundle\Templating\TranslatableStringHelper' - tags: - - { name: 'form.type' } - - Chill\BudgetBundle\Form\ChargeType: - arguments: - $configRepository: '@Chill\BudgetBundle\Config\ConfigRepository' - $translatableStringHelper: '@Chill\MainBundle\Templating\TranslatableStringHelper' + Chill\BudgetBundle\Form\: + autowire: true + resource: '../../Form' tags: - { name: 'form.type' } +# Chill\BudgetBundle\Form\Admin\: +# autowire: true +# resource: '../../Form/Admin' diff --git a/src/Bundle/ChillBudgetBundle/config/services/menu.yaml b/src/Bundle/ChillBudgetBundle/config/services/menu.yaml index 34128519b..ba8b301d5 100644 --- a/src/Bundle/ChillBudgetBundle/config/services/menu.yaml +++ b/src/Bundle/ChillBudgetBundle/config/services/menu.yaml @@ -1,8 +1,5 @@ services: - Chill\BudgetBundle\Menu\PersonMenuBuilder: - autowire: true - autoconfigure: true - - Chill\BudgetBundle\Menu\HouseholdMenuBuilder: + Chill\BudgetBundle\Menu\: autowire: true autoconfigure: true + resource: './../../Menu' diff --git a/src/Bundle/ChillBudgetBundle/config/services/repository.yaml b/src/Bundle/ChillBudgetBundle/config/services/repository.yaml new file mode 100644 index 000000000..7205a8378 --- /dev/null +++ b/src/Bundle/ChillBudgetBundle/config/services/repository.yaml @@ -0,0 +1,5 @@ +services: + Chill\BudgetBundle\Repository\: + autowire: true + autoconfigure: true + resource: './../../Repository' \ No newline at end of file diff --git a/src/Bundle/ChillBudgetBundle/config/services/templating.yaml b/src/Bundle/ChillBudgetBundle/config/services/templating.yaml index 903db5d67..2646f3892 100644 --- a/src/Bundle/ChillBudgetBundle/config/services/templating.yaml +++ b/src/Bundle/ChillBudgetBundle/config/services/templating.yaml @@ -1,7 +1,5 @@ services: - Chill\BudgetBundle\Templating\Twig: - arguments: - $configRepository: '@Chill\BudgetBundle\Config\ConfigRepository' - $translatableStringHelper: '@Chill\MainBundle\Templating\TranslatableStringHelper' - tags: - - { name: 'twig.extension' } \ No newline at end of file + Chill\BudgetBundle\Templating\: + autowire: true + autoconfigure: true + resource: '../../Templating' diff --git a/src/Bundle/ChillBudgetBundle/migrations/Version20180522080432.php b/src/Bundle/ChillBudgetBundle/migrations/Version20180522080432.php index 001506c0e..04fdecdea 100644 --- a/src/Bundle/ChillBudgetBundle/migrations/Version20180522080432.php +++ b/src/Bundle/ChillBudgetBundle/migrations/Version20180522080432.php @@ -1,14 +1,14 @@ addSql('DROP SEQUENCE chill_budget.charge_type_id_seq CASCADE'); + $this->addSql('DROP SEQUENCE chill_budget.resource_type_id_seq CASCADE'); + $this->addSql('DROP TABLE chill_budget.charge_type'); + $this->addSql('DROP TABLE chill_budget.resource_type'); + } + + public function getDescription(): string + { + return 'Create resource type and charge type'; + } + + public function up(Schema $schema): void + { + $this->addSql('CREATE SEQUENCE chill_budget.charge_type_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE chill_budget.resource_type_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE TABLE chill_budget.charge_type (id INT NOT NULL, isActive BOOLEAN DEFAULT TRUE NOT NULL, name JSONB DEFAULT \'{}\'::jsonb NOT NULL, ordering DOUBLE PRECISION DEFAULT \'0\' NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE TABLE chill_budget.resource_type (id INT NOT NULL, isActive BOOLEAN DEFAULT TRUE NOT NULL, name JSONB DEFAULT \'{}\'::jsonb NOT NULL, ordering DOUBLE PRECISION DEFAULT \'0\' NOT NULL, PRIMARY KEY(id))'); + } +} diff --git a/src/Bundle/ChillBudgetBundle/migrations/Version20221130101659.php b/src/Bundle/ChillBudgetBundle/migrations/Version20221130101659.php new file mode 100644 index 000000000..98d67b341 --- /dev/null +++ b/src/Bundle/ChillBudgetBundle/migrations/Version20221130101659.php @@ -0,0 +1,41 @@ +addSql('ALTER TABLE chill_budget.resource_type DROP kind'); + $this->addSql('ALTER TABLE chill_budget.resource_type DROP tags'); + $this->addSql('ALTER TABLE chill_budget.charge_type DROP kind'); + $this->addSql('ALTER TABLE chill_budget.charge_type DROP tags'); + } + + public function getDescription(): string + { + return 'Add kind and tags property to charge and resource types'; + } + + public function up(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_budget.charge_type ADD kind VARCHAR(255) DEFAULT \'\' NOT NULL'); + $this->addSql('ALTER TABLE chill_budget.charge_type ADD tags JSONB DEFAULT \'{}\'::jsonb NOT NULL'); + $this->addSql('COMMENT ON COLUMN chill_budget.charge_type.tags IS \'(DC2Type:jsonb)\''); + $this->addSql('ALTER TABLE chill_budget.resource_type ADD kind VARCHAR(255) DEFAULT \'\' NOT NULL'); + $this->addSql('ALTER TABLE chill_budget.resource_type ADD tags JSONB DEFAULT \'{}\'::jsonb NOT NULL'); + $this->addSql('COMMENT ON COLUMN chill_budget.resource_type.tags IS \'(DC2Type:jsonb)\''); + } +} diff --git a/src/Bundle/ChillBudgetBundle/migrations/Version20221202165608.php b/src/Bundle/ChillBudgetBundle/migrations/Version20221202165608.php new file mode 100644 index 000000000..c2152100b --- /dev/null +++ b/src/Bundle/ChillBudgetBundle/migrations/Version20221202165608.php @@ -0,0 +1,41 @@ +addSql('ALTER TABLE chill_budget.charge DROP CONSTRAINT FK_5C99D2C355284914'); + $this->addSql('ALTER TABLE chill_budget.charge DROP charge_id'); + $this->addSql('ALTER TABLE chill_budget.resource DROP CONSTRAINT FK_5E0A5E9789329D25'); + $this->addSql('ALTER TABLE chill_budget.resource DROP resource_id'); + } + + public function getDescription(): string + { + return 'Integrate budget admin entity'; + } + + public function up(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_budget.charge ADD charge_id INT DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_budget.charge ADD CONSTRAINT FK_5C99D2C355284914 FOREIGN KEY (charge_id) REFERENCES chill_budget.charge_type (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('CREATE INDEX IDX_5C99D2C355284914 ON chill_budget.charge (charge_id)'); + $this->addSql('ALTER TABLE chill_budget.resource ADD resource_id INT DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_budget.resource ADD CONSTRAINT FK_5E0A5E9789329D25 FOREIGN KEY (resource_id) REFERENCES chill_budget.resource_type (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('CREATE INDEX IDX_5E0A5E9789329D25 ON chill_budget.resource (resource_id)'); + } +} diff --git a/src/Bundle/ChillBudgetBundle/migrations/Version20221207105407.php b/src/Bundle/ChillBudgetBundle/migrations/Version20221207105407.php new file mode 100644 index 000000000..cfe3a0089 --- /dev/null +++ b/src/Bundle/ChillBudgetBundle/migrations/Version20221207105407.php @@ -0,0 +1,81 @@ +addSql('DELETE FROM chill_budget.resource_type;'); + $this->addSql('DELETE FROM chill_budget.charge_type;'); + } + + public function getDescription(): string + { + return 'Use new budget admin entities'; + } + + public function setContainer(?ContainerInterface $container = null) + { + $this->container = $container; + } + + public function up(Schema $schema): void + { + $resources = $this->container->getParameter('chill_budget.resources'); + $charges = $this->container->getParameter('chill_budget.charges'); + + foreach ($resources as $value) { + $lang = $value['labels'][0]['lang']; + $label = $value['labels'][0]['label']; + $kind = $value['key']; + $this->addSql( + 'INSERT INTO chill_budget.resource_type (id, isActive, name, ordering, kind) VALUES ( + nextval(\'chill_budget.resource_type_id_seq\'), true, jsonb_build_object(:lang::text, :label::text), 0, :kind::text)', + ['lang' => $lang, 'label' => $label, 'kind' => $kind], + ['lang' => Types::STRING, 'label' => Types::STRING, 'kind' => Types::STRING] + ); + $this->addSql( + 'UPDATE chill_budget.resource SET resource_id = resource_type.id + FROM chill_budget.resource_type WHERE resource.type = :kind AND resource_type.kind = resource.type;', + ['kind' => $kind], + ['kind' => Types::STRING] + ); + } + + foreach ($charges as $value) { + $lang = $value['labels'][0]['lang']; + $label = $value['labels'][0]['label']; + $kind = $value['key']; + $this->addSql( + 'INSERT INTO chill_budget.charge_type VALUES (nextval(\'chill_budget.charge_type_id_seq\'), true, + jsonb_build_object(:lang::text, :label::text), 0, :kind::text);', + ['lang' => $lang, 'label' => $label, 'kind' => $kind], + ['lang' => Types::STRING, 'label' => Types::STRING, 'kind' => Types::STRING] + ); + $this->addSql( + 'UPDATE chill_budget.charge SET charge_id = charge_type.id + FROM chill_budget.charge_type WHERE charge.type = :kind AND charge_type.kind = charge.type;', + ['kind' => $kind], + ['kind' => Types::STRING] + ); + } + } +} diff --git a/src/Bundle/ChillBudgetBundle/migrations/Version20230201131008.php b/src/Bundle/ChillBudgetBundle/migrations/Version20230201131008.php new file mode 100644 index 000000000..7131c31f2 --- /dev/null +++ b/src/Bundle/ChillBudgetBundle/migrations/Version20230201131008.php @@ -0,0 +1,37 @@ +addSql('COMMENT ON COLUMN chill_budget.resource_type.tags IS \'(DC2Type:json)\''); + $this->addSql('COMMENT ON COLUMN chill_budget.charge_type.tags IS \'(DC2Type:json)\''); + } +} diff --git a/src/Bundle/ChillBudgetBundle/migrations/Version20230209161546.php b/src/Bundle/ChillBudgetBundle/migrations/Version20230209161546.php new file mode 100644 index 000000000..4495db8a7 --- /dev/null +++ b/src/Bundle/ChillBudgetBundle/migrations/Version20230209161546.php @@ -0,0 +1,37 @@ +addSql('DROP INDEX resource_kind_unique_type_idx'); + $this->addSql('DROP INDEX charge_kind_unique_type_idx'); + } + + public function getDescription(): string + { + return 'Budget: add unique constraint on kind for charge_kind and resource_kind'; + } + + public function up(Schema $schema): void + { + $this->addSql("UPDATE chill_budget.resource_type SET kind=md5(random()::text) WHERE kind = ''"); + $this->addSql("UPDATE chill_budget.charge_type SET kind=md5(random()::text) WHERE kind = ''"); + $this->addSql('CREATE UNIQUE INDEX resource_kind_unique_type_idx ON chill_budget.resource_type (kind);'); + $this->addSql('CREATE UNIQUE INDEX charge_kind_unique_type_idx ON chill_budget.charge_type (kind);'); + } +} diff --git a/src/Bundle/ChillBudgetBundle/migrations/Version20230328155010.php b/src/Bundle/ChillBudgetBundle/migrations/Version20230328155010.php new file mode 100644 index 000000000..ed524940e --- /dev/null +++ b/src/Bundle/ChillBudgetBundle/migrations/Version20230328155010.php @@ -0,0 +1,56 @@ +addSql(<<<'SQL' + WITH type_to_id AS ( + SELECT DISTINCT charge.charge_id AS id, charge.type + FROM chill_budget.charge + WHERE type <> '' + ) + UPDATE chill_budget.charge_type + SET kind = type_to_id.type + FROM type_to_id + WHERE type_to_id.type <> '' AND type_to_id.id = charge_type.id + SQL); + + $this->addSql(<<<'SQL' + WITH type_to_id AS ( + SELECT DISTINCT resource.resource_id AS id, resource.type + FROM chill_budget. resource + WHERE type <> '' + ) + UPDATE chill_budget.resource_type + SET kind = type_to_id.type + FROM type_to_id + WHERE type_to_id.type <> '' AND type_to_id.id = resource_type.id + SQL); + } + + public function down(Schema $schema): void + { + $this->addSql("UPDATE chill_budget.resource_type SET kind=md5(random()::text) WHERE kind = ''"); + $this->addSql("UPDATE chill_budget.charge_type SET kind=md5(random()::text) WHERE kind = ''"); + } +} diff --git a/src/Bundle/ChillBudgetBundle/translations/messages.fr.yml b/src/Bundle/ChillBudgetBundle/translations/messages.fr.yml index 299fa295e..2ff258aea 100644 --- a/src/Bundle/ChillBudgetBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillBudgetBundle/translations/messages.fr.yml @@ -3,23 +3,23 @@ Resource: Ressource Charge: Charge Budget for %name%: Budget de %name% Budget for household %household%: Budget du ménage -Current budget household members: Budget actuel des membres du ménage +Budget household members: Budget des membres du ménage Show budget of %name%: Montrer budget de %name% See complete budget: Voir budget complet Hide budget: Masquer Hide budget of %name%: Masquer budget de %name% Resource element type: Nature de la ressource -Actual budget: Éléments actuels du budget +Actual budget: Éléments actuels Actual resources: Ressources actuelles Actual resources for %name%: Ressources actuelles de %name% Actual charges for %name%: Charges actuelles de %name% Actual charges: Charges actuelles -Past budget: Éléments du budget passé +Past budget: Éléments passés Show past budget: Montrer budget passé Show future budget: Montrer budget future Past resources: Ressources passées Past charges: Charges passées -Future budget: Futurs éléments du budget +Future budget: Éléments futurs Future resources: Ressources futures Future charges: Charges futures Budget element type: Nature @@ -29,7 +29,7 @@ End of validity period: Fin de la période de validité Total: Total Create new resource: Créer une nouvelle ressource Create new charge: Créer une nouvelle charge -See person: Voir personne +See person: Voir usagers There isn't any element recorded: Aucun élément enregistré No resources registered: Aucune ressource enregistrée @@ -49,6 +49,8 @@ Remove resource: Supprimer la ressource Remove charge: Supprimer la charge Are you sure you want to remove the ressource "%type%" associated to "%name%" ?: Êtes-vous sûr·e de vouloir supprimer la ressource de nature "%type%" associée à %name% ? Are you sure you want to remove the charge "%type%" associated to "%name%" ?: Êtes-vous sûr·e de vouloir supprimer la charge de nature "%type%" associée à %name% ? +Are you sure you want to remove the charge "%type%" associated to household "%household%" ?: Êtes-vous sur·e de vouloir supprimer la charge "%type%" associée au ménage ? +Are you sure you want to remove the resource "%type%" associated to household "%household%" ?: Êtes-vous sur·e de vouloir supprimer la ressource "%type%" associée au ménage ? Resource deleted: Ressource supprimée Charge deleted: Charge supprimée Charge created: Charge créée @@ -58,6 +60,8 @@ Charge updated: charge mise à jour Choose a resource type: Choisissez un type de ressource Choose a charge type: Choisissez un type de charge +Resource type: Type de ressource +Charge type: Type de charge Amount: Montant Comment: Commentaire @@ -74,3 +78,42 @@ The balance: Différence entre ressources et charges Valid since %startDate% until %endDate%: Valide depuis le %startDate% jusqu'au %endDate% Valid since %startDate%: Valide depuis le %startDate% + +budget: + admin: + form: + Charge_kind_key: Clé d'identification + Resource_kind_key: Clé d'identification + This kind must contains only alphabeticals characters, and dashes. This string is in use during document generation. Changes may have side effect on document: Cette clé sert à identifier le type de charge ou de revenu lors de la génération de document. Seuls les caractères alpha-numériques sont autorisés. Modifier cette clé peut avoir un effet lors de la génération de nouveaux documents. + +# ROLES +Budget elements: Budget +CHILL_BUDGET_ELEMENT_CREATE: Créer une ressource/charge +CHILL_BUDGET_ELEMENT_DELETE: Supprimer une ressource/charge +CHILL_BUDGET_ELEMENT_SEE: Voir les ressources/charges +CHILL_BUDGET_ELEMENT_UPDATE: Modifier une ressource/charge + +## admin + +crud: + resource_kind: + title_new: Nouveau type de ressource + title_edit: Modifier le type de ressource + charge_kind: + title_new: Nouveau type de charge + title_edit: Modifier le type de charge + +admin: + menu: + Resource types: Types de ressource + Charge types: Types de charge + title: + Charge Type List: Liste des types de charge + Resource Type List: Liste des types de ressource + Budget configuration: Configuration des éléments de budget + new: + Create a new charge type: Créér un nouveau type de charge + Create a new resource type: Créér un nouveau type de ressource + form: + Choose the type of resource: Choisissez une type de ressource + Choose the type of charge: Choisissez une type de charge diff --git a/src/Bundle/ChillBudgetBundle/translations/messages.nl.yml b/src/Bundle/ChillBudgetBundle/translations/messages.nl.yml index 5fd21520d..de334f79f 100644 --- a/src/Bundle/ChillBudgetBundle/translations/messages.nl.yml +++ b/src/Bundle/ChillBudgetBundle/translations/messages.nl.yml @@ -2,8 +2,8 @@ Budget: Budget Resource: Inkomsten Charge: Onkosten Budget for %name%: Budget van %name% -Budget for household %household%: Budget van gezin -Current budget household members: Actuele budget van gezinsleden +Budget for household %household%: Budget van huishouden +Current budget household members: Actuele budget van leden huishouden Show budget of %name%: Toon budget van %name% See complete budget: Toon volledige budget Hide budget: Verbergen diff --git a/src/Bundle/ChillBudgetBundle/translations/validators.fr.yml b/src/Bundle/ChillBudgetBundle/translations/validators.fr.yml index 7281b6380..01b240eac 100644 --- a/src/Bundle/ChillBudgetBundle/translations/validators.fr.yml +++ b/src/Bundle/ChillBudgetBundle/translations/validators.fr.yml @@ -1,2 +1,8 @@ The amount cannot be empty: Le montant ne peut pas être vide ou égal à zéro -The budget element's end date must be after the start date: La date de fin doit être après la date de début \ No newline at end of file +The budget element's end date must be after the start date: La date de fin doit être après la date de début + +budget: + admin: + form: + kind: + only_alphanumeric diff --git a/src/Bundle/ChillCalendarBundle/ChillCalendarBundle.php b/src/Bundle/ChillCalendarBundle/ChillCalendarBundle.php index 11b985224..e2042b114 100644 --- a/src/Bundle/ChillCalendarBundle/ChillCalendarBundle.php +++ b/src/Bundle/ChillCalendarBundle/ChillCalendarBundle.php @@ -1,18 +1,26 @@ addCompilerPass(new RemoteCalendarCompilerPass()); + } } diff --git a/src/Bundle/ChillCalendarBundle/Command/AzureGrantAdminConsentAndAcquireToken.php b/src/Bundle/ChillCalendarBundle/Command/AzureGrantAdminConsentAndAcquireToken.php new file mode 100644 index 000000000..400619990 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Command/AzureGrantAdminConsentAndAcquireToken.php @@ -0,0 +1,84 @@ +azure = $azure; + $this->clientRegistry = $clientRegistry; + $this->machineTokenStorage = $machineTokenStorage; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + /** @var FormatterHelper $formatter */ + $formatter = $this->getHelper('formatter'); + $this->azure->scope = ['https://graph.microsoft.com/.default']; + $authorizationUrl = explode('?', $this->azure->getAuthorizationUrl(['prompt' => 'admin_consent'])); + + // replace the first part by the admin consent authorization url + $authorizationUrl[0] = strtr('https://login.microsoftonline.com/{tenant}/adminconsent', ['{tenant}' => $this->azure->tenant]); + + $output->writeln('Go to the url'); + $output->writeln(implode('?', $authorizationUrl)); + $output->writeln('Authenticate as admin, and grant admin consent'); + + // not necessary ? + $helper = $this->getHelper('question'); + $question = new ConfirmationQuestion('Access granted ?'); + + if (!$helper->ask($input, $output, $question)) { + $messages = ['No problem, we will wait for you', 'Grant access and come back here']; + $output->writeln($formatter->formatBlock($messages, 'warning')); + + return 0; + } + + $token = $this->machineTokenStorage->getToken(); + + $messages = ['Token acquired!', 'We could acquire a machine token successfully']; + $output->writeln($formatter->formatBlock($messages, 'success')); + + $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())); + + return 0; + } +} diff --git a/src/Bundle/ChillCalendarBundle/Command/MapAndSubscribeUserCalendarCommand.php b/src/Bundle/ChillCalendarBundle/Command/MapAndSubscribeUserCalendarCommand.php new file mode 100644 index 000000000..b90fb83d4 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Command/MapAndSubscribeUserCalendarCommand.php @@ -0,0 +1,171 @@ +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(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); + $created = 0; + $renewed = 0; + + $this->logger->info(self::class . ' the number of user to get - renew', [ + 'total' => $total, + 'expiration' => $expiration->format(DateTimeImmutable::ATOM), + ]); + + while ($offset < $total) { + $users = $this->userRepository->findByMostOldSubscriptionOrWithoutSubscriptionOrData( + $interval, + $limit, + $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(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(), + ]); + } + } + } + + ++$offset; + } + + $this->em->flush(); + $this->em->clear(); + } + + $this->logger->warning(self::class . ' process executed', [ + 'created' => $created, + 'renewed' => $renewed, + ]); + + return 0; + } + + protected function configure() + { + parent::configure(); + + $this + ->setDescription('MSGraph: collect user metadata and create subscription on events for users') + ->addOption( + 'renew-before-end-interval', + 'r', + InputOption::VALUE_OPTIONAL, + 'delay before renewing subscription', + 'P1D' + ) + ->addOption( + 'subscription-duration', + 's', + InputOption::VALUE_OPTIONAL, + 'duration for the subscription', + 'PT4230M' + ); + } +} diff --git a/src/Bundle/ChillCalendarBundle/Command/SendShortMessageOnEligibleCalendar.php b/src/Bundle/ChillCalendarBundle/Command/SendShortMessageOnEligibleCalendar.php new file mode 100644 index 000000000..6625a7adb --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Command/SendShortMessageOnEligibleCalendar.php @@ -0,0 +1,48 @@ +messageSender = $messageSender; + } + + public function getName() + { + return 'chill:calendar:send-short-messages'; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $this->messageSender->sendBulkMessageToEligibleCalendars(); + + return 0; + } +} diff --git a/src/Bundle/ChillCalendarBundle/Command/SendTestShortMessageOnCalendarCommand.php b/src/Bundle/ChillCalendarBundle/Command/SendTestShortMessageOnCalendarCommand.php new file mode 100644 index 000000000..5891a7be8 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Command/SendTestShortMessageOnCalendarCommand.php @@ -0,0 +1,207 @@ +personRepository = $personRepository; + $this->phoneNumberUtil = $phoneNumberUtil; + $this->phoneNumberHelper = $phoneNumberHelper; + $this->messageForCalendarBuilder = $messageForCalendarBuilder; + $this->transporter = $transporter; + $this->userRepository = $userRepository; + } + + public function getName() + { + return 'chill:calendar:test-send-short-message'; + } + + protected function configure() + { + $this->setDescription('Test sending a SMS for a dummy calendar appointment'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $calendar = new Calendar(); + $calendar->setSendSMS(true); + + /** @var QuestionHelper $helper */ + $helper = $this->getHelper('question'); + + // start date + $question = new Question('When will start the appointment ? (default: "1 hour") ', '1 hour'); + $startDate = new DateTimeImmutable($helper->ask($input, $output, $question)); + + if (false === $startDate) { + 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)); + + if (false === $interval) { + throw new UnexpectedValueException('could not create the interval'); + } + + $calendar->setEndDate($calendar->getStartDate()->add($interval)); + + // a person + $question = new Question('Who will participate ? Give an id for a person. '); + $question + ->setValidator(function ($answer): Person { + if (!is_numeric($answer)) { + throw new UnexpectedValueException('the answer must be numeric'); + } + + if (0 >= (int) $answer) { + 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'); + } + + return $person; + }); + + $person = $helper->ask($input, $output, $question); + $calendar->addPerson($person); + + // a main user + $question = new Question('Who will be the main user ? Give an id for a user. '); + $question + ->setValidator(function ($answer): User { + if (!is_numeric($answer)) { + throw new UnexpectedValueException('the answer must be numeric'); + } + + if (0 >= (int) $answer) { + 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'); + } + + return $user; + }); + + $user = $helper->ask($input, $output, $question); + $calendar->setMainUser($user); + + // phonenumber + $phonenumberFormatted = null !== $person->getMobilenumber() ? + $this->phoneNumberUtil->format($person->getMobilenumber(), PhoneNumberFormat::E164) : ''; + $question = new Question( + sprintf('To which number are we going to send this fake message ? (default to: %s)', $phonenumberFormatted), + $phonenumberFormatted + ); + + $question->setNormalizer(function ($answer): PhoneNumber { + if (null === $answer) { + 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'); + } + + return $phone; + }); + + $phone = $helper->ask($input, $output, $question); + + $question = new ConfirmationQuestion('really send the message to the phone ?'); + $reallySend = (bool) $helper->ask($input, $output, $question); + + $messages = $this->messageForCalendarBuilder->buildMessageForCalendar($calendar); + + if (0 === count($messages)) { + $output->writeln('no message to send to this user'); + } + + foreach ($messages as $key => $message) { + $output->writeln("The short message for SMS {$key} will be: "); + $output->writeln($message->getContent()); + $message->setPhoneNumber($phone); + + if ($reallySend) { + $this->transporter->send($message); + } + } + + return 0; + } +} diff --git a/src/Bundle/ChillCalendarBundle/Controller/AdminController.php b/src/Bundle/ChillCalendarBundle/Controller/AdminController.php index 9b9195b82..23f39a295 100644 --- a/src/Bundle/ChillCalendarBundle/Controller/AdminController.php +++ b/src/Bundle/ChillCalendarBundle/Controller/AdminController.php @@ -1,14 +1,14 @@ calendarRepository = $calendarRepository; + } + + /** + * @Route("/api/1.0/calendar/calendar/by-user/{id}.{_format}", + * name="chill_api_single_calendar_list_by-user", + * requirements={"_format": "json"} + * ) + */ + public function listByUser(User $user, Request $request, string $_format): JsonResponse + { + $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, + $request->query->get('dateFrom') + )) { + throw new BadRequestHttpException('dateFrom not parsable'); + } + + if (!$request->query->has('dateTo')) { + throw new BadRequestHttpException('You must provide a dateTo parameter'); + } + + if (false === $dateTo = DateTimeImmutable::createFromFormat( + DateTimeImmutable::ATOM, + $request->query->get('dateTo') + )) { + throw new BadRequestHttpException('dateTo not parsable'); + } + + $total = $this->calendarRepository->countByUser($user, $dateFrom, $dateTo); + $paginator = $this->getPaginatorFactory()->create($total); + $ranges = $this->calendarRepository->findByUser( + $user, + $dateFrom, + $dateTo, + $paginator->getItemsPerPage(), + $paginator->getCurrentPageFirstItemNumber() + ); + + $collection = new Collection($ranges, $paginator); + + return $this->json($collection, Response::HTTP_OK, [], ['groups' => ['calendar:light']]); + } + protected function customizeQuery(string $action, Request $request, $qb): void { if ($request->query->has('main_user')) { diff --git a/src/Bundle/ChillCalendarBundle/Controller/CalendarController.php b/src/Bundle/ChillCalendarBundle/Controller/CalendarController.php index ff34aaf45..eed693451 100644 --- a/src/Bundle/ChillCalendarBundle/Controller/CalendarController.php +++ b/src/Bundle/ChillCalendarBundle/Controller/CalendarController.php @@ -1,65 +1,104 @@ eventDispatcher = $eventDispatcher; - $this->authorizationHelper = $authorizationHelper; + $this->calendarACLAwareRepository = $calendarACLAwareRepository; + $this->docGeneratorTemplateRepository = $docGeneratorTemplateRepository; + $this->filterOrderHelperFactory = $filterOrderHelperFactory; $this->logger = $logger; - $this->serializer = $serializer; $this->paginator = $paginator; - $this->calendarRepository = $calendarRepository; + $this->remoteCalendarConnector = $remoteCalendarConnector; + $this->serializer = $serializer; + $this->translatableStringHelper = $translatableStringHelper; + $this->personRepository = $personRepository; + $this->accompanyingPeriodRepository = $accompanyingPeriodRepository; + $this->userRepository = $userRepository; + $this->translator = $translator; } /** @@ -67,27 +106,23 @@ class CalendarController extends AbstractController * * @Route("/{_locale}/calendar/{id}/delete", name="chill_calendar_calendar_delete") */ - public function deleteAction(Request $request, int $id) + public function deleteAction(Request $request, Calendar $entity) { - $view = null; $em = $this->getDoctrine()->getManager(); - [$user, $accompanyingPeriod] = $this->getEntity($request); + [$person, $accompanyingPeriod] = [$entity->getPerson(), $entity->getAccompanyingPeriod()]; if ($accompanyingPeriod instanceof AccompanyingPeriod) { $view = '@ChillCalendar/Calendar/confirm_deleteByAccompanyingCourse.html.twig'; - } elseif ($user instanceof User) { - $view = '@ChillCalendar/Calendar/confirm_deleteByUser.html.twig'; + $redirectRoute = $this->generateUrl('chill_calendar_calendar_list_by_period', ['id' => $accompanyingPeriod->getId()]); + } elseif ($person instanceof Person) { + $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'); } - /** @var Calendar $entity */ - $entity = $em->getRepository(\Chill\CalendarBundle\Entity\Calendar::class)->find($id); - - if (!$entity) { - throw $this->createNotFoundException('Unable to find Calendar entity.'); - } - - $form = $this->createDeleteForm($id, $user, $accompanyingPeriod); + $form = $this->createDeleteForm($entity); if ($request->getMethod() === Request::METHOD_DELETE) { $form->handleRequest($request); @@ -101,23 +136,18 @@ class CalendarController extends AbstractController $em->remove($entity); $em->flush(); - $this->addFlash('success', $this->get('translator') + $this->addFlash('success', $this->translator ->trans('The calendar item has been successfully removed.')); - $params = $this->buildParamsToUrl($user, $accompanyingPeriod); - - return $this->redirectToRoute('chill_calendar_calendar_list', $params); + return new RedirectResponse($redirectRoute); } } - if (null === $view) { - throw $this->createNotFoundException('Template not found'); - } - return $this->render($view, [ 'calendar' => $entity, 'delete_form' => $form->createView(), 'accompanyingCourse' => $accompanyingPeriod, + 'person' => $person, ]); } @@ -126,48 +156,67 @@ class CalendarController extends AbstractController * * @Route("/{_locale}/calendar/calendar/{id}/edit", name="chill_calendar_calendar_edit") */ - public function editAction(int $id, Request $request): Response + public function editAction(Calendar $entity, Request $request): Response { - $view = null; + $this->denyAccessUnlessGranted(CalendarVoter::EDIT, $entity); + + if (!$this->remoteCalendarConnector->isReady()) { + return $this->remoteCalendarConnector->getMakeReadyResponse($request->getUri()); + } + $em = $this->getDoctrine()->getManager(); - [$user, $accompanyingPeriod] = $this->getEntity($request); + [$person, $accompanyingPeriod] = [$entity->getPerson(), $entity->getAccompanyingPeriod()]; if ($accompanyingPeriod instanceof AccompanyingPeriod) { $view = '@ChillCalendar/Calendar/editByAccompanyingCourse.html.twig'; - } elseif ($user instanceof User) { - $view = '@ChillCalendar/Calendar/editByUser.html.twig'; + $redirectRoute = $this->generateUrl('chill_calendar_calendar_list_by_period', ['id' => $accompanyingPeriod->getId()]); + } elseif ($person instanceof Person) { + $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'); } - $entity = $em->getRepository(\Chill\CalendarBundle\Entity\Calendar::class)->find($id); + $form = $this->createForm(CalendarType::class, $entity) + ->add('save', SubmitType::class); - if (!$entity) { - throw $this->createNotFoundException('Unable to find Calendar entity.'); + $form->add('save_and_upload_doc', SubmitType::class); + $templates = $this->docGeneratorTemplateRepository->findByEntity(Calendar::class); + + foreach ($templates as $template) { + $form->add('save_and_generate_doc_' . $template->getId(), SubmitType::class, [ + 'label' => $this->translatableStringHelper->localize($template->getName()), + ]); } - $form = $this->createForm(CalendarType::class, $entity, [ - 'accompanyingPeriod' => $accompanyingPeriod, - ])->handleRequest($request); + $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { - $em->persist($entity); $em->flush(); - $this->addFlash('success', $this->get('translator')->trans('Success : calendar item updated!')); + $this->addFlash('success', $this->translator->trans('Success : calendar item updated!')); - $params = $this->buildParamsToUrl($user, $accompanyingPeriod); + if ($form->get('save_and_upload_doc')->isClicked()) { + return $this->redirectToRoute('chill_calendar_calendardoc_new', ['id' => $entity->getId()]); + } - return $this->redirectToRoute('chill_calendar_calendar_list', $params); + foreach ($templates as $template) { + if ($form->get('save_and_generate_doc_' . $template->getId())->isClicked()) { + return $this->redirectToRoute('chill_docgenerator_generate_from_template', [ + 'entityClassName' => Calendar::class, + 'entityId' => $entity->getId(), + 'template' => $template->getId(), + 'returnPath' => $request->getRequestUri(), + ]); + } + } + + return new RedirectResponse($redirectRoute); } if ($form->isSubmitted() && !$form->isValid()) { - $this->addFlash('error', $this->get('translator')->trans('This form contains errors')); - } - - $deleteForm = $this->createDeleteForm($id, $user, $accompanyingPeriod); - - if (null === $view) { - throw $this->createNotFoundException('Template not found'); + $this->addFlash('error', $this->translator->trans('This form contains errors')); } $entity_array = $this->serializer->normalize($entity, 'json', ['groups' => 'read']); @@ -175,55 +224,101 @@ class CalendarController extends AbstractController return $this->render($view, [ 'entity' => $entity, 'form' => $form->createView(), - 'delete_form' => $deleteForm->createView(), - 'accompanyingCourse' => $accompanyingPeriod, - 'user' => $user, + 'accompanyingCourse' => $entity->getAccompanyingPeriod(), + 'person' => $entity->getPerson(), 'entity_json' => $entity_array, + 'templates' => $templates, ]); } /** * Lists all Calendar entities. * - * @Route("/{_locale}/calendar/calendar/", name="chill_calendar_calendar_list") + * @Route("/{_locale}/calendar/calendar/by-period/{id}", name="chill_calendar_calendar_list_by_period") */ - public function listAction(Request $request): Response + public function listActionByCourse(AccompanyingPeriod $accompanyingPeriod): Response { - $view = null; + $this->denyAccessUnlessGranted(CalendarVoter::SEE, $accompanyingPeriod); - [$user, $accompanyingPeriod] = $this->getEntity($request); + $filterOrder = $this->buildListFilterOrder(); + ['from' => $from, 'to' => $to] = $filterOrder->getDateRangeData('startDate'); - if ($user instanceof User) { - $calendarItems = $this->calendarRepository->findByUser($user); + $total = $this->calendarACLAwareRepository + ->countByAccompanyingPeriod($accompanyingPeriod, $from, $to); + $paginator = $this->paginator->create($total); + $calendarItems = $this->calendarACLAwareRepository->findByAccompanyingPeriod( + $accompanyingPeriod, + $from, + $to, + ['startDate' => 'DESC'], + $paginator->getCurrentPageFirstItemNumber(), + $paginator->getItemsPerPage() + ); - $view = '@ChillCalendar/Calendar/listByUser.html.twig'; + return $this->render('@ChillCalendar/Calendar/listByAccompanyingCourse.html.twig', [ + 'calendarItems' => $calendarItems, + 'accompanyingCourse' => $accompanyingPeriod, + 'paginator' => $paginator, + 'filterOrder' => $filterOrder, + 'nbIgnored' => $this->calendarACLAwareRepository->countIgnoredByAccompanyingPeriod($accompanyingPeriod, $from, $to), + 'templates' => $this->docGeneratorTemplateRepository->findByEntity(Calendar::class), + ]); + } - return $this->render($view, [ - 'calendarItems' => $calendarItems, - 'user' => $user, - ]); + /** + * Lists all Calendar entities on a person. + * + * @Route("/{_locale}/calendar/calendar/by-person/{id}", name="chill_calendar_calendar_list_by_person") + */ + public function listActionByPerson(Person $person): Response + { + $this->denyAccessUnlessGranted(CalendarVoter::SEE, $person); + + $filterOrder = $this->buildListFilterOrder(); + ['from' => $from, 'to' => $to] = $filterOrder->getDateRangeData('startDate'); + + $total = $this->calendarACLAwareRepository + ->countByPerson($person, $from, $to); + $paginator = $this->paginator->create($total); + $calendarItems = $this->calendarACLAwareRepository->findByPerson( + $person, + $from, + $to, + ['startDate' => 'DESC'], + $paginator->getCurrentPageFirstItemNumber(), + $paginator->getItemsPerPage() + ); + + return $this->render('@ChillCalendar/Calendar/listByPerson.html.twig', [ + 'calendarItems' => $calendarItems, + 'person' => $person, + 'paginator' => $paginator, + 'filterOrder' => $filterOrder, + 'nbIgnored' => $this->calendarACLAwareRepository->countIgnoredByPerson($person, $from, $to), + 'templates' => $this->docGeneratorTemplateRepository->findByEntity(Calendar::class), + ]); + } + + /** + * @Route("/{_locale}/calendar/calendar/my", name="chill_calendar_calendar_list_my") + */ + public function myCalendar(Request $request): Response + { + $this->denyAccessUnlessGranted('ROLE_USER'); + + if (!$this->remoteCalendarConnector->isReady()) { + return $this->remoteCalendarConnector->getMakeReadyResponse($request->getUri()); } - if ($accompanyingPeriod instanceof AccompanyingPeriod) { - $total = $this->calendarRepository->countByAccompanyingPeriod($accompanyingPeriod); - $paginator = $this->paginator->create($total); - $calendarItems = $this->calendarRepository->findBy( - ['accompanyingPeriod' => $accompanyingPeriod], - ['startDate' => 'DESC'], - $paginator->getItemsPerPage(), - $paginator->getCurrentPageFirstItemNumber() - ); - - $view = '@ChillCalendar/Calendar/listByAccompanyingCourse.html.twig'; - - return $this->render($view, [ - 'calendarItems' => $calendarItems, - 'accompanyingCourse' => $accompanyingPeriod, - 'paginator' => $paginator, - ]); + if (!$this->getUser() instanceof User) { + throw new UnauthorizedHttpException('you are not an user'); } - throw new Exception('Unable to list actions.'); + $view = '@ChillCalendar/Calendar/listByUser.html.twig'; + + return $this->render($view, [ + 'user' => $this->getUser(), + ]); } /** @@ -233,47 +328,81 @@ class CalendarController extends AbstractController */ public function newAction(Request $request): Response { + if (!$this->remoteCalendarConnector->isReady()) { + return $this->remoteCalendarConnector->getMakeReadyResponse($request->getUri()); + } + $view = null; $em = $this->getDoctrine()->getManager(); - [$user, $accompanyingPeriod] = $this->getEntity($request); + [$person, $accompanyingPeriod] = $this->getEntity($request); + + $entity = new Calendar(); + + $redirectRoute = ''; if ($accompanyingPeriod instanceof AccompanyingPeriod) { $view = '@ChillCalendar/Calendar/newByAccompanyingCourse.html.twig'; - } - // elseif ($user instanceof User) { - // $view = '@ChillCalendar/Calendar/newUser.html.twig'; - // } - - $entity = new Calendar(); - $entity->setUser($this->getUser()); - $entity->setStatus($entity::STATUS_VALID); - - // if ($user instanceof User) { - // $entity->setPerson($user); - // } - - if ($accompanyingPeriod instanceof AccompanyingPeriod) { $entity->setAccompanyingPeriod($accompanyingPeriod); + $redirectRoute = $this->generateUrl('chill_calendar_calendar_list_by_period', ['id' => $accompanyingPeriod->getId()]); + } elseif (null !== $person) { + $view = '@ChillCalendar/Calendar/newByPerson.html.twig'; + $entity->setPerson($person)->addPerson($person); + $redirectRoute = $this->generateUrl('chill_calendar_calendar_list_by_person', ['id' => $person->getId()]); } - $form = $this->createForm(CalendarType::class, $entity, [ - 'accompanyingPeriod' => $accompanyingPeriod, - ])->handleRequest($request); + if ($request->query->has('mainUser')) { + $entity->setMainUser($this->userRepository->find($request->query->getInt('mainUser'))); + } + + $this->denyAccessUnlessGranted(CalendarVoter::CREATE, $entity); + + $form = $this->createForm(CalendarType::class, $entity) + ->add('save', SubmitType::class); + + $templates = $this->docGeneratorTemplateRepository->findByEntity(Calendar::class); + $form->add('save_and_upload_doc', SubmitType::class); + + foreach ($templates as $template) { + $form->add('save_and_generate_doc_' . $template->getId(), SubmitType::class, [ + 'label' => $this->translatableStringHelper->localize($template->getName()), + ]); + } + + $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { $em->persist($entity); $em->flush(); - $this->addFlash('success', $this->get('translator')->trans('Success : calendar item created!')); + $this->addFlash('success', $this->translator->trans('Success : calendar item created!')); - $params = $this->buildParamsToUrl($user, $accompanyingPeriod); + if ($form->get('save_and_upload_doc')->isClicked()) { + return $this->redirectToRoute('chill_calendar_calendardoc_new', [ + 'id' => $entity->getId() + ]); + } - return $this->redirectToRoute('chill_calendar_calendar_list', $params); + foreach ($templates as $template) { + if ($form->get('save_and_generate_doc_' . $template->getId())->isClicked()) { + return $this->redirectToRoute('chill_docgenerator_generate_from_template', [ + 'entityClassName' => Calendar::class, + 'entityId' => $entity->getId(), + 'template' => $template->getId(), + 'returnPath' => $this->generateUrl('chill_calendar_calendar_edit', ['id' => $entity->getId()]), + ]); + } + } + + if ('' !== $redirectRoute) { + return new RedirectResponse($redirectRoute); + } + + throw new UnexpectedValueException('No person id or accompanying period id was given'); } if ($form->isSubmitted() && !$form->isValid()) { - $this->addFlash('error', $this->get('translator')->trans('This form contains errors')); + $this->addFlash('error', $this->translator->trans('This form contains errors')); } if (null === $view) { @@ -283,11 +412,13 @@ class CalendarController extends AbstractController $entity_array = $this->serializer->normalize($entity, 'json', ['groups' => 'read']); return $this->render($view, [ - 'user' => $user, + 'context' => $entity->getContext(), + 'person' => $person, 'accompanyingCourse' => $accompanyingPeriod, 'entity' => $entity, 'form' => $form->createView(), 'entity_json' => $entity_array, + 'templates' => $templates, ]); } @@ -298,6 +429,7 @@ class CalendarController extends AbstractController */ public function showAction(Request $request, int $id): Response { + throw new Exception('not implemented'); $view = null; $em = $this->getDoctrine()->getManager(); @@ -349,7 +481,7 @@ class CalendarController extends AbstractController 'professionalsId' => $professionalsId, 'date' => $entity->getStartDate()->format('Y-m-d'), 'durationTime' => $durationTimeInMinutes, - 'location' => $entity->getLocation()->getId(), + 'location' => $entity->getLocation() ? $entity->getLocation()->getId() : null, 'comment' => $entity->getComment()->getComment(), ]; @@ -362,67 +494,108 @@ class CalendarController extends AbstractController ]); } - private function buildParamsToUrl(?User $user, ?AccompanyingPeriod $accompanyingPeriod): array + /** + * @Route("/{_locale}/calendar/calendar/{id}/to-activity", name="chill_calendar_calendar_to_activity") + */ + public function toActivity(Request $request, Calendar $calendar): RedirectResponse { - $params = []; + $this->denyAccessUnlessGranted(CalendarVoter::SEE, $calendar); - if (null !== $user) { - $params['user_id'] = $user->getId(); + $personsId = array_map( + static fn (Person $p): int => $p->getId(), + $calendar->getPersons()->toArray() + ); + + $professionalsId = array_map( + static fn (ThirdParty $thirdParty): ?int => $thirdParty->getId(), + $calendar->getProfessionals()->toArray() + ); + + $usersId = array_map( + static fn (User $user): ?int => $user->getId(), + array_merge($calendar->getUsers()->toArray(), [$calendar->getMainUser()]) + ); + + $durationTime = $calendar->getEndDate()->diff($calendar->getStartDate()); + $durationTimeInMinutes = $durationTime->days * 1440 + $durationTime->h * 60 + $durationTime->i; + + $activityData = [ + 'calendarId' => $calendar->getId(), + 'personsId' => $personsId, + 'professionalsId' => $professionalsId, + 'usersId' => $usersId, + 'date' => $calendar->getStartDate()->format('Y-m-d'), + 'durationTime' => $durationTimeInMinutes, + 'location' => $calendar->getLocation() ? $calendar->getLocation()->getId() : null, + 'comment' => $calendar->getComment()->getComment(), + ]; + + $routeParams = [ + 'activityData' => $activityData, + 'returnPath' => $request->query->get('returnPath', null), + ]; + + if ($calendar->getContext() === 'accompanying_period') { + $routeParams['accompanying_period_id'] = $calendar->getAccompanyingPeriod()->getId(); + } elseif ($calendar->getContext() === 'person') { + $routeParams['person_id'] = $calendar->getPerson()->getId(); + } else { + throw new RuntimeException('context not found for this calendar'); } - if (null !== $accompanyingPeriod) { - $params['accompanying_period_id'] = $accompanyingPeriod->getId(); - } + return $this->redirectToRoute('chill_activity_activity_new', $routeParams); + } - return $params; + private function buildListFilterOrder(): FilterOrderHelper + { + $filterOrder = $this->filterOrderHelperFactory->create(self::class); + $filterOrder->addDateRange('startDate', null, new DateTimeImmutable('3 days ago'), null); + + return $filterOrder->build(); } /** * Creates a form to delete a Calendar entity by id. */ - private function createDeleteForm(int $id, ?User $user, ?AccompanyingPeriod $accompanyingPeriod): FormInterface + private function createDeleteForm(Calendar $calendar): FormInterface { - $params = $this->buildParamsToUrl($user, $accompanyingPeriod); - $params['id'] = $id; - return $this->createFormBuilder() - ->setAction($this->generateUrl('chill_calendar_calendar_delete', $params)) + ->setAction($this->generateUrl('chill_calendar_calendar_delete', ['id' => $calendar->getId()])) ->setMethod('DELETE') ->add('submit', SubmitType::class, ['label' => 'Delete']) ->getForm(); } + /** + * @return array{0: ?Person, 1: ?AccompanyingPeriod} + */ private function getEntity(Request $request): array { $em = $this->getDoctrine()->getManager(); - $user = $accompanyingPeriod = null; + $person = $accompanyingPeriod = null; - if ($request->query->has('user_id')) { - $user_id = $request->get('user_id'); - $user = $em->getRepository(User::class)->find($user_id); + if ($request->query->has('person_id')) { + $person = $this->personRepository->find($request->query->getInt('person_id')); - if (null === $user) { - throw $this->createNotFoundException('User not found'); + if (null === $person) { + throw $this->createNotFoundException('Person not found'); } - // TODO Add permission - // $this->denyAccessUnlessGranted('CHILL_PERSON_SEE', $user); + $this->denyAccessUnlessGranted(PersonVoter::SEE, $person); } elseif ($request->query->has('accompanying_period_id')) { - $accompanying_period_id = $request->get('accompanying_period_id'); - $accompanyingPeriod = $em->getRepository(AccompanyingPeriod::class)->find($accompanying_period_id); + $accompanyingPeriod = $this->accompanyingPeriodRepository->find($request->query->getInt('accompanying_period_id')); if (null === $accompanyingPeriod) { throw $this->createNotFoundException('Accompanying Period not found'); } - // TODO Add permission - // $this->denyAccessUnlessGranted('CHILL_PERSON_SEE', $person); + $this->denyAccessUnlessGranted(AccompanyingPeriodVoter::SEE, $accompanyingPeriod); } else { throw $this->createNotFoundException('Person or Accompanying Period not found'); } return [ - $user, $accompanyingPeriod, + $person, $accompanyingPeriod, ]; } } diff --git a/src/Bundle/ChillCalendarBundle/Controller/CalendarDocController.php b/src/Bundle/ChillCalendarBundle/Controller/CalendarDocController.php new file mode 100644 index 000000000..d02e55f32 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Controller/CalendarDocController.php @@ -0,0 +1,240 @@ +docGeneratorTemplateRepository = $docGeneratorTemplateRepository; + $this->engine = $engine; + $this->entityManager = $entityManager; + $this->formFactory = $formFactory; + $this->security = $security; + $this->urlGenerator = $urlGenerator; + } + + /** + * @Route("/{_locale}/calendar/calendar-doc/{id}/new", name="chill_calendar_calendardoc_new") + */ + public function create(Calendar $calendar, Request $request): Response + { + $calendarDoc = (new CalendarDoc($calendar, null))->setCalendar($calendar); + + if (!$this->security->isGranted(CalendarDocVoter::EDIT, $calendarDoc)) { + throw new AccessDeniedHttpException(); + } + + // set variables + switch ($calendarDoc->getCalendar()->getContext()) { + case 'accompanying_period': + $view = '@ChillCalendar/CalendarDoc/new_accompanying_period.html.twig'; + $returnRoute = 'chill_calendar_calendar_list_by_period'; + $returnParams = ['id' => $calendarDoc->getCalendar()->getAccompanyingPeriod()->getId()]; + + break; + + case 'person': + $view = '@ChillCalendar/CalendarDoc/new_person.html.twig'; + $returnRoute = 'chill_calendar_calendar_list_by_person'; + $returnParams = ['id' => $calendarDoc->getCalendar()->getPerson()->getId()]; + + break; + + default: + throw new UnexpectedValueException('Unsupported context'); + } + + $calendarDocDTO = new CalendarDoc\CalendarDocCreateDTO(); + $form = $this->formFactory->create(CalendarDocCreateType::class, $calendarDocDTO); + + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $calendarDoc->createFromDTO($calendarDocDTO); + + $this->entityManager->persist($calendarDoc); + $this->entityManager->flush(); + + if ($request->query->has('returnPath')) { + return new RedirectResponse($request->query->get('returnPath')); + } + + return new RedirectResponse( + $this->urlGenerator->generate($returnRoute, $returnParams) + ); + } + + return new Response( + $this->engine->render( + $view, + ['calendar_doc' => $calendarDoc, 'form' => $form->createView()] + ) + ); + } + + /** + * @Route("/{_locale}/calendar/calendar-doc/{id}/delete", name="chill_calendar_calendardoc_delete") + */ + public function delete(CalendarDoc $calendarDoc, Request $request): Response + { + if (!$this->security->isGranted(CalendarDocVoter::EDIT, $calendarDoc)) { + throw new AccessDeniedHttpException('Not authorized to delete document'); + } + + switch ($calendarDoc->getCalendar()->getContext()) { + case 'accompanying_period': + $view = '@ChillCalendar/CalendarDoc/delete_accompanying_period.html.twig'; + $returnRoute = 'chill_calendar_calendar_list_by_period'; + $returnParams = ['id' => $calendarDoc->getCalendar()->getAccompanyingPeriod()->getId()]; + + break; + + case 'person': + $view = '@ChillCalendar/CalendarDoc/delete_person.html.twig'; + $returnRoute = 'chill_calendar_calendar_list_by_person'; + $returnParams = ['id' => $calendarDoc->getCalendar()->getPerson()->getId()]; + + break; + + default: + throw new \LogicException(sprintf("This context '%s' is not supported", $calendarDoc->getCalendar()->getContext())); + } + + $form = $this->formFactory->createBuilder() + ->add('submit', SubmitType::class, [ + 'label' => 'Delete', + ]) + ->getForm(); + + $form->handleRequest($request); + + if ($form->isSubmitted()) { + $this->entityManager->remove($calendarDoc); + $this->entityManager->flush(); + + if ($request->query->has('returnPath')) { + return new RedirectResponse($request->query->get('returnPath')); + } + + return new RedirectResponse( + $this->urlGenerator->generate($returnRoute, $returnParams) + ); + } + + return new Response( + $this->engine->render( + $view, + [ + 'calendar_doc' => $calendarDoc, + 'form' => $form->createView(), + ] + ) + ); + } + + /** + * @Route("/{_locale}/calendar/calendar-doc/{id}/edit", name="chill_calendar_calendardoc_edit") + */ + public function edit(CalendarDoc $calendarDoc, Request $request): Response + { + if (!$this->security->isGranted(CalendarDocVoter::EDIT, $calendarDoc)) { + throw new AccessDeniedHttpException(); + } + + // set variables + switch ($calendarDoc->getCalendar()->getContext()) { + case 'accompanying_period': + $view = '@ChillCalendar/CalendarDoc/edit_accompanying_period.html.twig'; + $returnRoute = 'chill_calendar_calendar_list_by_period'; + $returnParams = ['id' => $calendarDoc->getCalendar()->getAccompanyingPeriod()->getId()]; + + break; + + case 'person': + $view = '@ChillCalendar/CalendarDoc/edit_person.html.twig'; + $returnRoute = 'chill_calendar_calendar_list_by_person'; + $returnParams = ['id' => $calendarDoc->getCalendar()->getPerson()->getId()]; + + break; + + default: + throw new UnexpectedValueException('Unsupported context'); + } + + $calendarDocEditDTO = new CalendarDoc\CalendarDocEditDTO($calendarDoc); + $form = $this->formFactory->create(CalendarDocEditType::class, $calendarDocEditDTO); + + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $calendarDoc->editFromDTO($calendarDocEditDTO); + + $this->entityManager->flush(); + + if ($request->query->has('returnPath')) { + return new RedirectResponse($request->query->get('returnPath')); + } + + return new RedirectResponse( + $this->urlGenerator->generate($returnRoute, $returnParams) + ); + } + + return new Response( + $this->engine->render( + $view, + ['calendar_doc' => $calendarDoc, 'form' => $form->createView()] + ) + ); + } +} diff --git a/src/Bundle/ChillCalendarBundle/Controller/CalendarRangeAPIController.php b/src/Bundle/ChillCalendarBundle/Controller/CalendarRangeAPIController.php index 018d23426..383e24efc 100644 --- a/src/Bundle/ChillCalendarBundle/Controller/CalendarRangeAPIController.php +++ b/src/Bundle/ChillCalendarBundle/Controller/CalendarRangeAPIController.php @@ -1,48 +1,82 @@ getDoctrine()->getManager(); + $this->calendarRangeRepository = $calendarRangeRepository; + } - $sql = 'SELECT c FROM ChillCalendarBundle:CalendarRange c - WHERE NOT EXISTS (SELECT cal.id FROM ChillCalendarBundle:Calendar cal WHERE cal.calendarRange = c.id)'; + /** + * @Route("/api/1.0/calendar/calendar-range-available/{id}.{_format}", + * name="chill_api_single_calendar_range_available", + * requirements={"_format": "json"} + * ) + */ + public function availableRanges(User $user, Request $request, string $_format): JsonResponse + { + //return new JsonResponse(['ok' => true], 200, [], false); + $this->denyAccessUnlessGranted('ROLE_USER'); - if ($request->query->has('user')) { - $user = $request->query->get('user'); - $sql = $sql . ' AND c.user = :user'; - $query = $em->createQuery($sql) - ->setParameter('user', $user); - } else { - $query = $em->createQuery($sql); + if (!$request->query->has('dateFrom')) { + throw new BadRequestHttpException('You must provide a dateFrom parameter'); } - $results = $query->getResult(); + if (false === $dateFrom = DateTimeImmutable::createFromFormat( + DateTimeImmutable::ATOM, + $request->query->get('dateFrom') + )) { + throw new BadRequestHttpException('dateFrom not parsable'); + } - return $this->json(['count' => count($results), 'results' => $results], Response::HTTP_OK, [], ['groups' => ['read']]); - //TODO use also the paginator, eg return $this->serializeCollection('get', $request, $_format, $paginator, $results); + if (!$request->query->has('dateTo')) { + throw new BadRequestHttpException('You must provide a dateTo parameter'); + } + + if (false === $dateTo = DateTimeImmutable::createFromFormat( + DateTimeImmutable::ATOM, + $request->query->get('dateTo') + )) { + throw new BadRequestHttpException('dateTo not parsable'); + } + + $total = $this->calendarRangeRepository->countByAvailableRangesForUser($user, $dateFrom, $dateTo); + $paginator = $this->getPaginatorFactory()->create($total); + $ranges = $this->calendarRangeRepository->findByAvailableRangesForUser( + $user, + $dateFrom, + $dateTo, + $paginator->getItemsPerPage(), + $paginator->getCurrentPageFirstItemNumber() + ); + + $collection = new Collection($ranges, $paginator); + + return $this->json($collection, Response::HTTP_OK, [], ['groups' => ['read']]); } } diff --git a/src/Bundle/ChillCalendarBundle/Controller/CancelReasonController.php b/src/Bundle/ChillCalendarBundle/Controller/CancelReasonController.php index 3be2a8f93..17cbecb02 100644 --- a/src/Bundle/ChillCalendarBundle/Controller/CancelReasonController.php +++ b/src/Bundle/ChillCalendarBundle/Controller/CancelReasonController.php @@ -1,14 +1,14 @@ entityManager = $entityManager; + $this->messageBus = $messageBus; + $this->security = $security; + } + + /** + * Give an answer to a calendar invite. + * + * @Route("/api/1.0/calendar/calendar/{id}/answer/{answer}.json", methods={"post"}) + */ + public function answer(Calendar $calendar, string $answer): Response + { + $user = $this->security->getUser(); + + if (!$user instanceof User) { + throw new AccessDeniedHttpException('not a regular user'); + } + + if (null === $invite = $calendar->getInviteForUser($user)) { + throw new AccessDeniedHttpException('not invited to this calendar'); + } + + if (!$this->security->isGranted(InviteVoter::ANSWER, $invite)) { + throw new AccessDeniedHttpException('not allowed to answer on this invitation'); + } + + if (!in_array($answer, Invite::STATUSES, true)) { + throw new BadRequestHttpException('answer not valid'); + } + + $invite->setStatus($answer); + $this->entityManager->flush(); + + $this->messageBus->dispatch(new InviteUpdateMessage($invite, $this->security->getUser())); + + return new JsonResponse(null, Response::HTTP_ACCEPTED, [], false); + } +} diff --git a/src/Bundle/ChillCalendarBundle/Controller/RemoteCalendarConnectAzureController.php b/src/Bundle/ChillCalendarBundle/Controller/RemoteCalendarConnectAzureController.php new file mode 100644 index 000000000..9352e42fa --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Controller/RemoteCalendarConnectAzureController.php @@ -0,0 +1,76 @@ +clientRegistry = $clientRegistry; + $this->MSGraphTokenStorage = $MSGraphTokenStorage; + } + + /** + * @Route("/{_locale}/connect/azure", name="chill_calendar_remote_connect_azure") + */ + public function connectAzure(Request $request): Response + { + $request->getSession()->set('azure_return_path', $request->query->get('returnPath', '/')); + + return $this->clientRegistry + ->getClient('azure') // key used in config/packages/knpu_oauth2_client.yaml + ->redirect(['https://graph.microsoft.com/.default', 'offline_access'], []); + } + + /** + * @Route("/connect/azure/check", name="chill_calendar_remote_connect_azure_check") + */ + public function connectAzureCheck(Request $request): Response + { + /** @var Azure $client */ + $client = $this->clientRegistry->getClient('azure'); + + try { + /** @var AccessToken $token */ + $token = $client->getAccessToken([]); + + $this->MSGraphTokenStorage->setToken($token); + } catch (IdentityProviderException $e) { + throw $e; + } + + return new RedirectResponse($request->getSession()->remove('azure_return_path', '/')); + } +} diff --git a/src/Bundle/ChillCalendarBundle/Controller/RemoteCalendarMSGraphSyncController.php b/src/Bundle/ChillCalendarBundle/Controller/RemoteCalendarMSGraphSyncController.php new file mode 100644 index 000000000..535dda2f5 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Controller/RemoteCalendarMSGraphSyncController.php @@ -0,0 +1,61 @@ +messageBus = $messageBus; + } + + /** + * @Route("/public/incoming-hook/calendar/msgraph/events/{userId}", name="chill_calendar_remote_msgraph_incoming_webhook_events", + * methods={"POST"}) + */ + public function webhookCalendarReceiver(int $userId, Request $request): Response + { + if ($request->query->has('validationToken')) { + return new Response($request->query->get('validationToken'), Response::HTTP_OK, [ + 'content-type' => 'text/plain', + ]); + } + + try { + $body = json_decode($request->getContent(), true, 512, JSON_THROW_ON_ERROR); + } catch (JsonException $e) { + throw new BadRequestHttpException('could not decode json', $e); + } + + $this->messageBus->dispatch(new MSGraphChangeNotificationMessage($body, $userId)); + + return new Response('', Response::HTTP_ACCEPTED); + } +} diff --git a/src/Bundle/ChillCalendarBundle/Controller/RemoteCalendarProxyController.php b/src/Bundle/ChillCalendarBundle/Controller/RemoteCalendarProxyController.php new file mode 100644 index 000000000..a95aa1a3f --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Controller/RemoteCalendarProxyController.php @@ -0,0 +1,114 @@ +paginatorFactory = $paginatorFactory; + $this->remoteCalendarConnector = $remoteCalendarConnector; + $this->serializer = $serializer; + } + + /** + * @Route("api/1.0/calendar/proxy/calendar/by-user/{id}/events") + */ + public function listEventForCalendar(User $user, Request $request): Response + { + if (!$request->query->has('dateFrom')) { + throw new BadRequestHttpException('You must provide a dateFrom parameter'); + } + + if (false === $dateFrom = DateTimeImmutable::createFromFormat( + DateTimeImmutable::ATOM, + $request->query->get('dateFrom') + )) { + throw new BadRequestHttpException('dateFrom not parsable'); + } + + if (!$request->query->has('dateTo')) { + throw new BadRequestHttpException('You must provide a dateTo parameter'); + } + + if (false === $dateTo = DateTimeImmutable::createFromFormat( + DateTimeImmutable::ATOM, + $request->query->get('dateTo') + )) { + throw new BadRequestHttpException('dateTo not parsable'); + } + + $total = $this->remoteCalendarConnector->countEventsForUser($user, $dateFrom, $dateTo); + $paginator = $this->paginatorFactory->create($total); + + if (0 === $total) { + return new JsonResponse( + $this->serializer->serialize(new Collection([], $paginator), 'json'), + JsonResponse::HTTP_OK, + [], + true + ); + } + + $events = $this->remoteCalendarConnector->listEventsForUser( + $user, + $dateFrom, + $dateTo, + $paginator->getCurrentPageFirstItemNumber(), + $paginator->getItemsPerPage() + ); + + // 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)); + } + + $collection = new Collection($events, $paginator); + + return new JsonResponse( + $this->serializer->serialize($collection, 'json', ['groups' => ['read']]), + JsonResponse::HTTP_OK, + [], + true + ); + } +} diff --git a/src/Bundle/ChillCalendarBundle/DataFixtures/ORM/LoadCalendarRange.php b/src/Bundle/ChillCalendarBundle/DataFixtures/ORM/LoadCalendarRange.php index e708672a9..805386669 100644 --- a/src/Bundle/ChillCalendarBundle/DataFixtures/ORM/LoadCalendarRange.php +++ b/src/Bundle/ChillCalendarBundle/DataFixtures/ORM/LoadCalendarRange.php @@ -1,23 +1,29 @@ userRepository->findAll(); + $location = (new Location()) + ->setAddress($address = new Address()) + ->setName('Centre A') + ->setEmail('centreA@test.chill.social') + ->setLocationType($type = new LocationType()) + ->setPhonenumber1(PhoneNumberUtil::getInstance()->parse('+3287653812')); + $type->setTitle(['fr' => 'Service']); + $address->setStreet('Rue des Épaules')->setStreetNumber('14') + ->setPostcode($postCode = new PostalCode()); + $postCode->setCode('4145')->setName('Houte-Si-Plout')->setCountry( + ($country = new Country())->setName(['fr' => 'Pays'])->setCountryCode('ZZ') + ); + $manager->persist($country); + $manager->persist($postCode); + $manager->persist($address); + $manager->persist($type); + $manager->persist($location); + $days = [ '2021-08-23', '2021-08-24', @@ -58,6 +82,8 @@ 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'), ]; $hours = [ @@ -76,7 +102,8 @@ class LoadCalendarRange extends Fixture implements FixtureGroupInterface, Ordere $calendarRange = (new CalendarRange()) ->setUser($u) ->setStartDate($startEvent) - ->setEndDate($endEvent); + ->setEndDate($endEvent) + ->setLocation($location); $manager->persist($calendarRange); } diff --git a/src/Bundle/ChillCalendarBundle/DataFixtures/ORM/LoadCancelReason.php b/src/Bundle/ChillCalendarBundle/DataFixtures/ORM/LoadCancelReason.php index fca57288e..bc5de7f70 100644 --- a/src/Bundle/ChillCalendarBundle/DataFixtures/ORM/LoadCancelReason.php +++ b/src/Bundle/ChillCalendarBundle/DataFixtures/ORM/LoadCancelReason.php @@ -1,14 +1,14 @@ ['fr' => 'Rendez-vous décliné']], - ['name' => ['fr' => 'Rendez-vous accepté']], + [ + 'name' => ['fr' => 'Rendez-vous décliné'], + 'status' => Invite::DECLINED, + ], + [ + 'name' => ['fr' => 'Rendez-vous accepté'], + 'status' => Invite::ACCEPTED, + ], ]; foreach ($arr as $a) { echo 'Creating calendar invite : ' . $a['name']['fr'] . "\n"; $invite = (new Invite()) - ->setStatus($a['name']); + ->setStatus($a['status']) + ->setUser($this->getRandomUser()); $manager->persist($invite); $reference = 'Invite_' . $a['name']['fr']; $this->addReference($reference, $invite); @@ -49,4 +58,11 @@ class LoadInvite extends Fixture implements FixtureGroupInterface $manager->flush(); } + + private function getRandomUser(): User + { + $userRef = array_rand(LoadUsers::$refs); + + return $this->getReference($userRef); + } } diff --git a/src/Bundle/ChillCalendarBundle/DependencyInjection/ChillCalendarExtension.php b/src/Bundle/ChillCalendarBundle/DependencyInjection/ChillCalendarExtension.php index 165365597..c848366bb 100644 --- a/src/Bundle/ChillCalendarBundle/DependencyInjection/ChillCalendarExtension.php +++ b/src/Bundle/ChillCalendarBundle/DependencyInjection/ChillCalendarExtension.php @@ -1,16 +1,17 @@ load('services.yml'); + $loader->load('services/exports.yaml'); $loader->load('services/controller.yml'); $loader->load('services/fixtures.yml'); $loader->load('services/form.yml'); $loader->load('services/event.yml'); + $loader->load('services/remote_calendar.yaml'); + + $container->setParameter('chill_calendar', $config); + + if ($config['short_messages']['enabled']) { + $container->setParameter('chill_calendar.short_messages', $config['short_messages']); + } else { + $container->setParameter('chill_calendar.short_messages', null); + } } public function prepend(ContainerBuilder $container) { $this->preprendRoutes($container); $this->prependCruds($container); + $this->prependRoleHierarchy($container); } - protected function prependCruds(ContainerBuilder $container) + private function prependCruds(ContainerBuilder $container) { $container->prependExtensionConfig('chill_main', [ 'cruds' => [ @@ -120,7 +132,18 @@ class ChillCalendarExtension extends Extension implements PrependExtensionInterf ]); } - protected function preprendRoutes(ContainerBuilder $container) + private function prependRoleHierarchy(ContainerBuilder $container): void + { + $container->prependExtensionConfig('security', [ + 'role_hierarchy' => [ + CalendarVoter::CREATE => [CalendarVoter::SEE], + CalendarVoter::EDIT => [CalendarVoter::SEE], + CalendarVoter::DELETE => [CalendarVoter::SEE], + ], + ]); + } + + private function preprendRoutes(ContainerBuilder $container) { $container->prependExtensionConfig('chill_main', [ 'routing' => [ diff --git a/src/Bundle/ChillCalendarBundle/DependencyInjection/Configuration.php b/src/Bundle/ChillCalendarBundle/DependencyInjection/Configuration.php index f1e744cb8..a3e4ae391 100644 --- a/src/Bundle/ChillCalendarBundle/DependencyInjection/Configuration.php +++ b/src/Bundle/ChillCalendarBundle/DependencyInjection/Configuration.php @@ -1,14 +1,14 @@ getRootNode('chill_calendar'); + $rootNode = $treeBuilder->getRootNode(); - // Here you should define the parameters that are allowed to - // configure your bundle. See the documentation linked above for - // more information on that topic. + $rootNode + ->children() + ->arrayNode('short_messages') + ->canBeDisabled() + ->children()->end() + ->end() // end for short_messages + ->arrayNode('remote_calendars_sync')->canBeEnabled() + ->children() + ->arrayNode('microsoft_graph')->canBeEnabled() + ->children() + ->end() // end of machine_access_token + ->end() // end of microsoft_graph children + ->end() // end of array microsoft_graph + ->end() // end of children's remote_calendars_sync + ->end() // end of array remote_calendars_sync + ->end(); return $treeBuilder; } diff --git a/src/Bundle/ChillCalendarBundle/Entity/Calendar.php b/src/Bundle/ChillCalendarBundle/Entity/Calendar.php index c40bddb44..79219a6e0 100644 --- a/src/Bundle/ChillCalendarBundle/Entity/Calendar.php +++ b/src/Bundle/ChillCalendarBundle/Entity/Calendar.php @@ -1,54 +1,101 @@ ''"})} + * ) + * @ORM\Entity + * @Serializer\DiscriminatorMap(typeProperty="type", mapping={ + * "chill_calendar_calendar": Calendar::class + * }) */ -class Calendar +class Calendar implements TrackCreationInterface, TrackUpdateInterface, HasCentersInterface { + use RemoteCalendarTrait; + + use TrackCreationTrait; + + use TrackUpdateTrait; + + public const SMS_CANCEL_PENDING = 'sms_cancel_pending'; + + public const SMS_PENDING = 'sms_pending'; + + public const SMS_SENT = 'sms_sent'; + public const STATUS_CANCELED = 'canceled'; + /** + * @deprecated + */ public const STATUS_MOVED = 'moved'; public const STATUS_VALID = 'valid'; /** - * @ORM\ManyToOne(targetEntity="Chill\PersonBundle\Entity\AccompanyingPeriod") - * @Groups({"read"}) + * a list of invite which have been added during this session. + * + * @var array|Invite[] */ - private AccompanyingPeriod $accompanyingPeriod; + public array $newInvites = []; + + /** + * a list of invite which have been removed during this session. + * + * @var array|Invite[] + */ + public array $oldInvites = []; + + public ?CalendarRange $previousCalendarRange = null; + + public ?User $previousMainUser = null; + + /** + * @ORM\ManyToOne(targetEntity="Chill\PersonBundle\Entity\AccompanyingPeriod", inversedBy="calendars") + * @Serializer\Groups({"calendar:read", "read"}) + */ + private ?AccompanyingPeriod $accompanyingPeriod = null; /** * @ORM\ManyToOne(targetEntity="Chill\ActivityBundle\Entity\Activity") @@ -56,7 +103,8 @@ class Calendar private ?Activity $activity = null; /** - * @ORM\ManyToOne(targetEntity="CalendarRange", inversedBy="calendars") + * @ORM\OneToOne(targetEntity="CalendarRange", inversedBy="calendar") + * @Serializer\Groups({"calendar:read", "read"}) */ private ?CalendarRange $calendarRange = null; @@ -67,13 +115,25 @@ class Calendar /** * @ORM\Embedded(class=CommentEmbeddable::class, columnPrefix="comment_") - * @Serializer\Groups({"calendar:read"}) + * @Serializer\Groups({"calendar:read", "read", "docgen:read"}) */ private CommentEmbeddable $comment; /** - * @ORM\Column(type="datetimetz_immutable") - * @Serializer\Groups({"calendar:read"}) + * @ORM\Column(type="integer", nullable=false, options={"default": 0}) + */ + private int $dateTimeVersion = 0; + + /** + * @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; @@ -81,38 +141,49 @@ class Calendar * @ORM\Id * @ORM\GeneratedValue * @ORM\Column(type="integer") - * @Serializer\Groups({"calendar:read"}) + * @Serializer\Groups({"calendar:read", "read", "calendar:light", "docgen:read"}) */ - private ?int $id; + private ?int $id = null; /** - * @ORM\ManyToMany( - * targetEntity="Invite", - * cascade={"persist", "remove", "merge", "detach"}) + * @ORM\OneToMany( + * targetEntity=Invite::class, + * mappedBy="calendar", + * orphanRemoval=true, + * cascade={"persist", "remove", "merge", "detach"} + * ) * @ORM\JoinTable(name="chill_calendar.calendar_to_invites") - * @Groups({"read"}) + * @Serializer\Groups({"read", "docgen:read"}) */ private Collection $invites; /** * @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\Location") - * @groups({"read"}) + * @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"}) + * @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; + private ?User $mainUser = null; /** - * @ORM\ManyToMany( - * targetEntity="Chill\PersonBundle\Entity\Person", - * cascade={"persist", "remove", "merge", "detach"}) + * @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") - * @Groups({"read"}) - * @Serializer\Groups({"calendar:read"}) + * @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.") */ private Collection $persons; @@ -123,69 +194,102 @@ class Calendar private PrivateCommentEmbeddable $privateComment; /** - * @ORM\ManyToMany( - * targetEntity="Chill\ThirdPartyBundle\Entity\ThirdParty", - * cascade={"persist", "remove", "merge", "detach"}) + * @ORM\ManyToMany(targetEntity="Chill\ThirdPartyBundle\Entity\ThirdParty") * @ORM\JoinTable(name="chill_calendar.calendar_to_thirdparties") - * @Groups({"read"}) - * @Serializer\Groups({"calendar:read"}) + * @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; + private ?bool $sendSMS = false; /** - * @ORM\Column(type="datetimetz_immutable") - * @Serializer\Groups({"calendar:read"}) + * @ORM\Column(type="text", nullable=false, options={"default": Calendar::SMS_PENDING}) + */ + private string $smsStatus = self::SMS_PENDING; + + /** + * @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; /** - * @ORM\Column(type="string", length=255) + * @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 = null; + private string $status = self::STATUS_VALID; /** - * @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\User") - * @Groups({"read"}) - * @Serializer\Groups({"calendar:read"}) + * @ORM\Column(type="boolean", nullable=true) + * @Serializer\Groups({"docgen:read"}) */ - private ?User $user = null; + private ?bool $urgent = false; public function __construct() { $this->comment = new CommentEmbeddable(); + $this->documents = new ArrayCollection(); $this->privateComment = new PrivateCommentEmbeddable(); $this->persons = new ArrayCollection(); $this->professionals = new ArrayCollection(); $this->invites = new ArrayCollection(); } - public function addInvite(?Invite $invite): self + /** + * @internal use @{CalendarDoc::__construct} instead + */ + public function addDocument(CalendarDoc $calendarDoc): self { - if (null !== $invite) { - $this->invites[] = $invite; + if ($this->documents->contains($calendarDoc)) { + $this->documents[] = $calendarDoc; } return $this; } - public function addPerson(?Person $person): self + /** + * @internal Use {@link (Calendar::addUser)} instead + */ + public function addInvite(Invite $invite): self { - if (null !== $person) { - $this->persons[] = $person; + if ($invite->getCalendar() instanceof Calendar && $invite->getCalendar() !== $this) { + throw new LogicException('Not allowed to move an invitation to another Calendar'); } + $this->invites[] = $invite; + $this->newInvites[] = $invite; + + $invite->setCalendar($this); + return $this; } - public function addProfessional(?ThirdParty $professional): self + public function addPerson(Person $person): self { - if (null !== $professional) { - $this->professionals[] = $professional; + $this->persons[] = $person; + + return $this; + } + + public function addProfessional(ThirdParty $professional): self + { + $this->professionals[] = $professional; + + return $this; + } + + public function addUser(User $user): self + { + if (!$this->getUsers()->contains($user) && $this->getMainUser() !== $user) { + $this->addInvite((new Invite())->setUser($user)); } return $this; @@ -211,11 +315,66 @@ class Calendar return $this->cancelReason; } + 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()); + } + } + public function getComment(): CommentEmbeddable { return $this->comment; } + /** + * @return 'person'|'accompanying_period'|null + */ + public function getContext(): ?string + { + if ($this->getAccompanyingPeriod() !== null) { + return 'accompanying_period'; + } + + if ($this->getPerson() !== null) { + return 'person'; + } + + return null; + } + + /** + * Each time the date and time is update, this version is incremented. + */ + public function getDateTimeVersion(): int + { + return $this->dateTimeVersion; + } + + public function getDocuments(): Collection + { + return $this->documents; + } + + /** + * @Serializer\Groups({"docgen:read"}) + */ + public function getDuration(): ?DateInterval + { + if ($this->getStartDate() === null || $this->getEndDate() === null) { + return null; + } + + return $this->getStartDate()->diff($this->getEndDate()); + } + public function getEndDate(): ?DateTimeImmutable { return $this->endDate; @@ -226,6 +385,21 @@ class Calendar return $this->id; } + public function getInviteForUser(User $user): ?Invite + { + $criteria = Criteria::create(); + $criteria->where(Criteria::expr()->eq('user', $user)); + + $matchings = $this->invites + ->matching($criteria); + + if (1 === $matchings->count()) { + return $matchings->first(); + } + + return null; + } + /** * @return Collection|Invite[] */ @@ -244,6 +418,11 @@ class Calendar return $this->mainUser; } + public function getPerson(): ?Person + { + return $this->person; + } + /** * @return Collection|Person[] */ @@ -304,6 +483,11 @@ class Calendar return $this->sendSMS; } + public function getSmsStatus(): string + { + return $this->smsStatus; + } + public function getStartDate(): ?DateTimeImmutable { return $this->startDate; @@ -319,14 +503,40 @@ class Calendar return $this->getProfessionals(); } - public function getUser(): ?User + public function getUrgent(): ?bool { - return $this->user; + return $this->urgent; } - public function getusers(): Collection + /** + * @return ReadableCollection<(int|string), User> + * @Serializer\Groups({"calendar:read", "read"}) + */ + public function getUsers(): ReadableCollection { - return $this->getInvites(); //TODO get users of the invite + return $this->getInvites()->map(static fn (Invite $i) => $i->getUser()); + } + + public function hasCalendarRange(): bool + { + return null !== $this->calendarRange; + } + + public function hasLocation(): bool + { + return null !== $this->getLocation(); + } + + /** + * return true if the user is invited. + */ + public function isInvited(User $user): bool + { + if ($this->getMainUser() === $user) { + return false; + } + + return $this->getUsers()->contains($user); } public static function loadValidatorMetadata(ClassMetadata $metadata): void @@ -343,9 +553,27 @@ class Calendar ])); } + /** + * @internal use @{CalendarDoc::setCalendar} with null instead + */ + public function removeDocument(CalendarDoc $calendarDoc): self + { + if ($calendarDoc->getCalendar() !== $this) { + throw new LogicException('cannot remove document of another calendar'); + } + + return $this; + } + + /** + * @internal Use {@link (Calendar::removeUser)} instead + */ public function removeInvite(Invite $invite): self { - $this->invites->removeElement($invite); + if ($this->invites->removeElement($invite)) { + $invite->setCalendar(null); + $this->oldInvites[] = $invite; + } return $this; } @@ -364,6 +592,20 @@ class Calendar return $this; } + public function removeUser(User $user): self + { + if (!$this->getUsers()->contains($user)) { + return $this; + } + + $invite = $this->invites + ->filter(static fn (Invite $invite) => $invite->getUser() === $user) + ->first(); + $this->removeInvite($invite); + + return $this; + } + public function setAccompanyingPeriod(?AccompanyingPeriod $accompanyingPeriod): self { $this->accompanyingPeriod = $accompanyingPeriod; @@ -380,8 +622,20 @@ class Calendar public function setCalendarRange(?CalendarRange $calendarRange): self { + if ($this->calendarRange !== $calendarRange) { + $this->previousCalendarRange = $this->calendarRange; + + if (null !== $this->previousCalendarRange) { + $this->previousCalendarRange->setCalendar(null); + } + } + $this->calendarRange = $calendarRange; + if ($this->calendarRange instanceof CalendarRange) { + $this->calendarRange->setCalendar($this); + } + return $this; } @@ -401,6 +655,10 @@ class Calendar public function setEndDate(DateTimeImmutable $endDate): self { + if (null === $this->endDate || $this->endDate->getTimestamp() !== $endDate->getTimestamp()) { + $this->increaseaDatetimeVersion(); + } + $this->endDate = $endDate; return $this; @@ -415,7 +673,19 @@ class Calendar public function setMainUser(?User $mainUser): self { + if ($this->mainUser !== $mainUser) { + $this->previousMainUser = $this->mainUser; + } + $this->mainUser = $mainUser; + $this->removeUser($mainUser); + + return $this; + } + + public function setPerson(?Person $person): Calendar + { + $this->person = $person; return $this; } @@ -434,8 +704,19 @@ class Calendar return $this; } + public function setSmsStatus(string $smsStatus): self + { + $this->smsStatus = $smsStatus; + + return $this; + } + public function setStartDate(DateTimeImmutable $startDate): self { + if (null === $this->startDate || $this->startDate->getTimestamp() !== $startDate->getTimestamp()) { + $this->increaseaDatetimeVersion(); + } + $this->startDate = $startDate; return $this; @@ -445,13 +726,22 @@ class Calendar { $this->status = $status; + if (self::STATUS_CANCELED === $status && $this->getSmsStatus() === self::SMS_SENT) { + $this->setSmsStatus(self::SMS_CANCEL_PENDING); + } + return $this; } - public function setUser(?User $user): self + public function setUrgent(bool $urgent): self { - $this->user = $user; + $this->urgent = $urgent; return $this; } + + private function increaseaDatetimeVersion(): void + { + ++$this->dateTimeVersion; + } } diff --git a/src/Bundle/ChillCalendarBundle/Entity/CalendarDoc.php b/src/Bundle/ChillCalendarBundle/Entity/CalendarDoc.php new file mode 100644 index 000000000..7012b64e0 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Entity/CalendarDoc.php @@ -0,0 +1,153 @@ +setCalendar($calendar); + + $this->storedObject = $storedObject; + $this->datetimeVersion = $calendar->getDateTimeVersion(); + } + + public function createFromDTO(CalendarDocCreateDTO $calendarDocCreateDTO): void + { + $this->storedObject = $calendarDocCreateDTO->doc; + $this->storedObject->setTitle($calendarDocCreateDTO->title); + } + + public function editFromDTO(CalendarDocEditDTO $calendarDocEditDTO): void + { + if (null !== $calendarDocEditDTO->doc) { + $calendarDocEditDTO->doc->setTitle($this->getStoredObject()->getTitle()); + $this->setStoredObject($calendarDocEditDTO->doc); + } + + $this->getStoredObject()->setTitle($calendarDocEditDTO->title); + } + + public function getCalendar(): Calendar + { + return $this->calendar; + } + + public function getDatetimeVersion(): int + { + return $this->datetimeVersion; + } + + public function getId(): ?int + { + return $this->id; + } + + public function getStoredObject(): StoredObject + { + return $this->storedObject; + } + + public function isTrackDateTimeVersion(): bool + { + return $this->trackDateTimeVersion; + } + + /** + * @internal use @see{Calendar::removeDocument} instead + * + * @param Calendar $calendar + */ + public function setCalendar(?Calendar $calendar): CalendarDoc + { + if (null === $calendar) { + $this->calendar->removeDocument($this); + } else { + $calendar->addDocument($this); + } + + $this->calendar = $calendar; + + $this->datetimeVersion = $calendar->getDateTimeVersion(); + + return $this; + } + + public function setDatetimeVersion(int $datetimeVersion): CalendarDoc + { + $this->datetimeVersion = $datetimeVersion; + + return $this; + } + + public function setStoredObject(StoredObject $storedObject): CalendarDoc + { + $this->storedObject = $storedObject; + + return $this; + } + + public function setTrackDateTimeVersion(bool $trackDateTimeVersion): CalendarDoc + { + $this->trackDateTimeVersion = $trackDateTimeVersion; + + return $this; + } +} diff --git a/src/Bundle/ChillCalendarBundle/Entity/CalendarDoc/CalendarDocCreateDTO.php b/src/Bundle/ChillCalendarBundle/Entity/CalendarDoc/CalendarDocCreateDTO.php new file mode 100644 index 000000000..b7209e46f --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Entity/CalendarDoc/CalendarDocCreateDTO.php @@ -0,0 +1,30 @@ +title = $calendarDoc->getStoredObject()->getTitle(); + } +} diff --git a/src/Bundle/ChillCalendarBundle/Entity/CalendarRange.php b/src/Bundle/ChillCalendarBundle/Entity/CalendarRange.php index b29a9db08..5dbc4286f 100644 --- a/src/Bundle/ChillCalendarBundle/Entity/CalendarRange.php +++ b/src/Bundle/ChillCalendarBundle/Entity/CalendarRange.php @@ -1,39 +1,51 @@ ''"})} + * ) + * @ORM\Entity */ -class CalendarRange +class CalendarRange implements TrackCreationInterface, TrackUpdateInterface { - /** - * @ORM\OneToMany(targetEntity=Calendar::class, - * mappedBy="calendarRange") - */ - private Collection $calendars; + use RemoteCalendarTrait; + + use TrackCreationTrait; + + use TrackUpdateTrait; /** - * @ORM\Column(type="datetimetz_immutable") - * @groups({"read", "write"}) + * @ORM\OneToOne(targetEntity=Calendar::class, mappedBy="calendarRange") + */ + private ?Calendar $calendar = null; + + /** + * @ORM\Column(type="datetime_immutable", nullable=false) + * @Groups({"read", "write", "calendar:read"}) + * @Assert\NotNull */ private ?DateTimeImmutable $endDate = null; @@ -41,27 +53,35 @@ class CalendarRange * @ORM\Id * @ORM\GeneratedValue * @ORM\Column(type="integer") - * @groups({"read"}) + * @Groups({"read"}) */ private $id; /** - * @ORM\Column(type="datetimetz_immutable") - * @groups({"read", "write"}) + * @ORM\ManyToOne(targetEntity=Location::class) + * @ORM\JoinColumn(nullable=false) + * @Groups({"read", "write", "calendar:read"}) + * @Assert\NotNull + */ + private ?Location $location = null; + + /** + * @ORM\Column(type="datetime_immutable", nullable=false) + * @groups({"read", "write", "calendar:read"}) + * @Assert\NotNull */ private ?DateTimeImmutable $startDate = null; /** * @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\User") - * @groups({"read", "write"}) + * @Groups({"read", "write", "calendar:read"}) + * @Assert\NotNull */ private ?User $user = null; - //TODO Lieu - - public function __construct() + public function getCalendar(): ?Calendar { - $this->calendars = new ArrayCollection(); + return $this->calendar; } public function getEndDate(): ?DateTimeImmutable @@ -74,6 +94,11 @@ class CalendarRange return $this->id; } + public function getLocation(): ?Location + { + return $this->location; + } + public function getStartDate(): ?DateTimeImmutable { return $this->startDate; @@ -84,6 +109,14 @@ class CalendarRange return $this->user; } + /** + * @internal use {@link (Calendar::setCalendarRange)} instead + */ + public function setCalendar(?Calendar $calendar): void + { + $this->calendar = $calendar; + } + public function setEndDate(DateTimeImmutable $endDate): self { $this->endDate = $endDate; @@ -91,6 +124,13 @@ class CalendarRange return $this; } + public function setLocation(?Location $location): self + { + $this->location = $location; + + return $this; + } + 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 635508671..a262842c2 100644 --- a/src/Bundle/ChillCalendarBundle/Entity/CancelReason.php +++ b/src/Bundle/ChillCalendarBundle/Entity/CancelReason.php @@ -1,14 +1,14 @@ ''"})} + * ) + * @ORM\Entity */ -class Invite +class Invite implements TrackUpdateInterface, TrackCreationInterface { + use RemoteCalendarTrait; + + use TrackCreationTrait; + + use TrackUpdateTrait; + + public const ACCEPTED = 'accepted'; + + public const DECLINED = 'declined'; + + public const PENDING = 'pending'; + + /** + * all statuses in one const. + */ + public const STATUSES = [ + self::ACCEPTED, + self::DECLINED, + self::PENDING, + self::TENTATIVELY_ACCEPTED, + ]; + + public const TENTATIVELY_ACCEPTED = 'tentative'; + + /** + * @ORM\ManyToOne(targetEntity=Calendar::class, inversedBy="invites") + */ + private ?Calendar $calendar = null; + /** * @ORM\Id * @ORM\GeneratedValue * @ORM\Column(type="integer") + * @Serializer\Groups(groups={"calendar:read", "read"}) */ - private $id; + private ?int $id = null; /** - * @ORM\Column(type="json") + * @ORM\Column(type="text", nullable=false, options={"default": "pending"}) + * @Serializer\Groups(groups={"calendar:read", "read", "docgen:read"}) */ - private array $status = []; + 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; + private ?User $user = null; + + public function getCalendar(): ?Calendar + { + return $this->calendar; + } public function getId(): ?int { return $this->id; } - public function getStatus(): ?array + public function getStatus(): string { return $this->status; } @@ -53,7 +104,15 @@ class Invite return $this->user; } - public function setStatus(array $status): self + /** + * @internal use Calendar::addInvite instead + */ + public function setCalendar(?Calendar $calendar): void + { + $this->calendar = $calendar; + } + + public function setStatus(string $status): self { $this->status = $status; @@ -62,6 +121,10 @@ class Invite 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'); + } + $this->user = $user; return $this; diff --git a/src/Bundle/ChillCalendarBundle/Entity/RemoteCalendarTrait.php b/src/Bundle/ChillCalendarBundle/Entity/RemoteCalendarTrait.php new file mode 100644 index 000000000..31282fb0b --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Entity/RemoteCalendarTrait.php @@ -0,0 +1,71 @@ +remoteAttributes = array_merge($this->remoteAttributes, $remoteAttributes); + + return $this; + } + + public function getRemoteAttributes(): array + { + return $this->remoteAttributes; + } + + public function getRemoteId(): string + { + return $this->remoteId; + } + + public function hasRemoteId(): bool + { + return '' !== $this->remoteId; + } + + public function setRemoteId(string $remoteId): self + { + $this->remoteId = $remoteId; + + return $this; + } +} diff --git a/src/Bundle/ChillCalendarBundle/Event/ListenToActivityCreate.php b/src/Bundle/ChillCalendarBundle/Event/ListenToActivityCreate.php index ec902bd64..298a1ef9a 100644 --- a/src/Bundle/ChillCalendarBundle/Event/ListenToActivityCreate.php +++ b/src/Bundle/ChillCalendarBundle/Event/ListenToActivityCreate.php @@ -1,14 +1,14 @@ userRepository = $userRepository; + $this->userRender = $userRender; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + if (!in_array('caluser', $qb->getAllAliases(), true)) { + $qb->join('cal.mainUser', 'caluser'); + } + + $qb->addSelect('caluser.id AS agent_aggregator'); + $qb->addGroupBy('agent_aggregator'); + } + + public function applyOn(): string + { + return Declarations::CALENDAR_TYPE; + } + + public function buildForm(FormBuilderInterface $builder) + { + // no form + } + + public function getLabels($key, array $values, $data): Closure + { + return function ($value): string { + if ('_header' === $value) { + return 'Agent'; + } + + if (null === $value || '' === $value) { + return ''; + } + + $r = $this->userRepository->find($value); + + return $this->userRender->renderString($r, []); + }; + } + + public function getQueryKeys($data): array + { + return ['agent_aggregator']; + } + + public function getTitle(): string + { + return 'Group calendars by agent'; + } +} diff --git a/src/Bundle/ChillCalendarBundle/Export/Aggregator/CancelReasonAggregator.php b/src/Bundle/ChillCalendarBundle/Export/Aggregator/CancelReasonAggregator.php new file mode 100644 index 000000000..611cb6d79 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Export/Aggregator/CancelReasonAggregator.php @@ -0,0 +1,91 @@ +cancelReasonRepository = $cancelReasonRepository; + $this->translatableStringHelper = $translatableStringHelper; + } + + public function addRole(): ?string + { + return null; + } + + 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)) { + $qb->join('cal.cancelReason', 'calcancel'); + } + + $qb->addSelect('IDENTITY(cal.cancelReason) as cancel_reason_aggregator'); + $qb->addGroupBy('cancel_reason_aggregator'); + } + + public function applyOn(): string + { + return Declarations::CALENDAR_TYPE; + } + + public function buildForm(FormBuilderInterface $builder) + { + // no form + } + + public function getLabels($key, array $values, $data): Closure + { + return function ($value): string { + if ('_header' === $value) { + return 'Cancel reason'; + } + + if (null === $value || '' === $value) { + return ''; + } + + $j = $this->cancelReasonRepository->find($value); + + return $this->translatableStringHelper->localize( + $j->getName() + ); + }; + } + + public function getQueryKeys($data): array + { + return ['cancel_reason_aggregator']; + } + + public function getTitle(): string + { + return 'Group calendars by cancel reason'; + } +} diff --git a/src/Bundle/ChillCalendarBundle/Export/Aggregator/JobAggregator.php b/src/Bundle/ChillCalendarBundle/Export/Aggregator/JobAggregator.php new file mode 100644 index 000000000..23292a5b0 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Export/Aggregator/JobAggregator.php @@ -0,0 +1,90 @@ +jobRepository = $jobRepository; + $this->translatableStringHelper = $translatableStringHelper; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + if (!in_array('caluser', $qb->getAllAliases(), true)) { + $qb->join('cal.mainUser', 'caluser'); + } + + $qb->addSelect('IDENTITY(caluser.userJob) as job_aggregator'); + $qb->addGroupBy('job_aggregator'); + } + + public function applyOn(): string + { + return Declarations::CALENDAR_TYPE; + } + + public function buildForm(FormBuilderInterface $builder) + { + // no form + } + + public function getLabels($key, array $values, $data): Closure + { + return function ($value): string { + if ('_header' === $value) { + return 'Job'; + } + + if (null === $value || '' === $value) { + return ''; + } + + $j = $this->jobRepository->find($value); + + return $this->translatableStringHelper->localize( + $j->getLabel() + ); + }; + } + + public function getQueryKeys($data): array + { + return ['job_aggregator']; + } + + public function getTitle(): string + { + return 'Group calendars by agent job'; + } +} diff --git a/src/Bundle/ChillCalendarBundle/Export/Aggregator/LocationAggregator.php b/src/Bundle/ChillCalendarBundle/Export/Aggregator/LocationAggregator.php new file mode 100644 index 000000000..940000f47 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Export/Aggregator/LocationAggregator.php @@ -0,0 +1,82 @@ +locationRepository = $locationRepository; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + if (!in_array('calloc', $qb->getAllAliases(), true)) { + $qb->join('cal.location', 'calloc'); + } + $qb->addSelect('IDENTITY(cal.location) as location_aggregator'); + $qb->addGroupBy('location_aggregator'); + } + + public function applyOn(): string + { + return Declarations::CALENDAR_TYPE; + } + + public function buildForm(FormBuilderInterface $builder) + { + // no form + } + + public function getLabels($key, array $values, $data): Closure + { + return function ($value): string { + if ('_header' === $value) { + return 'Location'; + } + + if (null === $value || '' === $value) { + return ''; + } + + $l = $this->locationRepository->find($value); + + return $l->getName(); + }; + } + + public function getQueryKeys($data): array + { + return ['location_aggregator']; + } + + public function getTitle(): string + { + return 'Group calendars by location'; + } +} diff --git a/src/Bundle/ChillCalendarBundle/Export/Aggregator/LocationTypeAggregator.php b/src/Bundle/ChillCalendarBundle/Export/Aggregator/LocationTypeAggregator.php new file mode 100644 index 000000000..6574e3934 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Export/Aggregator/LocationTypeAggregator.php @@ -0,0 +1,92 @@ +locationTypeRepository = $locationTypeRepository; + $this->translatableStringHelper = $translatableStringHelper; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + if (!in_array('calloc', $qb->getAllAliases(), true)) { + $qb->join('cal.location', 'calloc'); + } + + $qb->addSelect('IDENTITY(calloc.locationType) as location_type_aggregator'); + $qb->addGroupBy('location_type_aggregator'); + } + + public function applyOn(): string + { + return Declarations::CALENDAR_TYPE; + } + + public function buildForm(FormBuilderInterface $builder) + { + // no form + } + + public function getLabels($key, array $values, $data): Closure + { + return function ($value): string { + if ('_header' === $value) { + return 'Location type'; + } + + if (null === $value || '' === $value) { + return ''; + } + + if (null === $j = $this->locationTypeRepository->find($value)) { + return ''; + } + + return $this->translatableStringHelper->localize( + $j->getTitle() + ); + }; + } + + public function getQueryKeys($data): array + { + return ['location_type_aggregator']; + } + + public function getTitle(): string + { + return 'Group calendars by location type'; + } +} diff --git a/src/Bundle/ChillCalendarBundle/Export/Aggregator/MonthYearAggregator.php b/src/Bundle/ChillCalendarBundle/Export/Aggregator/MonthYearAggregator.php new file mode 100644 index 000000000..7b2a5e898 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Export/Aggregator/MonthYearAggregator.php @@ -0,0 +1,64 @@ +addSelect("to_char(cal.startDate, 'MM-YYYY') AS month_year_aggregator"); + // $qb->addSelect("extract(month from age(cal.startDate, cal.endDate)) AS month_aggregator"); + $qb->addGroupBy('month_year_aggregator'); + } + + public function applyOn(): string + { + return Declarations::CALENDAR_TYPE; + } + + public function buildForm(FormBuilderInterface $builder) + { + // No form needed + } + + public function getLabels($key, array $values, $data): Closure + { + return static function ($value): string { + if ('_header' === $value) { + return 'by month and year'; + } + + return $value; + }; + } + + public function getQueryKeys($data): array + { + return ['month_year_aggregator']; + } + + public function getTitle(): string + { + return 'Group calendars by month and year'; + } +} diff --git a/src/Bundle/ChillCalendarBundle/Export/Aggregator/ScopeAggregator.php b/src/Bundle/ChillCalendarBundle/Export/Aggregator/ScopeAggregator.php new file mode 100644 index 000000000..3aff3e0d8 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Export/Aggregator/ScopeAggregator.php @@ -0,0 +1,90 @@ +scopeRepository = $scopeRepository; + $this->translatableStringHelper = $translatableStringHelper; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + if (!in_array('caluser', $qb->getAllAliases(), true)) { + $qb->join('cal.mainUser', 'caluser'); + } + + $qb->addSelect('IDENTITY(caluser.mainScope) as scope_aggregator'); + $qb->addGroupBy('scope_aggregator'); + } + + public function applyOn(): string + { + return Declarations::CALENDAR_TYPE; + } + + public function buildForm(FormBuilderInterface $builder) + { + // no form + } + + public function getLabels($key, array $values, $data): Closure + { + 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 ['scope_aggregator']; + } + + public function getTitle(): string + { + return 'Group calendars by agent scope'; + } +} diff --git a/src/Bundle/ChillCalendarBundle/Export/Aggregator/UrgencyAggregator.php b/src/Bundle/ChillCalendarBundle/Export/Aggregator/UrgencyAggregator.php new file mode 100644 index 000000000..ad5910461 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Export/Aggregator/UrgencyAggregator.php @@ -0,0 +1,89 @@ +translator = $translator; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + $qb->addSelect('cal.urgent AS urgency_aggregator'); + $qb->addGroupBy('urgency_aggregator'); + } + + public function applyOn(): string + { + return Declarations::CALENDAR_TYPE; + } + + public function buildForm(FormBuilderInterface $builder) + { + // no form + } + + 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)); + } + }; + } + + public function getQueryKeys($data): array + { + return ['urgency_aggregator']; + } + + public function getTitle(): string + { + return 'Group calendars by urgency'; + } +} diff --git a/src/Bundle/ChillCalendarBundle/Export/Declarations.php b/src/Bundle/ChillCalendarBundle/Export/Declarations.php new file mode 100644 index 000000000..0df90749c --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Export/Declarations.php @@ -0,0 +1,20 @@ +calendarRepository = $calendarRepository; + } + + public function buildForm(FormBuilderInterface $builder) + { + // No form necessary + } + + public function getAllowedFormattersTypes(): array + { + return [FormatterInterface::TYPE_TABULAR]; + } + + public function getDescription(): string + { + return 'Count calendars by various parameters.'; + } + + public function getGroup(): string + { + return 'Exports of calendar'; + } + + public function getLabels($key, array $values, $data) + { + if ('export_result' !== $key) { + throw new LogicException("the key {$key} is not used by this export"); + } + + $labels = array_combine($values, $values); + $labels['_header'] = $this->getTitle(); + + return static fn ($value) => $labels[$value]; + } + + public function getQueryKeys($data): array + { + return ['export_result']; + } + + public function getResult($query, $data) + { + return $query->getQuery()->getResult(AbstractQuery::HYDRATE_SCALAR); + } + + public function getTitle(): string + { + return 'Count calendars'; + } + + public function getType(): string + { + return Declarations::CALENDAR_TYPE; + } + + /** + * Initiate the query. + */ + public function initiateQuery(array $requiredModifiers, array $acl, array $data = []): QueryBuilder + { + $centers = array_map(static fn ($el) => $el['center'], $acl); + + $qb = $this->calendarRepository->createQueryBuilder('cal'); + + $qb->select('COUNT(cal.id) AS export_result'); + + return $qb; + } + + public function requiredRole(): string + { + // which role should we give here? + return PersonVoter::STATS; + } + + public function supportsModifiers(): array + { + return [ + Declarations::CALENDAR_TYPE, + ]; + } +} diff --git a/src/Bundle/ChillCalendarBundle/Export/Export/StatCalendarAvgDuration.php b/src/Bundle/ChillCalendarBundle/Export/Export/StatCalendarAvgDuration.php new file mode 100644 index 000000000..14dd42d6b --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Export/Export/StatCalendarAvgDuration.php @@ -0,0 +1,108 @@ +calendarRepository = $calendarRepository; + } + + public function buildForm(FormBuilderInterface $builder): void + { + // no form needed + } + + public function getAllowedFormattersTypes(): array + { + return [FormatterInterface::TYPE_TABULAR]; + } + + public function getDescription(): string + { + return 'Get the average of calendar duration according to various filters'; + } + + public function getGroup(): string + { + return 'Exports of calendar'; + } + + public function getLabels($key, array $values, $data) + { + if ('export_result' !== $key) { + throw new LogicException("the key {$key} is not used by this export"); + } + + $labels = array_combine($values, $values); + $labels['_header'] = $this->getTitle(); + + return static fn ($value) => $labels[$value]; + } + + public function getQueryKeys($data): array + { + return ['export_result']; + } + + public function getResult($query, $data) + { + return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); + } + + public function getTitle(): string + { + return 'Average calendar duration'; + } + + public function getType(): string + { + return Declarations::CALENDAR_TYPE; + } + + public function initiateQuery(array $requiredModifiers, array $acl, array $data = []): QueryBuilder + { + $qb = $this->calendarRepository->createQueryBuilder('cal'); + + $qb + ->select('AVG(cal.endDate - cal.startDate) AS export_result'); + + return $qb; + } + + public function requiredRole(): string + { + return AccompanyingPeriodVoter::STATS; + } + + public function supportsModifiers(): array + { + return [ + Declarations::CALENDAR_TYPE, + ]; + } +} diff --git a/src/Bundle/ChillCalendarBundle/Export/Export/StatCalendarSumDuration.php b/src/Bundle/ChillCalendarBundle/Export/Export/StatCalendarSumDuration.php new file mode 100644 index 000000000..1d31bfa26 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Export/Export/StatCalendarSumDuration.php @@ -0,0 +1,108 @@ +calendarRepository = $calendarRepository; + } + + public function buildForm(FormBuilderInterface $builder): void + { + // no form needed + } + + public function getAllowedFormattersTypes(): array + { + return [FormatterInterface::TYPE_TABULAR]; + } + + public function getDescription(): string + { + return 'Get the sum of calendar durations according to various filters'; + } + + public function getGroup(): string + { + return 'Exports of calendar'; + } + + public function getLabels($key, array $values, $data) + { + if ('export_result' !== $key) { + throw new LogicException("the key {$key} is not used by this export"); + } + + $labels = array_combine($values, $values); + $labels['_header'] = $this->getTitle(); + + return static fn ($value) => $labels[$value]; + } + + public function getQueryKeys($data): array + { + return ['export_result']; + } + + public function getResult($query, $data) + { + return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); + } + + public function getTitle(): string + { + return 'Sum of calendar durations'; + } + + public function getType(): string + { + return Declarations::CALENDAR_TYPE; + } + + public function initiateQuery(array $requiredModifiers, array $acl, array $data = []): QueryBuilder + { + $qb = $this->calendarRepository->createQueryBuilder('cal'); + + $qb + ->select('SUM(cal.endDate - cal.startDate) AS export_result'); + + return $qb; + } + + public function requiredRole(): string + { + return AccompanyingPeriodVoter::STATS; + } + + public function supportsModifiers(): array + { + return [ + Declarations::CALENDAR_TYPE, + ]; + } +} diff --git a/src/Bundle/ChillCalendarBundle/Export/Filter/AgentFilter.php b/src/Bundle/ChillCalendarBundle/Export/Filter/AgentFilter.php new file mode 100644 index 000000000..b58cee594 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Export/Filter/AgentFilter.php @@ -0,0 +1,85 @@ +userRender = $userRender; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + $where = $qb->getDQLPart('where'); + $clause = $qb->expr()->in('cal.mainUser', ':agents'); + + if ($where instanceof Andx) { + $where->add($clause); + } else { + $where = $qb->expr()->andX($clause); + } + + $qb->add('where', $where); + $qb->setParameter('agents', $data['accepted_agents']); + } + + public function applyOn(): string + { + return Declarations::CALENDAR_TYPE; + } + + public function buildForm(FormBuilderInterface $builder) + { + $builder->add('accepted_agents', EntityType::class, [ + 'class' => User::class, + 'choice_label' => fn (User $u) => $this->userRender->renderString($u, []), + 'multiple' => true, + 'expanded' => true, + ]); + } + + public function describeAction($data, $format = 'string'): array + { + $users = []; + + foreach ($data['accepted_agents'] as $r) { + $users[] = $r; + } + + return [ + 'Filtered by agent: only %agents%', [ + '%agents' => implode(', ', $users), + ], ]; + } + + public function getTitle(): string + { + return 'Filter calendars by agent'; + } +} diff --git a/src/Bundle/ChillCalendarBundle/Export/Filter/BetweenDatesFilter.php b/src/Bundle/ChillCalendarBundle/Export/Filter/BetweenDatesFilter.php new file mode 100644 index 000000000..59019ac03 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Export/Filter/BetweenDatesFilter.php @@ -0,0 +1,83 @@ +rollingDateConverter = $rollingDateConverter; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + $clause = $qb->expr()->andX( + $qb->expr()->gte('cal.startDate', ':dateFrom'), + $qb->expr()->lte('cal.endDate', ':dateTo') + ); + + $qb->andWhere($clause); + $qb->setParameter( + 'dateFrom', + $this->rollingDateConverter->convert($data['date_from']) + ); + // modify dateTo so that entire day is also taken into account up until the beginning of the next day. + $qb->setParameter( + 'dateTo', + $this->rollingDateConverter->convert($data['date_to'])->modify('+1 day') + ); + } + + public function applyOn(): string + { + return Declarations::CALENDAR_TYPE; + } + + 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), + ]); + } + + public function describeAction($data, $format = 'string'): array + { + return ['Filtered by calendars between %dateFrom% and %dateTo%', [ + '%dateFrom%' => $this->rollingDateConverter->convert($data['date_from'])->format('d-m-Y'), + '%dateTo%' => $this->rollingDateConverter->convert($data['date_to'])->format('d-m-Y'), + ]]; + } + + public function getTitle(): string + { + return 'Filter calendars between certain dates'; + } +} diff --git a/src/Bundle/ChillCalendarBundle/Export/Filter/CalendarRangeFilter.php b/src/Bundle/ChillCalendarBundle/Export/Filter/CalendarRangeFilter.php new file mode 100644 index 000000000..d6c38e163 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Export/Filter/CalendarRangeFilter.php @@ -0,0 +1,99 @@ + true, + 'Made within a calendar range' => false, + ]; + + private const DEFAULT_CHOICE = false; + + private TranslatorInterface $translator; + + public function __construct(TranslatorInterface $translator) + { + $this->translator = $translator; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + if (null !== $data['hasCalendarRange']) { + $qb->andWhere($qb->expr()->isNotNull('cal.calendarRange')); + } else { + $qb->andWhere($qb->expr()->isNull('cal.calendarRange')); + } + } + + public function applyOn(): string + { + return Declarations::CALENDAR_TYPE; + } + + public function buildForm(FormBuilderInterface $builder) + { + $builder->add('hasCalendarRange', ChoiceType::class, [ + 'choices' => self::CHOICES, + 'label' => 'has calendar range', + 'multiple' => false, + 'expanded' => true, + 'empty_data' => self::DEFAULT_CHOICE, + 'data' => self::DEFAULT_CHOICE, + ]); + } + + public function describeAction($data, $format = 'string'): array + { + $choice = ''; + + foreach (self::CHOICES as $k => $v) { + if ($v === $data['hasCalendarRange']) { + $choice = $k; + } else { + $choice = 'Not made within a calendar range'; + } + } + + return [ + 'Filtered by calendar range: only %calendarRange%', [ + '%calendarRange%' => $this->translator->trans($choice), + ], + ]; + } + + public function getTitle(): string + { + return 'Filter by calendar range'; + } +} diff --git a/src/Bundle/ChillCalendarBundle/Export/Filter/JobFilter.php b/src/Bundle/ChillCalendarBundle/Export/Filter/JobFilter.php new file mode 100644 index 000000000..69fb24447 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Export/Filter/JobFilter.php @@ -0,0 +1,99 @@ +translator = $translator; + $this->translatableStringHelper = $translatableStringHelper; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + if (!in_array('caluser', $qb->getAllAliases(), true)) { + $qb->join('cal.mainUser', 'caluser'); + } + + $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']); + } + + public function applyOn(): string + { + return Declarations::CALENDAR_TYPE; + } + + public function buildForm(FormBuilderInterface $builder) + { + $builder->add('job', EntityType::class, [ + 'class' => UserJob::class, + 'choice_label' => fn (UserJob $j) => $this->translatableStringHelper->localize( + $j->getLabel() + ), + 'multiple' => true, + 'expanded' => true, + ]); + } + + public function describeAction($data, $format = 'string'): array + { + $userJobs = []; + + foreach ($data['job'] as $j) { + $userJobs[] = $this->translatableStringHelper->localize( + $j->getLabel() + ); + } + + return ['Filtered by agent job: only %jobs%', [ + '%jobs%' => implode(', ', $userJobs), + ]]; + } + + public function getTitle(): string + { + return 'Filter calendars by agent job'; + } +} diff --git a/src/Bundle/ChillCalendarBundle/Export/Filter/ScopeFilter.php b/src/Bundle/ChillCalendarBundle/Export/Filter/ScopeFilter.php new file mode 100644 index 000000000..08d9ae023 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Export/Filter/ScopeFilter.php @@ -0,0 +1,99 @@ +translator = $translator; + $this->translatableStringHelper = $translatableStringHelper; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + if (!in_array('caluser', $qb->getAllAliases(), true)) { + $qb->join('cal.mainUser', 'caluser'); + } + + $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']); + } + + public function applyOn() + { + return Declarations::CALENDAR_TYPE; + } + + public function buildForm(FormBuilderInterface $builder) + { + $builder->add('scope', EntityType::class, [ + 'class' => Scope::class, + 'choice_label' => fn (Scope $s) => $this->translatableStringHelper->localize( + $s->getName() + ), + 'multiple' => true, + 'expanded' => true, + ]); + } + + public function describeAction($data, $format = 'string') + { + $scopes = []; + + foreach ($data['scope'] as $s) { + $scopes[] = $this->translatableStringHelper->localize( + $s->getName() + ); + } + + return ['Filtered by agent scope: only %scopes%', [ + '%scopes%' => implode(', ', $scopes), + ]]; + } + + public function getTitle() + { + return 'Filter calendars by agent scope'; + } +} diff --git a/src/Bundle/ChillCalendarBundle/Form/CalendarDocCreateType.php b/src/Bundle/ChillCalendarBundle/Form/CalendarDocCreateType.php new file mode 100644 index 000000000..b77886097 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Form/CalendarDocCreateType.php @@ -0,0 +1,42 @@ +add('title', TextType::class, [ + 'label' => 'chill_calendar.Document title', + 'required' => true, + ]) + ->add('doc', StoredObjectType::class, [ + 'label' => 'chill_calendar.Document object', + 'required' => true, + ]); + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults([ + 'data_class' => CalendarDocCreateDTO::class, + ]); + } +} diff --git a/src/Bundle/ChillCalendarBundle/Form/CalendarDocEditType.php b/src/Bundle/ChillCalendarBundle/Form/CalendarDocEditType.php new file mode 100644 index 000000000..34a501028 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Form/CalendarDocEditType.php @@ -0,0 +1,38 @@ +add('title', TextType::class, [ + 'label' => 'chill_calendar.Document title', + 'required' => true, + ]); + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults([ + 'data_class' => CalendarDocEditDTO::class, + ]); + } +} diff --git a/src/Bundle/ChillCalendarBundle/Form/CalendarType.php b/src/Bundle/ChillCalendarBundle/Form/CalendarType.php index b1dccb5fd..a2d15484f 100644 --- a/src/Bundle/ChillCalendarBundle/Form/CalendarType.php +++ b/src/Bundle/ChillCalendarBundle/Form/CalendarType.php @@ -1,29 +1,27 @@ translatableStringHelper = $translatableStringHelper; - $this->om = $om; + $this->personsToIdDataTransformer = $personsToIdDataTransformer; + $this->idToUserDataTransformer = $idToUserDataTransformer; + $this->idToUsersDataTransformer = $idToUsersDataTransformer; + $this->idToLocationDataTransformer = $idToLocationDataTransformer; + $this->partiesToIdDataTransformer = $partiesToIdDataTransformer; + $this->calendarRangeDataTransformer = $idToCalendarRangeDataTransformer; } public function buildForm(FormBuilderInterface $builder, array $options) @@ -64,7 +78,6 @@ class CalendarType extends AbstractType // }, // ]) ->add('sendSMS', ChoiceType::class, [ - 'required' => false, 'choices' => [ 'Oui' => true, 'Non' => false, @@ -73,36 +86,26 @@ class CalendarType extends AbstractType ]); $builder->add('mainUser', HiddenType::class); - $builder->get('mainUser') - ->addModelTransformer(new CallbackTransformer( - static function (?User $user): int { - if (null !== $user) { - $res = $user->getId(); - } else { - $res = -1; //TODO cannot be null in any ways... - } - - return $res; - }, - function (?int $userId): User { - return $this->om->getRepository(user::class)->findOneBy(['id' => (int) $userId]); - } - )); + $builder->get('mainUser')->addModelTransformer($this->idToUserDataTransformer); $builder->add('startDate', HiddenType::class); $builder->get('startDate') ->addModelTransformer(new CallbackTransformer( static function (?DateTimeImmutable $dateTimeImmutable): string { if (null !== $dateTimeImmutable) { - $res = date_format($dateTimeImmutable, 'Y-m-d H:i:s'); + $res = date_format($dateTimeImmutable, DateTimeImmutable::ATOM); } else { $res = ''; } return $res; }, - static function (?string $dateAsString): DateTimeImmutable { - return new DateTimeImmutable($dateAsString); + static function (?string $dateAsString): ?DateTimeImmutable { + if ('' === $dateAsString || null === $dateAsString) { + return null; + } + + return DateTimeImmutable::createFromFormat(DateTimeImmutable::ATOM, $dateAsString); } )); @@ -111,115 +114,41 @@ class CalendarType extends AbstractType ->addModelTransformer(new CallbackTransformer( static function (?DateTimeImmutable $dateTimeImmutable): string { if (null !== $dateTimeImmutable) { - $res = date_format($dateTimeImmutable, 'Y-m-d H:i:s'); + $res = date_format($dateTimeImmutable, DateTimeImmutable::ATOM); } else { $res = ''; } return $res; }, - static function (?string $dateAsString): DateTimeImmutable { - return new DateTimeImmutable($dateAsString); + static function (?string $dateAsString): ?DateTimeImmutable { + if ('' === $dateAsString || null === $dateAsString) { + return null; + } + + return DateTimeImmutable::createFromFormat(DateTimeImmutable::ATOM, $dateAsString); } )); $builder->add('persons', HiddenType::class); $builder->get('persons') - ->addModelTransformer(new CallbackTransformer( - static function (iterable $personsAsIterable): string { - $personIds = []; - - foreach ($personsAsIterable as $value) { - $personIds[] = $value->getId(); - } - - return implode(',', $personIds); - }, - function (?string $personsAsString): array { - return array_map( - fn (string $id): ?Person => $this->om->getRepository(Person::class)->findOneBy(['id' => (int) $id]), - explode(',', $personsAsString) - ); - } - )); + ->addModelTransformer($this->personsToIdDataTransformer); $builder->add('professionals', HiddenType::class); $builder->get('professionals') - ->addModelTransformer(new CallbackTransformer( - static function (iterable $thirdpartyAsIterable): string { - $thirdpartyIds = []; + ->addModelTransformer($this->partiesToIdDataTransformer); - foreach ($thirdpartyAsIterable as $value) { - $thirdpartyIds[] = $value->getId(); - } - - return implode(',', $thirdpartyIds); - }, - function (?string $thirdpartyAsString): array { - return array_map( - fn (string $id): ?ThirdParty => $this->om->getRepository(ThirdParty::class)->findOneBy(['id' => (int) $id]), - explode(',', $thirdpartyAsString) - ); - } - )); + $builder->add('users', HiddenType::class); + $builder->get('users') + ->addModelTransformer($this->idToUsersDataTransformer); $builder->add('calendarRange', HiddenType::class); $builder->get('calendarRange') - ->addModelTransformer(new CallbackTransformer( - static function (?CalendarRange $calendarRange): ?int { - if (null !== $calendarRange) { - $res = $calendarRange->getId(); - } else { - $res = -1; - } - - return $res; - }, - function (?string $calendarRangeId): ?CalendarRange { - if (null !== $calendarRangeId) { - $res = $this->om->getRepository(CalendarRange::class)->findOneBy(['id' => (int) $calendarRangeId]); - } else { - $res = null; - } - - return $res; - } - )); + ->addModelTransformer($this->calendarRangeDataTransformer); $builder->add('location', HiddenType::class) ->get('location') - ->addModelTransformer(new CallbackTransformer( - static function (?Location $location): string { - if (null === $location) { - return ''; - } - - return $location->getId(); - }, - function (?string $id): ?Location { - return $this->om->getRepository(Location::class)->findOneBy(['id' => (int) $id]); - } - )); - - $builder->add('invites', HiddenType::class); - $builder->get('invites') - ->addModelTransformer(new CallbackTransformer( - static function (iterable $usersAsIterable): string { - $userIds = []; - - foreach ($usersAsIterable as $value) { - $userIds[] = $value->getId(); - } - - return implode(',', $userIds); - }, - function (?string $usersAsString): array { - return array_map( - fn (string $id): ?Invite => $this->om->getRepository(Invite::class)->findOneBy(['id' => (int) $id]), - explode(',', $usersAsString) - ); - } - )); + ->addModelTransformer($this->idToLocationDataTransformer); } public function configureOptions(OptionsResolver $resolver) @@ -227,14 +156,11 @@ class CalendarType extends AbstractType $resolver->setDefaults([ 'data_class' => Calendar::class, ]); - - $resolver - ->setRequired(['accompanyingPeriod']) - ->setAllowedTypes('accompanyingPeriod', [\Chill\PersonBundle\Entity\AccompanyingPeriod::class, 'null']); } public function getBlockPrefix() { - return 'chill_calendarbundle_calendar'; + // as the js share some hardcoded items from activity, we have to rewrite block prefix + return 'chill_activitybundle_activity'; } } diff --git a/src/Bundle/ChillCalendarBundle/Form/CancelReasonType.php b/src/Bundle/ChillCalendarBundle/Form/CancelReasonType.php index be1b4e809..c0ac4ddb0 100644 --- a/src/Bundle/ChillCalendarBundle/Form/CancelReasonType.php +++ b/src/Bundle/ChillCalendarBundle/Form/CancelReasonType.php @@ -1,14 +1,14 @@ security = $security; $this->translator = $translator; - $this->authorizationHelper = $authorizationHelper; - $this->tokenStorage = $tokenStorage; } public function buildMenu($menuId, MenuItem $menu, array $parameters) { $period = $parameters['accompanyingCourse']; - if (AccompanyingPeriod::STEP_DRAFT !== $period->getStep()) { - /* + if ($this->security->isGranted(CalendarVoter::SEE, $period)) { $menu->addChild($this->translator->trans('Calendar'), [ - 'route' => 'chill_calendar_calendar_list', + 'route' => 'chill_calendar_calendar_list_by_period', 'routeParameters' => [ - 'accompanying_period_id' => $period->getId(), + 'id' => $period->getId(), ], ]) ->setExtras(['order' => 35]); - */ } } diff --git a/src/Bundle/ChillCalendarBundle/Menu/AdminMenuBuilder.php b/src/Bundle/ChillCalendarBundle/Menu/AdminMenuBuilder.php index 961bab778..1658029a7 100644 --- a/src/Bundle/ChillCalendarBundle/Menu/AdminMenuBuilder.php +++ b/src/Bundle/ChillCalendarBundle/Menu/AdminMenuBuilder.php @@ -1,14 +1,14 @@ setAttribute('class', 'list-group-item-header') ->setExtras([ 'order' => 6000, - 'icons' => ['calendar'], ]); $menu->addChild('Cancel reason', [ diff --git a/src/Bundle/ChillCalendarBundle/Menu/PersonMenuBuilder.php b/src/Bundle/ChillCalendarBundle/Menu/PersonMenuBuilder.php new file mode 100644 index 000000000..eccbe1ffb --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Menu/PersonMenuBuilder.php @@ -0,0 +1,52 @@ +security = $security; + $this->translator = $translator; + } + + public function buildMenu($menuId, MenuItem $menu, array $parameters) + { + $person = $parameters['person']; + + if ($this->security->isGranted(CalendarVoter::SEE, $person)) { + $menu->addChild($this->translator->trans('Calendar'), [ + 'route' => 'chill_calendar_calendar_list_by_person', + 'routeParameters' => [ + 'id' => $person->getId(), + ], ]) + ->setExtras(['order' => 198]); + } + } + + public static function getMenuIds(): array + { + return ['person']; + } +} diff --git a/src/Bundle/ChillCalendarBundle/Menu/UserMenuBuilder.php b/src/Bundle/ChillCalendarBundle/Menu/UserMenuBuilder.php index 74512eda5..525039910 100644 --- a/src/Bundle/ChillCalendarBundle/Menu/UserMenuBuilder.php +++ b/src/Bundle/ChillCalendarBundle/Menu/UserMenuBuilder.php @@ -1,64 +1,40 @@ counter = $counter; - $this->tokenStorage = $tokenStorage; + $this->security = $security; $this->translator = $translator; - $this->authorizationChecker = $authorizationChecker; } public function buildMenu($menuId, MenuItem $menu, array $parameters) { - $user = $this->tokenStorage->getToken()->getUser(); - - if ($this->authorizationChecker->isGranted('ROLE_USER')) { + if ($this->security->isGranted('ROLE_USER')) { $menu->addChild('My calendar list', [ - 'route' => 'chill_calendar_calendar_list', + 'route' => 'chill_calendar_calendar_list_my', ]) ->setExtras([ 'order' => 9, diff --git a/src/Bundle/ChillCalendarBundle/Messenger/Doctrine/CalendarEntityListener.php b/src/Bundle/ChillCalendarBundle/Messenger/Doctrine/CalendarEntityListener.php new file mode 100644 index 000000000..f97cbc7b2 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Messenger/Doctrine/CalendarEntityListener.php @@ -0,0 +1,80 @@ +messageBus = $messageBus; + $this->security = $security; + } + + public function postPersist(Calendar $calendar, PostPersistEventArgs $args): void + { + if (!$calendar->preventEnqueueChanges) { + $this->messageBus->dispatch( + new CalendarMessage( + $calendar, + CalendarMessage::CALENDAR_PERSIST, + $this->security->getUser() + ) + ); + } + } + + public function postRemove(Calendar $calendar, PostRemoveEventArgs $args): void + { + if (!$calendar->preventEnqueueChanges) { + $this->messageBus->dispatch( + new CalendarRemovedMessage( + $calendar, + $this->security->getUser() + ) + ); + } + } + + public function postUpdate(Calendar $calendar, PostUpdateEventArgs $args): void + { + if (!$calendar->preventEnqueueChanges) { + $this->messageBus->dispatch( + new CalendarMessage( + $calendar, + CalendarMessage::CALENDAR_UPDATE, + $this->security->getUser() + ) + ); + } + } +} diff --git a/src/Bundle/ChillCalendarBundle/Messenger/Doctrine/CalendarRangeEntityListener.php b/src/Bundle/ChillCalendarBundle/Messenger/Doctrine/CalendarRangeEntityListener.php new file mode 100644 index 000000000..4df548277 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Messenger/Doctrine/CalendarRangeEntityListener.php @@ -0,0 +1,80 @@ +messageBus = $messageBus; + $this->security = $security; + } + + public function postPersist(CalendarRange $calendarRange, PostPersistEventArgs $eventArgs): void + { + if (!$calendarRange->preventEnqueueChanges) { + $this->messageBus->dispatch( + new CalendarRangeMessage( + $calendarRange, + CalendarRangeMessage::CALENDAR_RANGE_PERSIST, + $this->security->getUser() + ) + ); + } + } + + public function postRemove(CalendarRange $calendarRange, PostRemoveEventArgs $eventArgs): void + { + if (!$calendarRange->preventEnqueueChanges) { + $this->messageBus->dispatch( + new CalendarRangeRemovedMessage( + $calendarRange, + $this->security->getUser() + ) + ); + } + } + + public function postUpdate(CalendarRange $calendarRange, PostUpdateEventArgs $eventArgs): void + { + if (!$calendarRange->preventEnqueueChanges) { + $this->messageBus->dispatch( + new CalendarRangeMessage( + $calendarRange, + CalendarRangeMessage::CALENDAR_RANGE_UPDATE, + $this->security->getUser() + ) + ); + } + } +} diff --git a/src/Bundle/ChillCalendarBundle/Messenger/Handler/CalendarRangeRemoveToRemoteHandler.php b/src/Bundle/ChillCalendarBundle/Messenger/Handler/CalendarRangeRemoveToRemoteHandler.php new file mode 100644 index 000000000..4e3ae5891 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Messenger/Handler/CalendarRangeRemoveToRemoteHandler.php @@ -0,0 +1,52 @@ +remoteCalendarConnector = $remoteCalendarConnector; + $this->userRepository = $userRepository; + } + + public function __invoke(CalendarRangeRemovedMessage $calendarRangeRemovedMessage) + { + $this->remoteCalendarConnector->removeCalendarRange( + $calendarRangeRemovedMessage->getRemoteId(), + $calendarRangeRemovedMessage->getRemoteAttributes(), + $this->userRepository->find($calendarRangeRemovedMessage->getCalendarRangeUserId()) + ); + } +} diff --git a/src/Bundle/ChillCalendarBundle/Messenger/Handler/CalendarRangeToRemoteHandler.php b/src/Bundle/ChillCalendarBundle/Messenger/Handler/CalendarRangeToRemoteHandler.php new file mode 100644 index 000000000..79bcd24ee --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Messenger/Handler/CalendarRangeToRemoteHandler.php @@ -0,0 +1,64 @@ +calendarRangeRepository = $calendarRangeRepository; + $this->remoteCalendarConnector = $remoteCalendarConnector; + $this->entityManager = $entityManager; + } + + public function __invoke(CalendarRangeMessage $calendarRangeMessage): void + { + $range = $this->calendarRangeRepository->find($calendarRangeMessage->getCalendarRangeId()); + + if (null === $range) { + return; + } + + $this->remoteCalendarConnector->syncCalendarRange($range); + $range->preventEnqueueChanges = true; + + $this->entityManager->flush(); + } +} diff --git a/src/Bundle/ChillCalendarBundle/Messenger/Handler/CalendarRemoveHandler.php b/src/Bundle/ChillCalendarBundle/Messenger/Handler/CalendarRemoveHandler.php new file mode 100644 index 000000000..f087766ec --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Messenger/Handler/CalendarRemoveHandler.php @@ -0,0 +1,62 @@ +remoteCalendarConnector = $remoteCalendarConnector; + $this->calendarRangeRepository = $calendarRangeRepository; + $this->userRepository = $userRepository; + } + + public function __invoke(CalendarRemovedMessage $message) + { + if (null !== $message->getAssociatedCalendarRangeId()) { + $associatedRange = $this->calendarRangeRepository->find($message->getAssociatedCalendarRangeId()); + } else { + $associatedRange = null; + } + + $this->remoteCalendarConnector->removeCalendar( + $message->getRemoteId(), + $message->getRemoteAttributes(), + $this->userRepository->find($message->getCalendarUserId()), + $associatedRange + ); + } +} diff --git a/src/Bundle/ChillCalendarBundle/Messenger/Handler/CalendarToRemoteHandler.php b/src/Bundle/ChillCalendarBundle/Messenger/Handler/CalendarToRemoteHandler.php new file mode 100644 index 000000000..eabdf9063 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Messenger/Handler/CalendarToRemoteHandler.php @@ -0,0 +1,119 @@ +calendarConnector = $calendarConnector; + $this->calendarRepository = $calendarRepository; + $this->calendarRangeRepository = $calendarRangeRepository; + $this->entityManager = $entityManager; + $this->userRepository = $userRepository; + $this->inviteRepository = $inviteRepository; + } + + public function __invoke(CalendarMessage $calendarMessage) + { + $calendar = $this->calendarRepository->find($calendarMessage->getCalendarId()); + + if (null === $calendar) { + return; + } + + if (null !== $calendarMessage->getPreviousCalendarRangeId()) { + $previousCalendarRange = $this->calendarRangeRepository + ->find($calendarMessage->getPreviousCalendarRangeId()); + } else { + $previousCalendarRange = null; + } + + if (null !== $calendarMessage->getPreviousMainUserId()) { + $previousMainUser = $this->userRepository + ->find($calendarMessage->getPreviousMainUserId()); + } else { + $previousMainUser = null; + } + + $newInvites = array_filter( + array_map( + fn ($id) => $this->inviteRepository->find($id), + $calendarMessage->getNewInvitesIds(), + ), + static fn (?Invite $invite) => null !== $invite + ); + + $this->calendarConnector->syncCalendar( + $calendar, + $calendarMessage->getAction(), + $previousCalendarRange, + $previousMainUser, + $calendarMessage->getOldInvites(), + $newInvites + ); + + $calendar->preventEnqueueChanges = true; + + if ($calendar->hasCalendarRange()) { + $calendar->getCalendarRange()->preventEnqueueChanges = true; + } + + if ($previousCalendarRange instanceof CalendarRange) { + $previousCalendarRange->preventEnqueueChanges = true; + } + + $this->entityManager->flush(); + } +} diff --git a/src/Bundle/ChillCalendarBundle/Messenger/Handler/InviteUpdateHandler.php b/src/Bundle/ChillCalendarBundle/Messenger/Handler/InviteUpdateHandler.php new file mode 100644 index 000000000..e1df3ac3d --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Messenger/Handler/InviteUpdateHandler.php @@ -0,0 +1,57 @@ +em = $em; + $this->inviteRepository = $inviteRepository; + $this->remoteCalendarConnector = $remoteCalendarConnector; + } + + public function __invoke(InviteUpdateMessage $inviteUpdateMessage): void + { + if (null === $invite = $this->inviteRepository->find($inviteUpdateMessage->getInviteId())) { + return; + } + + $this->remoteCalendarConnector->syncInvite($invite); + + $this->em->flush(); + } +} diff --git a/src/Bundle/ChillCalendarBundle/Messenger/Handler/MSGraphChangeNotificationHandler.php b/src/Bundle/ChillCalendarBundle/Messenger/Handler/MSGraphChangeNotificationHandler.php new file mode 100644 index 000000000..a09c70c1e --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Messenger/Handler/MSGraphChangeNotificationHandler.php @@ -0,0 +1,110 @@ +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 __invoke(MSGraphChangeNotificationMessage $changeNotificationMessage): void + { + $user = $this->userRepository->find($changeNotificationMessage->getUserId()); + + if (null === $user) { + $this->logger->warning(self::class . ' notification concern non-existent user, skipping'); + + return; + } + + foreach ($changeNotificationMessage->getContent()['value'] as $notification) { + $secret = $this->mapCalendarToUser->getSubscriptionSecret($user); + + if ($secret !== ($notification['clientState'] ?? -1)) { + $this->logger->warning(self::class . ' could not validate secret, skipping'); + + continue; + } + + $remoteId = $notification['resourceData']['id']; + + // is this a calendar range ? + if (null !== $calendarRange = $this->calendarRangeRepository->findOneBy(['remoteId' => $remoteId])) { + $this->calendarRangeSyncer->handleCalendarRangeSync($calendarRange, $notification, $user); + $this->em->flush(); + } elseif (null !== $calendar = $this->calendarRepository->findOneBy(['remoteId' => $remoteId])) { + $this->calendarSyncer->handleCalendarSync($calendar, $notification, $user); + $this->em->flush(); + } else { + $this->logger->info(self::class . ' id not found in any calendar nor calendar range'); + } + } + + $this->em->flush(); + } +} diff --git a/src/Bundle/ChillCalendarBundle/Messenger/Message/CalendarMessage.php b/src/Bundle/ChillCalendarBundle/Messenger/Message/CalendarMessage.php new file mode 100644 index 000000000..c8a8f8aeb --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Messenger/Message/CalendarMessage.php @@ -0,0 +1,109 @@ + + */ + private array $oldInvites = []; + + private ?int $previousCalendarRangeId = null; + + private ?int $previousMainUserId = null; + + public function __construct( + Calendar $calendar, + 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 fn (Invite $i) => [ + 'inviteId' => $i->getId(), + 'userId' => $i->getUser()->getId(), + 'userEmail' => $i->getUser()->getEmail(), + 'userLabel' => $i->getUser()->getLabel(), + ], $calendar->oldInvites); + } + + public function getAction(): string + { + return $this->action; + } + + public function getByUserId(): ?int + { + return $this->byUserId; + } + + public function getCalendarId(): ?int + { + return $this->calendarId; + } + + /** + * @return array|int[]|null[] + */ + public function getNewInvitesIds(): array + { + return $this->newInvitesIds; + } + + /** + * @return array + */ + public function getOldInvites(): array + { + return $this->oldInvites; + } + + public function getPreviousCalendarRangeId(): ?int + { + return $this->previousCalendarRangeId; + } + + public function getPreviousMainUserId(): ?int + { + return $this->previousMainUserId; + } +} diff --git a/src/Bundle/ChillCalendarBundle/Messenger/Message/CalendarRangeMessage.php b/src/Bundle/ChillCalendarBundle/Messenger/Message/CalendarRangeMessage.php new file mode 100644 index 000000000..526826ff8 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Messenger/Message/CalendarRangeMessage.php @@ -0,0 +1,60 @@ +action = $action; + $this->calendarRangeId = $calendarRange->getId(); + + if (null !== $byUser) { + $this->byUserId = $byUser->getId(); + } + } + + public function getAction(): string + { + return $this->action; + } + + public function getByUserId(): ?int + { + return $this->byUserId; + } + + public function getCalendarRangeId(): ?int + { + return $this->calendarRangeId; + } +} diff --git a/src/Bundle/ChillCalendarBundle/Messenger/Message/CalendarRangeRemovedMessage.php b/src/Bundle/ChillCalendarBundle/Messenger/Message/CalendarRangeRemovedMessage.php new file mode 100644 index 000000000..783484592 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Messenger/Message/CalendarRangeRemovedMessage.php @@ -0,0 +1,64 @@ +remoteId = $calendarRange->getRemoteId(); + $this->remoteAttributes = $calendarRange->getRemoteAttributes(); + $this->calendarRangeUserId = $calendarRange->getUser()->getId(); + + if (null !== $byUser) { + $this->byUserId = $byUser->getId(); + } + } + + public function getByUserId(): ?int + { + return $this->byUserId; + } + + public function getCalendarRangeUserId(): ?int + { + return $this->calendarRangeUserId; + } + + public function getRemoteAttributes(): array + { + return $this->remoteAttributes; + } + + public function getRemoteId(): string + { + return $this->remoteId; + } +} diff --git a/src/Bundle/ChillCalendarBundle/Messenger/Message/CalendarRemovedMessage.php b/src/Bundle/ChillCalendarBundle/Messenger/Message/CalendarRemovedMessage.php new file mode 100644 index 000000000..65831ebe0 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Messenger/Message/CalendarRemovedMessage.php @@ -0,0 +1,75 @@ +remoteId = $calendar->getRemoteId(); + $this->remoteAttributes = $calendar->getRemoteAttributes(); + $this->calendarUserId = $calendar->getMainUser()->getId(); + + if ($calendar->hasCalendarRange()) { + $this->associatedCalendarRangeId = $calendar->getCalendarRange()->getId(); + } + + if (null !== $byUser) { + $this->byUserId = $byUser->getId(); + } + } + + public function getAssociatedCalendarRangeId(): ?int + { + return $this->associatedCalendarRangeId; + } + + public function getByUserId(): ?int + { + return $this->byUserId; + } + + public function getCalendarUserId(): ?int + { + return $this->calendarUserId; + } + + public function getRemoteAttributes(): array + { + return $this->remoteAttributes; + } + + public function getRemoteId(): string + { + return $this->remoteId; + } +} diff --git a/src/Bundle/ChillCalendarBundle/Messenger/Message/InviteUpdateMessage.php b/src/Bundle/ChillCalendarBundle/Messenger/Message/InviteUpdateMessage.php new file mode 100644 index 000000000..35f78fc7d --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Messenger/Message/InviteUpdateMessage.php @@ -0,0 +1,45 @@ +inviteId = $invite->getId(); + $this->byUserId = $byUser->getId(); + } + + public function getByUserId(): int + { + return $this->byUserId; + } + + public function getInviteId(): int + { + return $this->inviteId; + } +} diff --git a/src/Bundle/ChillCalendarBundle/Messenger/Message/MSGraphChangeNotificationMessage.php b/src/Bundle/ChillCalendarBundle/Messenger/Message/MSGraphChangeNotificationMessage.php new file mode 100644 index 000000000..f1d3b6b04 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Messenger/Message/MSGraphChangeNotificationMessage.php @@ -0,0 +1,42 @@ +content = $content; + $this->userId = $userId; + } + + public function getContent(): array + { + return $this->content; + } + + public function getUserId(): int + { + return $this->userId; + } +} diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/AddressConverter.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/AddressConverter.php new file mode 100644 index 000000000..8b86ba0ec --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/AddressConverter.php @@ -0,0 +1,48 @@ +addressRender = $addressRender; + $this->translatableStringHelper = $translatableStringHelper; + } + + public function addressToRemote(Address $address): array + { + return [ + 'city' => $address->getPostcode()->getName(), + 'postalCode' => $address->getPostcode()->getCode(), + 'countryOrRegion' => $this->translatableStringHelper->localize($address->getPostcode()->getCountry()->getName()), + 'street' => $address->isNoAddress() ? '' : + implode(', ', $this->addressRender->renderLines($address, false, false)), + 'state' => '', + ]; + } +} diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/EventsOnUserSubscriptionCreator.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/EventsOnUserSubscriptionCreator.php new file mode 100644 index 000000000..c400b6694 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/EventsOnUserSubscriptionCreator.php @@ -0,0 +1,140 @@ +logger = $logger; + $this->machineHttpClient = $machineHttpClient; + $this->mapCalendarToUser = $mapCalendarToUser; + $this->urlGenerator = $urlGenerator; + } + + /** + * @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 + { + if (null === $userId = $this->mapCalendarToUser->getUserId($user)) { + throw new LogicException('no user id'); + } + + $subscription = [ + 'changeType' => 'deleted,updated', + 'notificationUrl' => $this->urlGenerator->generate( + 'chill_calendar_remote_msgraph_incoming_webhook_events', + ['userId' => $user->getId()], + UrlGeneratorInterface::ABSOLUTE_URL + ), + 'resource' => "/users/{$userId}/calendar/events", + 'clientState' => $secret = base64_encode(openssl_random_pseudo_bytes(92, $cstrong)), + 'expirationDateTime' => $expiration->format(DateTimeImmutable::ATOM), + ]; + + try { + $subs = $this->machineHttpClient->request( + 'POST', + '/v1.0/subscriptions', + [ + 'json' => $subscription, + ] + )->toArray(); + } catch (ClientExceptionInterface $e) { + $this->logger->error('could not create subscription for user events', [ + 'body' => $e->getResponse()->getContent(false), + ]); + + return ['secret' => '', 'id' => '', 'expiration' => 0]; + } + + return ['secret' => $secret, 'id' => $subs['id'], 'expiration' => $expiration->getTimestamp()]; + } + + /** + * @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 + { + if (null === $userId = $this->mapCalendarToUser->getUserId($user)) { + throw new LogicException('no user id'); + } + + if (null === $subscriptionId = $this->mapCalendarToUser->getActiveSubscriptionId($user)) { + throw new LogicException('no user id'); + } + + $subscription = [ + 'expirationDateTime' => $expiration->format(DateTimeImmutable::ATOM), + ]; + + try { + $subs = $this->machineHttpClient->request( + 'PATCH', + "/v1.0/subscriptions/{$subscriptionId}", + [ + 'json' => $subscription, + ] + )->toArray(); + } catch (ClientExceptionInterface $e) { + $this->logger->error('could not patch subscription for user events', [ + 'body' => $e->getResponse()->getContent(false), + ]); + + return ['secret' => '', 'id' => '', 'expiration' => 0]; + } + + return ['secret' => $subs['clientState'], 'id' => $subs['id'], 'expiration' => $expiration->getTimestamp()]; + } +} diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/LocationConverter.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/LocationConverter.php new file mode 100644 index 000000000..396dfd931 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/LocationConverter.php @@ -0,0 +1,53 @@ +addressConverter = $addressConverter; + } + + public function locationToRemote(Location $location): array + { + $results = []; + + if ($location->hasAddress()) { + $results['address'] = $this->addressConverter->addressToRemote($location->getAddress()); + + if ($location->getAddress()->hasAddressReference() && $location->getAddress()->getAddressReference()->hasPoint()) { + $results['coordinates'] = [ + 'latitude' => $location->getAddress()->getAddressReference()->getPoint()->getLat(), + 'longitude' => $location->getAddress()->getAddressReference()->getPoint()->getLon(), + ]; + } + } + + if (null !== $location->getName()) { + $results['displayName'] = $location->getName(); + } + + return $results; + } +} diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MSGraphUserRepository.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MSGraphUserRepository.php new file mode 100644 index 000000000..c523a1e92 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MSGraphUserRepository.php @@ -0,0 +1,84 @@ +'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/MachineHttpClient.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MachineHttpClient.php new file mode 100644 index 000000000..cc1692fb7 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MachineHttpClient.php @@ -0,0 +1,82 @@ +decoratedClient = $decoratedClient ?? \Symfony\Component\HttpClient\HttpClient::create(); + $this->machineTokenStorage = $machineTokenStorage; + } + + /** + * @throws \Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface + * @throws LogicException if method is not supported + */ + public function request(string $method, string $url, array $options = []): ResponseInterface + { + $options['headers'] = array_merge( + $options['headers'] ?? [], + $this->getAuthorizationHeaders($this->machineTokenStorage->getToken()) + ); + $options['base_uri'] = 'https://graph.microsoft.com/v1.0/'; + + switch ($method) { + case 'GET': + case 'HEAD': + case 'DELETE': + $options['headers']['Accept'] = 'application/json'; + + break; + + case 'POST': + case 'PUT': + case 'PATCH': + $options['headers']['Content-Type'] = 'application/json'; + + break; + + default: + throw new LogicException("Method not supported: {$method}"); + } + + return $this->decoratedClient->request($method, $url, $options); + } + + 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 new file mode 100644 index 000000000..ac62d44ac --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MachineTokenStorage.php @@ -0,0 +1,57 @@ +azure = $azure; + $this->chillRedis = $chillRedis; + } + + public function getToken(): AccessTokenInterface + { + if (null === $this->accessToken || $this->accessToken->hasExpired()) { + $this->accessToken = $this->azure->getAccessToken('client_credentials', [ + 'scope' => 'https://graph.microsoft.com/.default', + ]); + } + + return $this->accessToken; + } + + public function storeToken(AccessToken $token): void + { + $this->chillRedis->set(self::KEY, serialize($token)); + } +} diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MapCalendarToUser.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MapCalendarToUser.php new file mode 100644 index 000000000..563bd3a38 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MapCalendarToUser.php @@ -0,0 +1,210 @@ +machineHttpClient = $machineHttpClient; + $this->logger = $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::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]; + } + + public function getCalendarId(User $user): ?string + { + if (null === $msKey = ($user->getAttributes()[self::METADATA_KEY] ?? null)) { + return null; + } + + return $msKey['defaultCalendarId'] ?? null; + } + + public function getDefaultUserCalendar(string $idOrUserPrincipalName): ?array + { + try { + $value = $this->machineHttpClient->request('GET', "users/{$idOrUserPrincipalName}/calendars", [ + 'query' => ['$filter' => 'isDefaultCalendar eq true'], + ])->toArray()['value']; + } catch (ClientExceptionInterface $e) { + $this->logger->error('[MapCalendarToUser] Error while listing calendars for a user', [ + 'http_status_code' => $e->getResponse()->getStatusCode(), + 'id_user' => $idOrUserPrincipalName, + ]); + + return null; + } + + return $value[0] ?? null; + } + + 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::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]; + } + + public function getUserByEmail(string $email): ?array + { + $value = $this->machineHttpClient->request('GET', 'users', [ + 'query' => ['$filter' => "mail eq '{$email}'"], + ])->toArray()['value']; + + return $value[0] ?? null; + } + + public function getUserId(User $user): ?string + { + if (null === $msKey = ($user->getAttributes()[self::METADATA_KEY] ?? null)) { + return null; + } + + return $msKey['id'] ?? null; + } + + public function hasActiveSubscription(User $user): bool + { + if (!array_key_exists(self::METADATA_KEY, $user->getAttributes())) { + return false; + } + + 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(); + } + + public function hasSubscriptionSecret(User $user): bool + { + if (!array_key_exists(self::METADATA_KEY, $user->getAttributes())) { + return false; + } + + return array_key_exists(self::SECRET_SUBSCRIPTION_EVENT, $user->getAttributes()[self::METADATA_KEY]); + } + + public function hasUserId(User $user): bool + { + if (null === $user->getEmail() || '' === $user->getEmail()) { + return false; + } + + if (!array_key_exists(self::METADATA_KEY, $user->getAttributes())) { + return false; + } + + return array_key_exists('id', $user->getAttributes()[self::METADATA_KEY]); + } + + public function writeMetadata(User $user): User + { + if (null === $user->getEmail() || '' === $user->getEmail()) { + return $user; + } + + if (null === $userData = $this->getUserByEmail($user->getEmailCanonical())) { + $this->logger->warning('[MapCalendarToUser] could not find user on msgraph', ['userId' => $user->getId(), 'email' => $user->getEmailCanonical()]); + + return $this->writeNullData($user); + } + + if (null === $defaultCalendar = $this->getDefaultUserCalendar($userData['id'])) { + $this->logger->warning('[MapCalendarToUser] could not find default calendar', ['userId' => $user->getId(), 'email' => $user->getEmailCanonical()]); + + return $this->writeNullData($user); + } + + return $user->setAttributes([self::METADATA_KEY => [ + 'id' => $userData['id'], + 'userPrincipalName' => $userData['userPrincipalName'], + 'defaultCalendarId' => $defaultCalendar['id'], + ]]); + } + + /** + * @param int $expiration the expiration time as unix timestamp + */ + public function writeSubscriptionMetadata( + User $user, + int $expiration, + ?string $id = null, + ?string $secret = null + ): void { + $user->setAttributeByDomain(self::METADATA_KEY, self::EXPIRATION_SUBSCRIPTION_EVENT, $expiration); + + if (null !== $id) { + $user->setAttributeByDomain(self::METADATA_KEY, self::ID_SUBSCRIPTION_EVENT, $id); + } + + if (null !== $secret) { + $user->setAttributeByDomain(self::METADATA_KEY, self::SECRET_SUBSCRIPTION_EVENT, $secret); + } + } + + private function writeNullData(User $user): User + { + return $user->unsetAttribute(self::METADATA_KEY); + } +} diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/OnBehalfOfUserHttpClient.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/OnBehalfOfUserHttpClient.php new file mode 100644 index 000000000..9777bf4c0 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/OnBehalfOfUserHttpClient.php @@ -0,0 +1,77 @@ +decoratedClient = $decoratedClient ?? \Symfony\Component\HttpClient\HttpClient::create(); + $this->tokenStorage = $tokenStorage; + } + + public function request(string $method, string $url, array $options = []): ResponseInterface + { + $options['headers'] = array_merge( + $options['headers'] ?? [], + $this->getAuthorizationHeaders($this->tokenStorage->getToken()) + ); + $options['base_uri'] = 'https://graph.microsoft.com/v1.0/'; + + switch ($method) { + case 'GET': + case 'HEAD': + $options['headers']['Accept'] = 'application/json'; + + break; + + case 'POST': + case 'PUT': + case 'PATCH': + $options['headers']['Content-Type'] = 'application/json'; + + break; + + default: + throw new LogicException("Method not supported: {$method}"); + } + + return $this->decoratedClient->request($method, $url, $options); + } + + 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 new file mode 100644 index 000000000..28f68e0e9 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/OnBehalfOfUserTokenStorage.php @@ -0,0 +1,72 @@ +azure = $azure; + $this->session = $session; + } + + public function getToken(): AccessToken + { + /** @var ?AccessToken $token */ + $token = $this->session->get(self::MS_GRAPH_ACCESS_TOKEN, null); + + if (null === $token) { + throw new LogicException('unexisting token'); + } + + if ($token->hasExpired()) { + $token = $this->azure->getAccessToken('refresh_token', [ + 'refresh_token' => $token->getRefreshToken(), + ]); + + $this->setToken($token); + } + + return $token; + } + + public function hasToken(): bool + { + return $this->session->has(self::MS_GRAPH_ACCESS_TOKEN); + } + + public function setToken(AccessToken $token): void + { + $this->session->set(self::MS_GRAPH_ACCESS_TOKEN, $token); + } +} diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/RemoteEventConverter.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/RemoteEventConverter.php new file mode 100644 index 000000000..176dbe0b0 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/RemoteEventConverter.php @@ -0,0 +1,280 @@ +engine = $engine; + $this->locationConverter = $locationConverter; + $this->logger = $logger; + $this->translator = $translator; + $this->personRender = $personRender; + $this->defaultDateTimeZone = (new DateTimeImmutable())->getTimezone(); + $this->remoteDateTimeZone = self::getRemoteTimeZone(); + } + + /** + * Transform a CalendarRange into a representation suitable for storing into MSGraph. + * + * @return array an array representation for event in MS Graph + */ + public function calendarRangeToEvent(CalendarRange $calendarRange): array + { + return [ + 'subject' => $this->translator->trans('remote_calendar.calendar_range_title'), + 'start' => [ + 'dateTime' => $calendarRange->getStartDate()->setTimezone($this->remoteDateTimeZone) + ->format(self::REMOTE_DATE_FORMAT), + 'timeZone' => 'UTC', + ], + 'end' => [ + 'dateTime' => $calendarRange->getEndDate()->setTimezone($this->remoteDateTimeZone) + ->format(self::REMOTE_DATE_FORMAT), + 'timeZone' => 'UTC', + ], + 'attendees' => [ + [ + 'emailAddress' => [ + 'address' => $calendarRange->getUser()->getEmailCanonical(), + 'name' => $calendarRange->getUser()->getLabel(), + ], + ], + ], + 'isReminderOn' => false, + 'location' => $this->locationConverter->locationToRemote($calendarRange->getLocation()), + ]; + } + + public function calendarToEvent(Calendar $calendar): array + { + $result = array_merge( + [ + 'subject' => '[Chill] ' . + implode( + ', ', + $calendar->getPersons()->map(fn (Person $p) => $this->personRender->renderString($p, []))->toArray() + ), + 'start' => [ + 'dateTime' => $calendar->getStartDate()->setTimezone($this->remoteDateTimeZone) + ->format(self::REMOTE_DATE_FORMAT), + 'timeZone' => 'UTC', + ], + 'end' => [ + 'dateTime' => $calendar->getEndDate()->setTimezone($this->remoteDateTimeZone) + ->format(self::REMOTE_DATE_FORMAT), + 'timeZone' => 'UTC', + ], + 'allowNewTimeProposals' => false, + 'transactionId' => 'calendar_' . $calendar->getId(), + 'body' => [ + 'contentType' => 'text', + 'content' => $this->engine->render( + '@ChillCalendar/MSGraph/calendar_event_body.html.twig', + ['calendar' => $calendar] + ), + ], + 'responseRequested' => true, + 'isReminderOn' => false, + ], + $this->calendarToEventAttendeesOnly($calendar) + ); + + if ($calendar->hasLocation()) { + $result['location'] = $this->locationConverter->locationToRemote($calendar->getLocation()); + } + + return $result; + } + + public function calendarToEventAttendeesOnly(Calendar $calendar): array + { + return [ + 'attendees' => $calendar->getInvites()->map( + fn (Invite $i) => $this->buildInviteToAttendee($i) + )->toArray(), + ]; + } + + public function convertAvailabilityToRemoteEvent(array $event): RemoteEvent + { + $startDate = + 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) + ->setTimezone($this->defaultDateTimeZone); + + return new RemoteEvent( + uniqid('generated_'), + $this->translator->trans('remote_ms_graph.freebusy_statuses.' . $event['status']), + '', + $startDate, + $endDate + ); + } + + public static function convertStringDateWithoutTimezone(string $date): DateTimeImmutable + { + $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}"); + } + + return $d->setTimezone((new DateTimeImmutable())->getTimezone()); + } + + public static function convertStringDateWithTimezone(string $date): DateTimeImmutable + { + $d = DateTimeImmutable::createFromFormat(self::REMOTE_DATETIMEZONE_FORMAT, $date); + + if (false === $d) { + throw new RuntimeException("could not convert string date to datetime: {$date}"); + } + + $d->setTimezone((new DateTimeImmutable())->getTimezone()); + + return $d; + } + + public function convertToRemote(array $event): RemoteEvent + { + $startDate = + 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) + ->setTimezone($this->defaultDateTimeZone); + + return new RemoteEvent( + $event['id'], + $event['subject'], + '', + $startDate, + $endDate, + $event['isAllDay'] + ); + } + + public function getLastModifiedDate(array $event): DateTimeImmutable + { + $date = DateTimeImmutable::createFromFormat(self::REMOTE_DATETIMEZONE_FORMAT, $event['lastModifiedDateTime']); + + if (false === $date) { + $date = DateTimeImmutable::createFromFormat(self::REMOTE_DATETIMEZONE_FORMAT_ALT, $event['lastModifiedDateTime']); + } + + if (false === $date) { + $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 + )); + } + + return $date; + } + + /** + * Return a string which format a DateTime to string. To be used in POST requests,. + */ + public static function getRemoteDateTimeSimpleFormat(): string + { + return 'Y-m-d\TH:i:s'; + } + + public static function getRemoteTimeZone(): DateTimeZone + { + return new DateTimeZone('UTC'); + } + + private function buildInviteToAttendee(Invite $invite): array + { + return [ + 'emailAddress' => [ + 'address' => $invite->getUser()->getEmail(), + 'name' => $invite->getUser()->getLabel(), + ], + 'type' => 'Required', + ]; + } +} diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/RemoteToLocalSync/CalendarRangeSyncer.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/RemoteToLocalSync/CalendarRangeSyncer.php new file mode 100644 index 000000000..d05adfed7 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/RemoteToLocalSync/CalendarRangeSyncer.php @@ -0,0 +1,110 @@ +em = $em; + $this->logger = $logger; + $this->machineHttpClient = $machineHttpClient; + } + + public function handleCalendarRangeSync(CalendarRange $calendarRange, array $notification, User $user): void + { + switch ($notification['changeType']) { + case 'deleted': + // test if the notification is not linked to a Calendar + if (null !== $calendarRange->getCalendar()) { + return; + } + $calendarRange->preventEnqueueChanges = true; + + $this->logger->info(self::class . ' remove a calendar range because deleted on remote calendar'); + $this->em->remove($calendarRange); + + break; + + case 'updated': + try { + $new = $this->machineHttpClient->request( + 'GET', + $notification['resource'] + )->toArray(); + } catch (ClientExceptionInterface $clientException) { + $this->logger->warning(self::class . ' could not retrieve event from ms graph. Already deleted ?', [ + 'calendarRangeId' => $calendarRange->getId(), + 'remoteEventId' => $notification['resource'], + ]); + + throw $clientException; + } + + $lastModified = RemoteEventConverter::convertStringDateWithTimezone($new['lastModifiedDateTime']); + + if ($calendarRange->getRemoteAttributes()['lastModifiedDateTime'] === $lastModified->getTimestamp()) { + $this->logger->info(self::class . ' change key is equals. Source is probably a local update', [ + 'calendarRangeId' => $calendarRange->getId(), + 'remoteEventId' => $notification['resource'], + ]); + + return; + } + + $startDate = RemoteEventConverter::convertStringDateWithoutTimezone($new['start']['dateTime']); + $endDate = RemoteEventConverter::convertStringDateWithoutTimezone($new['end']['dateTime']); + + $calendarRange + ->setStartDate($startDate)->setEndDate($endDate) + ->addRemoteAttributes([ + 'lastModifiedDateTime' => $lastModified->getTimestamp(), + 'changeKey' => $new['changeKey'], + ]) + ->preventEnqueueChanges = true; + + break; + + default: + 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 new file mode 100644 index 000000000..b3febf4d4 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/RemoteToLocalSync/CalendarSyncer.php @@ -0,0 +1,189 @@ +logger = $logger; + $this->machineHttpClient = $machineHttpClient; + $this->userRepository = $userRepository; + } + + public function handleCalendarSync(Calendar $calendar, array $notification, User $user): void + { + switch ($notification['changeType']) { + case 'deleted': + $this->handleDeleteCalendar($calendar, $notification, $user); + + break; + + case 'updated': + $this->handleUpdateCalendar($calendar, $notification, $user); + + break; + + default: + throw new RuntimeException('this change type is not supported: ' . $notification['changeType']); + } + } + + private function handleDeleteCalendar(Calendar $calendar, array $notification, User $user): void + { + $calendar + ->setStatus(Calendar::STATUS_CANCELED) + ->setCalendarRange(null); + $calendar->preventEnqueueChanges = true; + } + + private function handleUpdateCalendar(Calendar $calendar, array $notification, User $user): void + { + try { + $new = $this->machineHttpClient->request( + 'GET', + $notification['resource'] + )->toArray(); + } catch (ClientExceptionInterface $clientException) { + $this->logger->warning(self::class . ' could not retrieve event from ms graph. Already deleted ?', [ + 'calendarId' => $calendar->getId(), + 'remoteEventId' => $notification['resource'], + ]); + + throw $clientException; + } + + if (false === $new['isOrganizer']) { + return; + } + + $lastModified = RemoteEventConverter::convertStringDateWithTimezone( + $new['lastModifiedDateTime'] + ); + + if ($calendar->getRemoteAttributes()['lastModifiedDateTime'] === $lastModified->getTimestamp()) { + $this->logger->info(self::class . ' change key is equals. Source is probably a local update', [ + 'calendarRangeId' => $calendar->getId(), + 'remoteEventId' => $notification['resource'], + ]); + + return; + } + + $this->syncAttendees($calendar, $new['attendees']); + + $startDate = RemoteEventConverter::convertStringDateWithoutTimezone($new['start']['dateTime']); + $endDate = RemoteEventConverter::convertStringDateWithoutTimezone($new['end']['dateTime']); + + if ($startDate->getTimestamp() !== $calendar->getStartDate()->getTimestamp()) { + $calendar->setStartDate($startDate); + } + + if ($endDate->getTimestamp() !== $calendar->getEndDate()->getTimestamp()) { + $calendar->setEndDate($endDate); + } + + $calendar + ->addRemoteAttributes([ + 'lastModifiedDateTime' => $lastModified->getTimestamp(), + 'changeKey' => $new['changeKey'], + ]) + ->preventEnqueueChanges = true; + } + + private function syncAttendees(Calendar $calendar, array $attendees): void + { + $emails = []; + + foreach ($attendees as $attendee) { + $status = $attendee['status']['response']; + + if ('organizer' === $status) { + continue; + } + + $email = $attendee['emailAddress']['address']; + $emails[] = strtolower($email); + $user = $this->userRepository->findOneByUsernameOrEmail($email); + + if (null === $user) { + continue; + } + + if (!$calendar->isInvited($user)) { + $calendar->addUser($user); + } + + $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; + } + } + + foreach ($calendar->getUsers() as $user) { + 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 new file mode 100644 index 000000000..0e3beff5f --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraphRemoteCalendarConnector.php @@ -0,0 +1,760 @@ +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 + { + $userId = $this->mapCalendarToUser->getUserId($user); + + if (null === $userId) { + return 0; + } + + try { + $data = $this->userHttpClient->request( + 'GET', + 'users/' . $userId . '/calendarView', + [ + 'query' => [ + 'startDateTime' => $startDate->setTimezone(RemoteEventConverter::getRemoteTimeZone())->format(RemoteEventConverter::getRemoteDateTimeSimpleFormat()), + 'endDateTime' => $endDate->setTimezone(RemoteEventConverter::getRemoteTimeZone())->format(RemoteEventConverter::getRemoteDateTimeSimpleFormat()), + '$count' => 'true', + '$top' => 0, + ], + ] + )->toArray(); + } catch (ClientExceptionInterface $e) { + if (403 === $e->getResponse()->getStatusCode()) { + return count($this->getScheduleTimesForUser($user, $startDate, $endDate)); + } + + $this->logger->error('Could not get list of event on MSGraph', [ + 'error_code' => $e->getResponse()->getStatusCode(), + 'error' => $e->getResponse()->getInfo(), + ]); + + return 0; + } + + return $data['@odata.count']; + } + + public function getMakeReadyResponse(string $returnPath): Response + { + return new RedirectResponse($this->urlGenerator + ->generate('chill_calendar_remote_connect_azure', ['returnPath' => $returnPath])); + } + + public function isReady(): bool + { + $user = $this->security->getUser(); + + if (!$user instanceof User) { + // this is not a user from chill. This is not the role of this class to + // restrict access, so we will just say that we do not have to do anything more + // here... + return true; + } + + if (null === $this->mapCalendarToUser->getUserId($user)) { + // this user is not mapped with remote calendar. The user will have to wait for + // the next calendar subscription iteration + $this->logger->debug('mark user ready for msgraph calendar as he does not have any mapping', [ + 'userId' => $user->getId(), + ]); + return true; + } + + return $this->tokenStorage->hasToken(); + } + + /** + * @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 + { + $userId = $this->mapCalendarToUser->getUserId($user); + + if (null === $userId) { + return []; + } + + try { + $bareEvents = $this->userHttpClient->request( + 'GET', + 'users/' . $userId . '/calendarView', + [ + 'query' => [ + 'startDateTime' => $startDate->setTimezone(RemoteEventConverter::getRemoteTimeZone())->format(RemoteEventConverter::getRemoteDateTimeSimpleFormat()), + 'endDateTime' => $endDate->setTimezone(RemoteEventConverter::getRemoteTimeZone())->format(RemoteEventConverter::getRemoteDateTimeSimpleFormat()), + '$select' => 'id,subject,start,end,isAllDay', + '$top' => $limit, + '$skip' => $offset, + ], + ] + )->toArray(); + + $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( + fn ($item) => $this->remoteEventConverter->convertToRemote($item), + // filter all event to keep only the one not in range + array_filter( + $bareEvents['value'], + static fn ($item) => ((!$existingIdsInRange[$item['id']]) ?? true) && ((!$existingIdsInCalendar[$item['id']]) ?? true) + ) + ) + ); + } catch (ClientExceptionInterface $e) { + if (403 === $e->getResponse()->getStatusCode()) { + return $this->getScheduleTimesForUser($user, $startDate, $endDate); + } + + $this->logger->error('Could not get list of event on MSGraph', [ + 'error_code' => $e->getResponse()->getStatusCode(), + 'error' => $e->getResponse()->getInfo(), + ]); + + return []; + } + } + + public function removeCalendar(string $remoteId, array $remoteAttributes, User $user, ?CalendarRange $associatedCalendarRange = null): void + { + if ('' === $remoteId) { + return; + } + + $this->removeEvent($remoteId, $user); + + if (null !== $associatedCalendarRange) { + $this->syncCalendarRange($associatedCalendarRange); + } + } + + public function removeCalendarRange(string $remoteId, array $remoteAttributes, User $user): void + { + if ('' === $remoteId) { + return; + } + + $this->removeEvent($remoteId, $user); + } + + public function syncCalendar(Calendar $calendar, string $action, ?CalendarRange $previousCalendarRange, ?User $previousMainUser, ?array $oldInvites, ?array $newInvites): void + { + /* + * cases to support: + * + * * a calendar range is created: + * * create on remote + * * if calendar range is associated: remove the range + * * a Calendar change the CalendarRange: + * * re-create the previous calendar range; + * * remove the current calendar range + * * a calendar change the mainUser + * * cancel the calendar in the previous mainUser + * * recreate the previous calendar range in the previousMainUser, if any + * * delete the current calendar range in the current mainUser, if any + * * create the calendar in the current mainUser + * + */ + + if (!$calendar->hasRemoteId()) { + $this->createCalendarOnRemote($calendar); + } else { + if (null !== $previousMainUser) { + // cancel event in previousMainUserCalendar + $this->cancelOnRemote( + $calendar->getRemoteId(), + $this->translator->trans('remote_ms_graph.cancel_event_because_main_user_is_%label%', ['%label%' => $calendar->getMainUser()]), + $previousMainUser, + 'calendar_' . $calendar->getRemoteId() + ); + $this->createCalendarOnRemote($calendar); + } else { + $this->patchCalendarOnRemote($calendar, $newInvites); + } + } + + if ($calendar->hasCalendarRange() && $calendar->getCalendarRange()->hasRemoteId()) { + $this->removeEvent( + $calendar->getCalendarRange()->getRemoteId(), + $calendar->getCalendarRange()->getUser() + ); + + $calendar->getCalendarRange() + ->addRemoteAttributes([ + 'lastModifiedDateTime' => null, + 'changeKey' => null, + 'previousId' => $calendar->getCalendarRange()->getRemoteId(), + ]) + ->setRemoteId(''); + } + + if (null !== $previousCalendarRange) { + $this->createRemoteCalendarRange($previousCalendarRange); + } + } + + public function syncCalendarRange(CalendarRange $calendarRange): void + { + if ($calendarRange->hasRemoteId()) { + $this->updateRemoteCalendarRange($calendarRange); + } else { + $this->createRemoteCalendarRange($calendarRange); + } + } + + public function syncInvite(Invite $invite): void + { + if ('' === $remoteId = $invite->getCalendar()->getRemoteId()) { + return; + } + + if (null === $invite->getUser()) { + return; + } + + if (null === $userId = $this->mapCalendarToUser->getUserId($invite->getUser())) { + return; + } + + if ($invite->hasRemoteId()) { + $remoteIdAttendeeCalendar = $invite->getRemoteId(); + } else { + $remoteIdAttendeeCalendar = $this->findRemoteIdOnUserCalendar($invite->getCalendar(), $invite->getUser()); + $invite->setRemoteId($remoteIdAttendeeCalendar); + } + + switch ($invite->getStatus()) { + case Invite::PENDING: + return; + + case Invite::ACCEPTED: + $url = "/v1.0/users/{$userId}/calendar/events/{$remoteIdAttendeeCalendar}/accept"; + + break; + + case Invite::TENTATIVELY_ACCEPTED: + $url = "/v1.0/users/{$userId}/calendar/events/{$remoteIdAttendeeCalendar}/tentativelyAccept"; + + break; + + case Invite::DECLINED: + $url = "/v1.0/users/{$userId}/calendar/events/{$remoteIdAttendeeCalendar}/decline"; + + break; + + default: + throw new Exception('not supported'); + } + + try { + $this->machineHttpClient->request( + 'POST', + $url, + ['json' => ['sendResponse' => true]] + ); + } catch (ClientExceptionInterface $e) { + $this->logger->warning('could not update calendar range to remote', [ + 'exception' => $e->getTraceAsString(), + 'content' => $e->getResponse()->getContent(), + 'calendarRangeId' => 'invite_' . $invite->getId(), + ]); + + throw $e; + } + } + + private function cancelOnRemote(string $remoteId, string $comment, User $user, string $identifier): void + { + $userId = $this->mapCalendarToUser->getUserId($user); + + if (null === $userId) { + return; + } + + try { + $this->machineHttpClient->request( + 'POST', + "users/{$userId}/calendar/events/{$remoteId}/cancel", + [ + 'json' => ['Comment' => $comment], + ] + ); + } catch (ClientExceptionInterface $e) { + $this->logger->warning('could not update calendar range to remote', [ + 'exception' => $e->getTraceAsString(), + 'content' => $e->getResponse()->getContent(), + 'calendarRangeId' => $identifier, + ]); + + throw $e; + } + } + + private function createCalendarOnRemote(Calendar $calendar): void + { + $eventData = $this->remoteEventConverter->calendarToEvent($calendar); + + [ + 'id' => $id, + 'lastModifiedDateTime' => $lastModified, + 'changeKey' => $changeKey + ] = $this->createOnRemote($eventData, $calendar->getMainUser(), 'calendar_' . $calendar->getId()); + + if (null === $id) { + return; + } + + $calendar + ->setRemoteId($id) + ->addRemoteAttributes([ + 'lastModifiedDateTime' => $lastModified, + 'changeKey' => $changeKey, + ]); + } + + /** + * @param string $identifier an identifier for logging in case of something does not work + * + * @return array{?id: string, ?lastModifiedDateTime: int, ?changeKey: string} + */ + private function createOnRemote(array $eventData, User $user, string $identifier): array + { + $userId = $this->mapCalendarToUser->getUserId($user); + + if (null === $userId) { + $this->logger->warning('user does not have userId nor calendarId', [ + 'user_id' => $user->getId(), + 'calendar_identifier' => $identifier, + ]); + + return ['id' => null, 'lastModifiedDateTime' => null, 'changeKey' => null]; + } + + try { + $event = $this->machineHttpClient->request( + 'POST', + 'users/' . $userId . '/calendar/events', + [ + 'json' => $eventData, + ] + )->toArray(); + } catch (ClientExceptionInterface $e) { + $this->logger->warning('could not save calendar range to remote', [ + 'exception' => $e->getTraceAsString(), + 'content' => $e->getResponse()->getContent(), + 'calendar_identifier' => $identifier, + ]); + + throw $e; + } + + return [ + 'id' => $event['id'], + 'lastModifiedDateTime' => $this->remoteEventConverter->getLastModifiedDate($event)->getTimestamp(), + 'changeKey' => $event['changeKey'], + ]; + } + + private function createRemoteCalendarRange(CalendarRange $calendarRange): void + { + $userId = $this->mapCalendarToUser->getUserId($calendarRange->getUser()); + + if (null === $userId) { + $this->logger->warning('user does not have userId nor calendarId', [ + 'user_id' => $calendarRange->getUser()->getId(), + 'calendar_range_id' => $calendarRange->getId(), + ]); + + return; + } + + $eventData = $this->remoteEventConverter->calendarRangeToEvent($calendarRange); + + [ + 'id' => $id, + 'lastModifiedDateTime' => $lastModified, + 'changeKey' => $changeKey + ] = $this->createOnRemote( + $eventData, + $calendarRange->getUser(), + 'calendar_range_' . $calendarRange->getId() + ); + + $calendarRange->setRemoteId($id) + ->addRemoteAttributes([ + 'lastModifiedDateTime' => $lastModified, + 'changeKey' => $changeKey, + ]); + } + + /** + * the remoteId is not the same across different user calendars. This method allow to find + * the correct remoteId in another calendar. + * + * For achieving this, the iCalUid is used. + */ + private function findRemoteIdOnUserCalendar(Calendar $calendar, User $user): ?string + { + // find the icalUid on original user + $event = $this->getOnRemote($calendar->getMainUser(), $calendar->getRemoteId()); + $userId = $this->mapCalendarToUser->getUserId($user); + + if ('' === $iCalUid = ($event['iCalUId'] ?? '')) { + throw new Exception('no iCalUid for this event'); + } + + try { + $events = $this->machineHttpClient->request( + 'GET', + "/v1.0/users/{$userId}/calendar/events", + [ + 'query' => [ + '$select' => 'id', + '$filter' => "iCalUId eq '{$iCalUid}'", + ], + ] + )->toArray(); + } catch (ClientExceptionInterface $clientException) { + throw $clientException; + } + + if (1 !== count($events['value'])) { + throw new Exception('multiple events found with same iCalUid'); + } + + return $events['value'][0]['id']; + } + + private function getOnRemote(User $user, string $remoteId): array + { + $userId = $this->mapCalendarToUser->getUserId($user); + + if (null === $userId) { + 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 + )->toArray(); + } catch (ClientExceptionInterface $e) { + $this->logger->warning('Could not get event from calendar', [ + 'remoteId' => $remoteId, + ]); + + throw $e; + } + } + + private function getScheduleTimesForUser(User $user, DateTimeImmutable $startDate, DateTimeImmutable $endDate): array + { + $userId = $this->mapCalendarToUser->getUserId($user); + + if (array_key_exists($userId, $this->cacheScheduleTimeForUser)) { + return $this->cacheScheduleTimeForUser[$userId]; + } + + if (null === $userId) { + return []; + } + + if (null === $user->getEmailCanonical() || '' === $user->getEmailCanonical()) { + return []; + } + + $body = [ + 'schedules' => [$user->getEmailCanonical()], + 'startTime' => [ + 'dateTime' => ($startDate->setTimezone(RemoteEventConverter::getRemoteTimeZone())->format(RemoteEventConverter::getRemoteDateTimeSimpleFormat())), + 'timeZone' => 'UTC', + ], + 'endTime' => [ + 'dateTime' => ($endDate->setTimezone(RemoteEventConverter::getRemoteTimeZone())->format(RemoteEventConverter::getRemoteDateTimeSimpleFormat())), + 'timeZone' => 'UTC', + ], + ]; + + try { + $response = $this->userHttpClient->request('POST', 'users/' . $userId . '/calendar/getSchedule', [ + 'json' => $body, + ])->toArray(); + } catch (ClientExceptionInterface $e) { + $this->logger->debug('Could not get schedule on MSGraph', [ + 'error_code' => $e->getResponse()->getStatusCode(), + 'error' => $e->getResponse()->getInfo(), + ]); + + return []; + } + + $this->cacheScheduleTimeForUser[$userId] = array_map( + fn ($item) => $this->remoteEventConverter->convertAvailabilityToRemoteEvent($item), + $response['value'][0]['scheduleItems'] + ); + + return $this->cacheScheduleTimeForUser[$userId]; + } + + private function patchCalendarOnRemote(Calendar $calendar, array $newInvites): void + { + $eventDatas = []; + $eventDatas[] = $this->remoteEventConverter->calendarToEvent($calendar); + + 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); + } + + foreach ($eventDatas as $eventData) { + [ + 'id' => $id, + 'lastModifiedDateTime' => $lastModified, + 'changeKey' => $changeKey + ] = $this->patchOnRemote( + $calendar->getRemoteId(), + $eventData, + $calendar->getMainUser(), + 'calendar_' . $calendar->getId() + ); + + $calendar->addRemoteAttributes([ + 'lastModifiedDateTime' => $lastModified, + 'changeKey' => $changeKey, + ]); + } + } + + /** + * @param string $identifier an identifier for logging in case of something does not work + * + * @return array{?id: string, ?lastModifiedDateTime: int, ?changeKey: string} + */ + private function patchOnRemote(string $remoteId, array $eventData, User $user, string $identifier): array + { + $userId = $this->mapCalendarToUser->getUserId($user); + + if (null === $userId) { + $this->logger->warning('user does not have userId nor calendarId', [ + 'user_id' => $user->getId(), + 'calendar_identifier' => $identifier, + ]); + + return ['id' => null, 'lastModifiedDateTime' => null, 'changeKey' => null]; + } + + try { + $event = $this->machineHttpClient->request( + 'PATCH', + 'users/' . $userId . '/calendar/events/' . $remoteId, + [ + 'json' => $eventData, + ] + )->toArray(); + } catch (ClientExceptionInterface $e) { + $this->logger->warning('could not update calendar range to remote', [ + 'exception' => $e->getTraceAsString(), + 'calendarRangeId' => $identifier, + ]); + + throw $e; + } + + return [ + 'id' => $event['id'], + 'lastModifiedDateTime' => $this->remoteEventConverter->getLastModifiedDate($event)->getTimestamp(), + 'changeKey' => $event['changeKey'], + ]; + } + + private function removeEvent($remoteId, User $user): void + { + $userId = $this->mapCalendarToUser->getUserId($user); + + try { + $this->machineHttpClient->request( + 'DELETE', + 'users/' . $userId . '/calendar/events/' . $remoteId + ); + } catch (ClientExceptionInterface $e) { + $this->logger->warning('could not remove event from calendar', [ + 'event_remote_id' => $remoteId, + 'user_id' => $user->getId(), + ]); + } + } + + private function updateRemoteCalendarRange(CalendarRange $calendarRange): void + { + $userId = $this->mapCalendarToUser->getUserId($calendarRange->getUser()); + $calendarId = $this->mapCalendarToUser->getCalendarId($calendarRange->getUser()); + + if (null === $userId || null === $calendarId) { + $this->logger->warning('user does not have userId nor calendarId', [ + 'user_id' => $calendarRange->getUser()->getId(), + 'calendar_range_id' => $calendarRange->getId(), + ]); + + return; + } + + try { + $event = $this->machineHttpClient->request( + 'GET', + 'users/' . $userId . '/calendar/events/' . $calendarRange->getRemoteId() + )->toArray(); + } catch (ClientExceptionInterface $e) { + $this->logger->warning('Could not get event from calendar', [ + 'calendar_range_id' => $calendarRange->getId(), + 'calendar_range_remote_id' => $calendarRange->getRemoteId(), + ]); + + throw $e; + } + + if ($this->remoteEventConverter->getLastModifiedDate($event)->getTimestamp() > $calendarRange->getUpdatedAt()->getTimestamp()) { + $this->logger->info('Skip updating as the lastModified date seems more fresh than the database one', [ + 'calendar_range_id' => $calendarRange->getId(), + 'calendar_range_remote_id' => $calendarRange->getRemoteId(), + 'db_last_updated' => $calendarRange->getUpdatedAt()->getTimestamp(), + 'remote_last_updated' => $this->remoteEventConverter->getLastModifiedDate($event)->getTimestamp(), + ]); + + return; + } + + $eventData = $this->remoteEventConverter->calendarRangeToEvent($calendarRange); + + try { + $event = $this->machineHttpClient->request( + 'PATCH', + 'users/' . $userId . '/calendar/events/' . $calendarRange->getRemoteId(), + [ + 'json' => $eventData, + ] + )->toArray(); + } catch (ClientExceptionInterface $e) { + $this->logger->warning('could not update calendar range to remote', [ + 'exception' => $e->getTraceAsString(), + 'calendarRangeId' => $calendarRange->getId(), + ]); + + throw $e; + } + + $calendarRange + ->addRemoteAttributes([ + 'lastModifiedDateTime' => $this->remoteEventConverter->getLastModifiedDate($event)->getTimestamp(), + 'changeKey' => $event['changeKey'], + ]); + } +} diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/NullRemoteCalendarConnector.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/NullRemoteCalendarConnector.php new file mode 100644 index 000000000..211810abf --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/NullRemoteCalendarConnector.php @@ -0,0 +1,70 @@ + $oldInvites + */ + public function syncCalendar(Calendar $calendar, string $action, ?CalendarRange $previousCalendarRange, ?User $previousMainUser, ?array $oldInvites, ?array $newInvites): void; + + public function syncCalendarRange(CalendarRange $calendarRange): void; + + public function syncInvite(Invite $invite): void; +} diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/DependencyInjection/RemoteCalendarCompilerPass.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/DependencyInjection/RemoteCalendarCompilerPass.php new file mode 100644 index 000000000..f56735de7 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/DependencyInjection/RemoteCalendarCompilerPass.php @@ -0,0 +1,82 @@ +getParameter('chill_calendar'); + $connector = null; + + if (!$config['remote_calendars_sync']['enabled']) { + $connector = NullRemoteCalendarConnector::class; + } + + if ($config['remote_calendars_sync']['microsoft_graph']['enabled']) { + $connector = MSGraphRemoteCalendarConnector::class; + + $container->setAlias(HttpClientInterface::class . ' $machineHttpClient', MachineHttpClient::class); + } else { + // remove services which cannot be loaded + $container->removeDefinition(MapAndSubscribeUserCalendarCommand::class); + $container->removeDefinition(AzureGrantAdminConsentAndAcquireToken::class); + $container->removeDefinition(RemoteCalendarConnectAzureController::class); + $container->removeDefinition(MachineTokenStorage::class); + $container->removeDefinition(MachineHttpClient::class); + $container->removeDefinition(MSGraphRemoteCalendarConnector::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) { + if ($connector === $serviceId) { + $container->getDefinition($serviceId) + ->setDecoratedService(RemoteCalendarConnectorInterface::class); + } else { + // keep the container lighter by removing definitions + if ($container->hasDefinition($serviceId)) { + $container->removeDefinition($serviceId); + } + } + } + } +} diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Model/RemoteEvent.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Model/RemoteEvent.php new file mode 100644 index 000000000..13c4a1c6a --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Model/RemoteEvent.php @@ -0,0 +1,62 @@ +id = $id; + $this->title = $title; + $this->description = $description; + $this->startDate = $startDate; + $this->endDate = $endDate; + $this->isAllDay = $isAllDay; + } +} diff --git a/src/Bundle/ChillCalendarBundle/Repository/CalendarACLAwareRepository.php b/src/Bundle/ChillCalendarBundle/Repository/CalendarACLAwareRepository.php new file mode 100644 index 000000000..7c6ef4e13 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Repository/CalendarACLAwareRepository.php @@ -0,0 +1,230 @@ +accompanyingPeriodACLAwareRepository = $accompanyingPeriodACLAwareRepository; + $this->em = $em; + } + + public function buildQueryByAccompanyingPeriod(AccompanyingPeriod $period, ?DateTimeImmutable $startDate, ?DateTimeImmutable $endDate): QueryBuilder + { + $qb = $this->em->createQueryBuilder(); + $qb->from(Calendar::class, 'c'); + + $andX = $qb->expr()->andX($qb->expr()->eq('c.accompanyingPeriod', ':period')); + $qb->setParameter('period', $period); + + if (null !== $startDate) { + $andX->add($qb->expr()->gte('c.startDate', ':startDate')); + $qb->setParameter('startDate', $startDate); + } + + if (null !== $endDate) { + $andX->add($qb->expr()->lte('c.endDate', ':endDate')); + $qb->setParameter('endDate', $endDate); + } + + $qb->where($andX); + + return $qb; + } + + public function buildQueryByAccompanyingPeriodIgnoredByDates(AccompanyingPeriod $period, ?DateTimeImmutable $startDate, ?DateTimeImmutable $endDate): QueryBuilder + { + $qb = $this->em->createQueryBuilder(); + $qb->from(Calendar::class, 'c'); + + $andX = $qb->expr()->andX($qb->expr()->eq('c.accompanyingPeriod', ':period')); + $qb->setParameter('period', $period); + + if (null !== $startDate) { + $andX->add($qb->expr()->lt('c.startDate', ':startDate')); + $qb->setParameter('startDate', $startDate); + } + + if (null !== $endDate) { + $andX->add($qb->expr()->gt('c.endDate', ':endDate')); + $qb->setParameter('endDate', $endDate); + } + + $qb->where($andX); + + return $qb; + } + + /** + * Base implementation. The list of allowed accompanying period is retrieved "manually" from @see{AccompanyingPeriodACLAwareRepository}. + */ + public function buildQueryByPerson(Person $person, ?DateTimeImmutable $startDate, ?DateTimeImmutable $endDate): QueryBuilder + { + $qb = $this->em->createQueryBuilder() + ->from(Calendar::class, 'c'); + + $this->addQueryByPersonWithoutDate($qb, $person); + + // filter by date + if (null !== $startDate) { + $qb->andWhere($qb->expr()->gte('c.startDate', ':startDate')) + ->setParameter('startDate', $startDate); + } + + if (null !== $endDate) { + $qb->andWhere($qb->expr()->lte('c.endDate', ':endDate')) + ->setParameter('endDate', $endDate); + } + + return $qb; + } + + /** + * Base implementation. The list of allowed accompanying period is retrieved "manually" from @see{AccompanyingPeriodACLAwareRepository}. + */ + public function buildQueryByPersonIgnoredByDates(Person $person, ?DateTimeImmutable $startDate, ?DateTimeImmutable $endDate): QueryBuilder + { + $qb = $this->em->createQueryBuilder() + ->from(Calendar::class, 'c'); + + $this->addQueryByPersonWithoutDate($qb, $person); + + // filter by date + if (null !== $startDate) { + $qb->andWhere($qb->expr()->lt('c.startDate', ':startDate')) + ->setParameter('startDate', $startDate); + } + + if (null !== $endDate) { + $qb->andWhere($qb->expr()->gt('c.endDate', ':endDate')) + ->setParameter('endDate', $endDate); + } + + return $qb; + } + + 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 + { + return $this->buildQueryByPerson($person, $startDate, $endDate) + ->select('COUNT(c)') + ->getQuery() + ->getSingleScalarResult(); + } + + 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 + { + return $this->buildQueryByPersonIgnoredByDates($person, $startDate, $endDate) + ->select('COUNT(c)') + ->getQuery() + ->getSingleScalarResult(); + } + + /** + * @return array|Calendar[] + */ + 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); + } + + if (null !== $offset) { + $qb->setFirstResult($offset); + } + + if (null !== $limit) { + $qb->setMaxResults($limit); + } + + return $qb->getQuery()->getResult(); + } + + 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); + } + + if (null !== $offset) { + $qb->setFirstResult($offset); + } + + if (null !== $limit) { + $qb->setMaxResults($limit); + } + + return $qb->getQuery()->getResult(); + } + + private function addQueryByPersonWithoutDate(QueryBuilder $qb, Person $person): void + { + // find the reachable accompanying periods for person + $periods = $this->accompanyingPeriodACLAwareRepository->findByPerson($person, AccompanyingPeriodVoter::SEE); + + $qb + ->where( + $qb->expr()->orX( + // the calendar where the person is the main person: + $qb->expr()->eq('c.person', ':person'), + // when the calendar is in a reachable period, and contains person + $qb->expr()->andX( + $qb->expr()->in('c.accompanyingPeriod', ':periods'), + $qb->expr()->isMemberOf(':person', 'c.persons') + ) + ) + ) + ->setParameter('person', $person) + ->setParameter('periods', $periods); + } +} diff --git a/src/Bundle/ChillCalendarBundle/Repository/CalendarACLAwareRepositoryInterface.php b/src/Bundle/ChillCalendarBundle/Repository/CalendarACLAwareRepositoryInterface.php new file mode 100644 index 000000000..70fb02590 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Repository/CalendarACLAwareRepositoryInterface.php @@ -0,0 +1,63 @@ +repository = $entityManager->getRepository($this->getClassName()); + } + + public function find($id): ?CalendarDoc + { + return $this->repository->find($id); + } + + public function findAll(): array + { + return $this->repository->findAll(); + } + + public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null) + { + return $this->repository->findBy($criteria, $orderBy, $limit, $offset); + } + + public function findOneBy(array $criteria): ?CalendarDoc + { + return $this->findOneBy($criteria); + } + + public function getClassName() + { + return CalendarDoc::class; + } +} diff --git a/src/Bundle/ChillCalendarBundle/Repository/CalendarDocRepositoryInterface.php b/src/Bundle/ChillCalendarBundle/Repository/CalendarDocRepositoryInterface.php new file mode 100644 index 000000000..d2b1951df --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Repository/CalendarDocRepositoryInterface.php @@ -0,0 +1,33 @@ +em = $entityManager; + $this->repository = $entityManager->getRepository(CalendarRange::class); } - // /** - // * @return CalendarRange[] Returns an array of CalendarRange objects - // */ - /* - public function findByExampleField($value) + public function countByAvailableRangesForUser(User $user, DateTimeImmutable $from, DateTimeImmutable $to): int { - return $this->createQueryBuilder('c') - ->andWhere('c.exampleField = :val') - ->setParameter('val', $value) - ->orderBy('c.id', 'ASC') - ->setMaxResults(10) - ->getQuery() - ->getResult() - ; + return $this->buildQueryAvailableRangesForUser($user, $from, $to) + ->select('COUNT(cr)') + ->getQuery()->getSingleScalarResult(); } - */ - /* - public function findOneBySomeField($value): ?CalendarRange + public function find($id): ?CalendarRange { - return $this->createQueryBuilder('c') - ->andWhere('c.exampleField = :val') - ->setParameter('val', $value) - ->getQuery() - ->getOneOrNullResult() - ; + return $this->repository->find($id); } + + /** + * @return array|CalendarRange[] */ + public function findAll(): array + { + return $this->repository->findAll(); + } + + /** + * @return array|CalendarRange[] + */ + public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null) + { + return $this->repository->findBy($criteria, $orderBy, $limit, $offset); + } + + /** + * @return array|CalendarRange[] + */ + public function findByAvailableRangesForUser( + User $user, + DateTimeImmutable $from, + DateTimeImmutable $to, + ?int $limit = null, + ?int $offset = null + ): array { + $qb = $this->buildQueryAvailableRangesForUser($user, $from, $to); + + if (null !== $limit) { + $qb->setMaxResults($limit); + } + + if (null !== $offset) { + $qb->setFirstResult($offset); + } + + return $qb->getQuery()->getResult(); + } + + public function findOneBy(array $criteria): ?CalendarRange + { + return $this->repository->findOneBy($criteria); + } + + /** + * Given a list of remote ids, return an array where + * keys are the remoteIds, and value is a boolean, true if the + * id is present in database. + * + * @param array|list $remoteIds + * + * @return array + */ + public function findRemoteIdsPresent(array $remoteIds): array + { + if (0 === count($remoteIds)) { + return []; + } + + $sql = 'SELECT + sq.remoteId as remoteid, + EXISTS (SELECT 1 FROM chill_calendar.calendar_range cr WHERE cr.remoteId = sq.remoteId) AS present + FROM + ( + VALUES %remoteIds% + ) AS sq(remoteId); + '; + + $remoteIdsStr = implode( + ', ', + array_fill(0, count($remoteIds), '((?))') + ); + + $rsm = new ResultSetMapping(); + $rsm + ->addScalarResult('remoteid', 'remoteId', Types::STRING) + ->addScalarResult('present', 'present', Types::BOOLEAN); + + $rows = $this->em + ->createNativeQuery( + strtr($sql, ['%remoteIds%' => $remoteIdsStr]), + $rsm + ) + ->setParameters(array_values($remoteIds)) + ->getResult(); + + $results = []; + + foreach ($rows as $r) { + $results[$r['remoteId']] = $r['present']; + } + + return $results; + } + + public function getClassName(): string + { + return CalendarRange::class; + } + + private function buildQueryAvailableRangesForUser(User $user, DateTimeImmutable $from, DateTimeImmutable $to): QueryBuilder + { + $qb = $this->repository->createQueryBuilder('cr'); + + $qb->leftJoin('cr.calendar', 'calendar'); + + return $qb + ->where( + $qb->expr()->andX( + $qb->expr()->eq('cr.user', ':user'), + $qb->expr()->gte('cr.startDate', ':startDate'), + $qb->expr()->lte('cr.endDate', ':endDate'), + $qb->expr()->isNull('calendar') + ) + ) + ->setParameters([ + 'user' => $user, + 'startDate' => $from, + 'endDate' => $to, + ]); + } } diff --git a/src/Bundle/ChillCalendarBundle/Repository/CalendarRepository.php b/src/Bundle/ChillCalendarBundle/Repository/CalendarRepository.php index 55fba5f80..4fd9b8a29 100644 --- a/src/Bundle/ChillCalendarBundle/Repository/CalendarRepository.php +++ b/src/Bundle/ChillCalendarBundle/Repository/CalendarRepository.php @@ -1,63 +1,231 @@ repository = $entityManager->getRepository(AccompanyingPeriodWork::class); + $this->repository = $entityManager->getRepository(Calendar::class); + $this->em = $entityManager; } - // /** - // * @return Calendar[] Returns an array of Calendar objects - // */ - /* - public function findByExampleField($value) + public function countByAccompanyingPeriod(AccompanyingPeriod $period): int { - return $this->createQueryBuilder('c') - ->andWhere('c.exampleField = :val') - ->setParameter('val', $value) - ->orderBy('c.id', 'ASC') - ->setMaxResults(10) + return $this->repository->count(['accompanyingPeriod' => $period]); + } + + public function countByUser(User $user, DateTimeImmutable $from, DateTimeImmutable $to): int + { + return $this->buildQueryByUser($user, $from, $to) + ->select('COUNT(c)') ->getQuery() - ->getResult() - ; + ->getSingleScalarResult(); } - */ - /* - public function findOneBySomeField($value): ?Calendar + public function createQueryBuilder(string $alias, ?string $indexBy = null): QueryBuilder { - return $this->createQueryBuilder('c') - ->andWhere('c.exampleField = :val') - ->setParameter('val', $value) - ->getQuery() - ->getOneOrNullResult() - ; + return $this->repository->createQueryBuilder($alias, $indexBy); } + + public function find($id): ?Calendar + { + return $this->repository->find($id); + } + + /** + * @return array|Calendar[] */ + public function findAll(): array + { + return $this->repository->findAll(); + } + + /** + * @return array|Calendar[] + */ + public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array + { + return $this->repository->findBy($criteria, $orderBy, $limit, $offset); + } + + /** + * @return array|Calendar[] + */ + public function findByAccompanyingPeriod(AccompanyingPeriod $period, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array + { + return $this->findBy( + [ + 'accompanyingPeriod' => $period, + ], + $orderBy, + $limit, + $orderBy + ); + } + + public function findByNotificationAvailable(DateTimeImmutable $startDate, DateTimeImmutable $endDate, ?int $limit = null, ?int $offset = null): array + { + $qb = $this->queryByNotificationAvailable($startDate, $endDate)->select('c'); + + if (null !== $limit) { + $qb->setMaxResults($limit); + } + + if (null !== $offset) { + $qb->setFirstResult($offset); + } + + return $qb->getQuery()->getResult(); + } + + /** + * @return array|Calendar[] + */ + public function findByUser(User $user, DateTimeImmutable $from, DateTimeImmutable $to, ?int $limit = null, ?int $offset = null): array + { + $qb = $this->buildQueryByUser($user, $from, $to)->select('c'); + + if (null !== $limit) { + $qb->setMaxResults($limit); + } + + if (null !== $offset) { + $qb->setFirstResult($offset); + } + + return $qb->getQuery()->getResult(); + } + + public function findOneBy(array $criteria): ?Calendar + { + return $this->repository->findOneBy($criteria); + } + + /** + * Given a list of remote ids, return an array where + * keys are the remoteIds, and value is a boolean, true if the + * id is present in database. + * + * @param array|list $remoteIds + * + * @return array + */ + public function findRemoteIdsPresent(array $remoteIds): array + { + if (0 === count($remoteIds)) { + return []; + } + + $remoteIdsStr = implode( + ', ', + array_fill(0, count($remoteIds), '((?))') + ); + + $sql = "SELECT + sq.remoteId as remoteid, + EXISTS (SELECT 1 FROM chill_calendar.calendar c WHERE c.remoteId = sq.remoteId) AS present + FROM + ( + VALUES {$remoteIdsStr} + ) AS sq(remoteId); + "; + + $rsm = new ResultSetMapping(); + $rsm + ->addScalarResult('remoteid', 'remoteId', Types::STRING) + ->addScalarResult('present', 'present', Types::BOOLEAN); + + $rows = $this->em + ->createNativeQuery( + $sql, + $rsm + ) + ->setParameters(array_values($remoteIds)) + ->getResult(); + + $results = []; + + foreach ($rows as $r) { + $results[$r['remoteId']] = $r['present']; + } + + return $results; + } + + public function getClassName() + { + return Calendar::class; + } + + private function buildQueryByUser(User $user, DateTimeImmutable $from, DateTimeImmutable $to): QueryBuilder + { + $qb = $this->repository->createQueryBuilder('c'); + + return $qb + ->where( + $qb->expr()->andX( + $qb->expr()->eq('c.mainUser', ':user'), + $qb->expr()->gte('c.startDate', ':startDate'), + $qb->expr()->lte('c.endDate', ':endDate'), + ) + ) + ->setParameters([ + 'user' => $user, + 'startDate' => $from, + 'endDate' => $to, + ]); + } + + private function queryByNotificationAvailable(DateTimeImmutable $startDate, DateTimeImmutable $endDate): QueryBuilder + { + $qb = $this->repository->createQueryBuilder('c'); + + $qb->where( + $qb->expr()->andX( + $qb->expr()->eq('c.sendSMS', ':true'), + $qb->expr()->gte('c.startDate', ':startDate'), + $qb->expr()->lt('c.startDate', ':endDate'), + $qb->expr()->orX( + $qb->expr()->eq('c.smsStatus', ':pending'), + $qb->expr()->eq('c.smsStatus', ':cancel_pending') + ) + ) + ); + + $qb->setParameters([ + 'true' => true, + 'startDate' => $startDate, + 'endDate' => $endDate, + 'pending' => Calendar::SMS_PENDING, + 'cancel_pending' => Calendar::SMS_CANCEL_PENDING, + ]); + + return $qb; + } } diff --git a/src/Bundle/ChillCalendarBundle/Repository/CancelReasonRepository.php b/src/Bundle/ChillCalendarBundle/Repository/CancelReasonRepository.php index 47e0bfa44..102882c9e 100644 --- a/src/Bundle/ChillCalendarBundle/Repository/CancelReasonRepository.php +++ b/src/Bundle/ChillCalendarBundle/Repository/CancelReasonRepository.php @@ -1,14 +1,14 @@ entityRepository = $em->getRepository(Invite::class); } - // /** - // * @return Invite[] Returns an array of Invite objects - // */ - /* - public function findByExampleField($value) + public function find($id): ?Invite { - return $this->createQueryBuilder('i') - ->andWhere('i.exampleField = :val') - ->setParameter('val', $value) - ->orderBy('i.id', 'ASC') - ->setMaxResults(10) - ->getQuery() - ->getResult() - ; + return $this->entityRepository->find($id); } - */ - /* - public function findOneBySomeField($value): ?Invite - { - return $this->createQueryBuilder('i') - ->andWhere('i.exampleField = :val') - ->setParameter('val', $value) - ->getQuery() - ->getOneOrNullResult() - ; - } + /** + * @return array|Invite[] */ + public function findAll(): array + { + return $this->entityRepository->findAll(); + } + + /** + * @return array|Invite[] + */ + public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null) + { + return $this->entityRepository->findBy($criteria, $orderBy, $limit, $offset); + } + + public function findOneBy(array $criteria): ?Invite + { + return $this->entityRepository->findOneBy($criteria); + } + + public function getClassName(): string + { + return Invite::class; + } } diff --git a/src/Bundle/ChillCalendarBundle/Resources/config/services.yml b/src/Bundle/ChillCalendarBundle/Resources/config/services.yml index c41c2ad93..7e316acf3 100644 --- a/src/Bundle/ChillCalendarBundle/Resources/config/services.yml +++ b/src/Bundle/ChillCalendarBundle/Resources/config/services.yml @@ -3,12 +3,41 @@ services: Chill\CalendarBundle\Repository\: autowire: true + autoconfigure: true resource: '../../Repository/' - tags: - - { name: 'doctrine.repository_service' } Chill\CalendarBundle\Menu\: autowire: true autoconfigure: true resource: '../../Menu/' tags: ['chill.menu_builder'] + + Chill\CalendarBundle\Command\: + autowire: true + autoconfigure: true + resource: '../../Command/' + + Chill\CalendarBundle\Messenger\: + autowire: true + autoconfigure: true + resource: '../../Messenger/' + + Chill\CalendarBundle\Command\AzureGrantAdminConsentAndAcquireToken: + autoconfigure: true + autowire: true + arguments: + $azure: '@knpu.oauth2.provider.azure' + tags: ['console.command'] + + Chill\CalendarBundle\Security\: + autoconfigure: true + autowire: true + resource: '../../Security/' + + Chill\CalendarBundle\Service\: + autoconfigure: true + autowire: true + resource: '../../Service/' + + Chill\CalendarBundle\Service\ShortMessageForCalendarBuilderInterface: + alias: Chill\CalendarBundle\Service\DefaultShortMessageForCalendarBuider diff --git a/src/Bundle/ChillCalendarBundle/Resources/config/services/event.yml b/src/Bundle/ChillCalendarBundle/Resources/config/services/event.yml index 348677174..365d8c2da 100644 --- a/src/Bundle/ChillCalendarBundle/Resources/config/services/event.yml +++ b/src/Bundle/ChillCalendarBundle/Resources/config/services/event.yml @@ -7,4 +7,37 @@ services: name: 'doctrine.orm.entity_listener' event: 'postPersist' entity: 'Chill\ActivityBundle\Entity\Activity' - \ No newline at end of file + + Chill\CalendarBundle\Messenger\Doctrine\CalendarRangeEntityListener: + autowire: true + autoconfigure: true + tags: + - + name: 'doctrine.orm.entity_listener' + event: 'postPersist' + entity: 'Chill\CalendarBundle\Entity\CalendarRange' + - + name: 'doctrine.orm.entity_listener' + event: 'postUpdate' + entity: 'Chill\CalendarBundle\Entity\CalendarRange' + - + name: 'doctrine.orm.entity_listener' + event: 'postRemove' + entity: 'Chill\CalendarBundle\Entity\CalendarRange' + + Chill\CalendarBundle\Messenger\Doctrine\CalendarEntityListener: + autowire: true + autoconfigure: true + tags: + - + name: 'doctrine.orm.entity_listener' + event: 'postPersist' + entity: 'Chill\CalendarBundle\Entity\Calendar' + - + name: 'doctrine.orm.entity_listener' + event: 'postUpdate' + entity: 'Chill\CalendarBundle\Entity\Calendar' + - + name: 'doctrine.orm.entity_listener' + event: 'postRemove' + entity: 'Chill\CalendarBundle\Entity\Calendar' diff --git a/src/Bundle/ChillCalendarBundle/Resources/config/services/exports.yaml b/src/Bundle/ChillCalendarBundle/Resources/config/services/exports.yaml new file mode 100644 index 000000000..3eb4bbfdc --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/config/services/exports.yaml @@ -0,0 +1,118 @@ +services: + + ## Indicators + chill.calendar.export.count_calendars: + class: Chill\CalendarBundle\Export\Export\CountCalendars + autowire: true + autoconfigure: true + tags: + - { name: chill.export, alias: count_calendars } + + chill.calendar.export.average_duration_calendars: + class: Chill\CalendarBundle\Export\Export\StatCalendarAvgDuration + autowire: true + autoconfigure: true + tags: + - { name: chill.export, alias: average_duration_calendars } + + chill.calendar.export.sum_duration_calendars: + class: Chill\CalendarBundle\Export\Export\StatCalendarSumDuration + autowire: true + autoconfigure: true + tags: + - { name: chill.export, alias: sum_duration_calendars } + + ## Filters + + chill.calendar.export.agent_filter: + class: Chill\CalendarBundle\Export\Filter\AgentFilter + autowire: true + autoconfigure: true + tags: + - { name: chill.export_filter, alias: agent_filter } + + chill.calendar.export.job_filter: + class: Chill\CalendarBundle\Export\Filter\JobFilter + autowire: true + autoconfigure: true + tags: + - { name: chill.export_filter, alias: job_filter } + + chill.calendar.export.scope_filter: + class: Chill\CalendarBundle\Export\Filter\ScopeFilter + autowire: true + autoconfigure: true + tags: + - { name: chill.export_filter, alias: scope_filter } + + chill.calendar.export.between_dates_filter: + class: Chill\CalendarBundle\Export\Filter\BetweenDatesFilter + autowire: true + autoconfigure: true + tags: + - { name: chill.export_filter, alias: between_dates_filter } + + chill.calendar.export.calendar_range_filter: + class: Chill\CalendarBundle\Export\Filter\CalendarRangeFilter + autowire: true + autoconfigure: true + tags: + - { name: chill.export_filter, alias: calendar_range_filter } + + ## Aggregator + + chill.calendar.export.agent_aggregator: + class: Chill\CalendarBundle\Export\Aggregator\AgentAggregator + autowire: true + autoconfigure: true + tags: + - { name: chill.export_aggregator, alias: agent_aggregator } + + chill.calendar.export.job_aggregator: + class: Chill\CalendarBundle\Export\Aggregator\JobAggregator + autowire: true + autoconfigure: true + tags: + - { name: chill.export_aggregator, alias: job_aggregator } + + chill.calendar.export.scope_aggregator: + class: Chill\CalendarBundle\Export\Aggregator\ScopeAggregator + autowire: true + autoconfigure: true + tags: + - { name: chill.export_aggregator, alias: scope_aggregator } + + chill.calendar.export.location_type_aggregator: + class: Chill\CalendarBundle\Export\Aggregator\LocationTypeAggregator + autowire: true + autoconfigure: true + tags: + - { name: chill.export_aggregator, alias: location_type_aggregator } + + chill.calendar.export.location_aggregator: + class: Chill\CalendarBundle\Export\Aggregator\LocationAggregator + autowire: true + autoconfigure: true + tags: + - { name: chill.export_aggregator, alias: location_aggregator } + + chill.calendar.export.cancel_reason_aggregator: + class: Chill\CalendarBundle\Export\Aggregator\CancelReasonAggregator + autowire: true + autoconfigure: true + tags: + - { name: chill.export_aggregator, alias: cancel_reason_aggregator } + + chill.calendar.export.month_aggregator: + class: Chill\CalendarBundle\Export\Aggregator\MonthYearAggregator + autowire: true + autoconfigure: true + tags: + - { name: chill.export_aggregator, alias: month_aggregator } + + chill.calendar.export.urgency_aggregator: + class: Chill\CalendarBundle\Export\Aggregator\UrgencyAggregator + autowire: true + autoconfigure: true + tags: + - { name: chill.export_aggregator, alias: urgency_aggregator } diff --git a/src/Bundle/ChillCalendarBundle/Resources/config/services/form.yml b/src/Bundle/ChillCalendarBundle/Resources/config/services/form.yml index 85095dc9a..310fce05f 100644 --- a/src/Bundle/ChillCalendarBundle/Resources/config/services/form.yml +++ b/src/Bundle/ChillCalendarBundle/Resources/config/services/form.yml @@ -1,10 +1,6 @@ --- -services: - chill.calendar.form.type.calendar: - class: Chill\CalendarBundle\Form\CalendarType - arguments: - - "@chill.main.helper.translatable_string" - - "@doctrine.orm.entity_manager" - - tags: - - { name: form.type, alias: chill_calendarbundle_calendar } \ No newline at end of file +services: + Chill\CalendarBundle\Form\: + resource: './../../Form' + autowire: true + autoconfigure: true diff --git a/src/Bundle/ChillCalendarBundle/Resources/config/services/remote_calendar.yaml b/src/Bundle/ChillCalendarBundle/Resources/config/services/remote_calendar.yaml new file mode 100644 index 000000000..af55692ca --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/config/services/remote_calendar.yaml @@ -0,0 +1,15 @@ +services: + _defaults: + autoconfigure: true + autowire: true + + Chill\CalendarBundle\RemoteCalendar\Connector\RemoteCalendarConnectorInterface: ~ + + Chill\CalendarBundle\RemoteCalendar\Connector\NullRemoteCalendarConnector: ~ + + Chill\CalendarBundle\RemoteCalendar\Connector\MSGraphRemoteCalendarConnector: ~ + + Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\: + resource: '../../RemoteCalendar/Connector/MSGraph/' + + diff --git a/src/Bundle/ChillCalendarBundle/Resources/public/module/Invite/answer.js b/src/Bundle/ChillCalendarBundle/Resources/public/module/Invite/answer.js new file mode 100644 index 000000000..a97fe584d --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/public/module/Invite/answer.js @@ -0,0 +1,33 @@ + +import { createApp } from 'vue'; +import Answer from 'ChillCalendarAssets/vuejs/Invite/Answer'; +import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n'; + +const i18n = _createI18n({}); + +document.addEventListener('DOMContentLoaded', function (e) { + console.log('dom loaded answer'); + document.querySelectorAll('div[invite-answer]').forEach(function (el) { + console.log('element found', el); + + const app = createApp({ + components: { + Answer, + }, + data() { + return { + status: el.dataset.status, + calendarId: Number.parseInt(el.dataset.calendarId), + } + }, + template: '', + methods: { + onStatusChanged: function(newStatus) { + this.$data.status = newStatus; + }, + } + }); + + app.use(i18n).mount(el); + }); +}); diff --git a/src/Bundle/ChillCalendarBundle/Resources/public/types.ts b/src/Bundle/ChillCalendarBundle/Resources/public/types.ts new file mode 100644 index 000000000..c18cfea13 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/public/types.ts @@ -0,0 +1,67 @@ +import {EventInput} from '@fullcalendar/core'; +import {DateTime, Location, User, UserAssociatedInterface} from '../../../ChillMainBundle/Resources/public/types' ; +import {Person} from "../../../ChillPersonBundle/Resources/public/types"; + +export interface CalendarRange { + id: number; + endDate: DateTime; + startDate: DateTime; + user: User; + location: Location; + createdAt: DateTime; + createdBy: User; + updatedAt: DateTime; + updatedBy: User; +} + +export interface CalendarRangeCreate { + user: UserAssociatedInterface; + startDate: DateTime; + endDate: DateTime; + location: Location; +} + +export interface CalendarRangeEdit { + startDate?: DateTime, + endDate?: DateTime + location?: Location; +} + +export interface Calendar { + id: number; +} + +export interface CalendarLight { + id: number; + endDate: DateTime; + startDate: DateTime; + mainUser: User; + persons: Person[]; + status: "valid" | "moved" | "canceled"; +} + +export interface CalendarRemote { + id: number; + endDate: DateTime; + startDate: DateTime; + title: string; + isAllDay: boolean; +} + +export type EventInputCalendarRange = EventInput & { + id: string, + userId: number, + userLabel: string, + calendarRangeId: number, + locationId: number, + locationName: string, + start: string, + end: string, + is: "range" +}; + +export function isEventInputCalendarRange(toBeDetermined: EventInputCalendarRange | EventInput): toBeDetermined is EventInputCalendarRange { + return typeof toBeDetermined.is === "string" && toBeDetermined.is === "range"; +} + +export {}; diff --git a/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/Calendar/App.vue b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/Calendar/App.vue index 2741f8b8f..e09175a4b 100644 --- a/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/Calendar/App.vue +++ b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/Calendar/App.vue @@ -1,156 +1,301 @@ + diff --git a/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/Calendar/Components/CalendarActive.vue b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/Calendar/Components/CalendarActive.vue new file mode 100644 index 000000000..cd6e52815 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/Calendar/Components/CalendarActive.vue @@ -0,0 +1,80 @@ + + + + + diff --git a/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/Calendar/api.ts b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/Calendar/api.ts new file mode 100644 index 000000000..17ef256ad --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/Calendar/api.ts @@ -0,0 +1,38 @@ +import {fetchResults} from '../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods'; +import {datetimeToISO} from '../../../../../ChillMainBundle/Resources/public/chill/js/date'; +import {User} from '../../../../../ChillMainBundle/Resources/public/types'; +import {CalendarLight, CalendarRange, CalendarRemote} from '../../types'; + +// re-export whoami +export {whoami} from "../../../../../ChillMainBundle/Resources/public/lib/api/user"; + +/** + * + * @param user + * @param Date start + * @param Date end + * @return Promise + */ +export const fetchCalendarRangeForUser = (user: User, start: Date, end: Date): Promise => { + const uri = `/api/1.0/calendar/calendar-range-available/${user.id}.json`; + const dateFrom = datetimeToISO(start); + const dateTo = datetimeToISO(end); + + return fetchResults(uri, {dateFrom, dateTo}); +} + +export const fetchCalendarRemoteForUser = (user: User, start: Date, end: Date): Promise => { + const uri = `/api/1.0/calendar/proxy/calendar/by-user/${user.id}/events`; + const dateFrom = datetimeToISO(start); + const dateTo = datetimeToISO(end); + + return fetchResults(uri, {dateFrom, dateTo}); +} + +export const fetchCalendarLocalForUser = (user: User, start: Date, end: Date): Promise => { + const uri = `/api/1.0/calendar/calendar/by-user/${user.id}.json`; + const dateFrom = datetimeToISO(start); + const dateTo = datetimeToISO(end); + + return fetchResults(uri, {dateFrom, dateTo}); +} diff --git a/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/Calendar/const.ts b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/Calendar/const.ts new file mode 100644 index 000000000..05a0d5627 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/Calendar/const.ts @@ -0,0 +1,19 @@ + +const COLORS = [ /* from https://colorbrewer2.org/#type=qualitative&scheme=Set3&n=12 */ + '#8dd3c7', + '#ffffb3', + '#bebada', + '#fb8072', + '#80b1d3', + '#fdb462', + '#b3de69', + '#fccde5', + '#d9d9d9', + '#bc80bd', + '#ccebc5', + '#ffed6f' +]; + +export { + COLORS, +}; diff --git a/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/Calendar/i18n.js b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/Calendar/i18n.js index 3020502e9..502db50b2 100644 --- a/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/Calendar/i18n.js +++ b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/Calendar/i18n.js @@ -1,19 +1,25 @@ -import { personMessages } from 'ChillPersonAssets/vuejs/_js/i18n' -import { calendarUserSelectorMessages } from '../_components/CalendarUserSelector/js/i18n'; -import { activityMessages } from 'ChillActivityAssets/vuejs/Activity/i18n'; +import {personMessages} from 'ChillPersonAssets/vuejs/_js/i18n' +import {calendarUserSelectorMessages} from '../_components/CalendarUserSelector/js/i18n'; +import {activityMessages} from 'ChillActivityAssets/vuejs/Activity/i18n'; const appMessages = { - fr: { - choose_your_date: "Sélectionnez votre plage", - activity: { - add_persons: "Ajouter des personnes concernées", - bloc_persons: "Usagers", - bloc_persons_associated: "Usagers du parcours", - bloc_persons_not_associated: "Tiers non-pro.", - bloc_thirdparty: "Tiers professionnels", - bloc_users: "T(M)S", - } - } + fr: { + choose_your_date: "Sélectionnez votre plage", + activity: { + add_persons: "Ajouter des personnes concernées", + bloc_persons: "Usagers", + bloc_persons_associated: "Usagers du parcours", + bloc_persons_not_associated: "Tiers non-pro.", + bloc_thirdparty: "Tiers professionnels", + bloc_users: "T(M)S", + }, + this_calendar_range_will_change_main_user: "Cette plage de disponibilité n'est pas celle de l'utilisateur principal. Si vous continuez, l'utilisateur principal sera adapté. Êtes-vous sûr·e ?", + will_change_main_user_for_me: "Vous ne pouvez pas écrire dans le calendrier d'un autre utilisateur. Voulez-vous être l'utilisateur principal de ce rendez-vous ?", + main_user_is_mandatory: "L'utilisateur principal est requis. Vous pouvez le modifier, mais pas le supprimer", + change_main_user_will_reset_event_data: "Modifier l'utilisateur principal nécessite de choisir une autre plage de disponibilité ou un autre horaire. Ces informations seront perdues. Êtes-vous sûr·e de vouloir continuer ?", + list_three_days: 'Liste 3 jours', + current_selected: 'Rendez-vous fixé', + } } Object.assign(appMessages.fr, personMessages.fr); @@ -21,5 +27,5 @@ Object.assign(appMessages.fr, calendarUserSelectorMessages.fr); Object.assign(appMessages.fr, activityMessages.fr); export { - appMessages + appMessages }; diff --git a/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/Calendar/store.js b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/Calendar/store.js deleted file mode 100644 index db2950bd4..000000000 --- a/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/Calendar/store.js +++ /dev/null @@ -1,228 +0,0 @@ -import 'es6-promise/auto'; -import { createStore } from 'vuex'; -import { postLocation } from 'ChillActivityAssets/vuejs/Activity/api'; -import { - getLocations, getLocationTypeByDefaultFor, - getUserCurrentLocation -} from "../../../../../ChillActivityBundle/Resources/public/vuejs/Activity/api"; - -const debug = process.env.NODE_ENV !== 'production'; - -const addIdToValue = (string, id) => { - let array = string ? string.split(',') : []; - array.push(id.toString()); - let str = array.join(); - return str; -}; - -const removeIdFromValue = (string, id) => { - let array = string.split(','); - array = array.filter(el => el !== id.toString()); - let str = array.join(); - return str; -}; - -/* -* Assign missing keys for the ConcernedGroups component -*/ -const mapEntity = (entity) => { - Object.assign(entity, {thirdParties: entity.professionals, users: entity.invites}); - return entity; -}; - -const store = createStore({ - strict: debug, - state: { - activity: mapEntity(window.entity), // activity is the calendar entity actually - currentEvent: null - }, - getters: { - suggestedEntities(state) { - if (typeof(state.activity.accompanyingPeriod) === 'undefined') { - return []; - } - const allEntities = [ - ...store.getters.suggestedPersons, - ...store.getters.suggestedRequestor, - ...store.getters.suggestedUser, - ...store.getters.suggestedResources - ]; - const uniqueIds = [...new Set(allEntities.map(i => `${i.type}-${i.id}`))]; - return Array.from(uniqueIds, id => allEntities.filter(r => `${r.type}-${r.id}` === id)[0]); - }, - suggestedPersons(state) { - const existingPersonIds = state.activity.persons.map(p => p.id); - return state.activity.accompanyingPeriod.participations - .filter(p => p.endDate === null) - .map(p => p.person) - .filter(p => !existingPersonIds.includes(p.id)) - }, - suggestedRequestor(state) { - const existingPersonIds = state.activity.persons.map(p => p.id); - const existingThirdPartyIds = state.activity.thirdParties.map(p => p.id); - return [state.activity.accompanyingPeriod.requestor] - .filter(r => - (r.type === 'person' && !existingPersonIds.includes(r.id)) || - (r.type === 'thirdparty' && !existingThirdPartyIds.includes(r.id)) - ); - }, - suggestedUser(state) { - const existingUserIds = state.activity.users.map(p => p.id); - return [state.activity.accompanyingPeriod.user] - .filter( - u => !existingUserIds.includes(u.id) - ); - }, - suggestedResources(state) { - const resources = state.activity.accompanyingPeriod.resources; - const existingPersonIds = state.activity.persons.map(p => p.id); - const existingThirdPartyIds = state.activity.thirdParties.map(p => p.id); - return state.activity.accompanyingPeriod.resources - .map(r => r.resource) - .filter(r => - (r.type === 'person' && !existingPersonIds.includes(r.id)) || - (r.type === 'thirdparty' && !existingThirdPartyIds.includes(r.id)) - ); - } - }, - mutations: { - - // ConcernedGroups - addPersonsInvolved(state, payload) { - //console.log('### mutation addPersonsInvolved', payload.result.type); - switch (payload.result.type) { - case 'person': - state.activity.persons.push(payload.result); - break; - case 'thirdparty': - state.activity.thirdParties.push(payload.result); - break; - case 'user': - state.activity.users.push(payload.result); - break; - }; - }, - removePersonInvolved(state, payload) { - //console.log('### mutation removePersonInvolved', payload.type); - switch (payload.type) { - case 'person': - state.activity.persons = state.activity.persons.filter(person => person !== payload); - break; - case 'thirdparty': - state.activity.thirdParties = state.activity.thirdParties.filter(thirdparty => thirdparty !== payload); - break; - case 'user': - state.activity.users = state.activity.users.filter(user => user !== payload); - break; - }; - }, - // Calendar - setEvents(state, payload) { - console.log(payload) - state.currentEvent = {start: payload.start, end: payload.end} - }, - // Location - updateLocation(state, value) { - console.log('### mutation: updateLocation', value); - state.activity.location = value; - } - }, - actions: { - addPersonsInvolved({ commit }, payload) { - console.log('### action addPersonsInvolved', payload.result.type); - switch (payload.result.type) { - case 'person': - let aPersons = document.getElementById("chill_calendarbundle_calendar_persons"); - aPersons.value = addIdToValue(aPersons.value, payload.result.id); - break; - case 'thirdparty': - let aThirdParties = document.getElementById("chill_calendarbundle_calendar_professionals"); - aThirdParties.value = addIdToValue(aThirdParties.value, payload.result.id); - break; - case 'user': - let aUsers = document.getElementById("chill_calendarbundle_calendar_invites"); - aUsers.value = addIdToValue(aUsers.value, payload.result.id); - break; - }; - commit('addPersonsInvolved', payload); - }, - removePersonInvolved({ commit }, payload) { - //console.log('### action removePersonInvolved', payload); - switch (payload.type) { - case 'person': - let aPersons = document.getElementById("chill_calendarbundle_calendar_persons"); - aPersons.value = removeIdFromValue(aPersons.value, payload.id); - break; - case 'thirdparty': - let aThirdParties = document.getElementById("chill_calendarbundle_calendar_professionals"); - aThirdParties.value = removeIdFromValue(aThirdParties.value, payload.id); - break; - case 'user': - let aUsers = document.getElementById("chill_calendarbundle_calendar_invites"); - aUsers.value = removeIdFromValue(aUsers.value, payload.id); - break; - }; - commit('removePersonInvolved', payload); - }, - - // Calendar - createEvent({ commit }, payload) { - console.log('### action createEvent', payload); - let startDateInput = document.getElementById("chill_calendarbundle_calendar_startDate"); - startDateInput.value = payload.startStr; - let endDateInput = document.getElementById("chill_calendarbundle_calendar_endDate"); - endDateInput.value = payload.endStr; - let mainUserInput = document.getElementById("chill_calendarbundle_calendar_mainUser"); - mainUserInput.value = payload.users.logged.id; - commit('setEvents', payload); - }, - updateEvent({ commit }, payload) { - console.log('### action updateEvent', payload); - let startDateInput = document.getElementById("chill_calendarbundle_calendar_startDate"); - startDateInput.value = payload.event.start.toISOString(); - let endDateInput = document.getElementById("chill_calendarbundle_calendar_endDate"); - endDateInput.value = payload.event.end.toISOString(); - let calendarRangeInput = document.getElementById("chill_calendarbundle_calendar_calendarRange"); - calendarRangeInput.value = Number(payload.event.extendedProps.calendarRangeId); - let mainUserInput = document.getElementById("chill_calendarbundle_calendar_mainUser"); - mainUserInput.value = Number(payload.event.source.id); - commit('setEvents', payload); - }, - - // Location - updateLocation({ commit }, value) { - console.log('### action: updateLocation', value); - let hiddenLocation = document.getElementById("chill_calendarbundle_calendar_location"); - if (value.onthefly) { - const body = { - "type": "location", - "name": value.name === '__AccompanyingCourseLocation__' ? null : value.name, - "locationType": { - "id": value.locationType.id, - "type": "location-type" - } - }; - if (value.address.id) { - Object.assign(body, { - "address": { - "id": value.address.id - }, - }) - } - postLocation(body) - .then( - location => hiddenLocation.value = location.id - ).catch( - err => { - console.log(err.message); - } - ); - } else { - hiddenLocation.value = value.id; - } - commit("updateLocation", value); - } - } -}); - -export default store; diff --git a/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/Calendar/store/actions.js b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/Calendar/store/actions.js new file mode 100644 index 000000000..0e4d91afc --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/Calendar/store/actions.js @@ -0,0 +1,243 @@ +import { + addIdToValue, + removeIdFromValue, +} from './utils'; +import { + fetchCalendarRangeForUser, + fetchCalendarRemoteForUser, + fetchCalendarLocalForUser, +} from './../api'; +import {datetimeToISO} from 'ChillMainAssets/chill/js/date'; +import {postLocation} from 'ChillActivityAssets/vuejs/Activity/api'; + +/** + * This will store a unique key for each value, and prevent to launch the same + * request multiple times, when fetching user calendars. + * + * Actually, each time a user is added or removed, the methods "dateSet" is executed and this + * sparkle a request by user to get the calendar data. When the calendar data is fetched, it is + * immediatly added to the calendar which, in turn , launch the event dateSet and re-launch fetch + * queries which has not yet ended. Storing the queries already executed prevent this loop. + * + * @type {Set} + */ +const fetchings = new Set(); + +export default { + setCurrentDatesView({commit, dispatch}, {start, end}) { + commit('setCurrentDatesView', {start, end}); + + return dispatch('fetchCalendarEvents'); + }, + fetchCalendarEvents({state, getters, dispatch}) { + if (state.currentView.start === null && state.currentView.end === null) { + return Promise.resolve(); + } + + let promises = []; + for (const uid of state.currentView.users.keys()) { + let unique = `${uid}, ${state.currentView.start.toISOString()}, ${state.currentView.end.toISOString()}`; + + if (fetchings.has(unique)) { + console.log('prevent from fetching for a user', unique); + continue; + } + + fetchings.add(unique); + + promises.push( + dispatch( + 'fetchCalendarRangeForUser', + {user: state.usersData.get(uid).user, start: state.currentView.start, end: state.currentView.end} + ) + ); + promises.push( + dispatch( + 'fetchCalendarRemotesForUser', + {user: state.usersData.get(uid).user, start: state.currentView.start, end: state.currentView.end} + ) + ); + promises.push( + dispatch( + 'fetchCalendarLocalsForUser', + {user: state.usersData.get(uid).user, start: state.currentView.start, end: state.currentView.end} + ) + ); + } + + return Promise.all(promises); + }, + fetchCalendarRangeForUser({commit, getters}, {user, start, end}) { + if (!getters.isCalendarRangeLoadedForUser({user, start, end})) { + return fetchCalendarRangeForUser(user, start, end).then((ranges) => { + commit('addCalendarRangesForUser', {user, ranges, start, end}); + + return Promise.resolve(); + }); + } + }, + fetchCalendarRemotesForUser({commit, getters}, {user, start, end}) { + if (!getters.isCalendarRemoteLoadedForUser({user, start, end})) { + return fetchCalendarRemoteForUser(user, start, end).then((remotes) => { + commit('addCalendarRemotesForUser', {user, remotes, start, end}); + + return Promise.resolve(); + }); + } + }, + fetchCalendarLocalsForUser({commit, getters}, {user, start, end}) { + if (!getters.isCalendarRemoteLoadedForUser({user, start, end})) { + return fetchCalendarLocalForUser(user, start, end).then((locals) => { + commit('addCalendarLocalsForUser', {user, locals, start, end}); + + return Promise.resolve(); + }); + } + }, + addPersonsInvolved({commit, dispatch}, payload) { + console.log('### action addPersonsInvolved', payload.result.type); + console.log('### action addPersonsInvolved payload result', payload.result); + switch (payload.result.type) { + case 'person': + let aPersons = document.getElementById("chill_activitybundle_activity_persons"); + aPersons.value = addIdToValue(aPersons.value, payload.result.id); + break; + case 'thirdparty': + let aThirdParties = document.getElementById("chill_activitybundle_activity_professionals"); + aThirdParties.value = addIdToValue(aThirdParties.value, payload.result.id); + break; + case 'user': + let aUsers = document.getElementById("chill_activitybundle_activity_users"); + aUsers.value = addIdToValue(aUsers.value, payload.result.id); + commit('showUserOnCalendar', {user: payload.result, ranges: false, remotes: true}); + dispatch('fetchCalendarEvents'); + break; + } + ; + commit('addPersonsInvolved', payload); + }, + removePersonInvolved({commit}, payload) { + //console.log('### action removePersonInvolved', payload); + switch (payload.type) { + case 'person': + let aPersons = document.getElementById("chill_activitybundle_activity_persons"); + aPersons.value = removeIdFromValue(aPersons.value, payload.id); + break; + case 'thirdparty': + let aThirdParties = document.getElementById("chill_activitybundle_activity_professionals"); + aThirdParties.value = removeIdFromValue(aThirdParties.value, payload.id); + break; + case 'user': + let aUsers = document.getElementById("chill_activitybundle_activity_users"); + aUsers.value = removeIdFromValue(aUsers.value, payload.id); + break; + } + ; + commit('removePersonInvolved', payload); + }, + + // Calendar + /** + * set event startDate and endDate. + * + * if the mainUser is different from "me", it will replace the mainUser + * + * @param commit + * @param state + * @param getters + * @param start + * @param end + */ + setEventTimes({commit, state, getters}, {start, end}) { + console.log('### action createEvent', {start, end}); + let startDateInput = document.getElementById("chill_activitybundle_activity_startDate"); + startDateInput.value = null !== start ? datetimeToISO(start) : ''; + let endDateInput = document.getElementById("chill_activitybundle_activity_endDate"); + endDateInput.value = null !== end ? datetimeToISO(end) : ''; + let calendarRangeInput = document.getElementById("chill_activitybundle_activity_calendarRange"); + calendarRangeInput.value = ""; + + if (getters.getMainUser === null || getters.getMainUser.id !== state.me.id) { + let mainUserInput = document.getElementById("chill_activitybundle_activity_mainUser"); + mainUserInput.value = state.me.id; + commit('setMainUser', state.me); + } + + commit('setEventTimes', {start, end}); + }, + associateCalendarToRange({state, commit, dispatch, getters}, {range}) { + console.log('### action associateCAlendarToRange', range); + let startDateInput = document.getElementById("chill_activitybundle_activity_startDate"); + startDateInput.value = null !== range ? datetimeToISO(range.start) : ""; + let endDateInput = document.getElementById("chill_activitybundle_activity_endDate"); + endDateInput.value = null !== range ? datetimeToISO(range.end) : ""; + let calendarRangeInput = document.getElementById("chill_activitybundle_activity_calendarRange"); + calendarRangeInput.value = null !== range ? Number(range.extendedProps.calendarRangeId) : ""; + + if (null !== range) { + let location = getters.getLocationById(range.extendedProps.locationId); + + if (null === location) { + console.error("location not found!", range.extendedProps.locationId); + } + + dispatch('updateLocation', location); + + const userId = range.extendedProps.userId; + if (state.activity.mainUser !== null && state.activity.mainUser.id !== userId) { + dispatch('setMainUser', state.usersData.get(userId).user); + + // TODO: remove persons involved with this user + } + } + + commit('associateCalendarToRange', {range}); + return Promise.resolve(); + }, + setMainUser({commit, dispatch, state}, mainUser) { + console.log('setMainUser', mainUser); + + let mainUserInput = document.getElementById("chill_activitybundle_activity_mainUser"); + mainUserInput.value = Number(mainUser.id); + + return dispatch('associateCalendarToRange', { range: null }).then(() => { + commit('setMainUser', mainUser); + + return dispatch('fetchCalendarEvents'); + }); + }, + + // Location + updateLocation({commit}, value) { + console.log('### action: updateLocation', value); + let hiddenLocation = document.getElementById("chill_activitybundle_activity_location"); + if (value.onthefly) { + const body = { + "type": "location", + "name": value.name === '__AccompanyingCourseLocation__' ? null : value.name, + "locationType": { + "id": value.locationType.id, + "type": "location-type" + } + }; + if (value.address.id) { + Object.assign(body, { + "address": { + "id": value.address.id + }, + }) + } + postLocation(body) + .then( + location => hiddenLocation.value = location.id + ).catch( + err => { + console.log(err.message); + } + ); + } else { + hiddenLocation.value = value.id; + } + commit("updateLocation", value); + } +} diff --git a/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/Calendar/store/getters.js b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/Calendar/store/getters.js new file mode 100644 index 000000000..b9d9b6a82 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/Calendar/store/getters.js @@ -0,0 +1,272 @@ +export default { + /** + * get the main user of the event/Calendar + * + * @param state + * @returns {*|null} + */ + getMainUser(state) { + return state.activity.mainUser || null; + }, + /** + * return the date of the event/Calendar + * + * @param state + * @returns {Date} + */ + getEventDate(state) { + if (null === state.activity.start) { + return new Date(); + } + throw 'transform date to object ?'; + }, + /** + * Compute the event sources to show on the FullCalendar + * + * @param state + * @param getters + * @returns {[]} + */ + getEventSources(state, getters) { + let sources = []; + + // current calendar + if (state.activity.startDate !== null && state.activity.endDate !== null) { + const s = { + id: 'current', + events: [ + { + title: "Rendez-vous", + start: state.activity.startDate, + end: state.activity.endDate, + allDay: false, + is: "current", + classNames: ['iscurrent'], + } + ], + editable: state.activity.calendarRange === null, + }; + + sources.push(s); + } + + for (const [userId, kinds] of state.currentView.users.entries()) { + if (!state.usersData.has(userId)) { + console.log('try to get events on a user which not exists', userId); + continue; + } + + const userData = state.usersData.get(userId); + + if (kinds.ranges && userData.calendarRanges.length > 0) { + const s = { + id: `ranges_${userId}`, + events: userData.calendarRanges.filter(r => state.activity.calendarRange === null || r.calendarRangeId !== state.activity.calendarRange.calendarRangeId), + color: userData.mainColor, + classNames: ['isrange'], + backgroundColor: 'white', + textColor: 'black', + editable: false, + }; + + sources.push(s); + } + + if (kinds.remotes && userData.remotes.length > 0) { + const s = { + 'id': `remote_${userId}`, + events: userData.remotes, + color: userData.mainColor, + textColor: 'black', + editable: false, + }; + + sources.push(s); + } + + // if remotes is checked, we display also the locals calendars + if (kinds.remotes && userData.locals.length > 0) { + const s = { + 'id': `local_${userId}`, + events: userData.locals.filter(l => l.originId !== state.activity.id), + color: userData.mainColor, + textColor: 'black', + editable: false, + }; + + sources.push(s); + } + } + + return sources; + }, + getInitialDate(state) { + return state.activity.startDate; + }, + getInviteForUser: (state) => (user) => { + return state.activity.invites.find(i => i.user.id === user.id); + }, + /** + * get the user data for a specific user + * + * @param state + * @returns {function(*): unknown} + */ + getUserData: (state) => (user) => { + return state.usersData.get(user.id); + }, + getUserDataById: (state) => (userId) => { + return state.usersData.get(userId); + }, + /** + * return true if the user has an entry in userData + * + * @param state + * @returns {function(*): boolean} + */ + hasUserData: (state) => (user) => { + return state.usersData.has(user.id); + }, + hasUserDataById: (state) => (userId) => { + return state.usersData.has(userId); + }, + /** + * return true if there was a fetch query for event between this date (start and end), + * those date are included. + * + * @param state + * @param getters + * @returns {(function({user: *, start: *, end: *}): (boolean))|*} + */ + isCalendarRangeLoadedForUser: (state, getters) => ({user, start, end}) => { + if (!getters.hasUserData(user)) { + return false; + } + + for (let interval of getters.getUserData(user).calendarRangesLoaded) { + if (start >= interval.start && end <= interval.end) { + return true; + } + } + + return false; + }, + /** + * return true if there was a fetch query for event between this date (start and end), + * those date are included. + * + * @param state + * @param getters + * @returns {(function({user: *, start: *, end: *}): (boolean))|*} + */ + isCalendarRemoteLoadedForUser: (state, getters) => ({user, start, end}) => { + if (!getters.hasUserData(user)) { + return false; + } + + for (let interval of getters.getUserData(user).remotesLoaded) { + if (start >= interval.start && end <= interval.end) { + return true; + } + } + + return false; + }, + /** + * return true if the user ranges are shown on calendar + * + * @param state + * @returns boolean + */ + isRangeShownOnCalendarForUser: (state) => (user) => { + const k = state.currentView.users.get(user.id); + if (typeof k === 'undefined') { + console.error('try to determinate if calendar range is shown and user is not in currentView'); + return false; + } + + return k.ranges; + }, + + /** + * return true if the user remote is shown on calendar + * @param state + * @returns boolean + */ + isRemoteShownOnCalendarForUser: (state) => (user) => { + const k = state.currentView.users.get(user.id); + if (typeof k === 'undefined') { + console.error('try to determinate if calendar range is shown and user is not in currentView'); + return false; + } + + return k.remotes; + }, + + getLocationById: (state) => (id) => { + for (let group of state.availableLocations) { + console.log('group', group); + const found = group.locations.find(l => l.id === id); + if (typeof found !== "undefined") { + return found; + } + } + + return null; + }, + + suggestedEntities(state, getters) { + if (typeof (state.activity.accompanyingPeriod) === 'undefined') { + return []; + } + const allEntities = [ + ...getters.suggestedPersons, + ...getters.suggestedRequestor, + ...getters.suggestedUser, + ...getters.suggestedResources + ]; + const uniqueIds = [...new Set(allEntities.map(i => `${i.type}-${i.id}`))]; + return Array.from(uniqueIds, id => allEntities.filter(r => `${r.type}-${r.id}` === id)[0]); + }, + suggestedPersons(state) { + const existingPersonIds = state.activity.persons.map(p => p.id); + return state.activity.accompanyingPeriod.participations + .filter(p => p.endDate === null) + .map(p => p.person) + .filter(p => !existingPersonIds.includes(p.id)) + }, + suggestedRequestor(state) { + if (state.activity.accompanyingPeriod.requestor === null) { + return []; + } + + const existingPersonIds = state.activity.persons.map(p => p.id); + const existingThirdPartyIds = state.activity.thirdParties.map(p => p.id); + return [state.activity.accompanyingPeriod.requestor] + .filter(r => + (r.type === 'person' && !existingPersonIds.includes(r.id)) || + (r.type === 'thirdparty' && !existingThirdPartyIds.includes(r.id)) + ); + }, + suggestedUser(state) { + if (null === state.activity.users) { + return []; + } + const existingUserIds = state.activity.users.map(p => p.id); + return [state.activity.accompanyingPeriod.user] + .filter( + u => u !== null && !existingUserIds.includes(u.id) + ); + }, + suggestedResources(state) { + const resources = state.activity.accompanyingPeriod.resources; + const existingPersonIds = state.activity.persons.map(p => p.id); + const existingThirdPartyIds = state.activity.thirdParties.map(p => p.id); + return state.activity.accompanyingPeriod.resources + .map(r => r.resource) + .filter(r => + (r.type === 'person' && !existingPersonIds.includes(r.id)) || + (r.type === 'thirdparty' && !existingThirdPartyIds.includes(r.id)) + ); + } +} diff --git a/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/Calendar/store/index.js b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/Calendar/store/index.js new file mode 100644 index 000000000..ffb186cdf --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/Calendar/store/index.js @@ -0,0 +1,59 @@ +import 'es6-promise/auto'; +import { createStore } from 'vuex'; +import { postLocation } from 'ChillActivityAssets/vuejs/Activity/api'; +import getters from './getters'; +import actions from './actions'; +import mutations from './mutations'; +import { mapEntity } from './utils'; +import { whoami } from '../api'; +import prepareLocations from "ChillActivityAssets/vuejs/Activity/store.locations"; + +const debug = process.env.NODE_ENV !== 'production'; + +const store = createStore({ + strict: debug, + state: { + activity: mapEntity(window.entity), // activity is the calendar entity actually + currentEvent: null, + availableLocations: [], + /** + * the current user + */ + me: null, + /** + * store information about current view + */ + currentView: { + start: null, + end: null, + users: new Map(), + }, + /** + * store a list of existing event, to avoid storing them twice + */ + existingEvents: new Set(), + /** + * store user data + */ + usersData: new Map(), + }, + getters, + mutations, + actions, +}); + +whoami().then(me => { + store.commit('setWhoAmiI', me); +}); + +if (null !== store.getters.getMainUser) { + store.commit('showUserOnCalendar', {ranges: true, remotes: true, user: store.getters.getMainUser}); +} + +for (let u of store.state.activity.users) { + store.commit('showUserOnCalendar', {ranges: false, remotes: false, user: u}); +} + +prepareLocations(store); + +export default store; diff --git a/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/Calendar/store/mutations.js b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/Calendar/store/mutations.js new file mode 100644 index 000000000..c956b0d87 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/Calendar/store/mutations.js @@ -0,0 +1,195 @@ +import { + createUserData, + calendarRangeToFullCalendarEvent, + remoteToFullCalendarEvent, + localsToFullCalendarEvent, +} from './utils'; + +export default { + setWhoAmiI(state, me) { + state.me = me; + }, + setCurrentDatesView(state, {start, end}) { + state.currentView.start = start; + state.currentView.end = end; + }, + showUserOnCalendar(state, {user, ranges, remotes}) { + if (!state.usersData.has(user.id)) { + state.usersData.set(user.id, createUserData(user, state.usersData.size)); + } + + const cur = state.currentView.users.get(user.id); + + state.currentView.users.set( + user.id, + { + ranges: typeof ranges !== 'undefined' ? ranges : cur.ranges, + remotes: typeof remotes !== 'undefined' ? remotes : cur.remotes, + } + ); + }, + /** + * Set the event start and end to the given start and end, + * and remove eventually the calendar range. + * + * @param state + * @param Date start + * @param Date end + */ + setEventTimes(state, {start, end}) { + state.activity.startDate = start; + state.activity.endDate = end; + state.activity.calendarRange = null; + }, + /** + * Set the event's start and end from the calendar range data, + * and associate event to calendar range. + * + * @param state + * @param range + */ + associateCalendarToRange(state, {range}) { + console.log('associateCalendarToRange', range); + + if (null === range) { + state.activity.calendarRange = null; + state.activity.startDate = null; + state.activity.endDate = null; + + return; + } + + console.log('userId', range.extendedProps.userId); + + const r = state.usersData.get(range.extendedProps.userId).calendarRanges + .find(r => r.calendarRangeId === range.extendedProps.calendarRangeId); + + if (typeof r === 'undefined') { + throw Error('Could not find managed calendar range'); + } + + console.log('range found', r); + + state.activity.startDate = range.start; + state.activity.endDate = range.end; + state.activity.calendarRange = r; + + console.log('activity', state.activity); + }, + + setMainUser(state, user) { + state.activity.mainUser = user; + }, + + // ConcernedGroups + addPersonsInvolved(state, payload) { + //console.log('### mutation addPersonsInvolved', payload.result.type); + switch (payload.result.type) { + case 'person': + state.activity.persons.push(payload.result); + break; + case 'thirdparty': + state.activity.thirdParties.push(payload.result); + break; + case 'user': + state.activity.users.push(payload.result); + break; + } + ; + }, + removePersonInvolved(state, payload) { + //console.log('### mutation removePersonInvolved', payload.type); + switch (payload.type) { + case 'person': + state.activity.persons = state.activity.persons.filter(person => person !== payload); + break; + case 'thirdparty': + state.activity.thirdParties = state.activity.thirdParties.filter(thirdparty => thirdparty !== payload); + break; + case 'user': + state.activity.users = state.activity.users.filter(user => user !== payload); + break; + } + ; + }, + /** + * Add CalendarRange object for an user + * + * @param state + * @param user + * @param ranges + * @param start + * @param end + */ + addCalendarRangesForUser(state, {user, ranges, start, end}) { + let userData; + if (state.usersData.has(user.id)) { + userData = state.usersData.get(user.id); + } else { + userData = createUserData(user, state.usersData.size); + state.usersData.set(user.id, userData); + } + + const eventRanges = ranges + .filter(r => !state.existingEvents.has(`range_${r.id}`)) + .map(r => { + // add to existing ids + state.existingEvents.add(`range_${r.id}`); + return r; + }) + .map(r => calendarRangeToFullCalendarEvent(r)); + + userData.calendarRanges = userData.calendarRanges.concat(eventRanges); + userData.calendarRangesLoaded.push({start, end}); + }, + addCalendarRemotesForUser(state, {user, remotes, start, end}) { + let userData; + if (state.usersData.has(user.id)) { + userData = state.usersData.get(user.id); + } else { + userData = createUserData(user, state.usersData.size); + state.usersData.set(user.id, userData); + } + + const eventRemotes = remotes + .filter(r => !state.existingEvents.has(`remote_${r.id}`)) + .map(r => { + // add to existing ids + state.existingEvents.add(`remote_${r.id}`); + return r; + }) + .map(r => remoteToFullCalendarEvent(r)); + + userData.remotes = userData.remotes.concat(eventRemotes); + userData.remotesLoaded.push({start, end}); + }, + addCalendarLocalsForUser(state, {user, locals, start, end}) { + let userData; + if (state.usersData.has(user.id)) { + userData = state.usersData.get(user.id); + } else { + userData = createUserData(user, state.usersData.size); + state.usersData.set(user.id, userData); + } + + const eventRemotes = locals + .filter(r => !state.existingEvents.has(`locals_${r.id}`)) + .map(r => { + // add to existing ids + state.existingEvents.add(`locals_${r.id}`); + return r; + }) + .map(r => localsToFullCalendarEvent(r)); + + userData.locals = userData.locals.concat(eventRemotes); + userData.localsLoaded.push({start, end}); + }, + // Location + updateLocation(state, value) { + console.log('### mutation: updateLocation', value); + state.activity.location = value; + }, + addAvailableLocationGroup(state, group) { + state.availableLocations.push(group); + }, +}; diff --git a/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/Calendar/store/utils.ts b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/Calendar/store/utils.ts new file mode 100644 index 000000000..e9b24a1f0 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/Calendar/store/utils.ts @@ -0,0 +1,108 @@ +import {COLORS} from '../const'; +import {ISOToDatetime} from '../../../../../../ChillMainBundle/Resources/public/chill/js/date'; +import {DateTime, User} from '../../../../../../ChillMainBundle/Resources/public/types'; +import {CalendarLight, CalendarRange, CalendarRemote} from '../../../types'; +import type {EventInputCalendarRange} from '../../../types'; +import {EventInput} from '@fullcalendar/core'; + +export interface UserData { + user: User, + calendarRanges: CalendarRange[], + calendarRangesLoaded: {}[], + remotes: CalendarRemote[], + remotesLoaded: {}[], + locals: CalendarRemote[], + localsLoaded: {}[], + mainColor: string, +} + +export const addIdToValue = (string: string, id: number): string => { + let array = string ? string.split(',') : []; + array.push(id.toString()); + let str = array.join(); + return str; +}; + +export const removeIdFromValue = (string: string, id: number) => { + let array = string.split(','); + array = array.filter(el => el !== id.toString()); + let str = array.join(); + return str; +}; + +/* +* Assign missing keys for the ConcernedGroups component +*/ +export const mapEntity = (entity: EventInput): EventInput => { + let calendar = { ...entity}; + Object.assign(calendar, {thirdParties: entity.professionals}); + + if (entity.startDate !== null ) { + calendar.startDate = ISOToDatetime(entity.startDate.datetime); + } + if (entity.endDate !== null) { + calendar.endDate = ISOToDatetime(entity.endDate.datetime); + } + + if (entity.calendarRange !== null) { + calendar.calendarRange.calendarRangeId = entity.calendarRange.id; + calendar.calendarRange.id = `range_${entity.calendarRange.id}`; + } + + return calendar; +}; + +export const createUserData = (user: User, colorIndex: number): UserData => { + const colorId = colorIndex % COLORS.length; + + return { + user: user, + calendarRanges: [], + calendarRangesLoaded: [], + remotes: [], + remotesLoaded: [], + locals: [], + localsLoaded: [], + mainColor: COLORS[colorId], + } +} + +// TODO move this function to a more global namespace, as it is also in use in MyCalendarRange app +export const calendarRangeToFullCalendarEvent = (entity: CalendarRange): EventInputCalendarRange => { + return { + id: `range_${entity.id}`, + title: "(" + entity.user.text + ")", + start: entity.startDate.datetime8601, + end: entity.endDate.datetime8601, + allDay: false, + userId: entity.user.id, + userLabel: entity.user.label, + calendarRangeId: entity.id, + locationId: entity.location.id, + locationName: entity.location.name, + is: 'range', + }; +} + +export const remoteToFullCalendarEvent = (entity: CalendarRemote): EventInput & {id: string} => { + return { + id: `range_${entity.id}`, + title: entity.title, + start: entity.startDate.datetime8601, + end: entity.endDate.datetime8601, + allDay: entity.isAllDay, + is: 'remote', + }; +} + +export const localsToFullCalendarEvent = (entity: CalendarLight): EventInput & {id: string; originId: number;} => { + return { + id: `local_${entity.id}`, + title: entity.persons.map(p => p.text).join(', '), + originId: entity.id, + start: entity.startDate.datetime8601, + end: entity.endDate.datetime8601, + allDay: false, + is: 'local', + }; +} diff --git a/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/Invite/Answer.vue b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/Invite/Answer.vue new file mode 100644 index 000000000..4607af7fc --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/Invite/Answer.vue @@ -0,0 +1,95 @@ + + + + + + diff --git a/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/App.vue b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/App.vue deleted file mode 100644 index a30dd2000..000000000 --- a/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/App.vue +++ /dev/null @@ -1,468 +0,0 @@ - - - diff --git a/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/App2.vue b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/App2.vue new file mode 100644 index 000000000..1d353baed --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/App2.vue @@ -0,0 +1,302 @@ + + + + + diff --git a/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/Components/EditLocation.vue b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/Components/EditLocation.vue new file mode 100644 index 000000000..279650ffd --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/Components/EditLocation.vue @@ -0,0 +1,88 @@ + + + + + diff --git a/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/i18n.js b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/i18n.ts similarity index 71% rename from src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/i18n.js rename to src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/i18n.ts index 2305b307b..2950c16da 100644 --- a/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/i18n.js +++ b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/i18n.ts @@ -1,8 +1,11 @@ const appMessages = { fr: { + created_availabilities: "Lieu des plages de disponibilités créées", edit_your_calendar_range: "Planifiez vos plages de disponibilités", show_my_calendar: "Afficher mon calendrier", show_weekends: "Afficher les week-ends", + copy_range: "Copier", + copy_range_from_to: "Copier les plages d'un jour à l'autre", copy_range_to_next_day: "Copier les plages du jour au jour suivant", copy_range_from_day: "Copier les plages du ", to_the_next_day: " au jour suivant", @@ -12,7 +15,13 @@ const appMessages = { update_range_to_save: "Plages à modifier", delete_range_to_save: "Plages à supprimer", by: "Par", - main_user_concerned: "Utilisateur concerné" + main_user_concerned: "Utilisateur concerné", + dateFrom: "De", + dateTo: "à", + day: "Jour", + week: "Semaine", + month: "Mois", + today: "Aujourd'hui", } } diff --git a/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/index.js b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/index.js deleted file mode 100644 index a10a16601..000000000 --- a/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/index.js +++ /dev/null @@ -1,16 +0,0 @@ -import { createApp } from 'vue'; -import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n' -import { appMessages } from './i18n' -import store from './store' - -import App from './App.vue'; - -const i18n = _createI18n(appMessages); - -const app = createApp({ - template: ``, -}) -.use(store) -.use(i18n) -.component('app', App) -.mount('#myCalendar'); diff --git a/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/index2.ts b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/index2.ts new file mode 100644 index 000000000..52dcd318d --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/index2.ts @@ -0,0 +1,19 @@ +import { createApp } from 'vue'; +import { _createI18n } from '../../../../../ChillMainBundle/Resources/public/vuejs/_js/i18n' +import { appMessages } from './i18n' +import futureStore, {key} from './store/index' + +import App2 from './App2.vue'; +import {useI18n} from "vue-i18n"; + +futureStore().then((store) => { + const i18n = _createI18n(appMessages, false); + + const app = createApp({ + template: ``, + }) + .use(store, key) + .use(i18n) + .component('app', App2) + .mount('#myCalendar'); +}); diff --git a/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/store.js b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/store.js deleted file mode 100644 index 997f95b11..000000000 --- a/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/store.js +++ /dev/null @@ -1,89 +0,0 @@ -import 'es6-promise/auto'; -import { createStore } from 'vuex'; -import { postCalendarRange, patchCalendarRange, deleteCalendarRange } from '../_api/api'; - -const debug = process.env.NODE_ENV !== 'production'; - -const store = createStore({ - strict: debug, - state: { - newCalendarRanges: [], - updateCalendarRanges: [], - deleteCalendarRanges: [] - }, - mutations: { - updateRange(state, payload) { - state.updateCalendarRanges.push({ - id: payload.event.extendedProps.calendarRangeId, - start: payload.event.start, - end: payload.event.end - }); - }, - addRange(state, payload) { - state.newCalendarRanges.push({ - start: payload.start, - end: payload.end - }); - }, - deleteRange(state, payload) { - state.deleteCalendarRanges.push({ - id: payload.extendedProps.calendarRangeId, - start: payload.start, - end: payload.end - }); - }, - clearNewCalendarRanges(state) { - state.newCalendarRanges = []; - }, - clearUpdateCalendarRanges(state) { - state.updateCalendarRanges = []; - }, - clearDeleteCalendarRanges(state) { - state.deleteCalendarRanges = []; - }, - removeNewCalendarRanges(state, payload) { - let filteredCollection = state.newCalendarRanges.filter( - (e) => e.start.toString() !== payload.start.toString() && e.end.toString() !== payload.end.toString() - ) - state.newCalendarRanges = filteredCollection; - }, - removeFromDeleteRange(state, payload) { - let filteredCollection = state.deleteCalendarRanges.filter( - (e) => e.start.toString() !== payload.start.toString() && e.end.toString() !== payload.end.toString() - ) - state.deleteCalendarRanges = filteredCollection; - }, - }, - actions: { - createRange({ commit }, payload) { - console.log('### action createRange', payload); - commit('addRange', payload); - }, - updateRange({ commit }, payload) { - console.log('### action updateRange', payload); - commit('updateRange', payload); - }, - deleteRange({ commit }, payload) { - console.log('### action deleteRange', payload); - commit('deleteRange', payload); - }, - clearNewCalendarRanges({ commit }, payload) { - commit('clearNewCalendarRanges', payload); - }, - clearUpdateCalendarRanges({ commit }, payload) { - commit('clearUpdateCalendarRanges', payload); - }, - clearDeleteCalendarRanges({ commit }, payload) { - commit('clearDeleteCalendarRanges', payload); - }, - removeNewCalendarRanges({ commit }, payload) { - commit('removeNewCalendarRanges', payload); - }, - removeFromDeleteRange({ commit }, payload) { - commit('removeFromDeleteRange', payload); - }, - } -}); - - -export default store; diff --git a/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/store/index.ts b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/store/index.ts new file mode 100644 index 000000000..f87832cb5 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/store/index.ts @@ -0,0 +1,50 @@ +import 'es6-promise/auto'; +import {Store, createStore} from 'vuex'; +import {InjectionKey} from "vue"; +import me, {MeState} from './modules/me'; +import fullCalendar, {FullCalendarState} from './modules/fullcalendar'; +import calendarRanges, {CalendarRangesState} from './modules/calendarRanges'; +import calendarRemotes, {CalendarRemotesState} from './modules/calendarRemotes'; +import {whoami} from "../../../../../../ChillMainBundle/Resources/public/lib/api/user"; +import {User} from '../../../../../../ChillMainBundle/Resources/public/types'; +import locations, {LocationState} from "./modules/location"; +import calendarLocals, {CalendarLocalsState} from "./modules/calendarLocals"; + +const debug = process.env.NODE_ENV !== 'production'; + +export interface State { + calendarRanges: CalendarRangesState, + calendarRemotes: CalendarRemotesState, + calendarLocals: CalendarLocalsState, + fullCalendar: FullCalendarState, + me: MeState, + locations: LocationState +} + +export const key: InjectionKey> = Symbol(); + +const futureStore = function(): Promise> { + return whoami().then((user: User) => { + const store = createStore({ + strict: debug, + modules: { + me, + fullCalendar, + calendarRanges, + calendarRemotes, + calendarLocals, + locations, + }, + mutations: {} + }); + + store.commit('me/setWhoAmi', user, {root: true}); + store.dispatch('locations/getLocations', null, {root: true}).then(_ => { + return store.dispatch('locations/getCurrentLocation', null, {root: true}); + }); + + return Promise.resolve(store); + }); +} + +export default futureStore; diff --git a/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/store/modules/calendarLocals.ts b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/store/modules/calendarLocals.ts new file mode 100644 index 000000000..893c0413c --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/store/modules/calendarLocals.ts @@ -0,0 +1,95 @@ +import {State} from './../index'; +import {ActionContext, Module} from 'vuex'; +import {CalendarLight} from '../../../../types'; +import {fetchCalendarLocalForUser} from '../../../Calendar/api'; +import {EventInput} from '@fullcalendar/core'; +import {localsToFullCalendarEvent} from "../../../Calendar/store/utils"; +import {TransportExceptionInterface} from "../../../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods"; +import {COLORS} from "../../../Calendar/const"; + +export interface CalendarLocalsState { + locals: EventInput[], + localsLoaded: {start: number, end: number}[], + localsIndex: Set, + key: number +} + +type Context = ActionContext; + +export default > { + namespaced: true, + state: (): CalendarLocalsState => ({ + locals: [], + localsLoaded: [], + localsIndex: new Set(), + key: 0 + }), + getters: { + isLocalsLoaded: (state: CalendarLocalsState) => ({start, end}: {start: Date, end: Date}): boolean => { + for (let range of state.localsLoaded) { + if (start.getTime() === range.start && end.getTime() === range.end) { + return true; + } + } + + return false; + }, + }, + mutations: { + addLocals(state: CalendarLocalsState, ranges: CalendarLight[]) { + console.log('addLocals', ranges); + + const toAdd = ranges + .map(cr => localsToFullCalendarEvent(cr)) + .filter(r => !state.localsIndex.has(r.id)); + + toAdd.forEach((r) => { + state.localsIndex.add(r.id); + state.locals.push(r); + }); + state.key = state.key + toAdd.length; + }, + addLoaded(state: CalendarLocalsState, payload: {start: Date, end: Date}) { + state.localsLoaded.push({start: payload.start.getTime(), end: payload.end.getTime()}); + }, + }, + actions: { + fetchLocals(ctx: Context, payload: {start: Date, end: Date}): Promise { + const start = payload.start; + const end = payload.end; + + if (ctx.rootGetters['me/getMe'] === null) { + return Promise.resolve(null); + } + + if (ctx.getters.isLocalsLoaded({start, end})) { + return Promise.resolve(ctx.getters.getRangeSource); + } + + ctx.commit('addLoaded', { + start: start, + end: end, + }); + + return fetchCalendarLocalForUser( + ctx.rootGetters['me/getMe'], + start, + end + ) + .then((remotes: CalendarLight[]) => { + // to be add when reactivity problem will be solve ? + //ctx.commit('addRemotes', remotes); + const inputs = remotes + .map(cr => localsToFullCalendarEvent(cr)) + .map(cr => ({...cr, backgroundColor: COLORS[0], textColor: 'black', editable: false})) + ctx.commit('calendarRanges/addExternals', inputs, {root: true}); + return Promise.resolve(null); + }) + .catch((e: TransportExceptionInterface) => { + console.error(e); + + return Promise.resolve(null); + }); + } + } +}; diff --git a/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/store/modules/calendarRanges.ts b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/store/modules/calendarRanges.ts new file mode 100644 index 000000000..aaa530909 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/store/modules/calendarRanges.ts @@ -0,0 +1,252 @@ +import {State} from './../index'; +import {ActionContext, Module} from 'vuex'; +import {CalendarRange, CalendarRangeCreate, CalendarRangeEdit, isEventInputCalendarRange} from "../../../../types"; +import {Location} from "../../../../../../../ChillMainBundle/Resources/public/types"; +import {fetchCalendarRangeForUser} from '../../../Calendar/api'; +import {calendarRangeToFullCalendarEvent} from '../../../Calendar/store/utils'; +import {EventInput} from '@fullcalendar/core'; +import {makeFetch} from "../../../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods"; +import { + datetimeToISO, + dateToISO, + ISOToDatetime +} from "../../../../../../../ChillMainBundle/Resources/public/chill/js/date"; +import type {EventInputCalendarRange} from "../../../../types"; + +export interface CalendarRangesState { + ranges: (EventInput | EventInputCalendarRange) [], + rangesLoaded: { start: number, end: number }[], + rangesIndex: Set, + key: number +} + +type Context = ActionContext; + +export default >{ + namespaced: true, + state: (): CalendarRangesState => ({ + ranges: [], + rangesLoaded: [], + rangesIndex: new Set(), + key: 0 + }), + getters: { + isRangeLoaded: (state: CalendarRangesState) => ({start, end}: { start: Date, end: Date }): boolean => { + for (let range of state.rangesLoaded) { + if (start.getTime() === range.start && end.getTime() === range.end) { + return true; + } + } + + return false; + }, + getRangesOnDate: (state: CalendarRangesState) => (date: Date): EventInputCalendarRange[] => { + const founds = []; + const dateStr = dateToISO(date); + + for (let range of state.ranges) { + if (isEventInputCalendarRange(range) + && range.start.startsWith(dateStr) + ) { + founds.push(range); + } + } + + return founds; + }, + }, + mutations: { + addRanges(state: CalendarRangesState, ranges: CalendarRange[]) { + const toAdd = ranges + .map(cr => calendarRangeToFullCalendarEvent(cr)) + .map(cr => ({ + ...cr, backgroundColor: 'white', borderColor: '#3788d8', + textColor: 'black' + })) + .filter(r => !state.rangesIndex.has(r.id)); + + toAdd.forEach((r) => { + state.rangesIndex.add(r.id); + state.ranges.push(r); + }); + state.key = state.key + toAdd.length; + }, + addExternals(state, externalEvents: (EventInput & { id: string })[]) { + const toAdd = externalEvents + .filter(r => !state.rangesIndex.has(r.id)); + + toAdd.forEach((r) => { + state.rangesIndex.add(r.id); + state.ranges.push(r); + }); + state.key = state.key + toAdd.length; + }, + addLoaded(state: CalendarRangesState, payload: { start: Date, end: Date }) { + state.rangesLoaded.push({start: payload.start.getTime(), end: payload.end.getTime()}); + }, + addRange(state: CalendarRangesState, payload: CalendarRange) { + const asEvent = calendarRangeToFullCalendarEvent(payload); + state.ranges.push({...asEvent, backgroundColor: 'white', borderColor: '#3788d8', textColor: 'black'}); + state.rangesIndex.add(asEvent.id); + state.key = state.key + 1; + }, + removeRange(state: CalendarRangesState, calendarRangeId: number) { + const found = state.ranges.find(r => r.calendarRangeId === calendarRangeId && r.is === "range"); + + if (found !== undefined) { + state.ranges = state.ranges.filter( + (r) => !(r.calendarRangeId === calendarRangeId && r.is === "range") + ); + + if (typeof found.id === "string") { // should always be true + state.rangesIndex.delete(found.id); + } + + state.key = state.key + 1; + } + }, + updateRange(state, range: CalendarRange) { + const found = state.ranges.find(r => r.calendarRangeId === range.id && r.is === "range"); + const newEvent = calendarRangeToFullCalendarEvent(range); + + if (found !== undefined) { + found.start = newEvent.start; + found.end = newEvent.end; + found.locationId = range.location.id; + found.locationName = range.location.name; + } + + state.key = state.key + 1; + } + }, + actions: { + fetchRanges(ctx: Context, payload: { start: Date, end: Date }): Promise { + const start = payload.start; + const end = payload.end; + + if (ctx.rootGetters['me/getMe'] === null) { + return Promise.resolve(ctx.getters.getRangeSource); + } + + if (ctx.getters.isRangeLoaded({start, end})) { + return Promise.resolve(ctx.getters.getRangeSource); + } + + ctx.commit('addLoaded', { + start: start, + end: end, + }); + + return fetchCalendarRangeForUser( + ctx.rootGetters['me/getMe'], + start, + end + ) + .then((ranges: CalendarRange[]) => { + ctx.commit('addRanges', ranges); + return Promise.resolve(null); + }); + }, + createRange(ctx, {start, end, location}: { start: Date, end: Date, location: Location }): Promise { + const url = `/api/1.0/calendar/calendar-range.json?`; + + if (ctx.rootState.me.me === null) { + throw new Error('user is currently null'); + } + + const body = { + user: { + id: ctx.rootState.me.me.id, + type: "user" + }, + startDate: { + datetime: datetimeToISO(start), + }, + endDate: { + datetime: datetimeToISO(end) + }, + location: { + id: location.id, + type: "location" + } + } as CalendarRangeCreate; + + return makeFetch('POST', url, body) + .then((newRange) => { + + ctx.commit('addRange', newRange); + + return Promise.resolve(null); + }) + .catch((error) => { + console.error(error); + + throw error; + }) + }, + deleteRange(ctx, calendarRangeId: number) { + const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`; + + makeFetch('DELETE', url) + .then((_) => { + ctx.commit('removeRange', calendarRangeId); + }); + }, + patchRangeTime(ctx, {calendarRangeId, start, end}: {calendarRangeId: number, start: Date, end: Date}): Promise { + const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`; + const body = { + startDate: { + datetime: datetimeToISO(start) + }, + endDate: { + datetime: datetimeToISO(end) + }, + } as CalendarRangeEdit; + + return makeFetch('PATCH', url, body) + .then((range) => { + ctx.commit('updateRange', range); + return Promise.resolve(null); + }) + .catch((error) => { + console.error(error); + return Promise.resolve(null); + }) + }, + patchRangeLocation(ctx, {location, calendarRangeId}: {location: Location, calendarRangeId: number}): Promise { + const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`; + const body = { + location: { + id: location.id, + type: "location" + }, + } as CalendarRangeEdit; + + return makeFetch('PATCH', url, body) + .then((range) => { + ctx.commit('updateRange', range); + return Promise.resolve(null); + }) + .catch((error) => { + console.error(error); + return Promise.resolve(null); + }) + }, + copyFromDayToAnotherDay(ctx, {from, to}: {from: Date, to: Date}): Promise { + const rangesToCopy: EventInputCalendarRange[] = ctx.getters['getRangesOnDate'](from); + const promises = []; + + for (let r of rangesToCopy) { + let start = new Date(ISOToDatetime(r.start)); + start.setFullYear(to.getFullYear(), to.getMonth(), to.getDate()) + let end = new Date(ISOToDatetime(r.end)); + end.setFullYear(to.getFullYear(), to.getMonth(), to.getDate()); + let location = ctx.rootGetters['locations/getLocationById'](r.locationId); + + promises.push(ctx.dispatch('createRange', {start, end, location})); + } + + return Promise.all(promises).then(_ => Promise.resolve(null)); + } + } +}; diff --git a/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/store/modules/calendarRemotes.ts b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/store/modules/calendarRemotes.ts new file mode 100644 index 000000000..a92e1f9b3 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/store/modules/calendarRemotes.ts @@ -0,0 +1,95 @@ +import {State} from './../index'; +import {ActionContext, Module} from 'vuex'; +import {CalendarRemote} from '../../../../types'; +import {fetchCalendarRemoteForUser} from '../../../Calendar/api'; +import {EventInput} from '@fullcalendar/core'; +import {remoteToFullCalendarEvent} from "../../../Calendar/store/utils"; +import {TransportExceptionInterface} from "../../../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods"; +import {COLORS} from "../../../Calendar/const"; + +export interface CalendarRemotesState { + remotes: EventInput[], + remotesLoaded: {start: number, end: number}[], + remotesIndex: Set, + key: number +} + +type Context = ActionContext; + +export default > { + namespaced: true, + state: (): CalendarRemotesState => ({ + remotes: [], + remotesLoaded: [], + remotesIndex: new Set(), + key: 0 + }), + getters: { + isRemotesLoaded: (state: CalendarRemotesState) => ({start, end}: {start: Date, end: Date}): boolean => { + for (let range of state.remotesLoaded) { + if (start.getTime() === range.start && end.getTime() === range.end) { + return true; + } + } + + return false; + }, + }, + mutations: { + addRemotes(state: CalendarRemotesState, ranges: CalendarRemote[]) { + console.log('addRemotes', ranges); + + const toAdd = ranges + .map(cr => remoteToFullCalendarEvent(cr)) + .filter(r => !state.remotesIndex.has(r.id)); + + toAdd.forEach((r) => { + state.remotesIndex.add(r.id); + state.remotes.push(r); + }); + state.key = state.key + toAdd.length; + }, + addLoaded(state: CalendarRemotesState, payload: {start: Date, end: Date}) { + state.remotesLoaded.push({start: payload.start.getTime(), end: payload.end.getTime()}); + }, + }, + actions: { + fetchRemotes(ctx: Context, payload: {start: Date, end: Date}): Promise { + const start = payload.start; + const end = payload.end; + + if (ctx.rootGetters['me/getMe'] === null) { + return Promise.resolve(null); + } + + if (ctx.getters.isRemotesLoaded({start, end})) { + return Promise.resolve(ctx.getters.getRangeSource); + } + + ctx.commit('addLoaded', { + start: start, + end: end, + }); + + return fetchCalendarRemoteForUser( + ctx.rootGetters['me/getMe'], + start, + end + ) + .then((remotes: CalendarRemote[]) => { + // to be add when reactivity problem will be solve ? + //ctx.commit('addRemotes', remotes); + const inputs = remotes + .map(cr => remoteToFullCalendarEvent(cr)) + .map(cr => ({...cr, backgroundColor: COLORS[0], textColor: 'black', editable: false})) + ctx.commit('calendarRanges/addExternals', inputs, {root: true}); + return Promise.resolve(null); + }) + .catch((e: TransportExceptionInterface) => { + console.error(e); + + return Promise.resolve(null); + }); + } + } +}; diff --git a/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/store/modules/fullcalendar.ts b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/store/modules/fullcalendar.ts new file mode 100644 index 000000000..de379753f --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/store/modules/fullcalendar.ts @@ -0,0 +1,56 @@ +import {State} from './../index'; +import {ActionContext} from 'vuex'; + +export interface FullCalendarState { + currentView: { + start: Date|null, + end: Date|null, + }, + key: number +} + +type Context = ActionContext; + +export default { + namespaced: true, + state: (): FullCalendarState => ({ + currentView: { + start: null, + end: null, + }, + key: 0, + }), + mutations: { + setCurrentDatesView: function(state: FullCalendarState, payload: {start: Date, end: Date}): void { + state.currentView.start = payload.start; + state.currentView.end = payload.end; + }, + increaseKey: function(state: FullCalendarState): void { + state.key = state.key + 1; + } + }, + actions: { + setCurrentDatesView(ctx: Context, {start, end}: {start: Date|null, end: Date|null}): Promise { + console.log('dispatch setCurrentDatesView', {start, end}); + + if (ctx.state.currentView.start !== start || ctx.state.currentView.end !== end) { + ctx.commit('setCurrentDatesView', {start, end}); + } + + if (start !== null && end !== null) { + + return Promise.all([ + ctx.dispatch('calendarRanges/fetchRanges', {start, end}, {root: true}).then(_ => Promise.resolve(null)), + ctx.dispatch('calendarRemotes/fetchRemotes', {start, end}, {root: true}).then(_ => Promise.resolve(null)), + ctx.dispatch('calendarLocals/fetchLocals', {start, end}, {root: true}).then(_ => Promise.resolve(null)) + ] + ).then(_ => Promise.resolve(null)); + + } else { + return Promise.resolve(null); + } + }, + } +} + + diff --git a/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/store/modules/location.ts b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/store/modules/location.ts new file mode 100644 index 000000000..7fbfe7fce --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/store/modules/location.ts @@ -0,0 +1,62 @@ +import {Location} from "../../../../../../../ChillMainBundle/Resources/public/types"; +import {State} from '../index'; +import {Module} from 'vuex'; +import {getLocations} from "../../../../../../../ChillMainBundle/Resources/public/lib/api/locations"; +import {whereami} from "../../../../../../../ChillMainBundle/Resources/public/lib/api/user"; + +export interface LocationState { + locations: Location[]; + locationPicked: Location | null; + currentLocation: Location | null; +} + +export default >{ + namespaced: true, + state: (): LocationState => { + return { + locations: [], + locationPicked: null, + currentLocation: null, + } + }, + getters: { + getLocationById: (state) => (id: number): Location|undefined => { + return state.locations.find(l => l.id === id); + }, + }, + mutations: { + setLocations(state, locations): void { + state.locations = locations; + }, + setLocationPicked(state, location: Location | null): void { + if (null === location) { + state.locationPicked = null; + return; + } + + state.locationPicked = state.locations.find(l => l.id === location.id) || null; + }, + setCurrentLocation(state, location: Location | null): void { + if (null === location) { + state.currentLocation = null; + return; + } + + state.currentLocation = state.locations.find(l => l.id === location.id) || null; + } + }, + actions: { + getLocations(ctx): Promise { + return getLocations().then(locations => { + ctx.commit('setLocations', locations); + return Promise.resolve(); + }); + }, + getCurrentLocation(ctx): Promise { + return whereami().then(location => { + ctx.commit('setCurrentLocation', location); + }) + } + } +} + diff --git a/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/store/modules/me.ts b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/store/modules/me.ts new file mode 100644 index 000000000..8610b76d3 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/store/modules/me.ts @@ -0,0 +1,29 @@ +import {State} from './../index'; +import {User} from '../../../../../../../ChillMainBundle/Resources/public/types'; +import {ActionContext} from 'vuex'; + +export interface MeState { + me: User|null, +} + +type Context = ActionContext; + +export default { + namespaced: true, + state: (): MeState => ({ + me: null, + }), + getters: { + getMe: function(state: MeState): User|null { + return state.me; + }, + }, + mutations: { + setWhoAmi(state: MeState, me: User) { + state.me = me; + }, + } +}; + + + diff --git a/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/_api/api.js b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/_api/api.js index 7aad8ace2..b2bee8afe 100644 --- a/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/_api/api.js +++ b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/_api/api.js @@ -4,6 +4,7 @@ * @returns {Promise} a promise containing all Calendar ranges objects */ const fetchCalendarRanges = () => { + return Promise.resolve([]); const url = `/api/1.0/calendar/calendar-range-available.json`; return fetch(url) .then(response => { @@ -13,6 +14,7 @@ const fetchCalendarRanges = () => { }; const fetchCalendarRangesByUser = (userId) => { + return Promise.resolve([]); const url = `/api/1.0/calendar/calendar-range-available.json?user=${userId}`; return fetch(url) .then(response => { @@ -27,6 +29,7 @@ const fetchCalendarRangesByUser = (userId) => { * @returns {Promise} a promise containing all Calendar objects */ const fetchCalendar = (mainUserId) => { + return Promise.resolve([]); const url = `/api/1.0/calendar/calendar.json?main_user=${mainUserId}&item_per_page=1000`; return fetch(url) .then(response => { diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/_documents.twig.html b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/_documents.twig.html new file mode 100644 index 000000000..c295d31a1 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/_documents.twig.html @@ -0,0 +1,52 @@ +{% if calendar.documents|length > 0 %} + +{% import "@ChillDocStore/Macro/macro.html.twig" as m %} +{% import "@ChillDocStore/Macro/macro_mimeicon.html.twig" as mm %} + +
      +
          {{ 'Budget calculator result'|trans }}
      {{ 'The balance'|trans }}  {{ result|format_currency('EUR') }}
      + + + + + {% for d in calendar.documents %} + {% if is_granted('CHILL_CALENDAR_DOC_SEE', d) %} + + + + {% endif %} + {% endfor %} + +
      +

      {{ 'chill_calendar.Documents'|trans }}

      +
      +
        +
      • +
        +
        + {{ d.storedObject.title }} +
        + {% if d.dateTimeVersion < d.calendar.dateTimeVersion %} +
        + {{ 'chill_calendar.Document outdated'|trans }} +
        + {% endif %} +
        +
          +
        • + {{ d.storedObject|chill_document_button_group(d.storedObject.title, is_granted('CHILL_CALENDAR_DOC_EDIT', d), {'small': true}) }} +
        • +
        • + +
        • +
        • + +
        • +
        +
        +
        +
      • +
      +
      + +{% endif %} diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/_list.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/_list.html.twig new file mode 100644 index 000000000..2c3cde01a --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/_list.html.twig @@ -0,0 +1,246 @@ +{# list used in context of person or accompanyingPeriod #} + +{% if calendarItems|length > 0 %} +
      + + {% for calendar in calendarItems %} + +
      +
      +
      +
      +
      +
      +

      + {% if context == 'person' and calendar.context == 'accompanying_period' %} + + + {{ calendar.accompanyingPeriod.id }} + + + {% endif %} + {% if calendar.endDate.diff(calendar.startDate).days >= 1 %} + {{ calendar.startDate|format_datetime('short', 'short') }} + - {{ calendar.endDate|format_datetime('short', 'short') }} + {% else %} + {{ calendar.startDate|format_datetime('short', 'short') }} + - {{ calendar.endDate|format_datetime('none', 'short') }} + {% endif %} +

      + +
      + + {{ calendar.duration|date('%H:%I') }} + {% if false == calendar.sendSMS or null == calendar.sendSMS %} + + {% else %} + {% if calendar.smsStatus == 'sms_sent' %} + + + + + {% else %} + + + + + {% endif %} + {% endif %} +
      + +
      +
      +
      + +
      +
        + {% if calendar.mainUser is not empty %} + {{ calendar.mainUser|chill_entity_render_box }} + {% endif %} +
      +
      + +
      +
      + + {% if calendar.comment.comment is not empty + or calendar.users|length > 0 + or calendar.thirdParties|length > 0 + or calendar.users|length > 0 %} +
      +
      + {% include 'ChillActivityBundle:Activity:concernedGroups.html.twig' with { + 'context': calendar.context == 'person' ? 'calendar_person' : 'calendar_accompanyingCourse', + 'render': 'wrap-list', + 'entity': calendar + } %} +
      + +
      + {% endif %} + + {% if calendar.comment.comment is not empty %} +
      +
      + {{ calendar.comment|chill_entity_render_box( { 'limit_lines': 3, 'metadata': false } ) }} +
      +
      + {% endif %} + + {% if calendar.location is not empty %} +
      +
      + {% if calendar.location.address is not same as(null) and calendar.location.name is not empty %} + {% endif %} + {% if calendar.location.name is not empty %}{{ calendar.location.name }}{% endif %} + {% if calendar.location.address is not same as(null) %}{{ calendar.location.address|chill_entity_render_box({'multiline': false, 'with_picto': (calendar.location.name is empty)}) }}{% else %} + {% endif %} + {% if calendar.location.phonenumber1 is not empty %} {{ calendar.location.phonenumber1|chill_format_phonenumber }}{% endif %} + {% if calendar.location.phonenumber2 is not empty %} {{ calendar.location.phonenumber2|chill_format_phonenumber }}{% endif %} +
      +
      + {% endif %} + +
      +
      + + {{ include('@ChillCalendar/Calendar/_documents.twig.html') }} +
      +
      + + {% if calendar.activity is not null %} +
      +
      +
      +
      +

      {{ 'Activity'|trans }}

      +
      +

      + + + {{ calendar.activity.type.name | localize_translatable_string }} + + {% if calendar.activity.emergency %} + {{ 'Emergency'|trans|upper }} + {% endif %} + +

      + +
        +
      • + + {{ 'Created by'|trans }} + {{ calendar.activity.createdBy|chill_entity_render_string }}, {{ 'on'|trans }} {{ calendar.activity.createdAt|format_datetime('short', 'short') }} + +
      • + {% if is_granted('CHILL_ACTIVITY_SEE', calendar.activity) %} +
      • + +
      • + {% endif %} +
      + +
      +
      +
      +
      +
      + {% endif %} + +
      +
        + {% if is_granted('CHILL_CALENDAR_CALENDAR_SEE', calendar) %} + {% if templates|length == 0 %} +
      • + + {{ 'chill_calendar.Add a document'|trans }} + +
      • + {% else %} +
      • + +
      • + {% endif %} + {% endif %} + {% if calendar.activity is null and ( + (calendar.context == 'accompanying_period' and is_granted('CHILL_ACTIVITY_CREATE', calendar.accompanyingPeriod)) + or + (calendar.context == 'person' and is_granted('CHILL_ACTIVITY_CREATE', calendar.person)) + ) + %} +
      • + + {{ 'Transform to activity'|trans }} + +
      • + {% endif %} + + {% if (calendar.isInvited(app.user)) %} + {% set invite = calendar.inviteForUser(app.user) %} +
      • +
        +
      • + {% endif %} + {% if false %} +
      • + +
      • + {% endif %} + {# TOOD + {% if is_granted('CHILL_ACTIVITY_UPDATE', calendar) %} + #} +
      • + +
      • + {# TOOD + {% endif %} + {% if is_granted('CHILL_ACTIVITY_DELETE', calendar) %} + #} +
      • + +
      • + {# + {% endif %} + #} +
      + +
      + +
      + {% endfor %} + + {% if calendarItems|length < paginator.getTotalItems %} + {{ chill_pagination(paginator) }} + {% endif %} + +
      +{% endif %} diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/confirm_deleteByAccompanyingCourse.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/confirm_deleteByAccompanyingCourse.html.twig index 26818bb6c..161f4d8b4 100644 --- a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/confirm_deleteByAccompanyingCourse.html.twig +++ b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/confirm_deleteByAccompanyingCourse.html.twig @@ -9,8 +9,8 @@ { 'title' : 'Remove calendar item'|trans, 'confirm_question' : 'Are you sure you want to remove the calendar item?'|trans, - 'cancel_route' : 'chill_calendar_calendar_list', - 'cancel_parameters' : { 'accompanying_period_id' : accompanyingCourse.id, 'id' : calendar.id }, + 'cancel_route' : 'chill_calendar_calendar_list_by_period', + 'cancel_parameters' : { 'id' : accompanyingCourse.id }, 'form' : delete_form } ) }} {% endblock %} diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/confirm_deleteByPerson.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/confirm_deleteByPerson.html.twig new file mode 100644 index 000000000..f72d0d21b --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/confirm_deleteByPerson.html.twig @@ -0,0 +1,16 @@ +{% extends "@ChillPerson/Person/layout.html.twig" %} + +{% set activeRouteKey = 'chill_calendar_calendar_list' %} + +{% block title 'Remove calendar item'|trans %} + +{% block content %} + {{ include('@ChillMain/Util/confirmation_template.html.twig', + { + 'title' : 'Remove calendar item'|trans, + 'confirm_question' : 'Are you sure you want to remove the calendar item?'|trans, + 'cancel_route' : 'chill_calendar_calendar_list_by_period', + 'cancel_parameters' : { 'id' : person.id }, + 'form' : delete_form + } ) }} +{% endblock %} diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/edit.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/edit.html.twig index b96d12c06..8978739ea 100644 --- a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/edit.html.twig +++ b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/edit.html.twig @@ -3,11 +3,11 @@ {{ form_start(form) }} {{ form_errors(form) }} -{%- if form.mainUser is defined -%} - {{ form_row(form.mainUser) }} -{% endif %} +
      {# <=== vue component #} -

      {{ 'Concerned groups'|trans }}

      +
      {# <=== vue component: mainUser #} + +

      {{ 'Concerned groups calendar'|trans }}

      {%- if form.persons is defined -%} {{ form_widget(form.persons) }} @@ -26,6 +26,13 @@

      {{ 'Calendar data'|trans }}

      +
      + +{%- if form.location is defined -%} + {{ form_row(form.location) }} +
      +{% endif %} + {%- if form.startDate is defined -%} {{ form_row(form.startDate) }} {% endif %} @@ -38,10 +45,7 @@ {{ form_row(form.calendarRange) }} {% endif %} -{%- if form.location is defined -%} - {{ form_row(form.location) }} -
      -{% endif %} +
      {%- if form.comment is defined -%} {{ form_row(form.comment) }} @@ -59,7 +63,6 @@
      {% endif %} -
      • @@ -68,17 +71,37 @@ {%- if context == 'user' -%} href="{{ chill_return_path_or('chill_calendar_calendar_list', { 'user_id': user.id } )}}" {%- elseif context == 'accompanyingCourse' -%} - href="{{ chill_return_path_or('chill_calendar_calendar_list', { 'accompanying_period_id': accompanyingCourse.id } )}}" + href="{{ chill_return_path_or('chill_calendar_calendar_list_by_period', { 'id': accompanyingCourse.id } )}}" {%- endif -%} > {{ 'Cancel'|trans|chill_return_path_label }}
      • -
      • - -
      • + {% if templates|length == 0 %} +
      • + {{ form_widget(form.save_and_upload_doc, { 'attr' : { 'class' : 'btn btn-create' }, 'label': 'chill_calendar.Create and add a document'|trans }) }} +
      • + {% else %} +
      • + +
      • + {% endif %} +
      • + {{ form_widget(form.save, { 'attr' : { 'class' : 'btn btn-create' }, 'label': 'Save'|trans }) }} +
      {{ form_end(form) }} diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/editByAccompanyingCourse.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/editByAccompanyingCourse.html.twig index 850bfbecb..a167d5c18 100644 --- a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/editByAccompanyingCourse.html.twig +++ b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/editByAccompanyingCourse.html.twig @@ -7,7 +7,6 @@ {% block content %}
      -
      {# <=== vue component #} {% include 'ChillCalendarBundle:Calendar:edit.html.twig' with {'context': 'accompanyingCourse'} %}
      @@ -16,10 +15,6 @@ {% block js %} {{ parent() }} + {{ encore_entry_script_tags('vue_calendar') }} +{% endblock %} + +{% block css %} + {{ parent() }} + {{ encore_entry_link_tags('vue_calendar') }} + {{ encore_entry_link_tags('page_calendar') }} +{% endblock %} + +{% block block_post_menu %} +
      +{% endblock %} diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/listByAccompanyingCourse.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/listByAccompanyingCourse.html.twig index 6cf97a58d..7ce1003bc 100644 --- a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/listByAccompanyingCourse.html.twig +++ b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/listByAccompanyingCourse.html.twig @@ -1,4 +1,4 @@ -{% extends "@ChillPerson/AccompanyingCourse/layout.html.twig" %} +{% extends "@ChillPerson/AccompanyingCourse/layout.html.twig" %} {% set activeRouteKey = 'chill_calendar_calendar_list' %} @@ -7,123 +7,51 @@ {% set user_id = null %} {% set accompanying_course_id = accompanyingCourse.id %} +{% block js %} + {{ parent() }} + {{ encore_entry_script_tags('mod_answer') }} + {{ encore_entry_script_tags('mod_document_action_buttons_group') }} +{% endblock %} + +{% block css %} + {{ parent() }} + {{ encore_entry_link_tags('mod_answer') }} + {{ encore_entry_link_tags('mod_document_action_buttons_group') }} +{% endblock %} + {% block content %} -

      {{ 'Calendar list' |trans }}

      +

      {{ 'Calendar list' |trans }}

      -{% if calendarItems|length == 0 %} -

      - {{ "There is no calendar items."|trans }} - -

      -{% else %} + {{ filterOrder|chill_render_filter_order_helper }} -
      + {% if calendarItems|length == 0 %} +

      + {% if nbIgnored == 0 %} + {{ "There is no calendar items."|trans }} + {% else %} + {{ 'chill_calendar.There are count ignored calendars by date filter'|trans({'nbIgnored': nbIgnored}) }} + {% endif %} +

      + {% else %} + {{ include('@ChillCalendar/Calendar/_list.html.twig', {context: 'accompanying_course'}) }} + {% endif %} - {% for calendar in calendarItems %} - -
      -
      -
      - - {% if calendar.startDate and calendar.endDate %} - {% if calendar.endDate.diff(calendar.startDate).days >= 1 %} -

      {{ "From the day"|trans }} {{ calendar.startDate|format_datetime('medium', 'short') }}

      -

      {{ "to the day"|trans }} {{ calendar.endDate|format_datetime('medium', 'short') }}

      - {% else %} -

      {{ calendar.startDate|format_date('full') }}

      -

      {{ calendar.startDate|format_datetime('none', 'short', locale='fr') }} - {{ calendar.endDate|format_datetime('none', 'short', locale='fr') }}

      - -
      -

      - - {{ calendar.endDate.diff(calendar.startDate)|date("%H:%M")}} -

      -
      - {% endif %} - - {% endif %} - -
      -
      -
        - {% if calendar.user %} -
      • - {{ 'by'|trans }}{{ calendar.user.usernameCanonical }} -
      • - {% endif %} - - {% if calendar.mainUser is not empty %} -
      • - {{ 'main user concerned'|trans }}: {{ calendar.mainUser.usernameCanonical }} -
      • - {% endif %} - -
      -
        -
      • - -
      • - {# TOOD - {% if is_granted('CHILL_ACTIVITY_UPDATE', calendar) %} - #} -
      • - -
      • - {# TOOD - {% endif %} - {% if is_granted('CHILL_ACTIVITY_DELETE', calendar) %} - #} -
      • - -
      • - {# - {% endif %} - #} -
      -
      -
      - - {% - if calendar.comment.comment is not empty - or calendar.users|length > 0 - or calendar.thirdParties|length > 0 - or calendar.users|length > 0 - %} -
      -
      - {% include 'ChillActivityBundle:Activity:concernedGroups.html.twig' with { - 'context': accompanyingCourse, - 'render': 'row', - 'entity': calendar - } %} -
      - - {% if calendar.comment.comment is not empty %} -
      - {{ calendar.comment|chill_entity_render_box( { 'limit_lines': 3, 'metadata': false } ) }} -
      - {% endif %} -
      - {% endif %} - -
      - {% endfor %} - - {% if calendarItems|length < paginator.getTotalItems %} - {{ chill_pagination(paginator) }} +
      -{% endif %} - - - +
    • + + {{ 'Create'|trans }} + +
    • +
    {% endblock %} diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/listByPerson.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/listByPerson.html.twig new file mode 100644 index 000000000..9e3b59d2a --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/listByPerson.html.twig @@ -0,0 +1,54 @@ +{% extends "@ChillPerson/Person/layout.html.twig" %} + +{% set activeRouteKey = 'chill_calendar_calendar_list' %} + +{% block title %}{{ 'Calendar list' |trans }}{% endblock title %} + +{% set user_id = null %} + +{% block js %} + {{ parent() }} + {{ encore_entry_script_tags('mod_answer') }} + {{ encore_entry_script_tags('mod_document_action_buttons_group') }} +{% endblock %} + +{% block css %} + {{ parent() }} + {{ encore_entry_link_tags('mod_answer') }} + {{ encore_entry_link_tags('mod_document_action_buttons_group') }} +{% endblock %} + +{% block content %} + +

    {{ 'Calendar list' |trans }}

    + + {{ filterOrder|chill_render_filter_order_helper }} + + {% if calendarItems|length == 0 %} +

    + {% if nbIgnored == 0 %} + {{ "There is no calendar items."|trans }} + {% else %} + {{ 'chill_calendar.There are count ignored calendars by date filter'|trans({'nbIgnored': nbIgnored}) }} + {% endif %} +

    + {% else %} + {{ include ('@ChillCalendar/Calendar/_list.html.twig', {context: 'person'}) }} + {% endif %} + + + +{% endblock %} diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/listByUser.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/listByUser.html.twig index 055d967a7..fa034a89b 100644 --- a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/listByUser.html.twig +++ b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/listByUser.html.twig @@ -10,7 +10,7 @@
    {% endblock %} - + {% block js %} {{ parent() }} {{ encore_entry_script_tags('vue_calendar') }} {% endblock %} @@ -28,6 +26,7 @@ {% block css %} {{ parent() }} {{ encore_entry_link_tags('vue_calendar') }} + {{ encore_entry_link_tags('mod_pickentity_type') }} {% endblock %} {% block block_post_menu %} diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/newByPerson.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/newByPerson.html.twig new file mode 100644 index 000000000..b561e6aa7 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/newByPerson.html.twig @@ -0,0 +1,34 @@ +{% extends "@ChillPerson/Person/layout.html.twig" %} + +{% set activeRouteKey = 'chill_calendar_calendar_new' %} + +{% block title 'Calendar item creation' |trans %} + +{% block content %} +
    + +
    {# <=== vue component #} + {% include 'ChillCalendarBundle:Calendar:new.html.twig' with {'context': 'person'} %} + +
    +{% endblock %} + +{% block js %} + {{ parent() }} + {{ encore_entry_script_tags('mod_pickentity_type') }} + + {{ encore_entry_script_tags('vue_calendar') }} +{% endblock %} + +{% block css %} + {{ parent() }} + {{ encore_entry_link_tags('vue_calendar') }} + {{ encore_entry_link_tags('mod_pickentity_type') }} +{% endblock %} + +{% block block_post_menu %} +
    +{% endblock %} diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/show.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/show.html.twig index 3a1bf03d5..d48844f67 100644 --- a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/show.html.twig +++ b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/show.html.twig @@ -1,12 +1,21 @@

    {{ "Calendar"|trans }}

    +

    + {% if entity.endDate.diff(entity.startDate).days >= 1 %} + {{ "From the day"|trans }} {{ entity.startDate|format_datetime('medium', 'short') }} + {{ "to the day"|trans }} {{ entity.endDate|format_datetime('medium', 'short') }} + {% else %} + {{ entity.startDate|format_date('full') }}, + {{ entity.startDate|format_datetime('none', 'short', locale='fr') }} - {{ entity.endDate|format_datetime('none', 'short', locale='fr') }} + {% endif %} +

    {{ 'main user concerned'|trans }}
    {{ entity.mainUser }}
    -

    {{ 'Concerned groups'|trans }}

    -{% include 'ChillActivityBundle:Activity:concernedGroups.html.twig' with {'context': context, 'render': 'bloc' } %} +

    {{ 'Concerned groups calendar'|trans }}

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

    {{ 'Calendar data'|trans }}

    @@ -79,8 +88,8 @@
    • - + {{ 'Back to the list'|trans }}
    • diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/CalendarDoc/delete_accompanying_period.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/CalendarDoc/delete_accompanying_period.html.twig new file mode 100644 index 000000000..b26b280ac --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/views/CalendarDoc/delete_accompanying_period.html.twig @@ -0,0 +1,19 @@ +{% extends "@ChillPerson/AccompanyingCourse/layout.html.twig" %} + +{% set activeRouteKey = 'chill_calendar_calendar_list' %} + +{% block title %}{{ 'chill_calendar.Remove a calendar document' |trans }}{% endblock title %} + +{% set accompanyingCourse = calendar_doc.calendar.accompanyingPeriod %} +{% set accompanyingCourseId = accompanyingCourse.id %} + +{% block content %} + {{ include('@ChillMain/Util/confirmation_template.html.twig', + { + 'title' : 'chill_calendar.Remove a calendar document'|trans, + 'confirm_question' : 'chill_calendar.Are you sure you want to remove the doc?'|trans, + 'cancel_route' : 'chill_calendar_calendar_list_by_period', + 'cancel_parameters' : { 'id' : accompanyingCourse.id }, + 'form' : form + } ) }} +{% endblock %} diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/CalendarDoc/delete_person.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/CalendarDoc/delete_person.html.twig new file mode 100644 index 000000000..64e03d37e --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/views/CalendarDoc/delete_person.html.twig @@ -0,0 +1,18 @@ +{% extends "@ChillPerson/Person/layout.html.twig" %} + +{% set activeRouteKey = 'chill_calendar_calendar_list' %} + +{% block title %}{{ 'chill_calendar.Edit a document' |trans }}{% endblock title %} + +{% set person = calendar_doc.calendar.person %} + +{% block content %} + {{ include('@ChillMain/Util/confirmation_template.html.twig', + { + 'title' : 'chill_calendar.Remove a calendar document'|trans, + 'confirm_question' : 'chill_calendar.Are you sure you want to remove the doc?'|trans, + 'cancel_route' : 'chill_calendar_calendar_list_by_person', + 'cancel_parameters' : { 'id' : person.id }, + 'form' : form + } ) }} +{% endblock %} diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/CalendarDoc/edit_accompanying_period.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/CalendarDoc/edit_accompanying_period.html.twig new file mode 100644 index 000000000..545db7100 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/views/CalendarDoc/edit_accompanying_period.html.twig @@ -0,0 +1,39 @@ +{% extends "@ChillPerson/AccompanyingCourse/layout.html.twig" %} + +{% set activeRouteKey = 'chill_calendar_calendar_list' %} + +{% block title %}{{ 'chill_calendar.Edit a document' |trans }}{% endblock title %} + +{% set accompanyingCourse = calendar_doc.calendar.accompanyingPeriod %} +{% set accompanyingCourseId = accompanyingCourse.id %} + +{% block js %} + {{ parent() }} + {{ encore_entry_script_tags('mod_async_upload') }} +{% endblock %} + +{% block css %} + {{ parent() }} + {{ encore_entry_link_tags('mod_async_upload') }} +{% endblock %} + +{% block content %} + +

      {{ 'chill_calendar.Edit a document'|trans }}

      + + {{ form_start(form) }} + + {{ form_row(form.title) }} + + + + {{ form_end(form) }} +{% endblock %} diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/CalendarDoc/edit_person.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/CalendarDoc/edit_person.html.twig new file mode 100644 index 000000000..b6d26ea69 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/views/CalendarDoc/edit_person.html.twig @@ -0,0 +1,38 @@ +{% extends "@ChillPerson/Person/layout.html.twig" %} + +{% set activeRouteKey = 'chill_calendar_calendar_list' %} + +{% block title %}{{ 'chill_calendar.Edit a document' |trans }}{% endblock title %} + +{% set person = calendar_doc.calendar.person %} + +{% block js %} + {{ parent() }} + {{ encore_entry_script_tags('mod_async_upload') }} +{% endblock %} + +{% block css %} + {{ parent() }} + {{ encore_entry_link_tags('mod_async_upload') }} +{% endblock %} + +{% block content %} + +

      {{ 'chill_calendar.Edit a document'|trans }}

      + + {{ form_start(form) }} + + {{ form_row(form.title) }} + + + + {{ form_end(form) }} +{% endblock %} diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/CalendarDoc/new_accompanying_period.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/CalendarDoc/new_accompanying_period.html.twig new file mode 100644 index 000000000..54f196622 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/views/CalendarDoc/new_accompanying_period.html.twig @@ -0,0 +1,40 @@ +{% extends "@ChillPerson/AccompanyingCourse/layout.html.twig" %} + +{% set activeRouteKey = 'chill_calendar_calendar_list' %} + +{% block title %}{{ 'chill_calendar.Add a document' |trans }}{% endblock title %} + +{% set accompanyingCourse = calendar_doc.calendar.accompanyingPeriod %} +{% set accompanyingCourseId = accompanyingCourse.id %} + +{% block js %} + {{ parent() }} + {{ encore_entry_script_tags('mod_async_upload') }} +{% endblock %} + +{% block css %} + {{ parent() }} + {{ encore_entry_link_tags('mod_async_upload') }} +{% endblock %} + +{% block content %} + +

      {{ 'chill_calendar.Add a document'|trans }}

      + + {{ form_start(form) }} + + {{ form_row(form.title) }} + {{ form_row(form.doc) }} + + + + {{ form_end(form) }} +{% endblock %} diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/CalendarDoc/new_person.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/CalendarDoc/new_person.html.twig new file mode 100644 index 000000000..0a5003aed --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/views/CalendarDoc/new_person.html.twig @@ -0,0 +1,39 @@ +{% extends "@ChillPerson/Person/layout.html.twig" %} + +{% set activeRouteKey = 'chill_calendar_calendar_list' %} + +{% block title %}{{ 'chill_calendar.Add a document' |trans }}{% endblock title %} + +{% set person = calendar_doc.calendar.person %} + +{% block js %} + {{ parent() }} + {{ encore_entry_script_tags('mod_async_upload') }} +{% endblock %} + +{% block css %} + {{ parent() }} + {{ encore_entry_link_tags('mod_async_upload') }} +{% endblock %} + +{% block content %} + +

      {{ 'chill_calendar.Add a document'|trans }}

      + + {{ form_start(form) }} + + {{ form_row(form.title) }} + {{ form_row(form.doc) }} + + + + {{ form_end(form) }} +{% endblock %} diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/CalendarShortMessage/short_message.txt.twig b/src/Bundle/ChillCalendarBundle/Resources/views/CalendarShortMessage/short_message.txt.twig new file mode 100644 index 000000000..1669ac1d1 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/views/CalendarShortMessage/short_message.txt.twig @@ -0,0 +1 @@ +Votre travailleur social {{ calendar.mainUser.label }} vous rencontrera le {{ calendar.startDate|format_date('short', locale='fr') }} à {{ calendar.startDate|format_time('short', locale='fr') }} - {% if calendar.location is not null%}{{ calendar.location.name }}{% endif %}{% if calendar.mainUser.mainLocation is not null and calendar.mainUser.mainLocation.phonenumber1 is not null %} En cas d'indisponibilité, appelez-nous au {{ calendar.mainUser.mainLocation.phonenumber1|chill_format_phonenumber }}.{% endif %} diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/CalendarShortMessage/short_message_canceled.twig b/src/Bundle/ChillCalendarBundle/Resources/views/CalendarShortMessage/short_message_canceled.twig new file mode 100644 index 000000000..82453a7a9 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/views/CalendarShortMessage/short_message_canceled.twig @@ -0,0 +1 @@ +Votre RDV avec votre travailleur social {{ calendar.mainUser.label }} prévu le {{ calendar.startDate|format_date('short', locale='fr') }} à {{ calendar.startDate|format_time('short', locale='fr') }} est annulé. {% if calendar.mainUser.mainLocation is not null and calendar.mainUser.mainLocation.phonenumber1 is not null %} En cas de question, appelez-nous au {{ calendar.mainUser.mainLocation.phonenumber1|chill_format_phonenumber }}.{% endif %} diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/MSGraph/calendar_event_body.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/MSGraph/calendar_event_body.html.twig new file mode 100644 index 000000000..63a206ae0 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/views/MSGraph/calendar_event_body.html.twig @@ -0,0 +1,16 @@ +Ce rendez-vous a été généré automatiquement par Chill. + +Pour modifier ou supprimer ce rendez-vous, utilisez l’adresse suivante: + +{{ absolute_url(path('chill_calendar_calendar_edit', {'id': calendar.id, '_locale': 'fr'})) }} + +{{ calendar.comment.comment|nl2br }} + +Usagers et tiers concernés: + +{% for p in calendar.persons %} +- {{ p|chill_entity_render_string }} +{%- endfor -%} +{% for t in calendar.professionals %} +- {{ t|chill_entity_render_string }} +{% endfor %} diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/_invite.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/_invite.html.twig new file mode 100644 index 000000000..c3f2aef58 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/views/_invite.html.twig @@ -0,0 +1,14 @@ +{% macro invite_span(invite) %} + {% if invite.status == 'accepted' %} + {% set fa = 'check' %} + {% elseif invite.status == 'declined' %} + {% set fa = 'times' %} + {% elseif invite.status == 'pending' %} + {% set fa = 'hourglass' %} + {% elseif invite.status == 'tentative' %} + {% set fa = 'question' %} + {% else %} + {% set fa = invite.status %} + {% endif %} + +{% endmacro %} diff --git a/src/Bundle/ChillCalendarBundle/Security/Voter/CalendarDocVoter.php b/src/Bundle/ChillCalendarBundle/Security/Voter/CalendarDocVoter.php new file mode 100644 index 000000000..452286e85 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Security/Voter/CalendarDocVoter.php @@ -0,0 +1,61 @@ +security = $security; + } + + protected function supports($attribute, $subject): bool + { + 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); + } + } +} diff --git a/src/Bundle/ChillCalendarBundle/Security/Voter/CalendarVoter.php b/src/Bundle/ChillCalendarBundle/Security/Voter/CalendarVoter.php new file mode 100644 index 000000000..0e2be1d0f --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Security/Voter/CalendarVoter.php @@ -0,0 +1,134 @@ +security = $security; + $this->voterHelper = $voterHelperFactory + ->generate(self::class) + ->addCheckFor(AccompanyingPeriod::class, [self::SEE, self::CREATE]) + ->addCheckFor(Person::class, [self::SEE, self::CREATE]) + ->addCheckFor(Calendar::class, [self::SEE, self::CREATE, self::EDIT, self::DELETE]) + ->build(); + } + + public function getRoles(): array + { + return [ + self::CREATE, + self::DELETE, + self::EDIT, + self::SEE, + ]; + } + + public function getRolesWithHierarchy(): array + { + return ['Calendar' => $this->getRoles()]; + } + + public function getRolesWithoutScope(): array + { + return [ + self::CREATE, + self::DELETE, + self::EDIT, + self::SEE, + ]; + } + + protected function supports($attribute, $subject): bool + { + return $this->voterHelper->supports($attribute, $subject); + } + + protected function voteOnAttribute($attribute, $subject, TokenInterface $token): bool + { + if ($subject instanceof AccompanyingPeriod) { + switch ($attribute) { + case self::SEE: + case self::CREATE: + if ($subject->getStep() === AccompanyingPeriod::STEP_DRAFT) { + return false; + } + + // we first check here that the user has read access to the period + if (!$this->security->isGranted(AccompanyingPeriodVoter::SEE, $subject)) { + return false; + } + + // There is no scope on Calendar, but there are some on accompanying period + // so, to ignore AccompanyingPeriod's scopes, we create a blank Calendar + // linked with an accompanying period. + return $this->voterHelper->voteOnAttribute($attribute, (new Calendar())->setAccompanyingPeriod($subject), $token); + } + } elseif ($subject instanceof Person) { + switch ($attribute) { + case self::SEE: + case self::CREATE: + return $this->voterHelper->voteOnAttribute($attribute, $subject, $token); + } + } elseif ($subject instanceof Calendar) { + switch ($attribute) { + case self::SEE: + case self::EDIT: + case self::CREATE: + case self::DELETE: + return $this->voterHelper->voteOnAttribute($attribute, $subject, $token); + } + } + + 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 new file mode 100644 index 000000000..36ca66d05 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Security/Voter/InviteVoter.php @@ -0,0 +1,42 @@ +getUser() === $subject->getUser(); + } +} diff --git a/src/Bundle/ChillCalendarBundle/Service/DocGenerator/CalendarContext.php b/src/Bundle/ChillCalendarBundle/Service/DocGenerator/CalendarContext.php new file mode 100644 index 000000000..cabb7dcce --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Service/DocGenerator/CalendarContext.php @@ -0,0 +1,287 @@ +baseContextData = $baseContextData; + $this->entityManager = $entityManager; + $this->normalizer = $normalizer; + $this->personRender = $personRender; + $this->personRepository = $personRepository; + $this->thirdPartyRender = $thirdPartyRender; + $this->thirdPartyRepository = $thirdPartyRepository; + $this->translatableStringHelper = $translatableStringHelper; + } + + public function adminFormReverseTransform(array $data): array + { + return array_merge( + [ + 'trackDatetime' => true, + 'askMainPerson' => true, + 'mainPersonLabel' => 'docgen.calendar.Destinee', + 'askThirdParty' => false, + 'thirdPartyLabel' => 'Third party', + ], + $data + ); + } + + public function adminFormTransform(array $data): array + { + return $data; + } + + public function buildAdminForm(FormBuilderInterface $builder): void + { + $builder + ->add('trackDatetime', CheckboxType::class, [ + 'required' => false, + 'label' => 'docgen.calendar.Track changes on datetime and warn user if date time is updated after the doc generation', + ]) + ->add('askMainPerson', CheckboxType::class, [ + 'required' => false, + 'label' => 'docgen.calendar.Ask main person', + ]) + ->add('mainPersonLabel', TextType::class, [ + 'required' => false, + 'label' => 'docgen.calendar.Main person label', + ]) + ->add('askThirdParty', CheckboxType::class, [ + 'required' => false, + 'label' => 'docgen.calendar.Ask third party', + ]) + ->add('thirdPartyLabel', TextType::class, [ + 'required' => false, + 'label' => 'docgen.calendar.Third party label', + ]); + } + + public function buildPublicForm(FormBuilderInterface $builder, DocGeneratorTemplate $template, $entity): void + { + $options = $this->getOptions($template); + + $builder->add('title', TextType::class, [ + 'required' => true, + 'label' => 'docgen.calendar.title of the generated document', + 'data' => $this->translatableStringHelper->localize($template->getName()), + ]); + + if ($options['askMainPerson']) { + $builder->add('mainPerson', EntityType::class, [ + 'class' => Person::class, + 'multiple' => false, + 'label' => $options['mainPersonLabel'] ?? 'docgen.calendar.Main person label', + 'required' => false, + 'choices' => $entity->getPersons(), + 'choice_label' => fn (Person $p) => $this->personRender->renderString($p, []), + 'expanded' => false, + ]); + } + + if ($options['askThirdParty']) { + $builder->add('thirdParty', EntityType::class, [ + 'class' => ThirdParty::class, + 'multiple' => false, + 'label' => $options['thirdPartyLabel'] ?? 'Third party', + 'choices' => $entity->getProfessionals(), + 'choice_label' => fn (ThirdParty $tp) => $this->thirdPartyRender->renderString($tp, []), + 'expanded' => false, + ]); + } + } + + /** + */ + public function getData(DocGeneratorTemplate $template, mixed $entity, array $contextGenerationData = []): array + { + $options = $this->getOptions($template); + + $data = array_merge( + $this->baseContextData->getData($contextGenerationData['creator'] ?? null), + [ + 'calendar' => $this->normalizer->normalize($entity, 'docgen', ['docgen:expects' => Calendar::class, 'groups' => ['docgen:read']]), + ] + ); + + if ($options['askMainPerson']) { + $data['mainPerson'] = $this->normalizer->normalize($contextGenerationData['mainPerson'] ?? null, 'docgen', [ + 'docgen:expects' => Person::class, + 'groups' => ['docgen:read'], + 'docgen:person:with-household' => true, + 'docgen:person:with-relations' => true, + 'docgen:person:with-budget' => true, + ]); + } + + if ($options['askThirdParty']) { + $data['thirdParty'] = $this->normalizer->normalize($contextGenerationData['thirdParty'] ?? null, 'docgen', [ + 'docgen:expects' => ThirdParty::class, + 'groups' => ['docgen:read'], + ]); + } + + return $data; + } + + public function getDescription(): string + { + return 'docgen.calendar.A base context for generating document on calendar'; + } + + public function getEntityClass(): string + { + return Calendar::class; + } + + public function getFormData(DocGeneratorTemplate $template, $entity): array + { + $options = $this->getOptions($template); + $data = []; + + if ($options['askMainPerson']) { + $data['mainPerson'] = null; + + if (1 === count($entity->getPersons())) { + $data['mainPerson'] = $entity->getPersons()->first(); + } + } + + if ($options['askThirdParty']) { + $data['thirdParty'] = null; + + if (1 === count($entity->getProfessionals())) { + $data['thirdParty'] = $entity->getProfessionals()->first(); + } + } + + return $data; + } + + public static function getKey(): string + { + return self::class; + } + + public function getName(): string + { + return 'docgen.calendar.Base context for calendar'; + } + + public function hasAdminForm(): bool + { + return true; + } + + public function hasPublicForm(DocGeneratorTemplate $template, $entity): bool + { + return true; + } + + public function contextGenerationDataNormalize(DocGeneratorTemplate $template, $entity, array $data): array + { + $normalized = []; + $normalized['title'] = $data['title'] ?? ''; + + foreach (['mainPerson', 'thirdParty'] as $k) { + if (isset($data[$k])) { + $normalized[$k] = $data[$k]->getId(); + } + } + + return $normalized; + } + + public function contextGenerationDataDenormalize(DocGeneratorTemplate $template, $entity, array $data): array + { + $denormalized = []; + $denormalized['title'] = $data['title']; + + if (null !== ($data['mainPerson'] ?? null)) { + if (null === $person = $this->personRepository->find($data['mainPerson'])) { + throw new \RuntimeException('person not found'); + } + $denormalized['mainPerson'] = $person; + } + + if (null !== ($data['thirdParty'] ?? null)) { + if (null === $thirdParty = $this->thirdPartyRepository->find($data['thirdParty'])) { + throw new \RuntimeException('third party not found'); + } + $denormalized['thirdParty'] = $thirdParty; + } + + return $denormalized; + } + + public function storeGenerated(DocGeneratorTemplate $template, StoredObject $storedObject, object $entity, array $contextGenerationData): void + { + $options = $this->getOptions($template); + $storedObject->setTitle($contextGenerationData['title']); + $doc = new CalendarDoc($entity, $storedObject); + $doc->setTrackDateTimeVersion($options['trackDatetime']); + + $this->entityManager->persist($doc); + } + + private function getOptions(DocGeneratorTemplate $template): array + { + return $template->getOptions(); + } +} diff --git a/src/Bundle/ChillCalendarBundle/Service/DocGenerator/CalendarContextInterface.php b/src/Bundle/ChillCalendarBundle/Service/DocGenerator/CalendarContextInterface.php new file mode 100644 index 000000000..527203003 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Service/DocGenerator/CalendarContextInterface.php @@ -0,0 +1,28 @@ + + * @extends DocGeneratorContextWithAdminFormInterface + */ +interface CalendarContextInterface extends DocGeneratorContextWithPublicFormInterface, DocGeneratorContextWithAdminFormInterface +{ +} diff --git a/src/Bundle/ChillCalendarBundle/Service/ShortMessageNotification/BulkCalendarShortMessageSender.php b/src/Bundle/ChillCalendarBundle/Service/ShortMessageNotification/BulkCalendarShortMessageSender.php new file mode 100644 index 000000000..83826bbcf --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Service/ShortMessageNotification/BulkCalendarShortMessageSender.php @@ -0,0 +1,71 @@ +provider = $provider; + $this->em = $em; + $this->logger = $logger; + $this->messageBus = $messageBus; + $this->messageForCalendarBuilder = $messageForCalendarBuilder; + } + + public function sendBulkMessageToEligibleCalendars() + { + $countCalendars = 0; + $countSms = 0; + + foreach ($this->provider->getCalendars(new DateTimeImmutable('now')) as $calendar) { + $smses = $this->messageForCalendarBuilder->buildMessageForCalendar($calendar); + + foreach ($smses as $sms) { + $this->messageBus->dispatch($sms); + ++$countSms; + } + + $this->em + ->createQuery('UPDATE ' . Calendar::class . ' c SET c.smsStatus = :smsStatus WHERE c.id = :id') + ->setParameters(['smsStatus' => Calendar::SMS_SENT, 'id' => $calendar->getId()]) + ->execute(); + ++$countCalendars; + $this->em->refresh($calendar); + } + + $this->logger->info(self::class . 'a bulk of messages was sent', ['count_calendars' => $countCalendars, 'count_sms' => $countSms]); + } +} diff --git a/src/Bundle/ChillCalendarBundle/Service/ShortMessageNotification/CalendarForShortMessageProvider.php b/src/Bundle/ChillCalendarBundle/Service/ShortMessageNotification/CalendarForShortMessageProvider.php new file mode 100644 index 000000000..2316a5408 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Service/ShortMessageNotification/CalendarForShortMessageProvider.php @@ -0,0 +1,81 @@ +calendarRepository = $calendarRepository; + $this->em = $em; + $this->rangeGenerator = $rangeGenerator; + } + + /** + * Generate calendars instance. + * + * Warning: this method takes care of clearing the EntityManager at regular interval + * + * @return iterable|Calendar[] + */ + public function getCalendars(DateTimeImmutable $at): iterable + { + $range = $this->rangeGenerator->generateRange($at); + + if (null === $range) { + return; + } + + ['startDate' => $startDate, 'endDate' => $endDate] = $range; + + $offset = 0; + $batchSize = 10; + + $calendars = $this->calendarRepository + ->findByNotificationAvailable($startDate, $endDate, $batchSize, $offset); + + do { + foreach ($calendars as $calendar) { + ++$offset; + + yield $calendar; + } + + $this->em->clear(); + + $calendars = $this->calendarRepository + ->findByNotificationAvailable($startDate, $endDate, $batchSize, $offset); + } while (count($calendars) === $batchSize); + } +} diff --git a/src/Bundle/ChillCalendarBundle/Service/ShortMessageNotification/DefaultRangeGenerator.php b/src/Bundle/ChillCalendarBundle/Service/ShortMessageNotification/DefaultRangeGenerator.php new file mode 100644 index 000000000..7d6da3ef0 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Service/ShortMessageNotification/DefaultRangeGenerator.php @@ -0,0 +1,75 @@ + Envoi des rdv du mardi et mercredi. + * * Mardi => Envoi des rdv du jeudi. + * * Mercredi => Envoi des rdv du vendredi + * * Jeudi => envoi des rdv du samedi et dimanche + * * Vendredi => Envoi des rdv du lundi. + */ +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'); + + switch ($dow = (int) $onMidnight->format('w')) { + case 6: // Saturday + case 0: // Sunday + return null; + + case 1: // Monday + // send for Tuesday and Wednesday + $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')); + + break; + + case 4: // thursday + $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')); + + break; + + default: + 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 new file mode 100644 index 000000000..cd61a8624 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Service/ShortMessageNotification/DefaultShortMessageForCalendarBuilder.php @@ -0,0 +1,65 @@ +engine = $engine; + } + + public function buildMessageForCalendar(Calendar $calendar): array + { + if (true !== $calendar->getSendSMS()) { + return []; + } + + $toUsers = []; + + foreach ($calendar->getPersons() as $person) { + if (false === $person->getAcceptSMS() || null === $person->getAcceptSMS() || null === $person->getMobilenumber()) { + continue; + } + + if (Calendar::SMS_PENDING === $calendar->getSmsStatus()) { + $toUsers[] = new ShortMessage( + $this->engine->render('@ChillCalendar/CalendarShortMessage/short_message.txt.twig', ['calendar' => $calendar]), + $person->getMobilenumber(), + ShortMessage::PRIORITY_LOW + ); + } elseif (Calendar::SMS_CANCEL_PENDING === $calendar->getSmsStatus()) { + $toUsers[] = new ShortMessage( + $this->engine->render('@ChillCalendar/CalendarShortMessage/short_message_canceled.txt.twig', ['calendar' => $calendar]), + $person->getMobilenumber(), + ShortMessage::PRIORITY_LOW + ); + } + } + + return $toUsers; + } +} diff --git a/src/Bundle/ChillCalendarBundle/Service/ShortMessageNotification/RangeGeneratorInterface.php b/src/Bundle/ChillCalendarBundle/Service/ShortMessageNotification/RangeGeneratorInterface.php new file mode 100644 index 000000000..357e264d9 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Service/ShortMessageNotification/RangeGeneratorInterface.php @@ -0,0 +1,29 @@ +request( + 'POST', + '/public/incoming-hook/calendar/msgraph/events/23', + [], + [], + [], + self::SAMPLE_BODY + ); + + $this->assertResponseIsSuccessful(); + $this->assertResponseStatusCodeSame(202); + + /** @var InMemoryTransport $transport */ + $transport = self::$container->get('messenger.transport.async'); + $this->assertCount(1, $transport->getSent()); + } + + public function testValidateSubscription(): void + { + $client = self::createClient(); + $client->request( + 'POST', + '/public/incoming-hook/calendar/msgraph/events/23?validationToken=something%20to%20decode' + ); + + $this->assertResponseIsSuccessful(); + + $response = $client->getResponse(); + + $this->assertResponseHasHeader('Content-Type'); + $this->assertStringContainsString('text/plain', $response->headers->get('Content-Type')); + $this->assertEquals('something to decode', $response->getContent()); + } +} diff --git a/src/Bundle/ChillCalendarBundle/Tests/Entity/CalendarDocTest.php b/src/Bundle/ChillCalendarBundle/Tests/Entity/CalendarDocTest.php new file mode 100644 index 000000000..fa6132b03 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Tests/Entity/CalendarDocTest.php @@ -0,0 +1,63 @@ +title = 'tagada'; + $create->doc = $obj1 = new StoredObject(); + + $doc->createFromDTO($create); + + $this->assertSame($obj1, $doc->getStoredObject()); + $this->assertEquals('tagada', $doc->getStoredObject()->getTitle()); + + $edit = new CalendarDoc\CalendarDocEditDTO($doc); + $edit->title = 'tsointsoin'; + + $doc->editFromDTO($edit); + + $this->assertSame($obj1, $doc->getStoredObject()); + $this->assertEquals('tsointsoin', $doc->getStoredObject()->getTitle()); + + $edit2 = new CalendarDoc\CalendarDocEditDTO($doc); + $edit2->doc = $obj2 = new StoredObject(); + + $doc->editFromDTO($edit2); + + $this->assertSame($obj2, $doc->getStoredObject()); + $this->assertEquals('tsointsoin', $doc->getStoredObject()->getTitle()); + + $edit3 = new CalendarDoc\CalendarDocEditDTO($doc); + $edit3->doc = $obj3 = new StoredObject(); + $edit3->title = 'tagada'; + + $doc->editFromDTO($edit3); + + $this->assertSame($obj3, $doc->getStoredObject()); + $this->assertEquals('tagada', $doc->getStoredObject()->getTitle()); + } +} diff --git a/src/Bundle/ChillCalendarBundle/Tests/Entity/CalendarTest.php b/src/Bundle/ChillCalendarBundle/Tests/Entity/CalendarTest.php new file mode 100644 index 000000000..e37436ad3 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Tests/Entity/CalendarTest.php @@ -0,0 +1,63 @@ +assertCount(0, $calendar->getInvites()); + $this->assertCount(0, $calendar->getUsers()); + + $calendar->addUser($user0 = new User()); + + $this->assertCount(1, $calendar->getInvites()); + $this->assertCount(1, $calendar->getUsers()); + $this->assertSame($user0, $calendar->getUsers()->first()); + + $calendar->addUser($user1 = new User()); + + $this->assertCount(2, $calendar->getInvites()); + $this->assertCount(2, $calendar->getUsers()); + $this->assertContains($user0, $calendar->getUsers()); + $this->assertContains($user1, $calendar->getUsers()); + + $calendar->removeUser($user0); + + $this->assertCount(1, $calendar->getInvites()); + $this->assertCount(1, $calendar->getUsers()); + $this->assertNotSame($user0, $calendar->getUsers()->first()); + $this->assertSame($user1, $calendar->getUsers()->first()); + + $calendar->removeUser($user1); + + $this->assertCount(0, $calendar->getInvites()); + $this->assertCount(0, $calendar->getUsers()); + } +} diff --git a/src/Bundle/ChillCalendarBundle/Tests/Export/Aggregator/AgentAggregatorTest.php b/src/Bundle/ChillCalendarBundle/Tests/Export/Aggregator/AgentAggregatorTest.php new file mode 100644 index 000000000..6746282a4 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Tests/Export/Aggregator/AgentAggregatorTest.php @@ -0,0 +1,68 @@ +aggregator = self::$container->get('chill.calendar.export.agent_aggregator'); + } + + public function getAggregator() + { + return $this->aggregator; + } + + public function getFormData(): array + { + return [ + [], + ]; + } + + public function getQueryBuilders(): array + { + if (null === self::$kernel) { + self::bootKernel(); + } + + $em = self::$container->get(EntityManagerInterface::class); + + return [ + $em->createQueryBuilder() + ->select('count(cal.id)') + ->from(Calendar::class, 'cal') + ->join('cal.mainUser', 'caluser'), + ]; + } +} diff --git a/src/Bundle/ChillCalendarBundle/Tests/Export/Aggregator/CancelReasonAggregatorTest.php b/src/Bundle/ChillCalendarBundle/Tests/Export/Aggregator/CancelReasonAggregatorTest.php new file mode 100644 index 000000000..956a81174 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Tests/Export/Aggregator/CancelReasonAggregatorTest.php @@ -0,0 +1,68 @@ +aggregator = self::$container->get('chill.calendar.export.cancel_reason_aggregator'); + } + + public function getAggregator() + { + return $this->aggregator; + } + + public function getFormData(): array + { + return [ + [], + ]; + } + + public function getQueryBuilders(): array + { + if (null === self::$kernel) { + self::bootKernel(); + } + + $em = self::$container->get(EntityManagerInterface::class); + + return [ + $em->createQueryBuilder() + ->select('count(cal.id)') + ->from(Calendar::class, 'cal') + ->join('cal.cancelReason', 'calcancel'), + ]; + } +} diff --git a/src/Bundle/ChillCalendarBundle/Tests/Export/Aggregator/JobAggregatorTest.php b/src/Bundle/ChillCalendarBundle/Tests/Export/Aggregator/JobAggregatorTest.php new file mode 100644 index 000000000..b389b1536 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Tests/Export/Aggregator/JobAggregatorTest.php @@ -0,0 +1,68 @@ +aggregator = self::$container->get('chill.calendar.export.job_aggregator'); + } + + public function getAggregator() + { + return $this->aggregator; + } + + public function getFormData(): array + { + return [ + [], + ]; + } + + public function getQueryBuilders(): array + { + if (null === self::$kernel) { + self::bootKernel(); + } + + $em = self::$container->get(EntityManagerInterface::class); + + return [ + $em->createQueryBuilder() + ->select('count(cal.id)') + ->from(Calendar::class, 'cal') + ->join('cal.user', 'caluser'), + ]; + } +} diff --git a/src/Bundle/ChillCalendarBundle/Tests/Export/Aggregator/LocationAggregatorTest.php b/src/Bundle/ChillCalendarBundle/Tests/Export/Aggregator/LocationAggregatorTest.php new file mode 100644 index 000000000..50ee456e5 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Tests/Export/Aggregator/LocationAggregatorTest.php @@ -0,0 +1,68 @@ +aggregator = self::$container->get('chill.calendar.export.location_aggregator'); + } + + public function getAggregator() + { + return $this->aggregator; + } + + public function getFormData(): array + { + return [ + [], + ]; + } + + public function getQueryBuilders(): array + { + if (null === self::$kernel) { + self::bootKernel(); + } + + $em = self::$container->get(EntityManagerInterface::class); + + return [ + $em->createQueryBuilder() + ->select('count(cal.id)') + ->from(Calendar::class, 'cal') + ->join('cal.location', 'calloc'), + ]; + } +} diff --git a/src/Bundle/ChillCalendarBundle/Tests/Export/Aggregator/LocationTypeAggregatorTest.php b/src/Bundle/ChillCalendarBundle/Tests/Export/Aggregator/LocationTypeAggregatorTest.php new file mode 100644 index 000000000..55dcf317f --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Tests/Export/Aggregator/LocationTypeAggregatorTest.php @@ -0,0 +1,68 @@ +aggregator = self::$container->get('chill.calendar.export.location_type_aggregator'); + } + + public function getAggregator() + { + return $this->aggregator; + } + + public function getFormData(): array + { + return [ + [], + ]; + } + + public function getQueryBuilders(): array + { + if (null === self::$kernel) { + self::bootKernel(); + } + + $em = self::$container->get(EntityManagerInterface::class); + + return [ + $em->createQueryBuilder() + ->select('count(cal.id)') + ->from(Calendar::class, 'cal') + ->join('cal.location', 'calloc'), + ]; + } +} diff --git a/src/Bundle/ChillCalendarBundle/Tests/Export/Aggregator/MonthYearAggregatorTest.php b/src/Bundle/ChillCalendarBundle/Tests/Export/Aggregator/MonthYearAggregatorTest.php new file mode 100644 index 000000000..8e016b54c --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Tests/Export/Aggregator/MonthYearAggregatorTest.php @@ -0,0 +1,67 @@ +aggregator = self::$container->get('chill.calendar.export.month_aggregator'); + } + + public function getAggregator() + { + return $this->aggregator; + } + + public function getFormData(): array + { + return [ + [], + ]; + } + + public function getQueryBuilders(): array + { + if (null === self::$kernel) { + self::bootKernel(); + } + + $em = self::$container->get(EntityManagerInterface::class); + + return [ + $em->createQueryBuilder() + ->select('count(cal.id)') + ->from(Calendar::class, 'cal'), + ]; + } +} diff --git a/src/Bundle/ChillCalendarBundle/Tests/Export/Aggregator/ScopeAggregatorTest.php b/src/Bundle/ChillCalendarBundle/Tests/Export/Aggregator/ScopeAggregatorTest.php new file mode 100644 index 000000000..fa816f4a5 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Tests/Export/Aggregator/ScopeAggregatorTest.php @@ -0,0 +1,68 @@ +aggregator = self::$container->get('chill.calendar.export.scope_aggregator'); + } + + public function getAggregator() + { + return $this->aggregator; + } + + public function getFormData(): array + { + return [ + [], + ]; + } + + public function getQueryBuilders(): array + { + if (null === self::$kernel) { + self::bootKernel(); + } + + $em = self::$container->get(EntityManagerInterface::class); + + return [ + $em->createQueryBuilder() + ->select('count(cal.id)') + ->from(Calendar::class, 'cal') + ->join('cal.user', 'caluser'), + ]; + } +} diff --git a/src/Bundle/ChillCalendarBundle/Tests/Export/Filter/AgentFilterTest.php b/src/Bundle/ChillCalendarBundle/Tests/Export/Filter/AgentFilterTest.php new file mode 100644 index 000000000..d5ec9b22d --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Tests/Export/Filter/AgentFilterTest.php @@ -0,0 +1,88 @@ +prophesize(); + + $request->willExtend(\Symfony\Component\HttpFoundation\Request::class); + $request->getLocale()->willReturn('fr'); + + $this->filter = self::$container->get('chill.calendar.export.agent_filter'); + } + + public function getFilter() + { + return $this->filter; + } + + public function getFormData(): array + { + $em = self::$container->get(EntityManagerInterface::class); + + $array = $em->createQueryBuilder() + ->from(User::class, 'u') + ->select('u') + ->getQuery() + ->getResult(); + + $data = []; + + foreach ($array as $a) { + $data[] = [ + 'accepted_agents' => $a, + ]; + } + + return $data; + } + + public function getQueryBuilders(): array + { + if (null === self::$kernel) { + self::bootKernel(); + } + + $em = self::$container->get(EntityManagerInterface::class); + + return [ + $em->createQueryBuilder() + ->select('cal.id') + ->from(Calendar::class, 'cal'), + ]; + } +} diff --git a/src/Bundle/ChillCalendarBundle/Tests/Export/Filter/BetweenDatesFilterTest.php b/src/Bundle/ChillCalendarBundle/Tests/Export/Filter/BetweenDatesFilterTest.php new file mode 100644 index 000000000..85db16c72 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Tests/Export/Filter/BetweenDatesFilterTest.php @@ -0,0 +1,77 @@ +prophesize(); + + $request->willExtend(\Symfony\Component\HttpFoundation\Request::class); + $request->getLocale()->willReturn('fr'); + + $this->filter = self::$container->get('chill.calendar.export.between_dates_filter'); + } + + public function getFilter() + { + return $this->filter; + } + + public function getFormData(): array + { + return [ + [ + 'date_from' => DateTime::createFromFormat('Y-m-d', '2022-05-01'), + 'date_to' => DateTime::createFromFormat('Y-m-d', '2022-06-01'), + ], + ]; + } + + public function getQueryBuilders(): array + { + if (null === self::$kernel) { + self::bootKernel(); + } + + $em = self::$container->get(EntityManagerInterface::class); + + return [ + $em->createQueryBuilder() + ->select('cal.id') + ->from(Calendar::class, 'cal'), + ]; + } +} diff --git a/src/Bundle/ChillCalendarBundle/Tests/Export/Filter/JobFilterTest.php b/src/Bundle/ChillCalendarBundle/Tests/Export/Filter/JobFilterTest.php new file mode 100644 index 000000000..61cb47120 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Tests/Export/Filter/JobFilterTest.php @@ -0,0 +1,89 @@ +prophesize(); + + $request->willExtend(\Symfony\Component\HttpFoundation\Request::class); + $request->getLocale()->willReturn('fr'); + + $this->filter = self::$container->get('chill.calendar.export.job_filter'); + } + + public function getFilter() + { + return $this->filter; + } + + public function getFormData(): array + { + $em = self::$container->get(EntityManagerInterface::class); + + $array = $em->createQueryBuilder() + ->from(UserJob::class, 'uj') + ->select('uj') + ->getQuery() + ->getResult(); + + $data = []; + + foreach ($array as $a) { + $data[] = [ + 'job' => $a, + ]; + } + + return $data; + } + + public function getQueryBuilders(): array + { + if (null === self::$kernel) { + self::bootKernel(); + } + + $em = self::$container->get(EntityManagerInterface::class); + + return [ + $em->createQueryBuilder() + ->select('cal.id') + ->from(Calendar::class, 'cal') + ->join('cal.user', 'caluser'), + ]; + } +} diff --git a/src/Bundle/ChillCalendarBundle/Tests/Export/Filter/ScopeFilterTest.php b/src/Bundle/ChillCalendarBundle/Tests/Export/Filter/ScopeFilterTest.php new file mode 100644 index 000000000..a6ab9ed7b --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Tests/Export/Filter/ScopeFilterTest.php @@ -0,0 +1,89 @@ +prophesize(); + + $request->willExtend(\Symfony\Component\HttpFoundation\Request::class); + $request->getLocale()->willReturn('fr'); + + $this->filter = self::$container->get('chill.calendar.export.scope_filter'); + } + + public function getFilter() + { + return $this->filter; + } + + public function getFormData(): array + { + $em = self::$container->get(EntityManagerInterface::class); + + $array = $em->createQueryBuilder() + ->from(Scope::class, 's') + ->select('s') + ->getQuery() + ->getResult(); + + $data = []; + + foreach ($array as $a) { + $data[] = [ + 'scope' => $a, + ]; + } + + return $data; + } + + public function getQueryBuilders(): array + { + if (null === self::$kernel) { + self::bootKernel(); + } + + $em = self::$container->get(EntityManagerInterface::class); + + return [ + $em->createQueryBuilder() + ->select('cal.id') + ->from(Calendar::class, 'cal') + ->join('cal.user', 'caluser'), + ]; + } +} diff --git a/src/Bundle/ChillCalendarBundle/Tests/Form/CalendarTypeTest.php b/src/Bundle/ChillCalendarBundle/Tests/Form/CalendarTypeTest.php new file mode 100644 index 000000000..9195d5eac --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Tests/Form/CalendarTypeTest.php @@ -0,0 +1,204 @@ +personsToIdDataTransformer = $this->buildMultiToIdDataTransformer(PersonsToIdDataTransformer::class, Person::class); + $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->calendarRangeDataTransformer = $this->buildSingleToIdDataTransformer(IdToCalendarRangeDataTransformer::class, CalendarRange::class); + $tokenStorage = $this->prophesize(TokenStorageInterface::class); + $token = $this->prophesize(TokenInterface::class); + $token->getUser()->willReturn(new User()); + $tokenStorage->getToken()->willReturn($token->reveal()); + $this->tokenStorage = $tokenStorage->reveal(); + + parent::setUp(); + } + + public function testSubmitValidData() + { + $formData = [ + 'mainUser' => '1', + 'users' => '2,3', + 'professionnals' => '4,5', + 'startDate' => '2022-05-05 14:00:00', + 'endDate' => '2022-05-05 14:30:00', + 'persons' => '7', + 'calendarRange' => '8', + 'location' => '9', + 'sendSMS' => '1', + ]; + + $calendar = new Calendar(); + $calendar->setMainUser(new class () extends User { + public function getId() + { + return '1'; + } + }); + + $form = $this->factory->create(CalendarType::class, $calendar); + + $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(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 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, + $this->idToUsersDataTransformer, + $this->idToLocationDataTransformer, + $this->partiesToIdDataTransformer, + $this->calendarRangeDataTransformer + ); + $commentType = new CommentType($this->tokenStorage); + + return array_merge( + parent::getExtensions(), + [new PreloadedExtension([$calendarType, $commentType], [])] + ); + } + + private function buildMultiToIdDataTransformer( + string $classTransformer, + string $objClass + ) { + $transformer = $this->prophesize($classTransformer); + $transformer->transform(Argument::type('array')) + ->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 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]) { + return []; + } + + return array_map( + static function ($id) use ($objClass) { + $obj = new $objClass(); + $reflectionProperty = new ReflectionProperty($objClass, 'id'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($obj, (int) $id); + + return $obj; + }, + explode(',', $args[0]) + ); + }); + + return $transformer->reveal(); + } + + private function buildSingleToIdDataTransformer( + string $classTransformer, + string $class + ) { + $transformer = $this->prophesize($classTransformer); + $transformer->transform(Argument::type('object')) + ->will(static fn ($args) => (string) $args[0]->getId()); + $transformer->transform(Argument::exact(null)) + ->willReturn(''); + $transformer->reverseTransform(Argument::type('string')) + ->will(static function ($args) use ($class) { + if (null === $args[0]) { + return null; + } + $obj = new $class(); + $reflectionProperty = new ReflectionProperty($class, 'id'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($obj, (int) $args[0]); + + return $obj; + }); + + return $transformer->reveal(); + } +} diff --git a/src/Bundle/ChillCalendarBundle/Tests/RemoteCalendar/Connector/MSGraph/AddressConverterTest.php b/src/Bundle/ChillCalendarBundle/Tests/RemoteCalendar/Connector/MSGraph/AddressConverterTest.php new file mode 100644 index 000000000..827e22381 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Tests/RemoteCalendar/Connector/MSGraph/AddressConverterTest.php @@ -0,0 +1,72 @@ +setName(['fr' => 'Belgique']); + $postalCode = (new PostalCode())->setName('Houte-Si-Plout')->setCode('4122') + ->setCountry($country); + $address = (new Address())->setPostcode($postalCode)->setStreet("Rue de l'Église") + ->setStreetNumber('15B')->setBuildingName('Résidence de la Truite'); + + $actual = $this->buildAddressConverter()->addressToRemote($address); + + $this->assertArrayHasKey('city', $actual); + $this->assertStringContainsString($actual['city'], 'Houte-Si-Plout'); + $this->assertArrayHasKey('postalCode', $actual); + $this->assertStringContainsString($actual['postalCode'], '4122'); + $this->assertArrayHasKey('countryOrRegion', $actual); + $this->assertStringContainsString('Belgique', $actual['countryOrRegion']); + $this->assertArrayHasKey('street', $actual); + $this->assertStringContainsString('Rue de l\'Église', $actual['street']); + $this->assertStringContainsString('15B', $actual['street']); + $this->assertStringContainsString('Résidence de la Truite', $actual['street']); + } + + private function buildAddressConverter(): AddressConverter + { + $engine = $this->prophesize(EngineInterface::class); + $translatableStringHelper = $this->prophesize(TranslatableStringHelperInterface::class); + $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()); + + return new AddressConverter($addressRender, $translatableStringHelper->reveal()); + } +} diff --git a/src/Bundle/ChillCalendarBundle/Tests/RemoteCalendar/Connector/MSGraph/CalendarRangeSyncerTest.php b/src/Bundle/ChillCalendarBundle/Tests/RemoteCalendar/Connector/MSGraph/CalendarRangeSyncerTest.php new file mode 100644 index 000000000..92280f6c1 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Tests/RemoteCalendar/Connector/MSGraph/CalendarRangeSyncerTest.php @@ -0,0 +1,255 @@ +prophesize(EntityManagerInterface::class); + $em->remove(Argument::type(CalendarRange::class))->shouldNotBeCalled(); + + $machineHttpClient = new MockHttpClient([ + new MockResponse(self::REMOTE_CALENDAR_RANGE, ['http_code' => 200]), + ]); + + $calendarRangeSyncer = new CalendarRangeSyncer( + $em->reveal(), + new NullLogger(), + $machineHttpClient + ); + + $calendarRange = new CalendarRange(); + $calendarRange + ->setUser($user = new User()); + $calendar = new Calendar(); + $calendar->setCalendarRange($calendarRange); + + $notification = json_decode(self::NOTIF_DELETE, true); + + $calendarRangeSyncer->handleCalendarRangeSync( + $calendarRange, + $notification['value'][0], + $user + ); + } + + public function testDeleteCalendarRangeWithoutAssociation(): void + { + $em = $this->prophesize(EntityManagerInterface::class); + $em->remove(Argument::type(CalendarRange::class))->shouldBeCalled(); + + $machineHttpClient = new MockHttpClient([ + new MockResponse(self::REMOTE_CALENDAR_RANGE, ['http_code' => 200]), + ]); + + $calendarRangeSyncer = new CalendarRangeSyncer( + $em->reveal(), + new NullLogger(), + $machineHttpClient + ); + + $calendarRange = new CalendarRange(); + $calendarRange + ->setUser($user = new User()); + $notification = json_decode(self::NOTIF_DELETE, true); + + $calendarRangeSyncer->handleCalendarRangeSync( + $calendarRange, + $notification['value'][0], + $user + ); + $this->assertTrue($calendarRange->preventEnqueueChanges); + } + + public function testUpdateCalendarRange(): void + { + $em = $this->prophesize(EntityManagerInterface::class); + $machineHttpClient = new MockHttpClient([ + new MockResponse(self::REMOTE_CALENDAR_RANGE, ['http_code' => 200]), + ]); + + $calendarRangeSyncer = new CalendarRangeSyncer( + $em->reveal(), + new NullLogger(), + $machineHttpClient + ); + + $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')) + ->addRemoteAttributes([ + 'lastModifiedDateTime' => 0, + 'changeKey' => 'abc', + ]); + $notification = json_decode(self::NOTIF_UPDATE, true); + + $calendarRangeSyncer->handleCalendarRangeSync( + $calendarRange, + $notification['value'][0], + $user + ); + + $this->assertStringContainsString( + '2022-06-10T15:30:00', + $calendarRange->getStartDate()->format(DateTimeImmutable::ATOM) + ); + $this->assertStringContainsString( + '2022-06-10T17:30:00', + $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 new file mode 100644 index 000000000..6ccd7a35e --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Tests/RemoteCalendar/Connector/MSGraph/CalendarSyncerTest.php @@ -0,0 +1,596 @@ +\r\n\r\n\r\n\r\n\r\n
      \r\n
      \r\n
      ________________________________________________________________________________\r\n
      \r\n
      \r\n
      Réunion Microsoft Teams\r\n
      \r\n
      \r\n
      Rejoindre sur votre ordinateur ou application mobile\r\n
      \r\nCliquez\r\n ici pour participer à la réunion
      \r\n\r\n
      \r\n
      \r\n
      \r\n
      \r\n
      \r\n
      ________________________________________________________________________________\r\n
      \r\n\r\n\r\n" + }, + "start": { + "dateTime": "2022-06-11T12:30:00.0000000", + "timeZone": "UTC" + }, + "end": { + "dateTime": "2022-06-11T13:30:00.0000000", + "timeZone": "UTC" + }, + "location": { + "displayName": "", + "locationType": "default", + "uniqueIdType": "unknown", + "address": {}, + "coordinates": {} + }, + "locations": [], + "recurrence": null, + "attendees": [ + { + "type": "required", + "status": { + "response": "accepted", + "time": "2022-06-08T16:22:15.1392583Z" + }, + "emailAddress": { + "name": "Alex Wilber", + "address": "AlexW@2zy74l.onmicrosoft.com" + } + }, + { + "type": "required", + "status": { + "response": "declined", + "time": "2022-06-08T16:22:15.1392583Z" + }, + "emailAddress": { + "name": "Alfred Nobel", + "address": "alfredN@2zy74l.onmicrosoft.com" + } + } + ], + "organizer": { + "emailAddress": { + "name": "Diego Siciliani", + "address": "DiegoS@2zy74l.onmicrosoft.com" + } + }, + "onlineMeeting": { + "joinUrl": "https://teams.microsoft.com/l/meetup-join/19%3ameeting_NTE3ODUxY2ItNGJhNi00Y2UwLTljN2QtMmQ3YjAxNWY1Nzk2%40thread.v2/0?context=%7b%22Tid%22%3a%22421bf216-3f48-47bd-a7cf-8b1995cb24bd%22%2c%22Oid%22%3a%224feb0ae3-7ffb-48dd-891e-c86b2cdeefd4%22%7d" + } + } + JSON; + + private const REMOTE_CALENDAR_WITH_ATTENDEES = <<<'JSON' + { + "@odata.etag": "W/\"B3bmsWoxX06b9JHIZPptRQAAJcrv7A==\"", + "id": "AAMkADM1MTdlMGIzLTZhZWUtNDQ0ZC05Y2M4LWViMjhmOWJlMDhhMQBGAAAAAAA5e3965gkBSLcU1p00sMSyBwAHduaxajFfTpv0kchk_m1FAAAAAAENAAAHduaxajFfTpv0kchk_m1FAAAl1BuqAAA=", + "createdDateTime": "2022-06-08T16:19:18.997293Z", + "lastModifiedDateTime": "2022-06-08T16:22:18.7276485Z", + "changeKey": "B3bmsWoxX06b9JHIZPptRQAAJcrv7A==", + "categories": [], + "transactionId": "42ac0b77-313e-20ca-2cac-1a3f58b3452d", + "originalStartTimeZone": "Romance Standard Time", + "originalEndTimeZone": "Romance Standard Time", + "iCalUId": "040000008200E00074C5B7101A82E00800000000A8955A81537BD801000000000000000010000000007EB443987CD641B6794C4BA48EE356", + "reminderMinutesBeforeStart": 15, + "isReminderOn": true, + "hasAttachments": false, + "subject": "test 2", + "bodyPreview": "________________________________________________________________________________\r\nRéunion Microsoft Teams\r\nRejoindre sur votre ordinateur ou application mobile\r\nCliquez ici pour participer à la réunion\r\nPour en savoir plus | Options de réunion\r\n________", + "importance": "normal", + "sensitivity": "normal", + "isAllDay": false, + "isCancelled": false, + "isOrganizer": true, + "responseRequested": true, + "seriesMasterId": null, + "showAs": "busy", + "type": "singleInstance", + "webLink": "https://outlook.office365.com/owa/?itemid=AAMkADM1MTdlMGIzLTZhZWUtNDQ0ZC05Y2M4LWViMjhmOWJlMDhhMQBGAAAAAAA5e3965gkBSLcU1p00sMSyBwAHduaxajFfTpv0kchk%2Bm1FAAAAAAENAAAHduaxajFfTpv0kchk%2Bm1FAAAl1BuqAAA%3D&exvsurl=1&path=/calendar/item", + "onlineMeetingUrl": null, + "isOnlineMeeting": true, + "onlineMeetingProvider": "teamsForBusiness", + "allowNewTimeProposals": true, + "occurrenceId": null, + "isDraft": false, + "hideAttendees": false, + "responseStatus": { + "response": "organizer", + "time": "0001-01-01T00:00:00Z" + }, + "body": { + "contentType": "html", + "content": "\r\n\r\n\r\n\r\n\r\n
      \r\n
      \r\n
      ________________________________________________________________________________\r\n
      \r\n
      \r\n
      Réunion Microsoft Teams\r\n
      \r\n
      \r\n
      Rejoindre sur votre ordinateur ou application mobile\r\n
      \r\nCliquez\r\n ici pour participer à la réunion
      \r\n\r\n
      \r\n
      \r\n
      \r\n
      \r\n
      \r\n
      ________________________________________________________________________________\r\n
      \r\n\r\n\r\n" + }, + "start": { + "dateTime": "2022-06-11T12:30:00.0000000", + "timeZone": "UTC" + }, + "end": { + "dateTime": "2022-06-11T13:30:00.0000000", + "timeZone": "UTC" + }, + "location": { + "displayName": "", + "locationType": "default", + "uniqueIdType": "unknown", + "address": {}, + "coordinates": {} + }, + "locations": [], + "recurrence": null, + "attendees": [ + { + "type": "required", + "status": { + "response": "accepted", + "time": "2022-06-08T16:22:15.1392583Z" + }, + "emailAddress": { + "name": "Alex Wilber", + "address": "AlexW@2zy74l.onmicrosoft.com" + } + }, + { + "type": "required", + "status": { + "response": "accepted", + "time": "2022-06-08T16:22:15.1392583Z" + }, + "emailAddress": { + "name": "External User", + "address": "external@example.com" + } + }, + { + "type": "required", + "status": { + "response": "declined", + "time": "2022-06-08T16:22:15.1392583Z" + }, + "emailAddress": { + "name": "Alfred Nobel", + "address": "alfredN@2zy74l.onmicrosoft.com" + } + } + ], + "organizer": { + "emailAddress": { + "name": "Diego Siciliani", + "address": "DiegoS@2zy74l.onmicrosoft.com" + } + }, + "onlineMeeting": { + "joinUrl": "https://teams.microsoft.com/l/meetup-join/19%3ameeting_NTE3ODUxY2ItNGJhNi00Y2UwLTljN2QtMmQ3YjAxNWY1Nzk2%40thread.v2/0?context=%7b%22Tid%22%3a%22421bf216-3f48-47bd-a7cf-8b1995cb24bd%22%2c%22Oid%22%3a%224feb0ae3-7ffb-48dd-891e-c86b2cdeefd4%22%7d" + } + } + JSON; + + protected function setUp(): void + { + parent::setUp(); + + // all tests should run when timezone = +02:00 + $brussels = new DateTimeZone('Europe/Brussels'); + + if (7200 === $brussels->getOffset(new DateTimeImmutable())) { + date_default_timezone_set('Europe/Brussels'); + } else { + date_default_timezone_set('Europe/Moscow'); + } + } + + public function testHandleAttendeesConfirmingCalendar(): void + { + $machineHttpClient = new MockHttpClient([ + new MockResponse(self::REMOTE_CALENDAR_WITH_ATTENDEES, ['http_code' => 200]), + ]); + + $userA = (new User())->setEmail('alexw@2zy74l.onmicrosoft.com') + ->setEmailCanonical('alexw@2zy74l.onmicrosoft.com'); + $userB = (new User())->setEmail('zzzzz@2zy74l.onmicrosoft.com') + ->setEmailCanonical('zzzzz@2zy74l.onmicrosoft.com'); + $userC = (new User())->setEmail('alfredN@2zy74l.onmicrosoft.com') + ->setEmailCanonical('alfredn@2zy74l.onmicrosoft.com'); + + $userRepository = $this->prophesize(UserRepositoryInterface::class); + $userRepository->findOneByUsernameOrEmail(Argument::exact('AlexW@2zy74l.onmicrosoft.com')) + ->willReturn($userA); + $userRepository->findOneByUsernameOrEmail(Argument::exact('zzzzz@2zy74l.onmicrosoft.com')) + ->willReturn($userB); + $userRepository->findOneByUsernameOrEmail(Argument::exact('alfredN@2zy74l.onmicrosoft.com')) + ->willReturn($userC); + $userRepository->findOneByUsernameOrEmail(Argument::exact('external@example.com')) + ->willReturn(null); + + $calendarSyncer = new CalendarSyncer( + new NullLogger(), + $machineHttpClient, + $userRepository->reveal() + ); + + $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')) + ->addUser($userA) + ->addUser($userB) + ->setCalendarRange(new CalendarRange()) + ->addRemoteAttributes([ + 'lastModifiedDateTime' => 0, + 'changeKey' => 'abcd', + ]); + + $notification = json_decode(self::NOTIF_UPDATE, true); + + $calendarSyncer->handleCalendarSync( + $calendar, + $notification['value'][0], + $user + ); + + $this->assertTrue($calendar->preventEnqueueChanges); + $this->assertEquals(Calendar::STATUS_VALID, $calendar->getStatus()); + // user A is invited, and accepted + $this->assertTrue($calendar->isInvited($userA)); + $this->assertEquals(Invite::ACCEPTED, $calendar->getInviteForUser($userA)->getStatus()); + $this->assertFalse($calendar->getInviteForUser($userA)->preventEnqueueChanges); + // user B is no more invited + $this->assertFalse($calendar->isInvited($userB)); + // user C is invited, but declined + $this->assertFalse($calendar->getInviteForUser($userC)->preventEnqueueChanges); + $this->assertTrue($calendar->isInvited($userC)); + $this->assertEquals(Invite::DECLINED, $calendar->getInviteForUser($userC)->getStatus()); + } + + public function testHandleDeleteCalendar(): void + { + $machineHttpClient = new MockHttpClient([]); + $userRepository = $this->prophesize(UserRepositoryInterface::class); + + $calendarSyncer = new CalendarSyncer( + new NullLogger(), + $machineHttpClient, + $userRepository->reveal() + ); + + $calendar = new Calendar(); + $calendar + ->setMainUser($user = new User()) + ->setCalendarRange($calendarRange = new CalendarRange()); + + $notification = json_decode(self::NOTIF_DELETE, true); + + $calendarSyncer->handleCalendarSync( + $calendar, + $notification['value'][0], + $user + ); + + $this->assertEquals(Calendar::STATUS_CANCELED, $calendar->getStatus()); + $this->assertNull($calendar->getCalendarRange()); + $this->assertTrue($calendar->preventEnqueueChanges); + } + + public function testHandleMoveCalendar(): void + { + $machineHttpClient = new MockHttpClient([ + new MockResponse(self::REMOTE_CALENDAR_NO_ATTENDEES, ['http_code' => 200]), + ]); + $userRepository = $this->prophesize(UserRepositoryInterface::class); + + $calendarSyncer = new CalendarSyncer( + new NullLogger(), + $machineHttpClient, + $userRepository->reveal() + ); + + $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')) + ->setCalendarRange(new CalendarRange()) + ->addRemoteAttributes([ + 'lastModifiedDateTime' => 0, + 'changeKey' => 'abcd', + ]); + $notification = json_decode(self::NOTIF_UPDATE, true); + + $calendarSyncer->handleCalendarSync( + $calendar, + $notification['value'][0], + $user + ); + + $this->assertStringContainsString( + '2022-06-10T15:30:00', + $calendar->getStartDate()->format(DateTimeImmutable::ATOM) + ); + $this->assertStringContainsString( + '2022-06-10T17:30:00', + $calendar->getEndDate()->format(DateTimeImmutable::ATOM) + ); + $this->assertTrue($calendar->preventEnqueueChanges); + $this->assertEquals(Calendar::STATUS_MOVED, $calendar->getStatus()); + } + + public function testHandleNotMovedCalendar(): void + { + $machineHttpClient = new MockHttpClient([ + new MockResponse(self::REMOTE_CALENDAR_NO_ATTENDEES, ['http_code' => 200]), + ]); + $userRepository = $this->prophesize(UserRepositoryInterface::class); + + $calendarSyncer = new CalendarSyncer( + new NullLogger(), + $machineHttpClient, + $userRepository->reveal() + ); + + $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')) + ->setCalendarRange(new CalendarRange()) + ->addRemoteAttributes([ + 'lastModifiedDateTime' => 0, + 'changeKey' => 'abcd', + ]); + $notification = json_decode(self::NOTIF_UPDATE, true); + + $calendarSyncer->handleCalendarSync( + $calendar, + $notification['value'][0], + $user + ); + + $this->assertStringContainsString( + '2022-06-10T15:30:00', + $calendar->getStartDate()->format(DateTimeImmutable::ATOM) + ); + $this->assertStringContainsString( + '2022-06-10T17:30:00', + $calendar->getEndDate()->format(DateTimeImmutable::ATOM) + ); + $this->assertTrue($calendar->preventEnqueueChanges); + $this->assertEquals(Calendar::STATUS_VALID, $calendar->getStatus()); + } + + public function testHandleNotOrganizer(): void + { + // when isOrganiser === false, nothing should happens + $machineHttpClient = new MockHttpClient([ + new MockResponse(self::REMOTE_CALENDAR_NOT_ORGANIZER, ['http_code' => 200]), + ]); + $userRepository = $this->prophesize(UserRepositoryInterface::class); + + $calendarSyncer = new CalendarSyncer( + new NullLogger(), + $machineHttpClient, + $userRepository->reveal() + ); + + $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')) + ->setCalendarRange(new CalendarRange()) + ->addRemoteAttributes([ + 'lastModifiedDateTime' => 0, + 'changeKey' => 'abcd', + ]); + $notification = json_decode(self::NOTIF_UPDATE, true); + + $calendarSyncer->handleCalendarSync( + $calendar, + $notification['value'][0], + $user + ); + + $this->assertStringContainsString( + '2020-01-01T10:00:00', + $calendar->getStartDate()->format(DateTimeImmutable::ATOM) + ); + $this->assertStringContainsString( + '2020-01-01T12:00:00', + $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 new file mode 100644 index 000000000..604b707c0 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Tests/RemoteCalendar/Connector/MSGraph/LocationConverterTest.php @@ -0,0 +1,86 @@ +setName('display')->setAddress($address = new Address()); + $address->setAddressReference($reference = new AddressReference()); + + $actual = $this->buildLocationConverter()->locationToRemote($location); + + $this->assertArrayHasKey('address', $actual); + $this->assertArrayNotHasKey('coordinates', $actual); + $this->assertArrayHasKey('displayName', $actual); + $this->assertEquals('display', $actual['displayName']); + } + + public function testConvertToRemoteWithAddressWithPoint(): void + { + $location = (new Location())->setName('display')->setAddress($address = new Address()); + $address->setAddressReference($reference = new AddressReference()); + $reference->setPoint($point = Point::fromLonLat(5.3134, 50.3134)); + + $actual = $this->buildLocationConverter()->locationToRemote($location); + + $this->assertArrayHasKey('address', $actual); + $this->assertArrayHasKey('coordinates', $actual); + $this->assertEquals(['latitude' => 50.3134, 'longitude' => 5.3134], $actual['coordinates']); + $this->assertArrayHasKey('displayName', $actual); + $this->assertEquals('display', $actual['displayName']); + } + + public function testConvertToRemoteWithoutAddressWithoutPoint(): void + { + $location = (new Location())->setName('display'); + + $actual = $this->buildLocationConverter()->locationToRemote($location); + + $this->assertArrayNotHasKey('address', $actual); + $this->assertArrayNotHasKey('coordinates', $actual); + $this->assertArrayHasKey('displayName', $actual); + $this->assertEquals('display', $actual['displayName']); + } + + private function buildLocationConverter(): LocationConverter + { + $addressConverter = $this->prophesize(AddressConverter::class); + $addressConverter->addressToRemote(Argument::type(Address::class))->willReturn(['street' => 'dummy']); + + return new LocationConverter($addressConverter->reveal()); + } +} diff --git a/src/Bundle/ChillCalendarBundle/Tests/Repository/CalendarACLAwareRepositoryTest.php b/src/Bundle/ChillCalendarBundle/Tests/Repository/CalendarACLAwareRepositoryTest.php new file mode 100644 index 000000000..0fb4a662b --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Tests/Repository/CalendarACLAwareRepositoryTest.php @@ -0,0 +1,78 @@ +entityManager = self::$container->get(EntityManagerInterface::class); + } + + public function testCountByPerosn() + { + $person = $this->getRandomPerson($this->entityManager); + + $periodRepository = $this->prophesize(AccompanyingPeriodACLAwareRepositoryInterface::class); + $periodRepository->findByPerson($person, AccompanyingPeriodVoter::SEE)->willReturn([]); + + $calendarRepository = new CalendarACLAwareRepository( + $periodRepository->reveal(), + $this->entityManager + ); + + $count = $calendarRepository->countByPerson($person, new DateTimeImmutable('yesterday'), new DateTimeImmutable('tomorrow')); + + $this->assertIsInt($count); + } + + /** + * Test that the query does not throw any error. + */ + public function testFindByPerson() + { + $person = $this->getRandomPerson($this->entityManager); + + $periodRepository = $this->prophesize(AccompanyingPeriodACLAwareRepositoryInterface::class); + $periodRepository->findByPerson($person, AccompanyingPeriodVoter::SEE)->willReturn([]); + + $calendarRepository = new CalendarACLAwareRepository( + $periodRepository->reveal(), + $this->entityManager + ); + + $calendars = $calendarRepository->findByPerson($person, null, null, ['startDate' => 'ASC'], 10, 1); + + $this->assertIsArray($calendars); + } +} diff --git a/src/Bundle/ChillCalendarBundle/Tests/Serializer/Normalizer/CalendarNormalizerTest.php b/src/Bundle/ChillCalendarBundle/Tests/Serializer/Normalizer/CalendarNormalizerTest.php new file mode 100644 index 000000000..2a0975206 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Tests/Serializer/Normalizer/CalendarNormalizerTest.php @@ -0,0 +1,109 @@ +normalizer = self::$container->get(NormalizerInterface::class); + } + + public function testNormalizationCalendar() + { + $calendar = (new Calendar()) + ->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')) + ->addPerson(new Person()) + ->addPerson(new Person()) + ->addUser(new User()) + ->addProfessional(new ThirdParty()); + + $expected = [ + 'type' => 'chill_calendar_calendar', + 'isNull' => false, + 'urgent' => false, + 'sendSMS' => false, + ]; + + $actual = $this->normalizer->normalize( + $calendar, + 'docgen', + ['groups' => ['docgen:read'], 'docgen:expects' => Calendar::class] + ); + + // we first check for the known key/value... + foreach ($expected as $key => $value) { + $this->assertArrayHasKey($key, $actual); + $this->assertEquals($value, $actual[$key]); + } + + // ... and then check for some other values + $this->assertArrayHasKey('persons', $actual); + $this->assertIsArray($actual['persons']); + $this->assertArrayHasKey('invites', $actual); + $this->assertIsArray($actual['invites']); + $this->assertArrayHasKey('startDate', $actual); + $this->assertIsArray($actual['startDate']); + $this->assertArrayHasKey('endDate', $actual); + $this->assertIsArray($actual['endDate']); + $this->assertArrayHasKey('professionals', $actual); + $this->assertIsArray($actual['professionals']); + $this->assertArrayHasKey('location', $actual); + $this->assertIsArray($actual['location']); + $this->assertArrayHasKey('mainUser', $actual); + $this->assertIsArray($actual['mainUser']); + $this->assertArrayHasKey('comment', $actual); + $this->assertIsArray($actual['comment']); + $this->assertArrayHasKey('duration', $actual); + $this->assertIsArray($actual['duration']); + } + + public function testNormalizationOnNullHasSameKeys() + { + $calendar = new Calendar(); + + $notNullCalendar = $this->normalizer->normalize( + $calendar, + 'docgen', + ['groups' => ['docgen:read'], 'docgen:expects' => Calendar::class] + ); + + $isNullCalendar = $this->normalizer->normalize( + null, + 'docgen', + ['groups' => ['docgen:read'], 'docgen:expects' => Calendar::class] + ); + + $this->assertEqualsCanonicalizing(array_keys($notNullCalendar), array_keys($isNullCalendar)); + } +} diff --git a/src/Bundle/ChillCalendarBundle/Tests/Service/DocGenerator/CalendarContextTest.php b/src/Bundle/ChillCalendarBundle/Tests/Service/DocGenerator/CalendarContextTest.php new file mode 100644 index 000000000..cbb4ea3af --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Tests/Service/DocGenerator/CalendarContextTest.php @@ -0,0 +1,236 @@ + true, + 'askMainPerson' => true, + 'mainPersonLabel' => 'docgen.calendar.Destinee', + 'askThirdParty' => false, + 'thirdPartyLabel' => 'Third party', + ]; + + $this->assertEqualsCanonicalizing($expected, $this->buildCalendarContext()->adminFormReverseTransform([])); + } + + public function testAdminFormTransform() + { + $expected = + [ + 'track_datetime' => true, + 'askMainPerson' => true, + 'mainPersonLabel' => 'docgen.calendar.Destinee', + 'askThirdParty' => false, + 'thirdPartyLabel' => 'Third party', + ]; + + $this->assertEqualsCanonicalizing($expected, $this->buildCalendarContext()->adminFormTransform($expected)); + } + + public function testBuildPublicForm() + { + $formBuilder = $this->prophesize(FormBuilderInterface::class); + $calendar = new Calendar(); + $calendar + ->addProfessional($tp1 = new ThirdParty()) + ->addProfessional($tp2 = new ThirdParty()) + ->addPerson($p1 = new Person()); + + // we will try once with askThirdParty = true, once with askPerson = true, and once with both + // 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))); + } + + $opts = $calls[0]->getArguments()[2]; + + if (!array_key_exists('label', $opts)) { + throw new FailedPredictionException('the $builder->add should have a label key'); + } + + if ('tplabel' !== $opts['label']) { + throw new FailedPredictionException('third party label not expected'); + } + + if (!$opts['choices']->contains($tp1) || !$opts['choices']->contains($tp2)) { + throw new FailedPredictionException('third party not present'); + } + }); + $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))); + } + + $opts = $calls[0]->getArguments()[2]; + + if (!array_key_exists('label', $opts)) { + throw new FailedPredictionException('the $builder->add should have a label key'); + } + + if ('personLabel' !== $opts['label']) { + throw new FailedPredictionException('person label not expected'); + } + + if (!$opts['choices']->contains($p1)) { + throw new FailedPredictionException('person not present'); + } + }); + + $formBuilder->add('title', TextType::class, Argument::type('array')) + ->shouldBeCalledTimes(3); + + foreach ([ + ['askMainPerson' => true, 'mainPersonLabel' => 'personLabel', 'askThirdParty' => true, 'thirdPartyLabel' => 'tplabel'], + ['askMainPerson' => false, 'mainPersonLabel' => 'personLabel', 'askThirdParty' => true, 'thirdPartyLabel' => 'tplabel'], + ['askMainPerson' => true, 'mainPersonLabel' => 'personLabel', 'askThirdParty' => false, 'thirdPartyLabel' => 'tplabel'], + ] as $options) { + $template = new DocGeneratorTemplate(); + $template->setOptions($options); + + $this->buildCalendarContext()->buildPublicForm($formBuilder->reveal(), $template, $calendar); + } + } + + public function testGetData() + { + $calendar = (new Calendar()) + ->addPerson($p1 = new Person()) + ->addProfessional($t1 = new ThirdParty()); + $template = (new DocGeneratorTemplate())->setOptions( + ['askMainPerson' => true, 'mainPersonLabel' => 'personLabel', 'askThirdParty' => true, 'thirdPartyLabel' => 'tplabel'], + ); + $contextData = [ + 'mainPerson' => $p1, + 'thirdParty' => $t1, + ]; + + $normalizer = $this->prophesize(NormalizerInterface::class); + $normalizer->normalize($p1, 'docgen', Argument::type('array'))->willReturn(['person' => '1']); + $normalizer->normalize($t1, 'docgen', Argument::type('array'))->willReturn(['tp' => '1']); + $normalizer->normalize($calendar, 'docgen', Argument::type('array'))->willReturn(['calendar' => '1']); + + $actual = $this->buildCalendarContext(null, $normalizer->reveal()) + ->getData($template, $calendar, $contextData); + + $this->assertEqualsCanonicalizing([ + 'calendar' => ['calendar' => '1'], + 'mainPerson' => ['person' => '1'], + 'thirdParty' => ['tp' => '1'], + 'base_context' => 'data', + ], $actual); + } + + public function testStoreGenerated() + { + $calendar = new Calendar(); + $storedObject = new StoredObject(); + $contextData = ['title' => 'blabla']; + $template = (new DocGeneratorTemplate())->setOptions(['trackDatetime' => true]); + + $em = $this->prophesize(EntityManagerInterface::class); + $em->persist(Argument::type(CalendarDoc::class))->should( + static function ($calls, $object, $method) use ($storedObject) { + if (1 !== count($calls)) { + throw new FailedPredictionException('the persist method should be called once'); + } + + /** @var CalendarDoc $calendarDoc */ + $calendarDoc = $calls[0]->getArguments()[0]; + + if ($calendarDoc->getStoredObject() !== $storedObject) { + throw new FailedPredictionException('the stored object is not correct'); + } + + if ($calendarDoc->getStoredObject()->getTitle() !== 'blabla') { + throw new FailedPredictionException('the doc title should be the one provided'); + } + + if (!$calendarDoc->isTrackDateTimeVersion()) { + throw new FailedPredictionException('the track date time should be true'); + } + } + ); + + $this->buildCalendarContext($em->reveal())->storeGenerated($template, $storedObject, $calendar, $contextData); + } + + private function buildCalendarContext( + ?EntityManagerInterface $entityManager = null, + ?NormalizerInterface $normalizer = null + ): CalendarContext { + $baseContext = $this->prophesize(BaseContextData::class); + $baseContext->getData(null)->willReturn(['base_context' => 'data']); + + $personRender = $this->prophesize(PersonRender::class); + $personRender->renderString(Argument::type(Person::class), [])->willReturn('person name'); + + $thirdPartyRender = $this->prophesize(ThirdPartyRender::class); + $thirdPartyRender->renderString(Argument::type(ThirdParty::class), [])->willReturn('third party name'); + + $translatableStringHelper = $this->prophesize(TranslatableStringHelperInterface::class); + $translatableStringHelper->localize(Argument::type('array'))->willReturn('blabla'); + + if (null === $normalizer) { + $normalizer = $this->prophesize(NormalizerInterface::class)->reveal(); + } + + if (null === $entityManager) { + $entityManager = $this->prophesize(EntityManagerInterface::class)->reveal(); + } + + return new CalendarContext( + $baseContext->reveal(), + $entityManager, + $normalizer, + $personRender->reveal(), + $thirdPartyRender->reveal(), + $translatableStringHelper->reveal() + ); + } +} diff --git a/src/Bundle/ChillCalendarBundle/Tests/Service/ShortMessageNotification/BulkCalendarShortMessageSenderTest.php b/src/Bundle/ChillCalendarBundle/Tests/Service/ShortMessageNotification/BulkCalendarShortMessageSenderTest.php new file mode 100644 index 000000000..0c190f51d --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Tests/Service/ShortMessageNotification/BulkCalendarShortMessageSenderTest.php @@ -0,0 +1,135 @@ +get(EntityManagerInterface::class); + + foreach ($this->toDelete as [$entity, $id]) { + $entity = $em->find($entity, $id); + $em->remove($entity); + } + + $em->flush(); + } + + public function testSendBulkMessageToEligibleCalendar() + { + $em = self::$container->get(EntityManagerInterface::class); + $calendar = new Calendar(); + $calendar + ->addPerson($this->getRandomPerson($em)) + ->setMainUser($user = $this->prepareUser([])) + ->setStartDate(new DateTimeImmutable('now')) + ->setEndDate($calendar->getStartDate()->add(new DateInterval('PT30M'))) + ->setSendSMS(true); + + $user->setUsername(uniqid()); + $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()]; + $em->persist($calendar); + //$this->toDelete[] = [Calendar::class, $calendar->getId()]; + $em->flush(); + + $provider = $this->prophesize(CalendarForShortMessageProvider::class); + $provider->getCalendars(Argument::type(DateTimeImmutable::class)) + ->willReturn(new ArrayIterator([$calendar])); + + $messageBuilder = $this->prophesize(ShortMessageForCalendarBuilderInterface::class); + $messageBuilder->buildMessageForCalendar(Argument::type(Calendar::class)) + ->willReturn( + [ + new ShortMessage( + 'content', + PhoneNumberUtil::getInstance()->parse('+32470123456', 'BE'), + ShortMessage::PRIORITY_MEDIUM + ), + ] + ); + + $bus = $this->prophesize(MessageBusInterface::class); + $bus->dispatch(Argument::type(ShortMessage::class)) + ->willReturn(new Envelope(new stdClass())) + ->shouldBeCalledTimes(1); + + $bulk = new BulkCalendarShortMessageSender( + $provider->reveal(), + $em, + new NullLogger(), + $bus->reveal(), + $messageBuilder->reveal() + ); + + $bulk->sendBulkMessageToEligibleCalendars(); + + $em->clear(); + $calendar = $em->find(Calendar::class, $calendar->getId()); + + $this->assertEquals(Calendar::SMS_SENT, $calendar->getSmsStatus()); + } +} diff --git a/src/Bundle/ChillCalendarBundle/Tests/Service/ShortMessageNotification/CalendarForShortMessageProviderTest.php b/src/Bundle/ChillCalendarBundle/Tests/Service/ShortMessageNotification/CalendarForShortMessageProviderTest.php new file mode 100644 index 000000000..34867d8bd --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Tests/Service/ShortMessageNotification/CalendarForShortMessageProviderTest.php @@ -0,0 +1,129 @@ +prophesize(CalendarRepository::class); + $calendarRepository->findByNotificationAvailable( + 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); + + $em = $this->prophesize(EntityManagerInterface::class); + $em->clear()->shouldNotBeCalled(); + + $provider = new CalendarForShortMessageProvider( + $calendarRepository->reveal(), + $em->reveal(), + $rangeGenerator->reveal() + ); + + $calendars = iterator_to_array($provider->getCalendars(new DateTimeImmutable('now'))); + + $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('int'), + Argument::exact(0) + )->will(static fn ($args) => array_fill(0, $args[2], new Calendar()))->shouldBeCalledTimes(1); + $calendarRepository->findByNotificationAvailable( + Argument::type(DateTimeImmutable::class), + Argument::type(DateTimeImmutable::class), + Argument::type('int'), + Argument::not(0) + )->will(static fn ($args) => array_fill(0, $args[2] - 1, new Calendar()))->shouldBeCalledTimes(1); + + $em = $this->prophesize(EntityManagerInterface::class); + $em->clear()->shouldBeCalled(); + + $provider = new CalendarForShortMessageProvider( + $calendarRepository->reveal(), + $em->reveal(), + new DefaultRangeGenerator() + ); + + $calendars = iterator_to_array($provider->getCalendars(new DateTimeImmutable('now'))); + + $this->assertGreaterThan(1, count($calendars)); + $this->assertLessThan(100, count($calendars)); + $this->assertContainsOnly(Calendar::class, $calendars); + } + + public function testGetCalendarsWithOnlyOneCalendar() + { + $calendarRepository = $this->prophesize(CalendarRepository::class); + $calendarRepository->findByNotificationAvailable( + Argument::type(DateTimeImmutable::class), + Argument::type(DateTimeImmutable::class), + Argument::type('int'), + Argument::exact(0) + )->will(static fn ($args) => array_fill(0, 1, new Calendar()))->shouldBeCalledTimes(1); + $calendarRepository->findByNotificationAvailable( + Argument::type(DateTimeImmutable::class), + Argument::type(DateTimeImmutable::class), + Argument::type('int'), + Argument::not(0) + )->will(static fn ($args) => [])->shouldBeCalledTimes(1); + + $em = $this->prophesize(EntityManagerInterface::class); + $em->clear()->shouldBeCalled(); + + $provider = new CalendarForShortMessageProvider( + $calendarRepository->reveal(), + $em->reveal(), + new DefaultRangeGenerator() + ); + + $calendars = iterator_to_array($provider->getCalendars(new DateTimeImmutable('now'))); + + $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 new file mode 100644 index 000000000..de2b97963 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Tests/Service/ShortMessageNotification/DefaultRangeGeneratorTest.php @@ -0,0 +1,101 @@ + Envoi des rdv du mardi et mercredi. + * * Mardi => Envoi des rdv du jeudi. + * * Mercredi => Envoi des rdv du vendredi + * * Jeudi => envoi des rdv du samedi et dimanche + * * Vendredi => Envoi des rdv du lundi. + */ + 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'), + ]; + + 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'), + ]; + + 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'), + ]; + + 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'), + ]; + + 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'), + ]; + + yield [ + new DateTimeImmutable('2022-06-18 21:30:55'), + null, + null, + ]; + + yield [ + new DateTimeImmutable('2022-06-19 21:30:55'), + null, + null, + ]; + } + + /** + * @dataProvider generateData + */ + public function testGenerateRange(DateTimeImmutable $date, ?DateTimeImmutable $startDate, ?DateTimeImmutable $endDate) + { + $generator = new DefaultRangeGenerator(); + + ['startDate' => $actualStartDate, 'endDate' => $actualEndDate] = $generator->generateRange($date); + + if (null === $startDate) { + $this->assertNull($actualStartDate); + $this->assertNull($actualEndDate); + } else { + $this->assertEquals($startDate->format(DateTimeImmutable::ATOM), $actualStartDate->format(DateTimeImmutable::ATOM)); + $this->assertEquals($endDate->format(DateTimeImmutable::ATOM), $actualEndDate->format(DateTimeImmutable::ATOM)); + } + } +} diff --git a/src/Bundle/ChillCalendarBundle/Tests/Service/ShortMessageNotification/DefaultShortMessageForCalendarBuilderTest.php b/src/Bundle/ChillCalendarBundle/Tests/Service/ShortMessageNotification/DefaultShortMessageForCalendarBuilderTest.php new file mode 100644 index 000000000..6d42540cc --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Tests/Service/ShortMessageNotification/DefaultShortMessageForCalendarBuilderTest.php @@ -0,0 +1,115 @@ +phoneNumberUtil = PhoneNumberUtil::getInstance(); + } + + public function testBuildMessageForCalendar() + { + $calendar = new Calendar(); + $calendar + ->setStartDate(new DateTimeImmutable('now')) + ->setEndDate($calendar->getStartDate()->add(new DateInterval('PT30M'))) + ->setMainUser($user = new User()) + ->addPerson($person = new Person()) + ->setSendSMS(false); + $user + ->setLabel('Alex') + ->setMainLocation($location = new Location()); + $location->setName('LOCAMAT'); + $person + ->setMobilenumber($this->phoneNumberUtil->parse('+32470123456', 'BE')) + ->setAcceptSMS(false); + + $engine = $this->prophesize(EngineInterface::class); + $engine->render(Argument::exact('@ChillCalendar/CalendarShortMessage/short_message.txt.twig'), Argument::withKey('calendar')) + ->willReturn('message content') + ->shouldBeCalledTimes(1); + $engine->render(Argument::exact('@ChillCalendar/CalendarShortMessage/short_message_canceled.txt.twig'), Argument::withKey('calendar')) + ->willReturn('message canceled') + ->shouldBeCalledTimes(1); + + $builder = new DefaultShortMessageForCalendarBuilder( + $engine->reveal() + ); + + // if the calendar should not send sms + $sms = $builder->buildMessageForCalendar($calendar); + $this->assertCount(0, $sms); + + // if the person do not accept sms + $calendar->setSendSMS(true); + $sms = $builder->buildMessageForCalendar($calendar); + $this->assertCount(0, $sms); + + // person accepts sms + $person->setAcceptSMS(true); + $sms = $builder->buildMessageForCalendar($calendar); + + $this->assertCount(1, $sms); + $this->assertEquals( + '+32470123456', + $this->phoneNumberUtil->format($sms[0]->getPhoneNumber(), PhoneNumberFormat::E164) + ); + $this->assertEquals('message content', $sms[0]->getContent()); + $this->assertEquals('low', $sms[0]->getPriority()); + + // if the calendar is canceled + $calendar + ->setSmsStatus(Calendar::SMS_SENT) + ->setStatus(Calendar::STATUS_CANCELED); + + $sms = $builder->buildMessageForCalendar($calendar); + + $this->assertCount(1, $sms); + $this->assertEquals( + '+32470123456', + $this->phoneNumberUtil->format($sms[0]->getPhoneNumber(), PhoneNumberFormat::E164) + ); + $this->assertEquals('message canceled', $sms[0]->getContent()); + $this->assertEquals('low', $sms[0]->getPriority()); + } +} diff --git a/src/Bundle/ChillCalendarBundle/chill.api.specs.yaml b/src/Bundle/ChillCalendarBundle/chill.api.specs.yaml index ebe0de88d..8d8df2568 100644 --- a/src/Bundle/ChillCalendarBundle/chill.api.specs.yaml +++ b/src/Bundle/ChillCalendarBundle/chill.api.specs.yaml @@ -1,181 +1,276 @@ --- -openapi: "3.0.0" -info: - version: "1.0.0" - title: "Chill api" - description: "Api documentation for chill. Currently, work in progress" -servers: - - url: "/api" - description: "Your current dev server" +#openapi: "3.0.0" +#info: +# version: "1.0.0" +# title: "Chill api" +# description: "Api documentation for chill. Currently, work in progress" +#servers: +# - url: "/api" +# description: "Your current dev server" components: - schemas: - Date: - type: object - properties: - datetime: - type: string - format: date-time - User: - type: object - properties: - id: - type: integer - type: - type: string - enum: - - user - username: - type: string - text: - type: string + schemas: + Date: + type: object + properties: + datetime: + type: string + format: date-time + User: + type: object + properties: + id: + type: integer + type: + type: string + enum: + - user + username: + type: string + text: + type: string paths: - /1.0/calendar/calendar.json: - get: - tags: - - calendar - summary: Return a list of all calendar items - responses: - 200: - description: "ok" + /1.0/calendar/calendar/{id}/answer/{answer}.json: + post: + tags: + - calendar + summary: Answer to a calendar's invite + parameters: + - + in: path + name: id + required: true + description: the calendar id + schema: + type: integer + format: integer + minimum: 0 + - + in: path + name: answer + required: true + description: the answer + schema: + type: string + enum: + - accepted + - declined + - tentative + responses: + 400: + description: bad answer + 403: + description: not invited + 404: + description: not found + 202: + description: accepted - /1.0/calendar/calendar/{id}.json: - get: - tags: - - calendar - summary: Return an calendar item by id - parameters: - - name: id - in: path - required: true - description: The calendar id - schema: - type: integer - format: integer - minimum: 1 - responses: - 200: - description: "ok" - 404: - description: "not found" - 401: - description: "Unauthorized" + /1.0/calendar/calendar.json: + get: + tags: + - calendar + summary: Return a list of all calendar items + responses: + 200: + description: "ok" - /1.0/calendar/calendar-range.json: - get: - tags: - - calendar - summary: Return a list of all calendar range items - responses: - 200: - description: "ok" - post: - tags: - - calendar - summary: create a new calendar range - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - user: - $ref: '#/components/schemas/User' - startDate: - $ref: '#/components/schemas/Date' - endDate: - $ref: '#/components/schemas/Date' - responses: - 401: - description: "Unauthorized" - 404: - description: "Not found" - 200: - description: "OK" - 422: - description: "Unprocessable entity (validation errors)" - 400: - description: "transition cannot be applyed" + /1.0/calendar/calendar/{id}.json: + get: + tags: + - calendar + summary: Return an calendar item by id + parameters: + - name: id + in: path + required: true + description: The calendar id + schema: + type: integer + format: integer + minimum: 1 + responses: + 200: + description: "ok" + 404: + description: "not found" + 403: + description: "Unauthorized" - /1.0/calendar/calendar-range/{id}.json: - get: - tags: - - calendar - summary: Return an calendar-range item by id - parameters: - - name: id - in: path - required: true - description: The calendar-range id - schema: - type: integer - format: integer - minimum: 1 - responses: - 200: - description: "ok" - 404: - description: "not found" - 401: - description: "Unauthorized" - patch: - tags: - - calendar - summary: update a calendar range - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - user: - $ref: '#/components/schemas/User' - startDate: - $ref: '#/components/schemas/Date' - endDate: - $ref: '#/components/schemas/Date' - responses: - 401: - description: "Unauthorized" - 404: - description: "Not found" - 200: - description: "OK" - 422: - description: "Unprocessable entity (validation errors)" - 400: - description: "transition cannot be applyed" - delete: - tags: - - calendar - summary: "Remove a calendar range" - parameters: - - name: id - in: path - required: true - description: The calendar range id - schema: - type: integer - format: integer - minimum: 1 - responses: - 401: - description: "Unauthorized" - 404: - description: "Not found" - 200: - description: "OK" - 422: - description: "object with validation errors" - - /1.0/calendar/calendar-range-available.json: - get: - tags: - - calendar - summary: Return a list of available calendar range items. Available means calendar-range not being taken by a calendar entity - responses: - 200: - description: "ok" \ No newline at end of file + /1.0/calendar/calendar/by-user/{userId}.json: + get: + tags: + - calendar + summary: Return a list of calendars for a user + parameters: + - name: userId + in: path + required: true + description: The user id + schema: + type: integer + format: integer + minimum: 1 + - name: dateFrom + in: query + required: true + description: The date from, formatted as ISO8601 string + schema: + type: string + format: date-time + - name: dateTo + in: query + required: true + description: The date to, formatted as ISO8601 string + schema: + type: string + format: date-time + responses: + 200: + description: "ok" + 404: + description: "not found" + 403: + description: "Unauthorized" + + /1.0/calendar/calendar-range.json: + get: + tags: + - calendar + summary: Return a list of all calendar range items + responses: + 200: + description: "ok" + post: + tags: + - calendar + summary: create a new calendar range + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + user: + $ref: '#/components/schemas/User' + startDate: + $ref: '#/components/schemas/Date' + endDate: + $ref: '#/components/schemas/Date' + responses: + 401: + description: "Unauthorized" + 404: + description: "Not found" + 200: + description: "OK" + 422: + description: "Unprocessable entity (validation errors)" + 400: + description: "transition cannot be applyed" + + /1.0/calendar/calendar-range/{id}.json: + get: + tags: + - calendar + summary: Return an calendar-range item by id + parameters: + - name: id + in: path + required: true + description: The calendar-range id + schema: + type: integer + format: integer + minimum: 1 + responses: + 200: + description: "ok" + 404: + description: "not found" + 401: + description: "Unauthorized" + patch: + tags: + - calendar + summary: update a calendar range + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + user: + $ref: '#/components/schemas/User' + startDate: + $ref: '#/components/schemas/Date' + endDate: + $ref: '#/components/schemas/Date' + responses: + 401: + description: "Unauthorized" + 404: + description: "Not found" + 200: + description: "OK" + 422: + description: "Unprocessable entity (validation errors)" + 400: + description: "transition cannot be applyed" + delete: + tags: + - calendar + summary: "Remove a calendar range" + parameters: + - name: id + in: path + required: true + description: The calendar range id + schema: + type: integer + format: integer + minimum: 1 + responses: + 401: + description: "Unauthorized" + 404: + description: "Not found" + 200: + description: "OK" + 422: + description: "object with validation errors" + + /1.0/calendar/calendar-range-available/{userId}.json: + get: + tags: + - calendar + summary: Return a list of available calendar range items. Available means calendar-range not being taken by a calendar entity + parameters: + - name: userId + in: path + required: true + description: The user id + schema: + type: integer + format: integer + minimum: 1 + - name: dateFrom + in: query + required: true + description: The date from, formatted as ISO8601 string + schema: + type: string + format: date-time + - name: dateTo + in: query + required: true + description: The date to, formatted as ISO8601 string + schema: + type: string + format: date-time + responses: + 200: + description: "ok" diff --git a/src/Bundle/ChillCalendarBundle/chill.webpack.config.js b/src/Bundle/ChillCalendarBundle/chill.webpack.config.js index f91b30268..e82210087 100644 --- a/src/Bundle/ChillCalendarBundle/chill.webpack.config.js +++ b/src/Bundle/ChillCalendarBundle/chill.webpack.config.js @@ -6,6 +6,7 @@ module.exports = function(encore, entries) { }); encore.addEntry('vue_calendar', __dirname + '/Resources/public/vuejs/Calendar/index.js'); - encore.addEntry('vue_mycalendarrange', __dirname + '/Resources/public/vuejs/MyCalendarRange/index.js'); + encore.addEntry('vue_mycalendarrange', __dirname + '/Resources/public/vuejs/MyCalendarRange/index2.ts'); encore.addEntry('page_calendar', __dirname + '/Resources/public/chill/index.js'); + encore.addEntry('mod_answer', __dirname + '/Resources/public/module/Invite/answer.js'); }; diff --git a/src/Bundle/ChillCalendarBundle/migrations/Version20210715141731.php b/src/Bundle/ChillCalendarBundle/migrations/Version20210715141731.php index 20d21f499..ee3fdaadd 100644 --- a/src/Bundle/ChillCalendarBundle/migrations/Version20210715141731.php +++ b/src/Bundle/ChillCalendarBundle/migrations/Version20210715141731.php @@ -1,14 +1,14 @@ addSql('ALTER TABLE chill_calendar.calendar_range DROP CONSTRAINT FK_38D57D0565FF1AEC'); + $this->addSql('ALTER TABLE chill_calendar.calendar_range DROP CONSTRAINT FK_38D57D053174800F'); + $this->addSql('DROP INDEX chill_calendar.IDX_38D57D0565FF1AEC'); + $this->addSql('DROP INDEX chill_calendar.IDX_38D57D053174800F'); + $this->addSql('DROP INDEX chill_calendar.idx_calendar_range_remote'); + $this->addSql('ALTER TABLE chill_calendar.calendar_range DROP remoteId'); + $this->addSql('ALTER TABLE chill_calendar.calendar_range DROP remoteAttributes'); + $this->addSql('ALTER TABLE chill_calendar.calendar_range DROP updatedAt'); + $this->addSql('ALTER TABLE chill_calendar.calendar_range DROP createdAt'); + $this->addSql('ALTER TABLE chill_calendar.calendar_range DROP updatedBy_id'); + $this->addSql('ALTER TABLE chill_calendar.calendar_range DROP createdBy_id'); + } + + public function getDescription(): string + { + return 'Add columns on calendar range to handle remote'; + } + + public function up(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_calendar.calendar_range ADD remoteId TEXT DEFAULT \'\' NOT NULL'); + $this->addSql('ALTER TABLE chill_calendar.calendar_range ADD remoteAttributes JSON DEFAULT \'[]\' NOT NULL'); + $this->addSql('ALTER TABLE chill_calendar.calendar_range ADD updatedAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_calendar.calendar_range ADD createdAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_calendar.calendar_range ADD updatedBy_id INT DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_calendar.calendar_range ADD createdBy_id INT DEFAULT NULL'); + $this->addSql('COMMENT ON COLUMN chill_calendar.calendar_range.updatedAt IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('COMMENT ON COLUMN chill_calendar.calendar_range.createdAt IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('ALTER TABLE chill_calendar.calendar_range ADD CONSTRAINT FK_38D57D0565FF1AEC FOREIGN KEY (updatedBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_calendar.calendar_range ADD CONSTRAINT FK_38D57D053174800F FOREIGN KEY (createdBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('CREATE INDEX IDX_38D57D0565FF1AEC ON chill_calendar.calendar_range (updatedBy_id)'); + $this->addSql('CREATE INDEX IDX_38D57D053174800F ON chill_calendar.calendar_range (createdBy_id)'); + $this->addSql('CREATE INDEX idx_calendar_range_remote ON chill_calendar.calendar_range (remoteId)'); + } +} diff --git a/src/Bundle/ChillCalendarBundle/migrations/Version20220511134619.php b/src/Bundle/ChillCalendarBundle/migrations/Version20220511134619.php new file mode 100644 index 000000000..b82d1b442 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/migrations/Version20220511134619.php @@ -0,0 +1,94 @@ +addSql('CREATE TABLE chill_calendar.calendar_to_invites (calendar_id INT NOT NULL, invite_id INT NOT NULL, PRIMARY KEY(calendar_id, invite_id))'); + $this->addSql('CREATE INDEX idx_fcbeaaaea417747 ON chill_calendar.calendar_to_invites (invite_id)'); + $this->addSql('CREATE INDEX idx_fcbeaaaa40a2c8 ON chill_calendar.calendar_to_invites (calendar_id)'); + $this->addSql('ALTER TABLE chill_calendar.calendar_to_invites ADD CONSTRAINT fk_fcbeaaaa40a2c8 FOREIGN KEY (calendar_id) REFERENCES chill_calendar.calendar (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_calendar.calendar_to_invites ADD CONSTRAINT fk_fcbeaaaea417747 FOREIGN KEY (invite_id) REFERENCES chill_calendar.invite (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_calendar.invite DROP CONSTRAINT FK_F517FFA7A40A2C8'); + $this->addSql('ALTER TABLE chill_calendar.invite DROP CONSTRAINT FK_F517FFA73174800F'); + $this->addSql('ALTER TABLE chill_calendar.invite DROP CONSTRAINT FK_F517FFA765FF1AEC'); + $this->addSql('DROP INDEX chill_calendar.IDX_F517FFA7A40A2C8'); + $this->addSql('DROP INDEX chill_calendar.IDX_F517FFA73174800F'); + $this->addSql('DROP INDEX chill_calendar.IDX_F517FFA765FF1AEC'); + $this->addSql('ALTER TABLE chill_calendar.invite DROP calendar_id'); + $this->addSql('ALTER TABLE chill_calendar.invite DROP createdAt'); + $this->addSql('ALTER TABLE chill_calendar.invite DROP updatedAt'); + $this->addSql('ALTER TABLE chill_calendar.invite DROP createdBy_id'); + $this->addSql('ALTER TABLE chill_calendar.invite DROP updatedBy_id'); + $this->addSql('ALTER TABLE chill_calendar.invite ALTER user_id DROP NOT NULL'); + $this->addSql('ALTER TABLE chill_calendar.invite DROP COLUMN status'); + $this->addSql('ALTER TABLE chill_calendar.invite ADD COLUMN status JSON'); + $this->addSql('ALTER TABLE chill_calendar.invite ALTER status DROP DEFAULT'); + $this->addSql('ALTER TABLE chill_calendar.calendar DROP CONSTRAINT FK_712315AC3174800F'); + $this->addSql('ALTER TABLE chill_calendar.calendar DROP CONSTRAINT FK_712315AC65FF1AEC'); + $this->addSql('DROP INDEX chill_calendar.IDX_712315AC3174800F'); + $this->addSql('DROP INDEX chill_calendar.IDX_712315AC65FF1AEC'); + $this->addSql('ALTER TABLE chill_calendar.calendar DROP createdAt'); + $this->addSql('ALTER TABLE chill_calendar.calendar DROP updatedAt'); + $this->addSql('ALTER TABLE chill_calendar.calendar DROP createdBy_id'); + $this->addSql('ALTER TABLE chill_calendar.calendar DROP updatedBy_id'); + } + + public function getDescription(): string + { + return 'Prepare schema for handling calendar invites'; + } + + public function up(Schema $schema): void + { + $this->addSql('DROP TABLE chill_calendar.calendar_to_invites'); + $this->addSql('ALTER TABLE chill_calendar.calendar ADD createdAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_calendar.calendar ADD updatedAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_calendar.calendar ADD createdBy_id INT DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_calendar.calendar ADD updatedBy_id INT DEFAULT NULL'); + $this->addSql('COMMENT ON COLUMN chill_calendar.calendar.createdAt IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('COMMENT ON COLUMN chill_calendar.calendar.updatedAt IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('ALTER TABLE chill_calendar.calendar ADD CONSTRAINT FK_712315AC3174800F FOREIGN KEY (createdBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_calendar.calendar ADD CONSTRAINT FK_712315AC65FF1AEC FOREIGN KEY (updatedBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('CREATE INDEX IDX_712315AC3174800F ON chill_calendar.calendar (createdBy_id)'); + $this->addSql('CREATE INDEX IDX_712315AC65FF1AEC ON chill_calendar.calendar (updatedBy_id)'); + $this->addSql('ALTER TABLE chill_calendar.invite ADD calendar_id INT DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_calendar.invite ADD createdAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_calendar.invite ADD updatedAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_calendar.invite ADD createdBy_id INT DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_calendar.invite ADD updatedBy_id INT DEFAULT NULL'); + $this->addSql('DELETE FROM chill_calendar.invite WHERE user_id IS NULL'); + $this->addSql('ALTER TABLE chill_calendar.invite ALTER user_id SET NOT NULL'); + $this->addSql('ALTER TABLE chill_calendar.invite DROP COLUMN status'); + $this->addSql('ALTER TABLE chill_calendar.invite ADD COLUMN status TEXT DEFAULT \'pending\''); + $this->addSql('COMMENT ON COLUMN chill_calendar.invite.createdAt IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('COMMENT ON COLUMN chill_calendar.invite.updatedAt IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('ALTER TABLE chill_calendar.invite ADD CONSTRAINT FK_F517FFA7A40A2C8 FOREIGN KEY (calendar_id) REFERENCES chill_calendar.calendar (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_calendar.invite ADD CONSTRAINT FK_F517FFA73174800F FOREIGN KEY (createdBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_calendar.invite ADD CONSTRAINT FK_F517FFA765FF1AEC FOREIGN KEY (updatedBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('CREATE INDEX IDX_F517FFA7A40A2C8 ON chill_calendar.invite (calendar_id)'); + $this->addSql('CREATE INDEX IDX_F517FFA73174800F ON chill_calendar.invite (createdBy_id)'); + $this->addSql('CREATE INDEX IDX_F517FFA765FF1AEC ON chill_calendar.invite (updatedBy_id)'); + } +} diff --git a/src/Bundle/ChillCalendarBundle/migrations/Version20220525080633.php b/src/Bundle/ChillCalendarBundle/migrations/Version20220525080633.php new file mode 100644 index 000000000..344d3a2e6 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/migrations/Version20220525080633.php @@ -0,0 +1,59 @@ +addSql('ALTER TABLE chill_calendar.invite ALTER status DROP NOT NULL'); + $this->addSql('DROP INDEX chill_calendar.UNIQ_712315ACC5CB285D'); + $this->addSql('DROP INDEX chill_calendar.idx_calendar_remote'); + $this->addSql('ALTER TABLE chill_calendar.calendar ADD user_id INT DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_calendar.calendar DROP remoteAttributes'); + $this->addSql('ALTER TABLE chill_calendar.calendar DROP remoteId'); + $this->addSql('ALTER TABLE chill_calendar.calendar ALTER status DROP DEFAULT'); + $this->addSql('ALTER TABLE chill_calendar.calendar ADD CONSTRAINT fk_712315aca76ed395 FOREIGN KEY (user_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('CREATE INDEX idx_712315acc5cb285d ON chill_calendar.calendar (calendarrange_id)'); + $this->addSql('CREATE INDEX idx_712315aca76ed395 ON chill_calendar.calendar (user_id)'); + } + + public function getDescription(): string + { + return 'Calendar: add remote infos and fix associations'; + } + + public function up(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_calendar.calendar DROP CONSTRAINT fk_712315aca76ed395'); + $this->addSql('DROP INDEX chill_calendar.idx_712315acc5cb285d'); + $this->addSql('DROP INDEX chill_calendar.idx_712315aca76ed395'); + $this->addSql('ALTER TABLE chill_calendar.calendar ADD remoteAttributes JSON DEFAULT \'[]\' NOT NULL'); + $this->addSql('ALTER TABLE chill_calendar.calendar ADD remoteId TEXT DEFAULT \'\' NOT NULL'); + $this->addSql('ALTER TABLE chill_calendar.calendar DROP user_id'); + $this->addSql('ALTER TABLE chill_calendar.calendar ALTER status SET DEFAULT \'valid\''); + $this->addSql('CREATE UNIQUE INDEX UNIQ_712315ACC5CB285D ON chill_calendar.calendar (calendarRange_id)'); + $this->addSql('CREATE INDEX idx_calendar_remote ON chill_calendar.calendar (remoteId)'); + $this->addSql('UPDATE chill_calendar.invite SET status=\'pending\' WHERE status IS NULL'); + $this->addSql('ALTER TABLE chill_calendar.invite ALTER status SET NOT NULL'); + } +} diff --git a/src/Bundle/ChillCalendarBundle/migrations/Version20220527124558.php b/src/Bundle/ChillCalendarBundle/migrations/Version20220527124558.php index 39c4cb7c3..fc430761e 100644 --- a/src/Bundle/ChillCalendarBundle/migrations/Version20220527124558.php +++ b/src/Bundle/ChillCalendarBundle/migrations/Version20220527124558.php @@ -1,14 +1,14 @@ addSql('ALTER TABLE chill_calendar.calendar ALTER endDate TYPE TIMESTAMP(0) WITH TIME ZONE'); + $this->addSql('ALTER TABLE chill_calendar.calendar ALTER endDate DROP DEFAULT'); + $this->addSql('ALTER TABLE chill_calendar.calendar ALTER startDate TYPE TIMESTAMP(0) WITH TIME ZONE'); + $this->addSql('ALTER TABLE chill_calendar.calendar ALTER startDate DROP DEFAULT'); + $this->addSql('COMMENT ON COLUMN chill_calendar.calendar.enddate IS \'(DC2Type:datetimetz_immutable)\''); + $this->addSql('COMMENT ON COLUMN chill_calendar.calendar.startdate IS \'(DC2Type:datetimetz_immutable)\''); + } + + public function getDescription(): string + { + return 'remove timezone from dates in calendar entity'; + } + + public function up(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_calendar.calendar ALTER startdate TYPE TIMESTAMP(0) WITHOUT TIME ZONE'); + $this->addSql('ALTER TABLE chill_calendar.calendar ALTER startdate DROP DEFAULT'); + $this->addSql('ALTER TABLE chill_calendar.calendar ALTER enddate TYPE TIMESTAMP(0) WITHOUT TIME ZONE'); + $this->addSql('ALTER TABLE chill_calendar.calendar ALTER enddate DROP DEFAULT'); + $this->addSql('COMMENT ON COLUMN chill_calendar.calendar.startDate IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('COMMENT ON COLUMN chill_calendar.calendar.endDate IS \'(DC2Type:datetime_immutable)\''); + } +} diff --git a/src/Bundle/ChillCalendarBundle/migrations/Version20220606154119.php b/src/Bundle/ChillCalendarBundle/migrations/Version20220606154119.php new file mode 100644 index 000000000..eec87a628 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/migrations/Version20220606154119.php @@ -0,0 +1,50 @@ +addSql('ALTER TABLE chill_calendar.calendar_range ALTER endDate TYPE TIMESTAMP(0) WITH TIME ZONE'); + $this->addSql('ALTER TABLE chill_calendar.calendar_range ALTER endDate DROP DEFAULT'); + $this->addSql('ALTER TABLE chill_calendar.calendar_range ALTER startDate TYPE TIMESTAMP(0) WITH TIME ZONE'); + $this->addSql('ALTER TABLE chill_calendar.calendar_range ALTER startDate DROP DEFAULT'); + $this->addSql('COMMENT ON COLUMN chill_calendar.calendar_range.enddate IS \'(DC2Type:datetimetz_immutable)\''); + $this->addSql('COMMENT ON COLUMN chill_calendar.calendar_range.startdate IS \'(DC2Type:datetimetz_immutable)\''); + } + + public function getDescription(): string + { + return 'remove timezone from calendar range'; + } + + public function up(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_calendar.calendar_range ALTER startdate TYPE TIMESTAMP(0) WITHOUT TIME ZONE'); + $this->addSql('ALTER TABLE chill_calendar.calendar_range ALTER startdate DROP DEFAULT'); + $this->addSql('ALTER TABLE chill_calendar.calendar_range ALTER enddate TYPE TIMESTAMP(0) WITHOUT TIME ZONE'); + $this->addSql('ALTER TABLE chill_calendar.calendar_range ALTER enddate DROP DEFAULT'); + $this->addSql('COMMENT ON COLUMN chill_calendar.calendar_range.startDate IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('COMMENT ON COLUMN chill_calendar.calendar_range.endDate IS \'(DC2Type:datetime_immutable)\''); + } +} diff --git a/src/Bundle/ChillCalendarBundle/migrations/Version20220608084052.php b/src/Bundle/ChillCalendarBundle/migrations/Version20220608084052.php new file mode 100644 index 000000000..8fa727533 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/migrations/Version20220608084052.php @@ -0,0 +1,43 @@ +addSql('ALTER TABLE chill_calendar.invite DROP remoteAttributes'); + $this->addSql('ALTER TABLE chill_calendar.invite DROP remoteId'); + } + + public function getDescription(): string + { + return 'Add remoteId for invitation'; + } + + public function up(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_calendar.invite ADD remoteAttributes JSON DEFAULT \'[]\' NOT NULL'); + $this->addSql('ALTER TABLE chill_calendar.invite ADD remoteId TEXT DEFAULT \'\' NOT NULL'); + $this->addSql('CREATE INDEX idx_calendar_invite_remote ON chill_calendar.invite (remoteId)'); + } +} diff --git a/src/Bundle/ChillCalendarBundle/migrations/Version20220609200857.php b/src/Bundle/ChillCalendarBundle/migrations/Version20220609200857.php new file mode 100644 index 000000000..75e3fa61c --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/migrations/Version20220609200857.php @@ -0,0 +1,50 @@ +addSql('DROP INDEX chill_calendar.idx_calendar_range_remote'); + $this->addSql('CREATE INDEX idx_calendar_range_remote ON chill_calendar.calendar_range (remoteId)'); + $this->addSql('DROP INDEX chill_calendar.idx_calendar_remote'); + $this->addSql('CREATE INDEX idx_calendar_remote ON chill_calendar.calendar (remoteId)'); + $this->addSql('DROP INDEX chill_calendar.idx_calendar_invite_remote'); + $this->addSql('CREATE INDEX idx_calendar_invite_remote ON chill_calendar.invite (remoteId)'); + } + + public function getDescription(): string + { + return 'Set an unique contraint on remoteId on calendar object which are synced to a remote'; + } + + public function up(Schema $schema): void + { + $this->addSql('DROP INDEX chill_calendar.idx_calendar_range_remote'); + $this->addSql('CREATE UNIQUE INDEX idx_calendar_range_remote ON chill_calendar.calendar_range (remoteId) WHERE remoteId <> \'\''); + $this->addSql('DROP INDEX chill_calendar.idx_calendar_remote'); + $this->addSql('CREATE UNIQUE INDEX idx_calendar_remote ON chill_calendar.calendar (remoteId) WHERE remoteId <> \'\''); + $this->addSql('DROP INDEX chill_calendar.idx_calendar_invite_remote'); + $this->addSql('CREATE UNIQUE INDEX idx_calendar_invite_remote ON chill_calendar.invite (remoteId) WHERE remoteId <> \'\''); + } +} diff --git a/src/Bundle/ChillCalendarBundle/migrations/Version20220613202636.php b/src/Bundle/ChillCalendarBundle/migrations/Version20220613202636.php new file mode 100644 index 000000000..82eda3800 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/migrations/Version20220613202636.php @@ -0,0 +1,40 @@ +addSql('ALTER TABLE chill_calendar.calendar DROP smsStatus'); + } + + public function getDescription(): string + { + return 'Add sms status on calendars'; + } + + public function up(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_calendar.calendar ADD smsStatus TEXT DEFAULT \'sms_pending\' NOT NULL'); + } +} diff --git a/src/Bundle/ChillCalendarBundle/migrations/Version20220629095515.php b/src/Bundle/ChillCalendarBundle/migrations/Version20220629095515.php new file mode 100644 index 000000000..7179bb79a --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/migrations/Version20220629095515.php @@ -0,0 +1,44 @@ +addSql('alter table chill_calendar.calendar_range DROP COLUMN location_id'); + } + + public function getDescription(): string + { + return 'Add location on calendar range'; + } + + public function up(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_calendar.calendar_range ADD location_id INT DEFAULT NULL'); + $this->addSql('UPDATE chill_calendar.calendar_range SET location_id = n.min FROM (SELECT min(id) FROM public.chill_main_location) AS n'); + $this->addSql('ALTER TABLE chill_calendar.calendar_range ALTER COLUMN location_id SET NOT NULL'); + $this->addSql('ALTER TABLE chill_calendar.calendar_range ADD CONSTRAINT FK_38D57D0564D218E FOREIGN KEY (location_id) REFERENCES chill_main_location (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('CREATE INDEX IDX_38D57D0564D218E ON chill_calendar.calendar_range (location_id)'); + } +} diff --git a/src/Bundle/ChillCalendarBundle/migrations/Version20220921101643.php b/src/Bundle/ChillCalendarBundle/migrations/Version20220921101643.php new file mode 100644 index 000000000..b9a3619c2 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/migrations/Version20220921101643.php @@ -0,0 +1,45 @@ +addSql('ALTER TABLE chill_calendar.calendar DROP urgent'); + } + + public function getDescription(): string + { + return 'Add urgent property to calendar entity'; + } + + public function up(Schema $schema): void + { + // this up() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE chill_calendar.calendar ADD urgent BOOLEAN DEFAULT NULL'); + } +} diff --git a/src/Bundle/ChillCalendarBundle/migrations/Version20221020101547.php b/src/Bundle/ChillCalendarBundle/migrations/Version20221020101547.php new file mode 100644 index 000000000..1fbfde60e --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/migrations/Version20221020101547.php @@ -0,0 +1,47 @@ +addSql('DROP SEQUENCE chill_calendar.calendar_doc_id_seq CASCADE'); + $this->addSql('DROP TABLE chill_calendar.calendar_doc'); + $this->addSql('ALTER TABLE chill_calendar.calendar DROP dateTimeVersion'); + } + + public function getDescription(): string + { + return 'Add calendardoc on Calendar'; + } + + public function up(Schema $schema): void + { + $this->addSql('CREATE SEQUENCE chill_calendar.calendar_doc_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE TABLE chill_calendar.calendar_doc (id INT NOT NULL, calendar_id INT NOT NULL, datetimeVersion INT DEFAULT 0 NOT NULL, trackDateTimeVersion BOOLEAN DEFAULT false NOT NULL, createdAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, updatedAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, storedObject_id INT NOT NULL, createdBy_id INT DEFAULT NULL, updatedBy_id INT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_4FD11573A40A2C8 ON chill_calendar.calendar_doc (calendar_id)'); + $this->addSql('CREATE INDEX IDX_4FD115736C99C13A ON chill_calendar.calendar_doc (storedObject_id)'); + $this->addSql('CREATE INDEX IDX_4FD115733174800F ON chill_calendar.calendar_doc (createdBy_id)'); + $this->addSql('CREATE INDEX IDX_4FD1157365FF1AEC ON chill_calendar.calendar_doc (updatedBy_id)'); + $this->addSql('COMMENT ON COLUMN chill_calendar.calendar_doc.createdAt IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('COMMENT ON COLUMN chill_calendar.calendar_doc.updatedAt IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('ALTER TABLE chill_calendar.calendar_doc ADD CONSTRAINT FK_4FD11573A40A2C8 FOREIGN KEY (calendar_id) REFERENCES chill_calendar.calendar (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_calendar.calendar_doc ADD CONSTRAINT FK_4FD115736C99C13A FOREIGN KEY (storedObject_id) REFERENCES chill_doc.stored_object (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_calendar.calendar_doc ADD CONSTRAINT FK_4FD115733174800F FOREIGN KEY (createdBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_calendar.calendar_doc ADD CONSTRAINT FK_4FD1157365FF1AEC FOREIGN KEY (updatedBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_calendar.calendar ADD dateTimeVersion INT DEFAULT 0 NOT NULL'); + } +} diff --git a/src/Bundle/ChillCalendarBundle/migrations/Version20221021092541.php b/src/Bundle/ChillCalendarBundle/migrations/Version20221021092541.php new file mode 100644 index 000000000..732b975e3 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/migrations/Version20221021092541.php @@ -0,0 +1,35 @@ +addSql('ALTER TABLE chill_calendar.calendar DROP person_id'); + } + + public function getDescription(): string + { + return 'Associate a calendar with a person'; + } + + public function up(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_calendar.calendar ADD person_id INT DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_calendar.calendar ADD CONSTRAINT FK_712315AC217BBB47 FOREIGN KEY (person_id) REFERENCES chill_person_person (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('CREATE INDEX IDX_712315AC217BBB47 ON chill_calendar.calendar (person_id)'); + } +} diff --git a/src/Bundle/ChillCalendarBundle/migrations/Version20221125144205.php b/src/Bundle/ChillCalendarBundle/migrations/Version20221125144205.php new file mode 100644 index 000000000..624fdda00 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/migrations/Version20221125144205.php @@ -0,0 +1,41 @@ +throwIrreversibleMigrationException(); + } + + public function getDescription(): string + { + return 'Calendar: remove association between scope and calendar'; + } + + public function up(Schema $schema): void + { + $this->addSql( + sprintf( + 'UPDATE role_scopes SET scope_id=NULL WHERE role IN (\'%s\', \'%s\', \'%s\', \'%s\')', + 'CHILL_CALENDAR_CALENDAR_CREATE', + 'CHILL_CALENDAR_CALENDAR_DELETE', + 'CHILL_CALENDAR_CALENDAR_EDIT', + 'CHILL_CALENDAR_CALENDAR_SEE' + ) + ); + } +} diff --git a/src/Bundle/ChillCalendarBundle/translations/messages+intl-icu.fr.yml b/src/Bundle/ChillCalendarBundle/translations/messages+intl-icu.fr.yml new file mode 100644 index 000000000..4bbc105b9 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/translations/messages+intl-icu.fr.yml @@ -0,0 +1,8 @@ +chill_calendar: + There are count ignored calendars by date filter: >- + {nbIgnored, plural, + =0 {Il n'y a aucun rendez-vous ignoré par le filtre de date.} + one {Il y a un rendez-vous ignoré par le filtre de date. Modifiez le filtre de date pour le voir apparaitre.} + few {# rendez-vous sont ignorés par le filtre de date. Modifiez le filtre de date pour les voir apparaitre.} + other {# rendez-vous sont ignorés par le filtre de date. Modifiez le filtre de date pour les voir apparaitre.} + } diff --git a/src/Bundle/ChillCalendarBundle/translations/messages.fr.yml b/src/Bundle/ChillCalendarBundle/translations/messages.fr.yml index d013cb5ef..a1228c381 100644 --- a/src/Bundle/ChillCalendarBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillCalendarBundle/translations/messages.fr.yml @@ -4,7 +4,7 @@ My calendar list: Mes rendez-vous There is no calendar items.: Il n'y a pas de rendez-vous Remove calendar item: Supprimer le rendez-vous Are you sure you want to remove the calendar item?: Êtes-vous sûr de vouloir supprimer le rendez-vous? -Concerned groups: Parties concernées +Concerned groups calendar: Parties concernées Calendar data: Données du rendez-vous Update calendar: Modifier le rendez-vous main user concerned: Utilisateur concerné @@ -14,7 +14,7 @@ start date: début du rendez-vous end date: fin du rendez-vous cancel reason: motif d'annulation status: Statut du rendez-vous -calendar location: Localistion du rendez-vous +calendar location: Localisation du rendez-vous calendar comment: Remarque sur le rendez-vous sendSMS: Envoi d'un SMS Send s m s: Envoi d'un SMS ? @@ -27,6 +27,9 @@ From the day: Du to the day: au Transform to activity: Transformer en échange Create a new calendar in accompanying course: Créer un rendez-vous dans le parcours +Will send SMS: Un SMS de rappel sera envoyé +Will not send SMS: Aucun SMS de rappel ne sera envoyé +SMS already sent: Un SMS a été envoyé canceledBy: supprimé par Canceled by: supprimé par @@ -39,3 +42,107 @@ crud: add_new: Ajouter un nouveau title_new: Nouveau motif d'annulation title_edit: Modifier le motif d'annulation + +chill_calendar: + 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 + start date filter: Début du rendez-vous + From: Du + To: Au + Next calendars: Prochains rendez-vous + Add a document: Ajouter un document + Documents: Documents + Create and add a document: Créer et ajouter un document + Save and add a document: Enregistrer et ajouter un document + Create for me: Créer un rendez-vous pour moi-même + Edit a document: Modifier un document + Document title: Titre + Document object: Document + Add a document from template: Ajouter un document depuis un gabarit + Upload a document: Téléverser un document + Remove a calendar document: Supprimer un document d'un rendez-vous + Are you sure you want to remove the doc?: Êtes-vous sûr·e de vouloir supprimer le document associé ? + 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é + free: Libre + tentative: En attente de confirmation + oof: En dehors du bureau + workingElsewhere: Travaille à l'extérieur + unknown: Inconnu + cancel_event_because_main_user_is_%label%: L'événement est transféré à l'utilisateur %label% + +remote_calendar: + calendar_range_title: Plage de disponibilité Chill + +invite: + accepted: Accepté + declined: Refusé + pending: En attente + tentative: Accepté provisoirement + +# exports +Exports of calendar: Exports des rendez-vous +Count calendars: Nombre de rendez-vous +Count calendars by various parameters.: Compte le nombre de rendez-vous en fonction de différents paramètres. + +Average appointment duration: Moyenne de la durée des rendez-vous +Get the average of appointment duration according to various filters: Calcule la moyenne des durées des rendez-vous en fonction de différents paramètres. + +Sum of appointment durations: Somme de la durée des rendez-vous +Get the sum of appointment durations according to various filters: Calcule la somme des durées des rendez-vous en fonction de différents paramètres. + +'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 + +Scope: Service +Job: Métier +Location type: Type de localisation +Location: Lieu de rendez-vous +by month and year: Par mois et année + +is urgent: Urgent +is not urgent: Pas urgent +has calendar range: Dans une plage de disponibilité? +Not made within a calendar range: Rendez-vous dans une plage de disponibilité +Made within a calendar range: Rendez-vous en dehors d'une plage de disponibilité + +docgen: + calendar: + Base context for calendar: 'Rendez-vous: contexte de base' + A base context for generating document on calendar: Contexte pour générer des documents à partir des rendez-vous + Track changes on datetime and warn user if date time is updated after the doc generation: Suivre les changements sur le document et prévenir les utilisateurs que la date et l'heure ont été modifiée après la génération du document + Ask main person: Demander de choisir un usager parmi les participants aux rendez-vous + Main person label: Label pour choisir l'usager + Ask third party: Demander de choisir un tiers parmi les participants aux rendez-vous + Third party label: Label pour choisir le tiers + Destinee: Destinataire + None: Aucun choix + title of the generated document: Titre du document généré + +CHILL_CALENDAR_CALENDAR_CREATE: Créer les rendez-vous +CHILL_CALENDAR_CALENDAR_EDIT: Modifier les rendez-vous +CHILL_CALENDAR_CALENDAR_DELETE: Supprimer les rendez-vous +CHILL_CALENDAR_CALENDAR_SEE: Voir les rendez-vous + diff --git a/src/Bundle/ChillCalendarBundle/translations/validators.fr.yml b/src/Bundle/ChillCalendarBundle/translations/validators.fr.yml new file mode 100644 index 000000000..03343ba8a --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/translations/validators.fr.yml @@ -0,0 +1,7 @@ +calendar: + At least {{ limit }} person is required.: Au moins {{ limit }} usager doit être associée à ce rendez-vous + An end date is required: Indiquez une date et heure de fin + A start date is required: Indiquez une date et heure de début + A location is required: Indiquez un lieu + A main user is mandator: Indiquez un utilisateur principal + diff --git a/src/Bundle/ChillCustomFieldsBundle/ChillCustomFieldsBundle.php b/src/Bundle/ChillCustomFieldsBundle/ChillCustomFieldsBundle.php index 65cded145..4167efd2e 100644 --- a/src/Bundle/ChillCustomFieldsBundle/ChillCustomFieldsBundle.php +++ b/src/Bundle/ChillCustomFieldsBundle/ChillCustomFieldsBundle.php @@ -1,14 +1,14 @@ getName()[$lang])) ? - $cf->getName()[$lang] : - 'Not available in this language'; + $names[] = $cf->getName()[$lang] ?? 'Not available in this language'; } if ($this->validator->validate($cf)) { @@ -249,9 +247,7 @@ class CreateFieldsOnGroupCommand extends Command 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'; + $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 26a252cf5..1813dd7b3 100644 --- a/src/Bundle/ChillCustomFieldsBundle/Controller/AdminController.php +++ b/src/Bundle/ChillCustomFieldsBundle/Controller/AdminController.php @@ -1,14 +1,14 @@ getOptions()[self::CHOICES] as $choice) { - if (false === $choices['active']) { + if (false === $choice['active']) { continue; } $choices[$choice['slug']] = $this->translatableStringHelper @@ -294,7 +294,7 @@ class CustomFieldChoice extends AbstractCustomField 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; + $data = $value['_choices'] ?? $value; $selected = (is_array($data)) ? $data : [$data]; $choices = $customField->getOptions()[self::CHOICES]; diff --git a/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldDate.php b/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldDate.php index 38a31491d..218bd5415 100644 --- a/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldDate.php +++ b/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldDate.php @@ -1,14 +1,14 @@ 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, diff --git a/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldNumber.php b/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldNumber.php index cd42ca751..82b35f9f5 100644 --- a/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldNumber.php +++ b/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldNumber.php @@ -1,14 +1,14 @@ persist($parent); //Load children - $expected_nb_children = mt_rand(10, 50); + $expected_nb_children = random_int(10, 50); for ($i = 0; $i < $expected_nb_children; ++$i) { $companyName = $this->fakerFr->company; @@ -144,7 +144,7 @@ class LoadOption extends AbstractFixture implements OrderedFixtureInterface $manager->persist($parent); //Load children - $expected_nb_children = mt_rand(10, 50); + $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 9c9ca7872..a90f770a4 100644 --- a/src/Bundle/ChillCustomFieldsBundle/DependencyInjection/ChillCustomFieldsExtension.php +++ b/src/Bundle/ChillCustomFieldsBundle/DependencyInjection/ChillCustomFieldsExtension.php @@ -1,14 +1,14 @@ 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 198028360..0cf1a67eb 100644 --- a/src/Bundle/ChillCustomFieldsBundle/DependencyInjection/CustomFieldCompilerPass.php +++ b/src/Bundle/ChillCustomFieldsBundle/DependencyInjection/CustomFieldCompilerPass.php @@ -1,14 +1,14 @@ getQuery() ->getScalarResult(); - return array_map(static function ($r) { - return $r['key']; - }, $keys); + return array_map(static fn ($r) => $r['key'], $keys); } } diff --git a/src/Bundle/ChillCustomFieldsBundle/Form/CustomFieldType.php b/src/Bundle/ChillCustomFieldsBundle/Form/CustomFieldType.php index e45cc7837..0630bccf5 100644 --- a/src/Bundle/ChillCustomFieldsBundle/Form/CustomFieldType.php +++ b/src/Bundle/ChillCustomFieldsBundle/Form/CustomFieldType.php @@ -1,14 +1,14 @@ add('customFieldsGroup', EntityType::class, [ 'class' => 'ChillCustomFieldsBundle:CustomFieldsGroup', - 'choice_label' => function ($g) { - return $this->translatableStringHelper->localize($g->getName()); - }, + 'choice_label' => fn ($g) => $this->translatableStringHelper->localize($g->getName()), ]); } elseif ('hidden' === $options['group_widget']) { $builder->add('customFieldsGroup', HiddenType::class); @@ -119,7 +117,7 @@ class CustomFieldType extends AbstractType public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ - 'data_class' => 'Chill\CustomFieldsBundle\Entity\CustomField', + 'data_class' => \Chill\CustomFieldsBundle\Entity\CustomField::class, ]); $resolver->setRequired(['type', 'group_widget']) diff --git a/src/Bundle/ChillCustomFieldsBundle/Form/CustomFieldsGroupType.php b/src/Bundle/ChillCustomFieldsBundle/Form/CustomFieldsGroupType.php index 5b6622e48..8eda50464 100644 --- a/src/Bundle/ChillCustomFieldsBundle/Form/CustomFieldsGroupType.php +++ b/src/Bundle/ChillCustomFieldsBundle/Form/CustomFieldsGroupType.php @@ -1,14 +1,14 @@ getLabel(); - }, + static fn ($e) => $e->getLabel(), $customFields ); @@ -108,7 +106,7 @@ class JsonCustomFieldToArrayTransformer implements DataTransformerInterface //echo json_encode($customFieldsArrayRet); - return json_encode($customFieldsArrayRet); + return json_encode($customFieldsArrayRet, JSON_THROW_ON_ERROR); } public function transform($customFieldsJSON) diff --git a/src/Bundle/ChillCustomFieldsBundle/Form/Extension/PostTextExtension.php b/src/Bundle/ChillCustomFieldsBundle/Form/Extension/PostTextExtension.php index 42428c21b..14ddf0472 100644 --- a/src/Bundle/ChillCustomFieldsBundle/Form/Extension/PostTextExtension.php +++ b/src/Bundle/ChillCustomFieldsBundle/Form/Extension/PostTextExtension.php @@ -1,14 +1,14 @@ getData(); if (null === $formData['slug']) { - $slug = uniqid(mt_rand(), true); + $slug = uniqid(random_int(0, mt_getrandmax()), true); $data['slug'] = $slug; $event->setData($data); diff --git a/src/Bundle/ChillCustomFieldsBundle/Form/Type/ChoicesType.php b/src/Bundle/ChillCustomFieldsBundle/Form/Type/ChoicesType.php index ed7b52fca..6730d2579 100644 --- a/src/Bundle/ChillCustomFieldsBundle/Form/Type/ChoicesType.php +++ b/src/Bundle/ChillCustomFieldsBundle/Form/Type/ChoicesType.php @@ -1,14 +1,14 @@ setRequired(['group']) - ->addAllowedTypes('group', ['Chill\CustomFieldsBundle\Entity\CustomFieldsGroup']); + ->addAllowedTypes('group', [\Chill\CustomFieldsBundle\Entity\CustomFieldsGroup::class]); } public function getBlockPrefix() diff --git a/src/Bundle/ChillCustomFieldsBundle/Form/Type/CustomFieldsTitleType.php b/src/Bundle/ChillCustomFieldsBundle/Form/Type/CustomFieldsTitleType.php index 95d5a649f..fa462737e 100644 --- a/src/Bundle/ChillCustomFieldsBundle/Form/Type/CustomFieldsTitleType.php +++ b/src/Bundle/ChillCustomFieldsBundle/Form/Type/CustomFieldsTitleType.php @@ -1,14 +1,14 @@ setAttribute('class', 'list-group-item-header') ->setExtras([ 'order' => 4500, - 'icons' => ['plus'], ]); $menu->addChild('Custom fields group', [ diff --git a/src/Bundle/ChillCustomFieldsBundle/Service/CustomFieldProvider.php b/src/Bundle/ChillCustomFieldsBundle/Service/CustomFieldProvider.php index 6c90f3951..30198f1a2 100644 --- a/src/Bundle/ChillCustomFieldsBundle/Service/CustomFieldProvider.php +++ b/src/Bundle/ChillCustomFieldsBundle/Service/CustomFieldProvider.php @@ -1,14 +1,14 @@ getSlug(); - $rawValue = (isset($fields[$slug])) ? $fields[$slug] : null; + $rawValue = $fields[$slug] ?? null; $customFieldType = $this->provider->getCustomFieldByType($customField->getType()); $deserializedValue = $customFieldType->deserialize($rawValue, $customField); @@ -71,7 +71,7 @@ class CustomFieldsHelper public function renderCustomField(array $fields, CustomField $customField, $documentType = 'html') { $slug = $customField->getSlug(); - $rawValue = (isset($fields[$slug])) ? $fields[$slug] : null; + $rawValue = $fields[$slug] ?? null; $customFieldType = $this->provider->getCustomFieldByType($customField->getType()); return $customFieldType->render($rawValue, $customField, $documentType); diff --git a/src/Bundle/ChillCustomFieldsBundle/Service/CustomFieldsHelperException.php b/src/Bundle/ChillCustomFieldsBundle/Service/CustomFieldsHelperException.php index 90c31612f..f1d143c92 100644 --- a/src/Bundle/ChillCustomFieldsBundle/Service/CustomFieldsHelperException.php +++ b/src/Bundle/ChillCustomFieldsBundle/Service/CustomFieldsHelperException.php @@ -1,14 +1,14 @@ selectButton('Créer')->form([ 'custom_fields_group[name][fr]' => 'Test', - 'custom_fields_group[entity]' => 'Chill\PersonBundle\Entity\Person', + 'custom_fields_group[entity]' => \Chill\PersonBundle\Entity\Person::class, ]); $crawler = $client->submit($form); diff --git a/src/Bundle/ChillCustomFieldsBundle/Tests/CustomFieldTestHelper.php b/src/Bundle/ChillCustomFieldsBundle/Tests/CustomFieldTestHelper.php index 065b32898..79f547fe2 100644 --- a/src/Bundle/ChillCustomFieldsBundle/Tests/CustomFieldTestHelper.php +++ b/src/Bundle/ChillCustomFieldsBundle/Tests/CustomFieldTestHelper.php @@ -1,14 +1,14 @@ createMock('Chill\CustomFieldsBundle\Entity\CustomFieldsGroup'); + $customFieldsGroup = $this->createMock(\Chill\CustomFieldsBundle\Entity\CustomFieldsGroup::class); $customFieldsGroup->expects($this->once()) ->method('getActiveCustomFields') ->will($this->returnValue([$field])); - $request = $this->createMock('Symfony\Component\HttpFoundation\Request'); + $request = $this->createMock(\Symfony\Component\HttpFoundation\Request::class); $request->expects($this->any()) ->method('getLocale') ->will($this->returnValue($locale)); diff --git a/src/Bundle/ChillCustomFieldsBundle/Tests/CustomFields/CustomFieldsChoiceTest.php b/src/Bundle/ChillCustomFieldsBundle/Tests/CustomFields/CustomFieldsChoiceTest.php index d3355b9ce..5b905cb4d 100644 --- a/src/Bundle/ChillCustomFieldsBundle/Tests/CustomFields/CustomFieldsChoiceTest.php +++ b/src/Bundle/ChillCustomFieldsBundle/Tests/CustomFields/CustomFieldsChoiceTest.php @@ -1,14 +1,14 @@ customFieldProvider->getCustomFieldByType('text'); $this->assertInstanceOf( - 'Chill\CustomFieldsBundle\CustomFields\CustomFieldInterface', + \Chill\CustomFieldsBundle\CustomFields\CustomFieldInterface::class, $customField ); $this->assertInstanceOf( - 'Chill\CustomFieldsBundle\CustomFields\CustomFieldText', + \Chill\CustomFieldsBundle\CustomFields\CustomFieldText::class, $customField ); } diff --git a/src/Bundle/ChillCustomFieldsBundle/Tests/Form/Extension/PostTextIntegerExtensionTest.php b/src/Bundle/ChillCustomFieldsBundle/Tests/Form/Extension/PostTextIntegerExtensionTest.php index 1c8b8e143..087b232ee 100644 --- a/src/Bundle/ChillCustomFieldsBundle/Tests/Form/Extension/PostTextIntegerExtensionTest.php +++ b/src/Bundle/ChillCustomFieldsBundle/Tests/Form/Extension/PostTextIntegerExtensionTest.php @@ -1,14 +1,14 @@ prophesize(); - $request->willExtend('Symfony\Component\HttpFoundation\Request'); + $request->willExtend(\Symfony\Component\HttpFoundation\Request::class); $request->getLocale()->willReturn('fr'); self::$kernel->getContainer()->get('request_stack') ->push($request->reveal()); diff --git a/src/Bundle/ChillCustomFieldsBundle/Tests/Templating/Twig/CustomFieldsGroupRenderingTwigTest.php b/src/Bundle/ChillCustomFieldsBundle/Tests/Templating/Twig/CustomFieldsGroupRenderingTwigTest.php index b950d562e..f3b93c390 100644 --- a/src/Bundle/ChillCustomFieldsBundle/Tests/Templating/Twig/CustomFieldsGroupRenderingTwigTest.php +++ b/src/Bundle/ChillCustomFieldsBundle/Tests/Templating/Twig/CustomFieldsGroupRenderingTwigTest.php @@ -1,14 +1,14 @@ prophesize(); - $request->willExtend('Symfony\Component\HttpFoundation\Request'); + $request->willExtend(\Symfony\Component\HttpFoundation\Request::class); $request->getLocale()->willReturn('fr'); self::$kernel->getContainer()->get('request_stack') ->push($request->reveal()); diff --git a/src/Bundle/ChillCustomFieldsBundle/migrations/Version20141128195430.php b/src/Bundle/ChillCustomFieldsBundle/migrations/Version20141128195430.php index 5a93f65d9..f96a578da 100644 --- a/src/Bundle/ChillCustomFieldsBundle/migrations/Version20141128195430.php +++ b/src/Bundle/ChillCustomFieldsBundle/migrations/Version20141128195430.php @@ -1,14 +1,14 @@ + */ interface DocGeneratorContextWithAdminFormInterface extends DocGeneratorContextInterface { public function adminFormReverseTransform(array $data): array; diff --git a/src/Bundle/ChillDocGeneratorBundle/Context/DocGeneratorContextWithPublicFormInterface.php b/src/Bundle/ChillDocGeneratorBundle/Context/DocGeneratorContextWithPublicFormInterface.php index e207cfb25..496524206 100644 --- a/src/Bundle/ChillDocGeneratorBundle/Context/DocGeneratorContextWithPublicFormInterface.php +++ b/src/Bundle/ChillDocGeneratorBundle/Context/DocGeneratorContextWithPublicFormInterface.php @@ -1,34 +1,57 @@ + */ interface DocGeneratorContextWithPublicFormInterface extends DocGeneratorContextInterface { /** * Generate the form that display. * - * @param mixed $entity + * @param T $entity */ - public function buildPublicForm(FormBuilderInterface $builder, DocGeneratorTemplate $template, $entity): void; + public function buildPublicForm(FormBuilderInterface $builder, DocGeneratorTemplate $template, mixed $entity): void; - public function getFormData(DocGeneratorTemplate $template, $entity): array; + /** + * Fill the form with initial data + * + * @param T $entity + */ + public function getFormData(DocGeneratorTemplate $template, mixed $entity): array; /** * has form. * - * @param mixed $entity + * @param T $entity */ - public function hasPublicForm(DocGeneratorTemplate $template, $entity): bool; + public function hasPublicForm(DocGeneratorTemplate $template, mixed $entity): bool; + + /** + * Transform the data from the form into serializable data, storable into messenger's message + * + * @param T $entity + */ + public function contextGenerationDataNormalize(DocGeneratorTemplate $template, mixed $entity, array $data): array; + + /** + * Reverse the data from the messenger's message into data usable for doc's generation + * + * @param T $entity + */ + public function contextGenerationDataDenormalize(DocGeneratorTemplate $template, mixed $entity, array $data): array; } diff --git a/src/Bundle/ChillDocGeneratorBundle/Context/Exception/ContextNotFoundException.php b/src/Bundle/ChillDocGeneratorBundle/Context/Exception/ContextNotFoundException.php index d4bbe856a..c1e5cabe8 100644 --- a/src/Bundle/ChillDocGeneratorBundle/Context/Exception/ContextNotFoundException.php +++ b/src/Bundle/ChillDocGeneratorBundle/Context/Exception/ContextNotFoundException.php @@ -1,14 +1,14 @@ addSelect('JSON_EXTRACT(e.name, :lang) AS HIDDEN name_lang') + ->setParameter('lang', $request->getLocale()) + ->addOrderBy('name_lang', 'ASC'); + } } diff --git a/src/Bundle/ChillDocGeneratorBundle/Controller/DocGeneratorTemplateController.php b/src/Bundle/ChillDocGeneratorBundle/Controller/DocGeneratorTemplateController.php index 4bbd95e3a..ef2d2d585 100644 --- a/src/Bundle/ChillDocGeneratorBundle/Controller/DocGeneratorTemplateController.php +++ b/src/Bundle/ChillDocGeneratorBundle/Controller/DocGeneratorTemplateController.php @@ -1,14 +1,14 @@ contextManager = $contextManager; $this->docGeneratorTemplateRepository = $docGeneratorTemplateRepository; - $this->driver = $driver; - $this->logger = $logger; + $this->generator = $generator; + $this->messageBus = $messageBus; $this->paginatorFactory = $paginatorFactory; - $this->client = $client; - $this->storedObjectManager = $storedObjectManager; $this->entityManager = $entityManager; } @@ -94,7 +85,6 @@ final class DocGeneratorTemplateController extends AbstractController ): Response { return $this->generateDocFromTemplate( $template, - $entityClassName, $entityId, $request, true @@ -115,7 +105,6 @@ final class DocGeneratorTemplateController extends AbstractController ): Response { return $this->generateDocFromTemplate( $template, - $entityClassName, $entityId, $request, false @@ -185,7 +174,6 @@ final class DocGeneratorTemplateController extends AbstractController private function generateDocFromTemplate( DocGeneratorTemplate $template, - string $entityClassName, int $entityId, Request $request, bool $isTest @@ -206,7 +194,7 @@ final class DocGeneratorTemplateController extends AbstractController if (null === $entity) { throw new NotFoundHttpException( - sprintf('Entity with classname %s and id %s is not found', $entityClassName, $entityId) + sprintf('Entity with classname %s and id %s is not found', $context->getEntityClass(), $entityId) ); } @@ -259,98 +247,69 @@ final class DocGeneratorTemplateController extends AbstractController } } - $document = $template->getFile(); - - if ($isTest && ($contextGenerationData['test_file'] instanceof File)) { - $dataDecrypted = file_get_contents($contextGenerationData['test_file']->getPathname()); - } else { - try { - $dataDecrypted = $this->storedObjectManager->read($document); - } catch (Throwable $exception) { - throw $exception; - } - } + // transform context generation data + $contextGenerationDataSanitized = + $context instanceof DocGeneratorContextWithPublicFormInterface ? + $context->contextGenerationDataNormalize($template, $entity, $contextGenerationData) + : []; + // if is test, render the data or generate the doc if ($isTest && isset($form) && $form['show_data']->getData()) { - // very ugly hack... - dd($context->getData($template, $entity, $contextGenerationData)); + return $this->render('@ChillDocGenerator/Generator/debug_value.html.twig', [ + 'datas' => json_encode($context->getData($template, $entity, $contextGenerationData), JSON_PRETTY_PRINT), + ]); } - - try { - $generatedResource = $this - ->driver - ->generateFromString( - $dataDecrypted, - $template->getFile()->getType(), - $context->getData($template, $entity, $contextGenerationData), - $template->getFile()->getFilename() - ); - } catch (TemplateException $e) { - return new Response( - implode("\n", $e->getErrors()), - 400, - [ - 'Content-Type' => 'text/plain', - ] - ); - } - if ($isTest) { + $generated = $this->generator->generateDocFromTemplate( + $template, + $entityId, + $contextGenerationDataSanitized, + null, + true, + isset($form) ? $form['test_file']->getData() : null + ); + return new Response( - $generatedResource, + $generated, Response::HTTP_OK, [ 'Content-Transfer-Encoding', 'binary', 'Content-Type' => 'application/vnd.oasis.opendocument.text', 'Content-Disposition' => 'attachment; filename="generated.odt"', - 'Content-Length' => strlen($generatedResource), + 'Content-Length' => strlen($generated), ], ); } - /** @var StoredObject $storedObject */ - $storedObject = (new ObjectNormalizer()) - ->denormalize( - [ - 'type' => $template->getFile()->getType(), - 'filename' => sprintf('%s_odt', uniqid('doc_', true)), - ], - StoredObject::class - ); - - try { - $this->storedObjectManager->write($storedObject, $generatedResource); - } catch (Throwable $exception) { - throw $exception; - } + // this is not a test + // we prepare the object to store the document + $storedObject = (new StoredObject()) + ->setStatus(StoredObject::STATUS_PENDING) + ; $this->entityManager->persist($storedObject); - try { - $context - ->storeGenerated( - $template, - $storedObject, - $entity, - $contextGenerationData - ); - } catch (Exception $e) { - $this - ->logger - ->error( - 'Unable to store the associated document to entity', - [ - 'entityClassName' => $entityClassName, - 'entityId' => $entityId, - 'contextKey' => $context->getName(), - ] - ); - - throw $e; - } + // we store the generated document + $context + ->storeGenerated( + $template, + $storedObject, + $entity, + $contextGenerationData + ); $this->entityManager->flush(); + $this->messageBus->dispatch( + new RequestGenerationMessage( + $this->getUser(), + $template, + $entityId, + $storedObject, + $contextGenerationDataSanitized, + ) + ); + return $this ->redirectToRoute( 'chill_wopi_file_edit', diff --git a/src/Bundle/ChillDocGeneratorBundle/DataFixtures/ORM/LoadDocGeneratorTemplate.php b/src/Bundle/ChillDocGeneratorBundle/DataFixtures/ORM/LoadDocGeneratorTemplate.php index 86cedfb3e..a52381f36 100644 --- a/src/Bundle/ChillDocGeneratorBundle/DataFixtures/ORM/LoadDocGeneratorTemplate.php +++ b/src/Bundle/ChillDocGeneratorBundle/DataFixtures/ORM/LoadDocGeneratorTemplate.php @@ -1,14 +1,14 @@ setFilename($template['file']['filename']) ->setKeyInfos(json_decode($template['file']['key'], true)) ->setIv(json_decode($template['file']['iv'], true)) - ->setCreationDate(new DateTime('today')) + ->setCreatedAt(new DateTime('today')) ->setType($template['file']['type']); $manager->persist($newStoredObj); diff --git a/src/Bundle/ChillDocGeneratorBundle/DependencyInjection/ChillDocGeneratorExtension.php b/src/Bundle/ChillDocGeneratorBundle/DependencyInjection/ChillDocGeneratorExtension.php index b57cbe72b..9edca1675 100644 --- a/src/Bundle/ChillDocGeneratorBundle/DependencyInjection/ChillDocGeneratorExtension.php +++ b/src/Bundle/ChillDocGeneratorBundle/DependencyInjection/ChillDocGeneratorExtension.php @@ -1,14 +1,14 @@ create('options', null, ['compound' => true]) ->addModelTransformer(new CallbackTransformer( - static function (array $data) use ($context) { - return $context->adminFormTransform($data); - }, - static function (array $data) use ($context) { - return $context->adminFormReverseTransform($data); - } + static fn (array $data) => $context->adminFormTransform($data), + static fn (array $data) => $context->adminFormReverseTransform($data) )); $context->buildAdminForm($sub); $builder->add($sub); diff --git a/src/Bundle/ChillDocGeneratorBundle/GeneratorDriver/DriverInterface.php b/src/Bundle/ChillDocGeneratorBundle/GeneratorDriver/DriverInterface.php index d787523c9..4d802d2ca 100644 --- a/src/Bundle/ChillDocGeneratorBundle/GeneratorDriver/DriverInterface.php +++ b/src/Bundle/ChillDocGeneratorBundle/GeneratorDriver/DriverInterface.php @@ -1,14 +1,14 @@ json_encode($data), + 'variables' => json_encode($data, JSON_THROW_ON_ERROR), 'template' => new DataPart($template, $templateName ?? uniqid('template_'), $resourceType), ] ); @@ -53,6 +53,7 @@ final class RelatorioDriver implements DriverInterface $response = $this->client->request('POST', $this->url, [ 'headers' => $form->getPreparedHeaders()->toArray(), 'body' => $form->bodyToIterable(), + 'timeout' => '300', ]); return $response->getContent(); @@ -60,7 +61,7 @@ final class RelatorioDriver implements DriverInterface $content = $e->getResponse()->getContent(false); if (400 === $e->getResponse()->getStatusCode()) { - $content = json_decode($content, true); + $content = json_decode($content, true, 512, JSON_THROW_ON_ERROR); $this->logger->error('relatorio: template error', [ 'error' => $content['message'] ?? '_not defined', ]); diff --git a/src/Bundle/ChillDocGeneratorBundle/Menu/AdminMenuBuilder.php b/src/Bundle/ChillDocGeneratorBundle/Menu/AdminMenuBuilder.php index 5eed2ae9f..b325e6e12 100644 --- a/src/Bundle/ChillDocGeneratorBundle/Menu/AdminMenuBuilder.php +++ b/src/Bundle/ChillDocGeneratorBundle/Menu/AdminMenuBuilder.php @@ -1,14 +1,14 @@ repository = $entityManager->getRepository(DocGeneratorTemplate::class); + $this->requestStack = $requestStack; } public function countByEntity(string $entity): int @@ -32,6 +36,7 @@ final class DocGeneratorTemplateRepository implements ObjectRepository $builder ->select('count(t)') ->where('t.entity LIKE :entity') + ->andWhere($builder->expr()->eq('t.active', "'TRUE'")) ->setParameter('entity', addslashes($entity)); return $builder->getQuery()->getSingleScalarResult(); @@ -71,7 +76,10 @@ final class DocGeneratorTemplateRepository implements ObjectRepository $builder ->where('t.entity LIKE :entity') ->andWhere($builder->expr()->eq('t.active', "'TRUE'")) - ->setParameter('entity', addslashes($entity)); + ->setParameter('entity', addslashes($entity)) + ->addSelect('JSON_EXTRACT(t.name, :lang) AS HIDDEN name_lang') + ->setParameter('lang', $this->requestStack->getCurrentRequest()->getLocale()) + ->addOrderBy('name_lang', 'ASC'); return $builder ->getQuery() diff --git a/src/Bundle/ChillDocGeneratorBundle/Resources/public/api/pickTemplate.js b/src/Bundle/ChillDocGeneratorBundle/Resources/public/api/pickTemplate.js index a668fe29d..4c5673d05 100644 --- a/src/Bundle/ChillDocGeneratorBundle/Resources/public/api/pickTemplate.js +++ b/src/Bundle/ChillDocGeneratorBundle/Resources/public/api/pickTemplate.js @@ -1,4 +1,4 @@ -import { fetchResults } from "ChillMainAssets/lib/api/apiMethods.js"; +import { fetchResults } from "ChillMainAssets/lib/api/apiMethods.ts"; const fetchTemplates = (entityClass) => { let fqdnEntityClass = encodeURI(entityClass); diff --git a/src/Bundle/ChillDocGeneratorBundle/Resources/views/DocGeneratorTemplate/index.html.twig b/src/Bundle/ChillDocGeneratorBundle/Resources/views/DocGeneratorTemplate/index.html.twig index 73527edb3..1adb6872b 100644 --- a/src/Bundle/ChillDocGeneratorBundle/Resources/views/DocGeneratorTemplate/index.html.twig +++ b/src/Bundle/ChillDocGeneratorBundle/Resources/views/DocGeneratorTemplate/index.html.twig @@ -27,9 +27,7 @@
    - - {{ 'Edit'|trans }} - +
    +
    - + @@ -23,7 +23,7 @@ -
    {{ 'Creator bundle id' | trans }} {{ 'Internal id inside creator bundle' | trans }} {{ 'Document class' | trans }} {{ 'Name' | trans }}{{ 'Actions' | trans }}{{ 'Actions' | trans }}
    {{ document_category.documentClass }} {{ document_category.name | localize_translatable_string}} +
    + {% if document.object.isPending %} +
    {{ 'docgen.Doc generation is pending'|trans }}
    + {% elseif document.object.isFailure %} +
    {{ 'docgen.Doc generation failed'|trans }}
    + {% endif %}
    {{ document.title }}
    -
    - {{ mm.mimeIcon(document.object.type) }} -
    + {% if document.object.type is not empty %} +
    + {{ mm.mimeIcon(document.object.type) }} +
    + {% endif %}

    {{ document.category.name|localize_translatable_string }}

    - {% if document.template is not null %} + {% if document.object.hasTemplate %}
    -

    {{ document.template.name.fr }}

    +

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

    {% endif %}
    @@ -44,54 +51,44 @@
      {% if document.course is defined %} - {% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_DELETE', document) %} -
    • - +
    • + {{ chill_entity_workflow_list('Chill\\DocStoreBundle\\Entity\\AccompanyingCourseDocument', document.id) }} +
    • + {% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_SEE_DETAILS', document) %} +
    • + {{ document.object|chill_document_button_group(document.title, is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_UPDATE', document)) }} +
    • +
    • +
    • {% endif %} {% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_UPDATE', document) %}
    • - {% if chill_document_is_editable(document.object) %} -
    • - {{ document.object|chill_document_edit_button }} -
    • - {% endif %} {% endif %} - {% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_SEE_DETAILS', document) %} -
    • - {{ m.download_button(document.object, document.title) }} -
    • -
    • - -
    • - {% endif %} -
    • - {{ chill_entity_workflow_list('Chill\\DocStoreBundle\\Entity\\AccompanyingCourseDocument', document.id) }} -
    • - {% else %} - {% if is_granted('CHILL_PERSON_DOCUMENT_DELETE', document) %} + {% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_DELETE', document) %}
    • - + +
    • + {% endif %} + {% else %} + {% if is_granted('CHILL_PERSON_DOCUMENT_SEE_DETAILS', document) %} +
    • + {{ document.object|chill_document_button_group(document.title, is_granted('CHILL_PERSON_DOCUMENT_UPDATE', document)) }} +
    • +
    • +
    • {% endif %} {% if is_granted('CHILL_PERSON_DOCUMENT_UPDATE', document) %}
    • - {% if chill_document_is_editable(document.object) %} -
    • - {{ document.object|chill_document_edit_button }} -
    • - {% endif %} {% endif %} - {% if is_granted('CHILL_PERSON_DOCUMENT_SEE_DETAILS', document) %} -
    • - {{ m.download_button(document.object, document.title) }} -
    • -
    • - + {% if is_granted('CHILL_PERSON_DOCUMENT_DELETE', document) %} +
    • +
    • {% endif %} {% endif %} diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/Macro/macro.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/Macro/macro.html.twig index 886544b1e..199a86c15 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/views/Macro/macro.html.twig +++ b/src/Bundle/ChillDocStoreBundle/Resources/views/Macro/macro.html.twig @@ -2,14 +2,47 @@ {% if storedObject is null %} {% else %} - {{ 'Download'|trans }} {% endif %} {% endmacro %} + +{% macro download_button_small(storedObject, filename = null) %} + {% if storedObject is null %} + + {% else %} + + {{ 'Download'|trans }} + {% endif %} +{% endmacro %} + +{% macro download_button_group(storedObject, canEdit = true, filename = null, options = {}) %} + {% if storedObject is null %} + + {% else %} +
      + {% endif %} +{% endmacro %} diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/Macro/macro_mimeicon.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/Macro/macro_mimeicon.html.twig index a9bc07e2f..7079a0d94 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/views/Macro/macro_mimeicon.html.twig +++ b/src/Bundle/ChillDocStoreBundle/Resources/views/Macro/macro_mimeicon.html.twig @@ -48,8 +48,8 @@ {% endif %} {% endfor %} - + {% endmacro %} \ No newline at end of file diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/PersonDocument/index.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/PersonDocument/index.html.twig index 5dc359fa8..8d201605e 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/views/PersonDocument/index.html.twig +++ b/src/Bundle/ChillDocStoreBundle/Resources/views/PersonDocument/index.html.twig @@ -27,16 +27,16 @@ {% block js %} {{ parent() }} - {{ encore_entry_script_tags('mod_async_upload') }} {{ encore_entry_script_tags('mod_docgen_picktemplate') }} {{ encore_entry_script_tags('mod_entity_workflow_pick') }} + {{ encore_entry_script_tags('mod_document_action_buttons_group') }} {% endblock %} {% block css %} {{ parent() }} - {{ encore_entry_link_tags('mod_async_upload') }} {{ encore_entry_link_tags('mod_docgen_picktemplate') }} {{ encore_entry_link_tags('mod_entity_workflow_pick') }} + {{ encore_entry_link_tags('mod_document_action_buttons_group') }} {% endblock %} {% block content %} diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/PersonDocument/show.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/PersonDocument/show.html.twig index b26aa4b8b..c276e067e 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/views/PersonDocument/show.html.twig +++ b/src/Bundle/ChillDocStoreBundle/Resources/views/PersonDocument/show.html.twig @@ -24,7 +24,11 @@ {% block title %}{{ 'Detail of document of %name%'|trans({ '%name%': person|chill_entity_render_string } ) }}{% endblock %} {% block js %} - {{ encore_entry_script_tags('mod_async_upload') }} + {{ encore_entry_script_tags('mod_document_action_buttons_group') }} +{% endblock %} + +{% block css %} + {{ encore_entry_link_tags('mod_document_action_buttons_group') }} {% endblock %} {% block content %} @@ -70,6 +74,10 @@ {% endif %} +
    • + {{ document.object|chill_document_button_group(document.title, is_granted('CHILL_PERSON_DOCUMENT_UPDATE', document)) }} +
    • + {% if is_granted('CHILL_PERSON_DOCUMENT_UPDATE', document) %}
    • @@ -77,16 +85,4 @@
    • {% endif %} - -
    • - {{ m.download_button(document.object, document.title) }} -
    • - - {% if chill_document_is_editable(document.object) %} -
    • - {{ document.object|chill_document_edit_button }} -
    • - {% endif %} - - {# {{ include('ChillDocStoreBundle:PersonDocument:_delete_form.html.twig') }} #} {% endblock %} diff --git a/src/Bundle/ChillDocStoreBundle/Security/Authorization/AccompanyingCourseDocumentVoter.php b/src/Bundle/ChillDocStoreBundle/Security/Authorization/AccompanyingCourseDocumentVoter.php index 35fde9b28..0ef1d4d49 100644 --- a/src/Bundle/ChillDocStoreBundle/Security/Authorization/AccompanyingCourseDocumentVoter.php +++ b/src/Bundle/ChillDocStoreBundle/Security/Authorization/AccompanyingCourseDocumentVoter.php @@ -1,14 +1,14 @@ voterHelper->supports($attribute, $subject); } - protected function voteOnAttribute($attribute, $subject, TokenInterface $token) + protected function voteOnAttribute($attribute, $subject, TokenInterface $token): bool { if (!$token->getUser() instanceof User) { return false; @@ -106,6 +106,10 @@ class AccompanyingCourseDocumentVoter extends AbstractChillVoter implements Prov ) { return false; } + + if (self::CREATE === $attribute && null !== $subject->getCourse()) { + return $this->voterHelper->voteOnAttribute($attribute, $subject->getCourse(), $token); + } } return $this->voterHelper->voteOnAttribute($attribute, $subject, $token); diff --git a/src/Bundle/ChillDocStoreBundle/Security/Authorization/PersonDocumentVoter.php b/src/Bundle/ChillDocStoreBundle/Security/Authorization/PersonDocumentVoter.php index dbaa474a3..059a2c7d9 100644 --- a/src/Bundle/ChillDocStoreBundle/Security/Authorization/PersonDocumentVoter.php +++ b/src/Bundle/ChillDocStoreBundle/Security/Authorization/PersonDocumentVoter.php @@ -1,14 +1,14 @@ tempUrlGenerator ->generate( - Request::METHOD_PUT, + Request::METHOD_HEAD, $document->getFilename() ) ->url diff --git a/src/Bundle/ChillDocStoreBundle/Service/StoredObjectManagerInterface.php b/src/Bundle/ChillDocStoreBundle/Service/StoredObjectManagerInterface.php index ad40b571c..caafb3937 100644 --- a/src/Bundle/ChillDocStoreBundle/Service/StoredObjectManagerInterface.php +++ b/src/Bundle/ChillDocStoreBundle/Service/StoredObjectManagerInterface.php @@ -1,14 +1,14 @@ true, 'is_safe' => ['html'], ]), + new TwigFilter('chill_document_button_group', [WopiEditTwigExtensionRuntime::class, 'renderButtonGroup'], [ + 'needs_environment' => true, + 'is_safe' => ['html'], + ]), ]; } diff --git a/src/Bundle/ChillDocStoreBundle/Templating/WopiEditTwigExtensionRuntime.php b/src/Bundle/ChillDocStoreBundle/Templating/WopiEditTwigExtensionRuntime.php index a99a7d351..ec1df1465 100644 --- a/src/Bundle/ChillDocStoreBundle/Templating/WopiEditTwigExtensionRuntime.php +++ b/src/Bundle/ChillDocStoreBundle/Templating/WopiEditTwigExtensionRuntime.php @@ -1,18 +1,20 @@ false, + ]; + private const TEMPLATE = '@ChillDocStore/Button/wopi_edit_document.html.twig'; + private const TEMPLATE_BUTTON_GROUP = '@ChillDocStore/Button/button_group.html.twig'; + private DiscoveryInterface $discovery; - public function __construct(DiscoveryInterface $discovery) + private NormalizerInterface $normalizer; + + public function __construct(DiscoveryInterface $discovery, NormalizerInterface $normalizer) { $this->discovery = $discovery; + $this->normalizer = $normalizer; } + /** + * return true if the document is editable. + * + * **NOTE**: as the Vue button does have similar test, this is not required if in use with + * the dedicated Vue component (GroupDownloadButton.vue, WopiEditButton.vue) + */ public function isEditable(StoredObject $document): bool { return in_array($document->getType(), self::SUPPORTED_MIMES, true); } + /** + * @param array{small: boolean} $options + * + * @throws \Twig\Error\LoaderError + * @throws \Twig\Error\RuntimeError + * @throws \Twig\Error\SyntaxError + */ + public function renderButtonGroup(Environment $environment, StoredObject $document, ?string $title = null, bool $canEdit = true, array $options = []): string + { + return $environment->render(self::TEMPLATE_BUTTON_GROUP, [ + 'document' => $document, + 'document_json' => $this->normalizer->normalize($document, 'json', [AbstractNormalizer::GROUPS => ['read']]), + 'title' => $title, + 'can_edit' => $canEdit, + 'options' => array_merge(self::DEFAULT_OPTIONS_TEMPLATE_BUTTON_GROUP, $options), + ]); + } + public function renderEditButton(Environment $environment, StoredObject $document, ?array $options = null): string { return $environment->render(self::TEMPLATE, [ diff --git a/src/Bundle/ChillDocStoreBundle/Tests/StoredObjectManagerTest.php b/src/Bundle/ChillDocStoreBundle/Tests/StoredObjectManagerTest.php index 589515a05..354a301e8 100644 --- a/src/Bundle/ChillDocStoreBundle/Tests/StoredObjectManagerTest.php +++ b/src/Bundle/ChillDocStoreBundle/Tests/StoredObjectManagerTest.php @@ -1,14 +1,14 @@ getCurrentParticipations()->map(static function (AccompanyingPeriodParticipation $participation) { - return $participation->getPerson(); - })->toArray(); + $persons = $course->getCurrentParticipations()->map(static fn (AccompanyingPeriodParticipation $participation) => $participation->getPerson())->toArray(); } return [ @@ -65,6 +63,10 @@ class AccompanyingCourseDocumentWorkflowHandler implements EntityWorkflowHandler { $doc = $this->getRelatedEntity($entityWorkflow); + if (null === $doc) { + return $this->translator->trans('workflow.Document deleted'); + } + return $this->translator->trans('workflow.Document (n°%doc%)', ['%doc%' => $entityWorkflow->getRelatedEntityId()]) . ' - ' . $doc->getTitle(); } @@ -91,6 +93,16 @@ class AccompanyingCourseDocumentWorkflowHandler implements EntityWorkflowHandler return null; } + public function getSuggestedUsers(EntityWorkflow $entityWorkflow): array + { + $suggestedUsers = $entityWorkflow->getUsersInvolved(); + + $referrer = $this->getRelatedEntity($entityWorkflow)->getCourse()->getUser(); + $suggestedUsers[spl_object_hash($referrer)] = $referrer; + + return $suggestedUsers; + } + public function getTemplate(EntityWorkflow $entityWorkflow, array $options = []): string { return '@ChillDocStore/AccompanyingCourseDocument/_workflow.html.twig'; diff --git a/src/Bundle/ChillDocStoreBundle/chill.webpack.config.js b/src/Bundle/ChillDocStoreBundle/chill.webpack.config.js index c9b2b8877..3499fcf55 100644 --- a/src/Bundle/ChillDocStoreBundle/chill.webpack.config.js +++ b/src/Bundle/ChillDocStoreBundle/chill.webpack.config.js @@ -4,4 +4,5 @@ module.exports = function(encore) ChillDocStoreAssets: __dirname + '/Resources/public' }); encore.addEntry('mod_async_upload', __dirname + '/Resources/public/module/async_upload/index.js'); + encore.addEntry('mod_document_action_buttons_group', __dirname + '/Resources/public/module/document_action_buttons_group/index'); }; diff --git a/src/Bundle/ChillDocStoreBundle/migrations/Version20180605102533.php b/src/Bundle/ChillDocStoreBundle/migrations/Version20180605102533.php index 30e008d1f..917e82186 100644 --- a/src/Bundle/ChillDocStoreBundle/migrations/Version20180605102533.php +++ b/src/Bundle/ChillDocStoreBundle/migrations/Version20180605102533.php @@ -1,14 +1,14 @@ addSql('ALTER TABLE chill_doc.stored_object ADD generationTrialsCounter INT DEFAULT 0 NOT NULL;'); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_doc.stored_object DROP generationTrialsCounter'); + } +} diff --git a/src/Bundle/ChillDocStoreBundle/translations/messages.fr.yml b/src/Bundle/ChillDocStoreBundle/translations/messages.fr.yml index 17326b299..1d06c58e4 100644 --- a/src/Bundle/ChillDocStoreBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillDocStoreBundle/translations/messages.fr.yml @@ -14,11 +14,14 @@ Edit attributes: Modifier les propriétés du document Existing document: Document existant No document to download: Aucun document à télécharger 'Choose a document category': Choisissez une catégorie de document -Any document found: Aucun document trouvé +No document found: Aucun document trouvé The document is successfully registered: Le document est enregistré The document is successfully updated: Le document est mis à jour Any description: Aucune description +document: + Any title: Aucun titre + # delete Delete document ?: Supprimer le document ? Are you sure you want to remove this document ?: Êtes-vous sûr·e de vouloir supprimer ce document ? @@ -63,3 +66,14 @@ Create new DocumentCategory: Créer une nouvelle catégorie de document # WOPI EDIT online_edit_document: Éditer en ligne + +workflow: + Document deleted: Document supprimé + +# ROLES +accompanyingCourseDocument: Documents dans les parcours d'accompagnement +CHILL_ACCOMPANYING_COURSE_DOCUMENT_CREATE: Créer un document +CHILL_ACCOMPANYING_COURSE_DOCUMENT_DELETE: Supprimer un document +CHILL_ACCOMPANYING_COURSE_DOCUMENT_SEE: Voir les documents +CHILL_ACCOMPANYING_COURSE_DOCUMENT_SEE_DETAILS: Voir les détails d'un document +CHILL_ACCOMPANYING_COURSE_DOCUMENT_UPDATE: Modifier un document diff --git a/src/Bundle/ChillEventBundle/ChillEventBundle.php b/src/Bundle/ChillEventBundle/ChillEventBundle.php index 66438716e..d5a1b43cf 100644 --- a/src/Bundle/ChillEventBundle/ChillEventBundle.php +++ b/src/Bundle/ChillEventBundle/ChillEventBundle.php @@ -1,14 +1,14 @@ current($participations)->getEvent()->getId(), 'persons_ids' => implode(',', array_map( - static function (Participation $p) { - return $p->getPerson()->getId(); - }, + static fn (Participation $p) => $p->getPerson()->getId(), $participations )), ] @@ -649,19 +647,14 @@ class ParticipationController extends AbstractController // create a collection of person's id participating to the event /** @var \Doctrine\Common\Collections\ArrayCollection $peopleParticipating */ - $peopleParticipating = $peopleParticipating ?? - $participation->getEvent()->getParticipations()->map( - static function (Participation $p) { - return $p->getPerson()->getId(); - } - ); + $peopleParticipating ??= $participation->getEvent()->getParticipations()->map( + static fn (Participation $p) => $p->getPerson()->getId() + ); // check that the user is not already in the event if ($peopleParticipating->contains($participation->getPerson()->getId())) { $ignoredParticipations[] = $participation ->getEvent()->getParticipations()->filter( - static function (Participation $p) use ($participation) { - return $p->getPerson()->getId() === $participation->getPerson()->getId(); - } + static fn (Participation $p) => $p->getPerson()->getId() === $participation->getPerson()->getId() )->first(); } else { $newParticipations[] = $participation; diff --git a/src/Bundle/ChillEventBundle/Controller/RoleController.php b/src/Bundle/ChillEventBundle/Controller/RoleController.php index 8bf8affbd..9ffe0d2d0 100644 --- a/src/Bundle/ChillEventBundle/Controller/RoleController.php +++ b/src/Bundle/ChillEventBundle/Controller/RoleController.php @@ -1,14 +1,14 @@ setDate($this->faker->dateTimeBetween('-2 years', '+6 months')) - ->setName($this->faker->words(mt_rand(2, 4), true)) + ->setName($this->faker->words(random_int(2, 4), true)) ->setType($this->getReference(LoadEventTypes::$refs[array_rand(LoadEventTypes::$refs)])) ->setCenter($center) ->setCircle( @@ -75,7 +75,7 @@ class LoadParticipation extends AbstractFixture implements OrderedFixtureInterfa /** @var \Chill\PersonBundle\Entity\Person $person */ foreach ($people as $person) { - $nb = mt_rand(0, 3); + $nb = random_int(0, 3); for ($i = 0; $i < $nb; ++$i) { $event = $events[array_rand($events)]; diff --git a/src/Bundle/ChillEventBundle/DataFixtures/ORM/LoadRolesACL.php b/src/Bundle/ChillEventBundle/DataFixtures/ORM/LoadRolesACL.php index 8bd1a3513..26a3144bf 100644 --- a/src/Bundle/ChillEventBundle/DataFixtures/ORM/LoadRolesACL.php +++ b/src/Bundle/ChillEventBundle/DataFixtures/ORM/LoadRolesACL.php @@ -1,14 +1,14 @@ getRootNode('chill_event'); + $rootNode = $treeBuilder->getRootNode(); // Here you should define the parameters that are allowed to // configure your bundle. See the documentation linked above for diff --git a/src/Bundle/ChillEventBundle/Entity/Event.php b/src/Bundle/ChillEventBundle/Entity/Event.php index 3c90967f5..d55a7b8a8 100644 --- a/src/Bundle/ChillEventBundle/Entity/Event.php +++ b/src/Bundle/ChillEventBundle/Entity/Event.php @@ -1,14 +1,14 @@ participations->getIterator(); - $iterator->uasort(static function ($first, $second) { - return strnatcasecmp($first->getPerson()->getFirstName(), $second->getPerson()->getFirstName()); - }); + $iterator->uasort(static fn ($first, $second) => strnatcasecmp($first->getPerson()->getFirstName(), $second->getPerson()->getFirstName())); return $iterator; } diff --git a/src/Bundle/ChillEventBundle/Entity/EventType.php b/src/Bundle/ChillEventBundle/Entity/EventType.php index 2a2c932d9..e661a9839 100644 --- a/src/Bundle/ChillEventBundle/Entity/EventType.php +++ b/src/Bundle/ChillEventBundle/Entity/EventType.php @@ -1,14 +1,14 @@ getPerson(); - - break; - case 'role': return $this->getRole(); - - break; - case 'status': return $this->getStatus(); - - break; - case 'event': return $this->getEvent(); - - break; + default: + throw new \LogicException("this offset does not exists : " . $offset); } } @@ -238,28 +229,28 @@ class Participation implements ArrayAccess, HasCenterInterface, HasScopeInterfac * @param mixed $offset * @param mixed $value * - * @return Participation|void + * @return void */ - public function offsetSet($offset, $value) + public function offsetSet($offset, $value): void { switch ($offset) { case 'person': - return $this->setPerson($value); + $this->setPerson($value); break; case 'role': - return $this->setRole($value); + $this->setRole($value); break; case 'status': - return $this->setStatus($value); + $this->setStatus($value); break; case 'event': - return $this->setEvent($value); + $this->setEvent($value); break; } @@ -268,7 +259,7 @@ class Participation implements ArrayAccess, HasCenterInterface, HasScopeInterfac /** * @param mixed $offset */ - public function offsetUnset($offset) + public function offsetUnset($offset): void { $this->offsetSet($offset, null); } diff --git a/src/Bundle/ChillEventBundle/Entity/Role.php b/src/Bundle/ChillEventBundle/Entity/Role.php index 999116700..b508cda43 100644 --- a/src/Bundle/ChillEventBundle/Entity/Role.php +++ b/src/Bundle/ChillEventBundle/Entity/Role.php @@ -1,14 +1,14 @@ lazyLoadedEvents, - static function (Event $p) use ($value) { - return call_user_func($value, $p); - } + static fn (Event $p) => call_user_func($value, $p) ); } diff --git a/src/Bundle/ChillEventBundle/Form/EventType.php b/src/Bundle/ChillEventBundle/Form/EventType.php index 6fe0535b3..a72b90494 100644 --- a/src/Bundle/ChillEventBundle/Form/EventType.php +++ b/src/Bundle/ChillEventBundle/Form/EventType.php @@ -1,14 +1,14 @@ setDefaults([ - 'data_class' => 'Chill\EventBundle\Entity\Event', + 'data_class' => \Chill\EventBundle\Entity\Event::class, ]); $resolver ->setRequired(['center', 'role']) diff --git a/src/Bundle/ChillEventBundle/Form/EventTypeType.php b/src/Bundle/ChillEventBundle/Form/EventTypeType.php index dee3fea25..bab13c374 100644 --- a/src/Bundle/ChillEventBundle/Form/EventTypeType.php +++ b/src/Bundle/ChillEventBundle/Form/EventTypeType.php @@ -1,14 +1,14 @@ setDefaults([ - 'data_class' => 'Chill\EventBundle\Entity\EventType', + 'data_class' => \Chill\EventBundle\Entity\EventType::class, ]); } } diff --git a/src/Bundle/ChillEventBundle/Form/ParticipationType.php b/src/Bundle/ChillEventBundle/Form/ParticipationType.php index 70efcb7b9..998dc91a3 100644 --- a/src/Bundle/ChillEventBundle/Form/ParticipationType.php +++ b/src/Bundle/ChillEventBundle/Form/ParticipationType.php @@ -1,14 +1,14 @@ add('active') ->add('type', EntityType::class, [ 'class' => EventType::class, - 'choice_label' => function (EventType $e) { - return $this->translatableStringHelper->localize($e->getName()); - }, + 'choice_label' => fn (EventType $e) => $this->translatableStringHelper->localize($e->getName()), ]); } @@ -55,7 +53,7 @@ class RoleType extends AbstractType public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults([ - 'data_class' => 'Chill\EventBundle\Entity\Role', + 'data_class' => \Chill\EventBundle\Entity\Role::class, ]); } } diff --git a/src/Bundle/ChillEventBundle/Form/StatusType.php b/src/Bundle/ChillEventBundle/Form/StatusType.php index cea82049a..5801f3264 100644 --- a/src/Bundle/ChillEventBundle/Form/StatusType.php +++ b/src/Bundle/ChillEventBundle/Form/StatusType.php @@ -1,14 +1,14 @@ setDefaults([ - 'data_class' => 'Chill\EventBundle\Entity\Status', + 'data_class' => \Chill\EventBundle\Entity\Status::class, ]); } } diff --git a/src/Bundle/ChillEventBundle/Form/Type/PickEventType.php b/src/Bundle/ChillEventBundle/Form/Type/PickEventType.php index 68500e55e..05436b3e8 100644 --- a/src/Bundle/ChillEventBundle/Form/Type/PickEventType.php +++ b/src/Bundle/ChillEventBundle/Form/Type/PickEventType.php @@ -1,14 +1,14 @@ setDefaults([ 'class' => Event::class, - 'choice_label' => static function (Event $e) { - return $e->getDate()->format('d/m/Y, H:i') . ' → ' . - // $e->getType()->getName()['fr'] . ': ' . // display the type of event - $e->getName(); - }, + 'choice_label' => static fn (Event $e) => $e->getDate()->format('d/m/Y, H:i') . ' → ' . + // $e->getType()->getName()['fr'] . ': ' . // display the type of event + $e->getName(), 'placeholder' => 'Pick an event', 'attr' => ['class' => 'select2 '], - 'choice_attr' => static function (Event $e) { - return ['data-center' => $e->getCenter()->getId()]; - }, + 'choice_attr' => static fn (Event $e) => ['data-center' => $e->getCenter()->getId()], 'choiceloader' => function (Options $options) { $centers = $this->filterCenters($options); @@ -143,15 +139,13 @@ class PickEventType extends AbstractType // option role if (null === $options['role']) { $centers = array_map( - static function (GroupCenter $g) { - return $g->getCenter(); - }, + static fn (GroupCenter $g) => $g->getCenter(), $this->user->getGroupCenters()->toArray() ); } else { $centers = $this->authorizationHelper->getReachableCenters( $this->user, - (string) $options['role'] + (string) $options['role']->getRole() ); } @@ -173,9 +167,7 @@ class PickEventType extends AbstractType if ( !in_array($c->getId(), array_map( - static function (Center $c) { - return $c->getId(); - }, + static fn (Center $c) => $c->getId(), $centers ), true) ) { diff --git a/src/Bundle/ChillEventBundle/Form/Type/PickEventTypeType.php b/src/Bundle/ChillEventBundle/Form/Type/PickEventTypeType.php index bad43a349..d027c05cf 100644 --- a/src/Bundle/ChillEventBundle/Form/Type/PickEventTypeType.php +++ b/src/Bundle/ChillEventBundle/Form/Type/PickEventTypeType.php @@ -1,14 +1,14 @@ setDefaults( [ 'class' => EventType::class, - 'query_builder' => static function (EntityRepository $er) { - return $er->createQueryBuilder('et') - ->where('et.active = true'); - }, - 'choice_label' => static function (EventType $t) use ($helper) { - return $helper->localize($t->getName()); - }, - 'choice_attrs' => static function (EventType $t) { - return ['data-link-category' => $t->getId()]; - }, + 'query_builder' => static fn (EntityRepository $er) => $er->createQueryBuilder('et') + ->where('et.active = true'), + 'choice_label' => static fn (EventType $t) => $helper->localize($t->getName()), + 'choice_attrs' => static fn (EventType $t) => ['data-link-category' => $t->getId()], ] ); } diff --git a/src/Bundle/ChillEventBundle/Form/Type/PickRoleType.php b/src/Bundle/ChillEventBundle/Form/Type/PickRoleType.php index 928fd4180..8fd8ff7d7 100644 --- a/src/Bundle/ChillEventBundle/Form/Type/PickRoleType.php +++ b/src/Bundle/ChillEventBundle/Form/Type/PickRoleType.php @@ -1,14 +1,14 @@ getOptions(); $form->getParent()->add($name, $type, array_replace($options, [ - 'group_by' => function (Role $r) { - return $this->translatableStringHelper->localize($r->getType()->getName()); - }, + 'group_by' => fn (Role $r) => $this->translatableStringHelper->localize($r->getType()->getName()), ])); } } @@ -111,17 +109,13 @@ class PickRoleType extends AbstractType 'class' => Role::class, 'query_builder' => $qb, 'group_by' => null, - 'choice_attr' => static function (Role $r) { - return [ - 'data-event-type' => $r->getType()->getId(), - 'data-link-category' => $r->getType()->getId(), - ]; - }, - 'choice_label' => static function (Role $r) use ($translatableStringHelper, $translator) { - return $translatableStringHelper->localize($r->getName()) . - ($r->getActive() === true ? '' : - ' (' . $translator->trans('unactive') . ')'); - }, + 'choice_attr' => static fn (Role $r) => [ + 'data-event-type' => $r->getType()->getId(), + 'data-link-category' => $r->getType()->getId(), + ], + 'choice_label' => static fn (Role $r) => $translatableStringHelper->localize($r->getName()) . + ($r->getActive() === true ? '' : + ' (' . $translator->trans('unactive') . ')'), ]); } diff --git a/src/Bundle/ChillEventBundle/Form/Type/PickStatusType.php b/src/Bundle/ChillEventBundle/Form/Type/PickStatusType.php index a85f4e4c3..dcc3ba205 100644 --- a/src/Bundle/ChillEventBundle/Form/Type/PickStatusType.php +++ b/src/Bundle/ChillEventBundle/Form/Type/PickStatusType.php @@ -1,14 +1,14 @@ getType()->getName(); $options = $config->getOptions(); $form->getParent()->add($name, $type, array_replace($options, [ - 'group_by' => function (Status $s) { - return $this->translatableStringHelper->localize($s->getType()->getName()); - }, + 'group_by' => fn (Status $s) => $this->translatableStringHelper->localize($s->getType()->getName()), ])); } ); @@ -112,17 +110,13 @@ class PickStatusType extends AbstractType 'class' => Status::class, 'query_builder' => $qb, 'group_by' => null, - 'choice_attr' => static function (Status $s) { - return [ - 'data-event-type' => $s->getType()->getId(), - 'data-link-category' => $s->getType()->getId(), - ]; - }, - 'choice_label' => static function (Status $s) use ($translatableStringHelper, $translator) { - return $translatableStringHelper->localize($s->getName()) . - ($s->getActive() === true ? '' : - ' (' . $translator->trans('unactive') . ')'); - }, + 'choice_attr' => static fn (Status $s) => [ + 'data-event-type' => $s->getType()->getId(), + 'data-link-category' => $s->getType()->getId(), + ], + 'choice_label' => static fn (Status $s) => $translatableStringHelper->localize($s->getName()) . + ($s->getActive() === true ? '' : + ' (' . $translator->trans('unactive') . ')'), ]); } diff --git a/src/Bundle/ChillEventBundle/Menu/AdminMenuBuilder.php b/src/Bundle/ChillEventBundle/Menu/AdminMenuBuilder.php index cede2a5b8..9db1713b6 100644 --- a/src/Bundle/ChillEventBundle/Menu/AdminMenuBuilder.php +++ b/src/Bundle/ChillEventBundle/Menu/AdminMenuBuilder.php @@ -1,14 +1,14 @@ addChild('Events', [ 'route' => 'chill_event_admin_index', - ]) + ]) ->setAttribute('class', 'list-group-item-header') ->setExtras([ - 'order' => 6500 + 'order' => 6500, ]); $menu->addChild('Event type', [ diff --git a/src/Bundle/ChillEventBundle/Menu/PersonMenuBuilder.php b/src/Bundle/ChillEventBundle/Menu/PersonMenuBuilder.php index e16466b33..8abfc7cd6 100644 --- a/src/Bundle/ChillEventBundle/Menu/PersonMenuBuilder.php +++ b/src/Bundle/ChillEventBundle/Menu/PersonMenuBuilder.php @@ -1,14 +1,14 @@ {{ include('@ChillMain/Util/confirmation_template.html.twig', { @@ -15,6 +16,6 @@ 'form' : delete_form } ) }} - + {% endblock %} diff --git a/src/Bundle/ChillEventBundle/Resources/views/Event/edit.html.twig b/src/Bundle/ChillEventBundle/Resources/views/Event/edit.html.twig index 288c81edd..a528f1f5c 100644 --- a/src/Bundle/ChillEventBundle/Resources/views/Event/edit.html.twig +++ b/src/Bundle/ChillEventBundle/Resources/views/Event/edit.html.twig @@ -3,6 +3,7 @@ {% block title 'Event edit'|trans %} {% block event_content -%} +

      {{ 'Event edit'|trans }}

      {{ form_start(edit_form) }} @@ -28,6 +29,8 @@ {{ form_widget(edit_form.submit, { 'attr' : { 'class' : 'btn btn-update' } }) }}
    - + {{ form_end(edit_form) }} + + {% endblock %} diff --git a/src/Bundle/ChillEventBundle/Resources/views/Event/list.html.twig b/src/Bundle/ChillEventBundle/Resources/views/Event/list.html.twig index 6e77b6df8..d6f871125 100644 --- a/src/Bundle/ChillEventBundle/Resources/views/Event/list.html.twig +++ b/src/Bundle/ChillEventBundle/Resources/views/Event/list.html.twig @@ -2,46 +2,50 @@

    {% transchoice total with { '%pattern%' : pattern } %}%total% events match the search %pattern%{% endtranschoice %}

    + {% if events|length > 0 %}

    {{ 'Results %start%-%end% of %total%'|trans({ '%start%' : start, '%end%': start + events|length, '%total%' : total } ) }}

    - - +
    - - - - - - + + + + + + - {% for event in events %} - - - - - + + + + - - {% endfor %} + {% endif %} + + + + + {% endfor %}
    {{ 'Name'|trans }}{{ 'Date'|trans }}{{ 'Event type'|trans }} 
    {{ 'Name'|trans }}{{ 'Date'|trans }}{{ 'Event type'|trans }} 
    {{ event.name }}{{ event.date|format_date('long') }}{{ event.type.name|localize_translatable_string }} -
      -
    • - {# {% if is_granted('CHILL_EVENT_SEE_DETAILS', event) %} #} - - {{ 'See'|trans }} - - {# {% endif %} #} - {% if is_granted('CHILL_EVENT_UPDATE', event) %} + {% for event in events %} +
    {{ event.name }}{{ event.date|format_date('long') }}{{ event.type.name|localize_translatable_string }} + -
    - + {% endif %} {% if preview == false %} -{{ chill_pagination(paginator) }} + {{ chill_pagination(paginator) }} {% endif %} + diff --git a/src/Bundle/ChillEventBundle/Resources/views/Event/listByPerson.html.twig b/src/Bundle/ChillEventBundle/Resources/views/Event/listByPerson.html.twig index 53e97c3c4..ec9ebf76a 100644 --- a/src/Bundle/ChillEventBundle/Resources/views/Event/listByPerson.html.twig +++ b/src/Bundle/ChillEventBundle/Resources/views/Event/listByPerson.html.twig @@ -24,7 +24,7 @@ {% block content %}

    {{ 'Events participation' |trans }}

    - +
    @@ -44,59 +44,59 @@ {% endfor %} @@ -108,31 +108,17 @@ {{ chill_pagination(paginator) }} {% endif %} -
    +
    {{ form_start(form_add_event_participation_by_person) }} - {# - - #} - {{ form_widget(form_add_event_participation_by_person.event_id, { 'attr' : { 'class' : 'form-control' } } ) }} -
    - {# - - #} - {{ form_widget(form_add_event_participation_by_person.submit, { 'attr' : { 'class' : 'btn btn-success' } } ) }} + {{ form_widget(form_add_event_participation_by_person.event_id, { 'attr' : { + 'class' : 'custom-select', + 'style': 'min-width: 15em; max-width: 18em; display: inline-block;' + }}) }} +
    + {{ form_widget(form_add_event_participation_by_person.submit, { 'attr' : { 'class' : 'btn btn-sm btn-save' } } ) }}
    {{ form_rest(form_add_event_participation_by_person) }} {{ form_end(form_add_event_participation_by_person) }}
    -{# -{{ form(form_add_event_participation_by_person) }} -#} - -
    - -
    - -
    -
    - {% endblock %} diff --git a/src/Bundle/ChillEventBundle/Resources/views/Event/new.html.twig b/src/Bundle/ChillEventBundle/Resources/views/Event/new.html.twig index 44d24040b..f0d1b0ea2 100644 --- a/src/Bundle/ChillEventBundle/Resources/views/Event/new.html.twig +++ b/src/Bundle/ChillEventBundle/Resources/views/Event/new.html.twig @@ -3,6 +3,7 @@ {% block title 'Event creation'|trans %} {% block event_content -%} +

    {{ 'Event creation'|trans }}

    {{ form_start(form) }} @@ -26,4 +27,5 @@ {{ form_end(form) }} +
    {% endblock %} diff --git a/src/Bundle/ChillEventBundle/Resources/views/Event/newPickCenter.html.twig b/src/Bundle/ChillEventBundle/Resources/views/Event/newPickCenter.html.twig index c4fb4aacd..71c68002a 100644 --- a/src/Bundle/ChillEventBundle/Resources/views/Event/newPickCenter.html.twig +++ b/src/Bundle/ChillEventBundle/Resources/views/Event/newPickCenter.html.twig @@ -3,6 +3,7 @@ {% block title 'Event creation'|trans %} {% block event_content -%} +

    {{ 'Event creation'|trans }}

    {{ form_start(form) }} @@ -17,10 +18,10 @@
  • - {{ form_widget(form.submit, { 'attr' : { 'class' : 'btn btn-chill-green' } }) }} + {{ form_widget(form.submit, { 'attr' : { 'class' : 'btn btn-submit' } }) }}
  • - - {{ form_end(form) }} + {{ form_end(form) }} +
    {% endblock %} diff --git a/src/Bundle/ChillEventBundle/Resources/views/Event/show.html.twig b/src/Bundle/ChillEventBundle/Resources/views/Event/show.html.twig index 6af5dd80a..31eaf0df6 100644 --- a/src/Bundle/ChillEventBundle/Resources/views/Event/show.html.twig +++ b/src/Bundle/ChillEventBundle/Resources/views/Event/show.html.twig @@ -1,13 +1,14 @@ {% extends '@ChillEvent/layout.html.twig' %} {% block title 'Event : %label%'|trans({ '%label%' : event.name } ) %} - -{% import 'ChillPersonBundle:Person:macro.html.twig' as person_macro %} + +{% import 'ChillPersonBundle:Person:macro.html.twig' as person_macro %} {% block event_content -%} +

    {{ 'Details of an event'|trans }}

    -
    {{ 'Date'|trans }}{{ participation.role.name|localize_translatable_string }} {{ participation.status.name|localize_translatable_string }} -
      +
      {% set currentPath = path(app.request.attributes.get('_route'), app.request.attributes.get('_route_params')) %} {% set returnLabel = 'Back to %person% events'|trans({ '%person%' : currentPerson } ) %} {% if is_granted('CHILL_EVENT_SEE_DETAILS', participation.event) %} -
    • -
    • + class="btn btn-primary btn-sm" title="{{ 'See details of the event'|trans }}"> + + {% endif %} {% if is_granted('CHILL_EVENT_UPDATE', participation.event) and is_granted('CHILL_EVENT_PARTICIPATION_UPDATE', participation) %} -
    • -
    • + +
    + {% else %} -
  • {% if is_granted('CHILL_EVENT_UPDATE', participation.event) %} - - {{ 'Edit the event'|trans }} - + + {{ 'Edit the event'|trans }} + {% endif %} {% if is_granted('CHILL_EVENT_PARTICIPATION_UPDATE', participation) %} - - {{ 'Edit the participation'|trans }} - + + {{ 'Edit the participation'|trans }} + {% endif %} -
  • {% endif %} - +
    +
    @@ -32,7 +33,7 @@
    {{ 'Name'|trans }}
    - +
      {% set returnPath = app.request.get('return_path') %} @@ -63,13 +64,13 @@
    - +

    {{ 'Participations'|trans }}

    {% set count = event.participations|length %}

    {% transchoice count %}%count% participations to this event{% endtranschoice %}

    - + {% if count > 0 %} - +
    @@ -107,17 +108,17 @@ {% endfor %}
    {{ 'Person'|trans }}
    - - + + {% endif %} - + -
    +
    {{ form_start(form_add_participation_by_person) }}
    @@ -150,5 +151,5 @@
    {{ chill_delegated_block('block_footer_show', { 'event': event }) }}
    - +
    {% endblock %} diff --git a/src/Bundle/ChillEventBundle/Resources/views/Participation/confirm_delete.html.twig b/src/Bundle/ChillEventBundle/Resources/views/Participation/confirm_delete.html.twig index 9b023a915..0d993b075 100644 --- a/src/Bundle/ChillEventBundle/Resources/views/Participation/confirm_delete.html.twig +++ b/src/Bundle/ChillEventBundle/Resources/views/Participation/confirm_delete.html.twig @@ -5,7 +5,7 @@ {% block title 'Remove participation'|trans %} {% block event_content %} - +
    {{ include('@ChillMain/Util/confirmation_template.html.twig', { 'title' : 'Remove participation'|trans, @@ -15,6 +15,6 @@ 'form' : delete_form } ) }} - +
    {% endblock %} diff --git a/src/Bundle/ChillEventBundle/Resources/views/Participation/edit-multiple.html.twig b/src/Bundle/ChillEventBundle/Resources/views/Participation/edit-multiple.html.twig index a50615414..f0f522a3d 100644 --- a/src/Bundle/ChillEventBundle/Resources/views/Participation/edit-multiple.html.twig +++ b/src/Bundle/ChillEventBundle/Resources/views/Participation/edit-multiple.html.twig @@ -3,9 +3,10 @@ {% import 'ChillPersonBundle:Person:macro.html.twig' as person_macro %} {% block event_content -%} +

    {{ 'Participation Edit'|trans }}

    - +
    @@ -18,11 +19,11 @@
    {{ 'Associated event'|trans }}
    -

    {{ 'Participations'|trans }}

    +

    {{ 'Participations'|trans }}

    {{ form_start(form) }} - +
    @@ -59,4 +60,5 @@ {{ form_end(form) }} + {% endblock %} diff --git a/src/Bundle/ChillEventBundle/Resources/views/Participation/edit.html.twig b/src/Bundle/ChillEventBundle/Resources/views/Participation/edit.html.twig index 19592526e..db716b3dc 100644 --- a/src/Bundle/ChillEventBundle/Resources/views/Participation/edit.html.twig +++ b/src/Bundle/ChillEventBundle/Resources/views/Participation/edit.html.twig @@ -3,6 +3,7 @@ {% import 'ChillPersonBundle:Person:macro.html.twig' as person_macro %} {% block event_content -%} +

    {{ 'Participation Edit'|trans }}

    {{ 'Person'|trans }}
    @@ -42,4 +43,5 @@ {{ form_end(form) }} + {% endblock %} diff --git a/src/Bundle/ChillEventBundle/Resources/views/Participation/new-multiple.html.twig b/src/Bundle/ChillEventBundle/Resources/views/Participation/new-multiple.html.twig index a0551ff1a..224b7265f 100644 --- a/src/Bundle/ChillEventBundle/Resources/views/Participation/new-multiple.html.twig +++ b/src/Bundle/ChillEventBundle/Resources/views/Participation/new-multiple.html.twig @@ -18,7 +18,7 @@ {% block event_content -%}

    {{ 'Participation creation'|trans }}

    -
    +
    @@ -30,7 +30,7 @@ {% include 'ChillEventBundle:Participation:_ignored_participations.html.twig' with ignored_participations %} {{ form_start(form) }} -
    {{ 'Associated event'|trans }}
    +
    diff --git a/src/Bundle/ChillEventBundle/Resources/views/Participation/new.html.twig b/src/Bundle/ChillEventBundle/Resources/views/Participation/new.html.twig index 33f7bc213..3fbea8b68 100644 --- a/src/Bundle/ChillEventBundle/Resources/views/Participation/new.html.twig +++ b/src/Bundle/ChillEventBundle/Resources/views/Participation/new.html.twig @@ -5,6 +5,7 @@ {% block title 'Participation creation'|trans %} {% block event_content -%} +

    {{ 'Participation creation'|trans }}

    {{ 'Person'|trans }}
    @@ -43,5 +44,5 @@ {{ form_end(form) }} - + {% endblock %} diff --git a/src/Bundle/ChillEventBundle/Resources/views/layout.html.twig b/src/Bundle/ChillEventBundle/Resources/views/layout.html.twig index 45eb6e481..e34a10e18 100644 --- a/src/Bundle/ChillEventBundle/Resources/views/layout.html.twig +++ b/src/Bundle/ChillEventBundle/Resources/views/layout.html.twig @@ -16,9 +16,9 @@ * along with this program. If not, see . #} -{% extends "@ChillMain/layoutWithVerticalMenu.html.twig" %} +{% extends "@ChillMain/layout.html.twig" %} -{% block layout_wvm_content %} +{% block content %} {% block event_content %}

    {{ 'Event' |trans }}

    {% endblock %} diff --git a/src/Bundle/ChillEventBundle/Search/EventSearch.php b/src/Bundle/ChillEventBundle/Search/EventSearch.php index 9b2052305..6bb3d435d 100644 --- a/src/Bundle/ChillEventBundle/Search/EventSearch.php +++ b/src/Bundle/ChillEventBundle/Search/EventSearch.php @@ -1,24 +1,25 @@ user = $tokenStorage->getToken()->getUser(); + $this->security = $security; $this->er = $eventRepository; - $this->helper = $authorizationHelper; + $this->authorizationHelper = $authorizationHelper; $this->templating = $templating; $this->paginationFactory = $paginatorFactory; } @@ -101,7 +99,7 @@ class EventSearch extends AbstractSearch if ('html' === $format) { return $this->templating->render( - 'ChillEventBundle:Event:list.html.twig', + '@ChillEvent/Event/list.html.twig', [ 'events' => $this->search($terms, $start, $limit, $options), 'pattern' => $this->recomposePattern($terms, $this->getAvailableTerms(), $terms['_domain']), @@ -113,27 +111,23 @@ class EventSearch extends AbstractSearch ] ); } + // format is "json" + $results = []; + $search = $this->search($terms, $start, $limit, $options); - if ('json' === $format) { - $results = []; - $search = $this->search($terms, $start, $limit, $options); - - foreach ($search as $item) { - $results[] = [ - 'id' => $item->getId(), - 'text' => $item->getDate()->format('d/m/Y, H:i') . ' → ' . - // $item->getType()->getName()['fr'] . ': ' . // display the type of event - $item->getName(), - ]; - } - - return [ - 'results' => $results, - 'pagination' => [ - 'more' => $paginator->hasNextPage(), - ], + foreach ($search as $item) { + $results[] = [ + 'id' => $item->getId(), + 'text' => $item->getDate()->format('d/m/Y, H:i') . ' → ' . + // $item->getType()->getName()['fr'] . ': ' . // display the type of event + $item->getName(), ]; } + + return [ + 'results' => $results, + 'more' => $paginator->hasNextPage(), + ]; } public function supports($domain, $format) @@ -144,8 +138,10 @@ class EventSearch extends AbstractSearch protected function composeQuery(QueryBuilder &$qb, $terms) { // add security clauses - $reachableCenters = $this->helper - ->getReachableCenters($this->user, 'CHILL_EVENT_SEE'); + $reachableCenters = $this->authorizationHelper->getReachableCenters( + $this->security->getUser(), + 'CHILL_EVENT_SEE' + ); if (count($reachableCenters) === 0) { // add a clause to block all events @@ -156,8 +152,9 @@ class EventSearch extends AbstractSearch $orWhere = $qb->expr()->orX(); foreach ($reachableCenters as $center) { - $circles = $this->helper->getReachableScopes( - $this->user, + $n = $n+1; + $circles = $this->authorizationHelper->getReachableScopes( + $this->security->getUser(), 'CHILL_EVENT_SEE', $center ); @@ -174,8 +171,8 @@ class EventSearch extends AbstractSearch } if ( - (isset($terms['name']) || isset($terms['_default'])) - && (!empty($terms['name']) || !empty($terms['_default'])) + (isset($terms['name']) || isset($terms['_default'])) + && (!empty($terms['name']) || !empty($terms['_default'])) ) { // the form with name:"xyz" has precedence $name = $terms['name'] ?? $terms['_default']; diff --git a/src/Bundle/ChillEventBundle/Security/Authorization/EventVoter.php b/src/Bundle/ChillEventBundle/Security/Authorization/EventVoter.php index 9d7c88500..ade1b48de 100644 --- a/src/Bundle/ChillEventBundle/Security/Authorization/EventVoter.php +++ b/src/Bundle/ChillEventBundle/Security/Authorization/EventVoter.php @@ -1,14 +1,14 @@ personsIdsCache = array_merge( $this->personsIdsCache, $event->getParticipations()->map( - static function ($p) { return $p->getPerson()->getId(); } + static fn ($p) => $p->getPerson()->getId() ) ->toArray() ); @@ -303,7 +303,7 @@ final class ParticipationControllerTest extends WebTestCase $event = $this->getRandomEventWithMultipleParticipations(); $persons_id = implode(',', $event->getParticipations()->map( - static function ($p) { return $p->getPerson()->getId(); } + static fn ($p) => $p->getPerson()->getId() )->toArray()); $crawler = $this->client->request( @@ -329,7 +329,7 @@ final class ParticipationControllerTest extends WebTestCase $nbParticipations = $event->getParticipations()->count(); // get the persons_id participating on this event $persons_id = $event->getParticipations()->map( - static function ($p) { return $p->getPerson()->getId(); } + static fn ($p) => $p->getPerson()->getId() )->toArray(); // exclude the existing persons_ids from the new person $this->personsIdsCache = array_merge($this->personsIdsCache, $persons_id); @@ -458,9 +458,7 @@ final class ParticipationControllerTest extends WebTestCase $circles = $this->em->getRepository(\Chill\MainBundle\Entity\Scope::class) ->findAll(); - array_filter($circles, static function ($circle) use ($circleName) { - return in_array($circleName, $circle->getName(), true); - }); + array_filter($circles, static fn ($circle) => in_array($circleName, $circle->getName(), true)); $circle = $circles[0]; $events = $this->em->getRepository(\Chill\EventBundle\Entity\Event::class) diff --git a/src/Bundle/ChillEventBundle/Tests/Controller/RoleControllerTest.php b/src/Bundle/ChillEventBundle/Tests/Controller/RoleControllerTest.php index d720f7e7a..937bf8e49 100644 --- a/src/Bundle/ChillEventBundle/Tests/Controller/RoleControllerTest.php +++ b/src/Bundle/ChillEventBundle/Tests/Controller/RoleControllerTest.php @@ -1,14 +1,14 @@ getId(); - }, + static fn (Scope $scope) => $scope->getId(), $this->helper->getReachableCircles($this->user, $role, $person->getCenter()) ); $centerAndScopeLines[] = sprintf( diff --git a/src/Bundle/ChillEventBundle/config/services/repositories.yaml b/src/Bundle/ChillEventBundle/config/services/repositories.yaml index 4d627f625..ff20d094e 100644 --- a/src/Bundle/ChillEventBundle/config/services/repositories.yaml +++ b/src/Bundle/ChillEventBundle/config/services/repositories.yaml @@ -17,7 +17,7 @@ services: factory: ['@doctrine.orm.entity_manager', getRepository] arguments: - 'Chill\EventBundle\Entity\Role' - + chill_event.repository.status: class: Doctrine\ORM\EntityRepository factory: ['@doctrine.orm.entity_manager', getRepository] diff --git a/src/Bundle/ChillEventBundle/config/services/search.yaml b/src/Bundle/ChillEventBundle/config/services/search.yaml index 2df3db26f..5c55fc388 100644 --- a/src/Bundle/ChillEventBundle/config/services/search.yaml +++ b/src/Bundle/ChillEventBundle/config/services/search.yaml @@ -1,12 +1,11 @@ services: - chill_event.search_events: - class: Chill\EventBundle\Search\EventSearch + Chill\EventBundle\Search\EventSearch: arguments: - - "@security.token_storage" - - "@chill_event.repository.event" - - "@chill.main.security.authorization.helper" - - "@templating" - - "@chill_main.paginator_factory" + $security: '@Symfony\Component\Security\Core\Security' + $eventRepository: "@chill_event.repository.event" + $authorizationHelper: "@chill.main.security.authorization.helper" + $templating: "@templating" + $paginatorFactory: "@chill_main.paginator_factory" tags: - { name: chill.search, alias: 'event_regular' } - + diff --git a/src/Bundle/ChillEventBundle/migrations/Version20160318111334.php b/src/Bundle/ChillEventBundle/migrations/Version20160318111334.php index 241003abf..ab7669cc2 100644 --- a/src/Bundle/ChillEventBundle/migrations/Version20160318111334.php +++ b/src/Bundle/ChillEventBundle/migrations/Version20160318111334.php @@ -1,14 +1,14 @@ getRootNode('chill_amli_family_members'); + $rootNode = $treeBuilder->getRootNode(); $rootNode ->children() diff --git a/src/Bundle/ChillFamilyMembersBundle/Entity/AbstractFamilyMember.php b/src/Bundle/ChillFamilyMembersBundle/Entity/AbstractFamilyMember.php index 5a9b52e5e..2d8ff8040 100644 --- a/src/Bundle/ChillFamilyMembersBundle/Entity/AbstractFamilyMember.php +++ b/src/Bundle/ChillFamilyMembersBundle/Entity/AbstractFamilyMember.php @@ -1,14 +1,14 @@ getActionConfig($action); + + if (null !== $actionConfig['roles'][$request->getMethod()]) { + return $actionConfig['roles'][$request->getMethod()]; + } + + if ($this->crudConfig['base_role']) { + return $this->crudConfig['base_role']; + } + + throw new \RuntimeException(sprintf('the config does not have any role for the ' . + 'method %s nor a global role for the whole action. Add those to your ' . + 'configuration or override the required method', $request->getMethod())); + } + public static function getSubscribedServices(): array { return array_merge( diff --git a/src/Bundle/ChillMainBundle/CRUD/Controller/ApiController.php b/src/Bundle/ChillMainBundle/CRUD/Controller/ApiController.php index 9bf43b37e..645f7525c 100644 --- a/src/Bundle/ChillMainBundle/CRUD/Controller/ApiController.php +++ b/src/Bundle/ChillMainBundle/CRUD/Controller/ApiController.php @@ -1,14 +1,14 @@ getSerializer()->deserialize($request->getContent(), $postedDataType, $_format, $postedDataContext); } catch (\Symfony\Component\Serializer\Exception\UnexpectedValueException $e) { throw new BadRequestHttpException(sprintf('Unable to deserialize posted ' . - 'data: %s', $e->getMessage()), 0, $e); + 'data: %s', $e->getMessage()), $e, 0); } switch ($request->getMethod()) { case Request::METHOD_DELETE: // oups... how to use property accessor to remove element ? + /* @phpstan-ignore-next-line as we do not find a simpler way to do this */ $entity->{'remove' . ucfirst($property)}($postedData); break; case Request::METHOD_POST: + /* @phpstan-ignore-next-line as we do not find a simpler way to do this */ $entity->{'add' . ucfirst($property)}($postedData); break; @@ -499,28 +501,6 @@ class ApiController extends AbstractCRUDController return ['groups' => ['read']]; } - /** - * get the role given from the config. - * - * @param mixed $entity - * @param mixed $_format - */ - protected function getRoleFor(string $action, Request $request, $entity, $_format): string - { - $actionConfig = $this->getActionConfig($action); - - if (null !== $actionConfig['roles'][$request->getMethod()]) { - return $actionConfig['roles'][$request->getMethod()]; - } - - if ($this->crudConfig['base_role']) { - return $this->crudConfig['base_role']; - } - - throw new RuntimeException(sprintf('the config does not have any role for the ' . - 'method %s nor a global role for the whole action. Add those to your ' . - 'configuration or override the required method', $request->getMethod())); - } protected function getSerializer(): SerializerInterface { diff --git a/src/Bundle/ChillMainBundle/CRUD/Controller/CRUDController.php b/src/Bundle/ChillMainBundle/CRUD/Controller/CRUDController.php index edca07b41..730ac32c1 100644 --- a/src/Bundle/ChillMainBundle/CRUD/Controller/CRUDController.php +++ b/src/Bundle/ChillMainBundle/CRUD/Controller/CRUDController.php @@ -1,14 +1,14 @@ getFormClassFor($action); + $formClass ??= $this->getFormClassFor($action); $form = $this->createForm($formClass, $entity, $formOptions); diff --git a/src/Bundle/ChillMainBundle/CRUD/Form/CRUDDeleteEntityForm.php b/src/Bundle/ChillMainBundle/CRUD/Form/CRUDDeleteEntityForm.php index 00cb7bfc4..3ff3cf134 100644 --- a/src/Bundle/ChillMainBundle/CRUD/Form/CRUDDeleteEntityForm.php +++ b/src/Bundle/ChillMainBundle/CRUD/Form/CRUDDeleteEntityForm.php @@ -1,14 +1,14 @@ $value, ARRAY_FILTER_USE_BOTH )); diff --git a/src/Bundle/ChillMainBundle/CRUD/Templating/TwigCRUDResolver.php b/src/Bundle/ChillMainBundle/CRUD/Templating/TwigCRUDResolver.php index 8704a79ca..afa3eab30 100644 --- a/src/Bundle/ChillMainBundle/CRUD/Templating/TwigCRUDResolver.php +++ b/src/Bundle/ChillMainBundle/CRUD/Templating/TwigCRUDResolver.php @@ -1,14 +1,14 @@ addTag('chill.count_notification.user'); $container->registerForAutoconfiguration(EntityWorkflowHandlerInterface::class) ->addTag('chill_main.workflow_handler'); + $container->registerForAutoconfiguration(CronJobInterface::class) + ->addTag('chill_main.cron_job'); + $container->registerForAutoconfiguration(ViewEntityInfoProviderInterface::class) + ->addTag('chill_main.entity_info_provider'); $container->addCompilerPass(new SearchableServicesCompilerPass()); $container->addCompilerPass(new ConfigConsistencyCompilerPass()); @@ -68,7 +75,7 @@ class ChillMainBundle extends Bundle $container->addCompilerPass(new NotificationCounterCompilerPass()); $container->addCompilerPass(new MenuCompilerPass()); $container->addCompilerPass(new ACLFlagsCompilerPass()); - $container->addCompilerPass(new GroupingCenterCompilerPass()); $container->addCompilerPass(new CRUDControllerCompilerPass()); + $container->addCompilerPass(new ShortMessageCompilerPass()); } } diff --git a/src/Bundle/ChillMainBundle/Command/ChillImportUsersCommand.php b/src/Bundle/ChillMainBundle/Command/ChillImportUsersCommand.php index 383fd501d..e6f49c6b5 100644 --- a/src/Bundle/ChillMainBundle/Command/ChillImportUsersCommand.php +++ b/src/Bundle/ChillMainBundle/Command/ChillImportUsersCommand.php @@ -1,14 +1,14 @@ cronManager = $cronManager; + } + + protected function configure() + { + $this + ->setDescription('Execute the cronjob(s) given as argument, or one cronjob scheduled by system.') + ->setHelp("If no job is specified, the next available cronjob will be executed by system.\nThis command should be execute every 15 minutes (more or less)") + ->addArgument('job', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'one or more job to force execute (by default, all jobs are executed)', []) + ->addUsage(''); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + if ([] === $input->getArgument('job')) { + $this->cronManager->run(); + + return 0; + } + + foreach ($input->getArgument('job') as $jobName) { + $this->cronManager->run($jobName); + } + + return 0; + } +} diff --git a/src/Bundle/ChillMainBundle/Command/LoadAddressesBEFromBestAddressCommand.php b/src/Bundle/ChillMainBundle/Command/LoadAddressesBEFromBestAddressCommand.php new file mode 100644 index 000000000..cc26b7687 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Command/LoadAddressesBEFromBestAddressCommand.php @@ -0,0 +1,53 @@ +addressImporter = $addressImporter; + $this->postalCodeBEFromBestAddressImporter = $postalCodeBEFromBestAddressImporter; + } + + protected function configure() + { + $this + ->setName('chill:main:address-ref-from-best-addresses') + ->addArgument('lang', InputArgument::REQUIRED, "Language code, for example 'fr'") + ->addArgument('list', InputArgument::IS_ARRAY, "The list to add, for example 'full', or 'extract' (dev) or '1xxx' (brussel CP)") + ->setDescription('Import BE addresses from BeST Address (see https://osoc19.github.io/best/)'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $this->postalCodeBEFromBestAddressImporter->import(); + + $this->addressImporter->import($input->getArgument('lang'), $input->getArgument('list')); + + return 0; + } +} diff --git a/src/Bundle/ChillMainBundle/Command/LoadAddressesFRFromBANOCommand.php b/src/Bundle/ChillMainBundle/Command/LoadAddressesFRFromBANOCommand.php new file mode 100644 index 000000000..40772d52b --- /dev/null +++ b/src/Bundle/ChillMainBundle/Command/LoadAddressesFRFromBANOCommand.php @@ -0,0 +1,47 @@ +addressReferenceFromBano = $addressReferenceFromBano; + } + + protected function configure() + { + $this->setName('chill:main:address-ref-from-bano') + ->addArgument('departementNo', InputArgument::REQUIRED | InputArgument::IS_ARRAY, 'a list of departement numbers') + ->setDescription('Import FR addresses from bano (see https://bano.openstreetmap.fr'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + foreach ($input->getArgument('departementNo') as $departementNo) { + $output->writeln('Import addresses for ' . $departementNo); + + $this->addressReferenceFromBano->import($departementNo); + } + + return 0; + } +} diff --git a/src/Bundle/ChillMainBundle/Command/LoadAndUpdateLanguagesCommand.php b/src/Bundle/ChillMainBundle/Command/LoadAndUpdateLanguagesCommand.php index a32fd5d97..8c5351b07 100644 --- a/src/Bundle/ChillMainBundle/Command/LoadAndUpdateLanguagesCommand.php +++ b/src/Bundle/ChillMainBundle/Command/LoadAndUpdateLanguagesCommand.php @@ -1,14 +1,14 @@ loader = $loader; + + parent::__construct(); + } + + public function configure(): void + { + $this->setName('chill:main:postal-code:load:FR') + ->setDescription('Load France\'s postal code from online open data'); + } + + public function execute(InputInterface $input, OutputInterface $output): int + { + $this->loader->import(); + + return 0; + } +} diff --git a/src/Bundle/ChillMainBundle/Command/LoadPostalCodesCommand.php b/src/Bundle/ChillMainBundle/Command/LoadPostalCodesCommand.php index bc4616b7b..ea42fde78 100644 --- a/src/Bundle/ChillMainBundle/Command/LoadPostalCodesCommand.php +++ b/src/Bundle/ChillMainBundle/Command/LoadPostalCodesCommand.php @@ -1,14 +1,14 @@ addPostalCode($row, $output); ++$num; - } catch (ExistingPostalCodeException $ex) { - $output->writeln(' on line ' . $line . ' : ' . $ex->getMessage() . ''); - } catch (CountryCodeNotFoundException $ex) { - $output->writeln(' on line ' . $line . ' : ' . $ex->getMessage() . ''); - } catch (PostalCodeNotValidException $ex) { + } catch (ExistingPostalCodeException|CountryCodeNotFoundException|PostalCodeNotValidException $ex) { $output->writeln(' on line ' . $line . ' : ' . $ex->getMessage() . ''); } ++$line; diff --git a/src/Bundle/ChillMainBundle/Command/SetPasswordCommand.php b/src/Bundle/ChillMainBundle/Command/SetPasswordCommand.php index 07e514e6d..1a570c3a9 100644 --- a/src/Bundle/ChillMainBundle/Command/SetPasswordCommand.php +++ b/src/Bundle/ChillMainBundle/Command/SetPasswordCommand.php @@ -1,14 +1,14 @@ setDescription('Update or create sql views which provide info for various entities'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $this->viewEntityInfoManager->synchronizeOnDB(); + + return 0; + } + +} diff --git a/src/Bundle/ChillMainBundle/Controller/AbsenceController.php b/src/Bundle/ChillMainBundle/Controller/AbsenceController.php new file mode 100644 index 000000000..5dafa4615 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Controller/AbsenceController.php @@ -0,0 +1,65 @@ +getUser(); + $form = $this->createForm(AbsenceType::class, $user); + + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $em = $this->getDoctrine()->getManager(); + $em->flush(); + + return $this->redirect($this->generateUrl('chill_main_user_absence_index')); + } + + return $this->render('@ChillMain/Menu/absence.html.twig', [ + 'user' => $user, + 'form' => $form->createView(), + ]); + } + + /** + * @Route( + * "/{_locale}/absence/unset", + * name="chill_main_user_absence_unset", + * methods={"GET", "POST"} + * ) + */ + public function unsetAbsence(Request $request) + { + $user = $this->getUser(); + + $user->setAbsenceStart(null); + $em = $this->getDoctrine()->getManager(); + $em->flush(); + + return $this->redirect($this->generateUrl('chill_main_user_absence_index')); + } +} diff --git a/src/Bundle/ChillMainBundle/Controller/AddressApiController.php b/src/Bundle/ChillMainBundle/Controller/AddressApiController.php index db87874e2..6bc952923 100644 --- a/src/Bundle/ChillMainBundle/Controller/AddressApiController.php +++ b/src/Bundle/ChillMainBundle/Controller/AddressApiController.php @@ -1,14 +1,14 @@ security = $security; + $this->entityManager = $entityManager; + $this->serializer = $serializer; + } + + /** + * @Route("/api/1.0/main/address/reference-match/{id}/set/reviewed", methods={"POST"}) + */ + public function markAddressAsReviewed(Address $address): JsonResponse + { + if (!$this->security->isGranted('ROLE_USER')) { + throw new AccessDeniedHttpException(); + } + + $address->setRefStatus(Address::ADDR_REFERENCE_STATUS_REVIEWED); + + $this->entityManager->flush(); + + return new JsonResponse( + $this->serializer->serialize($address, 'json', [AbstractNormalizer::GROUPS => ['read']]), + JsonResponse::HTTP_OK, + [], + true + ); + } + + /** + * Set an address back to "to review". Only if the address is in "reviewed" state. + * + * @Route("/api/1.0/main/address/reference-match/{id}/set/to_review", methods={"POST"}) + */ + public function markAddressAsToReview(Address $address): JsonResponse + { + if (!$this->security->isGranted('ROLE_USER')) { + throw new AccessDeniedHttpException(); + } + + if (Address::ADDR_REFERENCE_STATUS_REVIEWED !== $address->getRefStatus()) { + throw new AccessDeniedHttpException("forbidden to mark a matching address to 'to review'"); + } + + $address->setRefStatus(Address::ADDR_REFERENCE_STATUS_TO_REVIEW); + + $this->entityManager->flush(); + + return new JsonResponse( + $this->serializer->serialize($address, 'json', [AbstractNormalizer::GROUPS => ['read']]), + JsonResponse::HTTP_OK, + [], + true + ); + } + + /** + * @Route("/api/1.0/main/address/reference-match/{id}/sync-with-reference", methods={"POST"}) + */ + public function syncAddressWithReference(Address $address): JsonResponse + { + if (null === $address->getAddressReference()) { + throw new BadRequestHttpException('this address does not have any address reference'); + } + + $address->syncWithReference($address->getAddressReference()); + + $this->entityManager->flush(); + + return new JsonResponse( + $this->serializer->serialize($address, 'json', [AbstractNormalizer::GROUPS => ['read']]), + JsonResponse::HTTP_OK, + [], + true + ); + } +} diff --git a/src/Bundle/ChillMainBundle/Controller/AdminController.php b/src/Bundle/ChillMainBundle/Controller/AdminController.php index 56eac11e4..7d3826823 100644 --- a/src/Bundle/ChillMainBundle/Controller/AdminController.php +++ b/src/Bundle/ChillMainBundle/Controller/AdminController.php @@ -1,14 +1,14 @@ entityManager = $entityManager; $this->redis = $chillRedis; $this->exportManager = $exportManager; $this->formFactory = $formFactory; @@ -86,6 +101,9 @@ class ExportController extends AbstractController { /** @var \Chill\MainBundle\Export\ExportManager $exportManager */ $exportManager = $this->exportManager; + + $export = $exportManager->getExport($alias); + $key = $request->query->get('key', null); [$dataCenters, $dataExport, $dataFormatter] = $this->rebuildData($key); @@ -100,7 +118,8 @@ class ExportController extends AbstractController $viewVariables = [ 'alias' => $alias, - 'export' => $exportManager->getExport($alias), + 'export' => $export, + 'export_group' => $this->getExportGroup($export), ]; if ($formater instanceof \Chill\MainBundle\Export\Formatter\CSVListFormatter) { @@ -137,11 +156,32 @@ class ExportController extends AbstractController } /** - * Render the list of available exports. + * @Route("/{_locale}/exports/generate-from-saved/{id}", name="chill_main_export_generate_from_saved") * - * @return \Symfony\Component\HttpFoundation\Response + * @throws RedisException */ - public function indexAction(Request $request) + public function generateFromSavedExport(SavedExport $savedExport): RedirectResponse + { + $this->denyAccessUnlessGranted(SavedExportVoter::GENERATE, $savedExport); + + $key = md5(uniqid((string) random_int(0, mt_getrandmax()), false)); + + $this->redis->setEx($key, 3600, serialize($savedExport->getOptions())); + + return $this->redirectToRoute( + 'chill_main_export_download', + [ + 'alias' => $savedExport->getExportAlias(), + 'key' => $key, 'prevent_save' => true, + 'returnPath' => $this->generateUrl('chill_main_export_saved_list_my'), + ] + ); + } + + /** + * Render the list of available exports. + */ + public function indexAction(): Response { $exportManager = $this->exportManager; @@ -188,33 +228,65 @@ class ExportController extends AbstractController case 'export': return $this->exportFormStep($request, $export, $alias); - break; - case 'formatter': return $this->formatterFormStep($request, $export, $alias); - break; - case 'generate': return $this->forwardToGenerate($request, $export, $alias); - break; - default: throw $this->createNotFoundException("The given step '{$step}' is invalid"); } } + /** + * @Route("/{_locale}/export/save-from-key/{alias}/{key}", name="chill_main_export_save_from_key") + */ + public function saveFromKey(string $alias, string $key, Request $request): Response + { + $this->denyAccessUnlessGranted('ROLE_USER'); + $user = $this->getUser(); + + if (!$user instanceof User) { + throw new AccessDeniedHttpException(); + } + + $data = $this->rebuildRawData($key); + + $savedExport = new SavedExport(); + $savedExport + ->setOptions($data) + ->setExportAlias($alias) + ->setUser($user); + + $form = $this->createForm(SavedExportType::class, $savedExport); + + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $this->entityManager->persist($savedExport); + $this->entityManager->flush(); + + return $this->redirectToRoute('chill_main_export_index'); + } + + return $this->render( + '@ChillMain/SavedExport/new.html.twig', + [ + 'form' => $form->createView(), + 'saved_export' => $savedExport, + ] + ); + } + /** * create a form to show on different steps. * * @param string $alias * @param array $data the data from previous step. Required for steps 'formatter' and 'generate_formatter' * @param mixed $step - * - * @return \Symfony\Component\Form\Form */ - protected function createCreateFormExport($alias, $step, $data = []) + protected function createCreateFormExport($alias, $step, $data = []): FormInterface { /** @var \Chill\MainBundle\Export\ExportManager $exportManager */ $exportManager = $this->exportManager; @@ -226,6 +298,8 @@ class ExportController extends AbstractController 'csrf_protection' => $isGenerate ? false : true, ]); + // TODO: add a condition to be able to select a regroupment of centers? + if ('centers' === $step || 'generate_centers' === $step) { $builder->add('centers', PickCenterType::class, [ 'export_alias' => $alias, @@ -316,6 +390,7 @@ class ExportController extends AbstractController 'form' => $form->createView(), 'export_alias' => $alias, 'export' => $export, + 'export_group' => $this->getExportGroup($export), ]); } @@ -371,6 +446,7 @@ class ExportController extends AbstractController [ 'form' => $form->createView(), 'export' => $export, + 'export_group' => $this->getExportGroup($export), ] ); } @@ -405,7 +481,7 @@ class ExportController extends AbstractController 'alias' => $alias, ]; unset($parameters['_token']); - $key = md5(uniqid((string) mt_rand(), false)); + $key = md5(uniqid((string) random_int(0, mt_getrandmax()), false)); $this->redis->setEx($key, 3600, serialize($parameters)); @@ -420,23 +496,8 @@ class ExportController extends AbstractController protected function rebuildData($key) { - if (null === $key) { - throw $this->createNotFoundException('key does not exists'); - } + $rawData = $this->rebuildRawData($key); - if ($this->redis->exists($key) !== 1) { - $this->addFlash('error', $this->translator->trans('This report is not available any more')); - - throw $this->createNotFoundException('key does not exists'); - } - - $serialized = $this->redis->get($key); - - if (false === $serialized) { - throw new LogicException('the key could not be reached from redis'); - } - - $rawData = unserialize($serialized); $alias = $rawData['alias']; $formCenters = $this->createCreateFormExport($alias, 'generate_centers'); @@ -464,8 +525,6 @@ class ExportController extends AbstractController * @param \Chill\MainBundle\Export\DirectExportInterface|\Chill\MainBundle\Export\ExportInterface $export * @param string $alias * - * @throws type - * * @return Response */ protected function selectCentersStep(Request $request, $export, $alias) @@ -514,10 +573,28 @@ class ExportController extends AbstractController [ 'form' => $form->createView(), 'export' => $export, + 'export_group' => $this->getExportGroup($export), ] ); } + private function getExportGroup($target): string + { + $exportManager = $this->exportManager; + + $groups = $exportManager->getExportsGrouped(true); + + foreach ($groups as $group => $array) { + foreach ($array as $alias => $export) { + if ($export === $target) { + return $group; + } + } + } + + return ''; + } + /** * get the next step. If $reverse === true, the previous step is returned. * @@ -565,4 +642,32 @@ class ExportController extends AbstractController throw new LogicException("the step {$step} is not defined."); } } + + private function rebuildRawData(string $key): array + { + if (null === $key) { + throw $this->createNotFoundException('key does not exists'); + } + + if ($this->redis->exists($key) !== 1) { + $this->addFlash('error', $this->translator->trans('This report is not available any more')); + + throw $this->createNotFoundException('key does not exists'); + } + + $serialized = $this->redis->get($key); + + if (false === $serialized) { + throw new LogicException('the key could not be reached from redis'); + } + + $rawData = unserialize($serialized); + + $this->logger->notice('[export] choices for an export unserialized', [ + 'key' => $key, + 'rawData' => json_encode($rawData, JSON_THROW_ON_ERROR), + ]); + + return $rawData; + } } diff --git a/src/Bundle/ChillMainBundle/Controller/GeographicalUnitByAddressApiController.php b/src/Bundle/ChillMainBundle/Controller/GeographicalUnitByAddressApiController.php new file mode 100644 index 000000000..988439de5 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Controller/GeographicalUnitByAddressApiController.php @@ -0,0 +1,75 @@ +paginatorFactory = $paginatorFactory; + $this->geographicalUnitRepository = $geographicalUnitRepository; + $this->security = $security; + $this->serializer = $serializer; + } + + /** + * @Route("/api/1.0/main/geographical-unit/by-address/{id}.{_format}", requirements={"_format": "json"}) + */ + public function getGeographicalUnitCoveringAddress(Address $address): JsonResponse + { + if (!$this->security->isGranted('ROLE_USER')) { + throw new AccessDeniedHttpException(); + } + + $count = $this->geographicalUnitRepository->countGeographicalUnitContainingAddress($address); + $pagination = $this->paginatorFactory->create($count); + $units = $this->geographicalUnitRepository->findGeographicalUnitContainingAddress($address, $pagination->getCurrentPageFirstItemNumber(), $pagination->getItemsPerPage()); + + $collection = new Collection($units, $pagination); + + return new JsonResponse( + $this->serializer->serialize($collection, 'json', [AbstractNormalizer::GROUPS => ['read']]), + JsonResponse::HTTP_OK, + [], + true + ); + } +} diff --git a/src/Bundle/ChillMainBundle/Controller/LanguageController.php b/src/Bundle/ChillMainBundle/Controller/LanguageController.php index 4612a3728..4e72d8983 100644 --- a/src/Bundle/ChillMainBundle/Controller/LanguageController.php +++ b/src/Bundle/ChillMainBundle/Controller/LanguageController.php @@ -1,14 +1,14 @@ where('e.availableForUsers = true'); //TODO not working + $query->where('e.availableForUsers = TRUE'); } protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator) diff --git a/src/Bundle/ChillMainBundle/Controller/LocationTypeApiController.php b/src/Bundle/ChillMainBundle/Controller/LocationTypeApiController.php index ce1b5e825..bd86d72dd 100644 --- a/src/Bundle/ChillMainBundle/Controller/LocationTypeApiController.php +++ b/src/Bundle/ChillMainBundle/Controller/LocationTypeApiController.php @@ -1,14 +1,14 @@ query->has('edit')) { $commentId = $request->query->getInt('edit'); - $editedComment = $notification->getComments()->filter(static function (NotificationComment $c) use ($commentId) { - return $c->getId() === $commentId; - })->first(); + $editedComment = $notification->getComments()->filter(static fn (NotificationComment $c) => $c->getId() === $commentId)->first(); if (false === $editedComment) { throw $this->createNotFoundException("Comment with id {$commentId} does not exists nor belong to this notification"); diff --git a/src/Bundle/ChillMainBundle/Controller/PasswordController.php b/src/Bundle/ChillMainBundle/Controller/PasswordController.php index 4d6a13545..a9a14b7a1 100644 --- a/src/Bundle/ChillMainBundle/Controller/PasswordController.php +++ b/src/Bundle/ChillMainBundle/Controller/PasswordController.php @@ -1,14 +1,14 @@ translatableStringHelper = $translatableStringHelper; - $this->roleProvider = $roleProvider; - $this->roleHierarchy = $roleHierarchy; - $this->translator = $translator; - $this->validator = $validator; } /** - * @param int $id - * - * @throws type - * - * @return Respon */ - public function addLinkRoleScopeAction(Request $request, $id) + public function addLinkRoleScopeAction(Request $request, int $id): Response { - $em = $this->getDoctrine()->getManager(); - - $permissionsGroup = $em->getRepository(\Chill\MainBundle\Entity\PermissionsGroup::class)->find($id); + $permissionsGroup = $this->permissionsGroupRepository->find($id); if (!$permissionsGroup) { throw $this->createNotFoundException('Unable to find PermissionsGroup entity.'); @@ -106,7 +76,7 @@ class PermissionsGroupController extends AbstractController $violations = $this->validator->validate($permissionsGroup); if ($violations->count() === 0) { - $em->flush(); + $this->em->flush(); $this->addFlash( 'notice', @@ -158,9 +128,7 @@ class PermissionsGroupController extends AbstractController 'edit_form' => $editForm->createView(), 'role_scopes_sorted' => $roleScopesSorted, 'expanded_roles' => $this->getExpandedRoles($permissionsGroup->getRoleScopes()->toArray()), - 'delete_role_scopes_form' => array_map(static function ($form) { - return $form->createView(); - }, $deleteRoleScopesForm), + 'delete_role_scopes_form' => array_map(static fn ($form) => $form->createView(), $deleteRoleScopesForm), 'add_role_scopes_form' => $addRoleScopesForm->createView(), ]); } @@ -168,16 +136,15 @@ class PermissionsGroupController extends AbstractController /** * Creates a new PermissionsGroup entity. */ - public function createAction(Request $request) + public function createAction(Request $request): Response { $permissionsGroup = new PermissionsGroup(); $form = $this->createCreateForm($permissionsGroup); $form->handleRequest($request); if ($form->isValid()) { - $em = $this->getDoctrine()->getManager(); - $em->persist($permissionsGroup); - $em->flush(); + $this->em->persist($permissionsGroup); + $this->em->flush(); return $this->redirect($this->generateUrl( 'admin_permissionsgroup_edit', @@ -193,18 +160,12 @@ class PermissionsGroupController extends AbstractController /** * remove an association between permissionsGroup and roleScope. - * - * @param int $pgid permissionsGroup id - * @param int $rsid roleScope id - * - * @return redirection to edit form */ - public function deleteLinkRoleScopeAction($pgid, $rsid) + public function deleteLinkRoleScopeAction(int $pgid, int $rsid): Response { - $em = $this->getDoctrine()->getManager(); - $permissionsGroup = $em->getRepository(\Chill\MainBundle\Entity\PermissionsGroup::class)->find($pgid); - $roleScope = $em->getRepository(\Chill\MainBundle\Entity\RoleScope::class)->find($rsid); + $permissionsGroup = $this->permissionsGroupRepository->find($pgid); + $roleScope = $this->roleScopeRepository->find($rsid); if (!$permissionsGroup) { throw $this->createNotFoundException('Unable to find PermissionsGroup entity.'); @@ -216,7 +177,7 @@ class PermissionsGroupController extends AbstractController try { $permissionsGroup->removeRoleScope($roleScope); - } catch (RuntimeException $ex) { + } catch (RuntimeException) { $this->addFlash( 'notice', $this->translator->trans("The role '%role%' and circle " @@ -233,7 +194,7 @@ class PermissionsGroupController extends AbstractController )); } - $em->flush(); + $this->em->flush(); if ($roleScope->getScope() !== null) { $this->addFlash( @@ -262,14 +223,10 @@ class PermissionsGroupController extends AbstractController /** * Displays a form to edit an existing PermissionsGroup entity. - * - * @param mixed $id */ - public function editAction($id) + public function editAction(int $id): Response { - $em = $this->getDoctrine()->getManager(); - - $permissionsGroup = $em->getRepository(\Chill\MainBundle\Entity\PermissionsGroup::class)->find($id); + $permissionsGroup = $this->permissionsGroupRepository->find($id); if (!$permissionsGroup) { throw $this->createNotFoundException('Unable to find PermissionsGroup entity.'); @@ -305,9 +262,7 @@ class PermissionsGroupController extends AbstractController 'role_scopes_sorted' => $roleScopesSorted, 'edit_form' => $editForm->createView(), 'expanded_roles' => $this->getExpandedRoles($permissionsGroup->getRoleScopes()->toArray()), - 'delete_role_scopes_form' => array_map(static function ($form) { - return $form->createView(); - }, $deleteRoleScopesForm), + 'delete_role_scopes_form' => array_map(static fn ($form) => $form->createView(), $deleteRoleScopesForm), 'add_role_scopes_form' => $addRoleScopesForm->createView(), ]); } @@ -315,11 +270,9 @@ class PermissionsGroupController extends AbstractController /** * Lists all PermissionsGroup entities. */ - public function indexAction() + public function indexAction(): Response { - $em = $this->getDoctrine()->getManager(); - - $entities = $em->getRepository(\Chill\MainBundle\Entity\PermissionsGroup::class)->findAll(); + $entities = $this->permissionsGroupRepository->findAllOrderedAlphabetically(); return $this->render('@ChillMain/PermissionsGroup/index.html.twig', [ 'entities' => $entities, @@ -329,7 +282,7 @@ class PermissionsGroupController extends AbstractController /** * Displays a form to create a new PermissionsGroup entity. */ - public function newAction() + public function newAction(): Response { $permissionsGroup = new PermissionsGroup(); $form = $this->createCreateForm($permissionsGroup); @@ -342,14 +295,10 @@ class PermissionsGroupController extends AbstractController /** * Finds and displays a PermissionsGroup entity. - * - * @param mixed $id */ - public function showAction($id) + public function showAction(int $id): Response { - $em = $this->getDoctrine()->getManager(); - - $permissionsGroup = $em->getRepository(\Chill\MainBundle\Entity\PermissionsGroup::class)->find($id); + $permissionsGroup = $this->permissionsGroupRepository->find($id); if (!$permissionsGroup) { throw $this->createNotFoundException('Unable to find PermissionsGroup entity.'); @@ -397,15 +346,10 @@ class PermissionsGroupController extends AbstractController /** * Edits an existing PermissionsGroup entity. - * - * @param mixed $id */ - public function updateAction(Request $request, $id) + public function updateAction(Request $request, int $id): Response { - $em = $this->getDoctrine()->getManager(); - - $permissionsGroup = $em - ->getRepository(\Chill\MainBundle\Entity\PermissionsGroup::class) + $permissionsGroup = $this->permissionsGroupRepository ->find($id); if (!$permissionsGroup) { @@ -417,7 +361,7 @@ class PermissionsGroupController extends AbstractController $editForm->handleRequest($request); if ($editForm->isValid()) { - $em->flush(); + $this->em->flush(); return $this->redirect($this->generateUrl('admin_permissionsgroup_edit', ['id' => $id])); } @@ -449,27 +393,18 @@ class PermissionsGroupController extends AbstractController 'role_scopes_sorted' => $roleScopesSorted, 'edit_form' => $editForm->createView(), 'expanded_roles' => $this->getExpandedRoles($permissionsGroup->getRoleScopes()->toArray()), - 'delete_role_scopes_form' => array_map(static function ($form) { - return $form->createView(); - }, $deleteRoleScopesForm), + 'delete_role_scopes_form' => array_map(static fn ($form) => $form->createView(), $deleteRoleScopesForm), 'add_role_scopes_form' => $addRoleScopesForm->createView(), ]); } /** * get a role scope by his parameters. The role scope is persisted if it - * doesn't exists in database. - * - * @param Scope $scope - * @param string $role - * - * @return RoleScope + * doesn't exist in database. */ - protected function getPersistentRoleScopeBy($role, ?Scope $scope = null) + protected function getPersistentRoleScopeBy(string $role, ?Scope $scope = null): RoleScope { - $em = $this->getDoctrine()->getManager(); - - $roleScope = $em->getRepository(\Chill\MainBundle\Entity\RoleScope::class) + $roleScope = $this->roleScopeRepository ->findOneBy(['role' => $role, 'scope' => $scope]); if (null === $roleScope) { @@ -477,7 +412,7 @@ class PermissionsGroupController extends AbstractController ->setRole($role) ->setScope($scope); - $em->persist($roleScope); + $this->em->persist($roleScope); } return $roleScope; @@ -485,10 +420,8 @@ class PermissionsGroupController extends AbstractController /** * creates a form to add a role scope to permissionsgroup. - * - * @return \Symfony\Component\Form\Form The form */ - private function createAddRoleScopeForm(PermissionsGroup $permissionsGroup) + private function createAddRoleScopeForm(PermissionsGroup $permissionsGroup): FormInterface { return $this->createFormBuilder() ->setAction($this->generateUrl( @@ -505,10 +438,8 @@ class PermissionsGroupController extends AbstractController * Creates a form to create a PermissionsGroup entity. * * @param PermissionsGroup $permissionsGroup The entity - * - * @return \Symfony\Component\Form\Form The form */ - private function createCreateForm(PermissionsGroup $permissionsGroup) + private function createCreateForm(PermissionsGroup $permissionsGroup): FormInterface { $form = $this->createForm(PermissionsGroupType::class, $permissionsGroup, [ 'action' => $this->generateUrl('admin_permissionsgroup_create'), @@ -524,13 +455,11 @@ class PermissionsGroupController extends AbstractController * Creates a form to delete a link to roleScope. * * @param mixed $permissionsGroup The entity id - * - * @return \Symfony\Component\Form\Form The form */ private function createDeleteRoleScopeForm( PermissionsGroup $permissionsGroup, RoleScope $roleScope - ) { + ): FormInterface { return $this->createFormBuilder() ->setAction($this->generateUrl( 'admin_permissionsgroup_delete_role_scope', @@ -543,12 +472,8 @@ class PermissionsGroupController extends AbstractController /** * Creates a form to edit a PermissionsGroup entity. - * - * @param PermissionsGroup $permissionsGroup The entity - * - * @return \Symfony\Component\Form\Form The form */ - private function createEditForm(PermissionsGroup $permissionsGroup) + private function createEditForm(PermissionsGroup $permissionsGroup): FormInterface { $form = $this->createForm(PermissionsGroupType::class, $permissionsGroup, [ 'action' => $this->generateUrl('admin_permissionsgroup_update', ['id' => $permissionsGroup->getId()]), @@ -562,10 +487,8 @@ class PermissionsGroupController extends AbstractController /** * expand roleScopes to be easily shown in template. - * - * @return array */ - private function getExpandedRoles(array $roleScopes) + private function getExpandedRoles(array $roleScopes): array { $expandedRoles = []; @@ -573,12 +496,10 @@ class PermissionsGroupController extends AbstractController if (!array_key_exists($roleScope->getRole(), $expandedRoles)) { $expandedRoles[$roleScope->getRole()] = array_map( - static function (Role $role) { - return $role->getRole(); - }, + static fn ($role) => $role, $this->roleHierarchy - ->getReachableRoles( - [new Role($roleScope->getRole())] + ->getReachableRoleNames( + [$roleScope->getRole()] ) ); } diff --git a/src/Bundle/ChillMainBundle/Controller/PostalCodeAPIController.php b/src/Bundle/ChillMainBundle/Controller/PostalCodeAPIController.php index fa1a29296..8e679b228 100644 --- a/src/Bundle/ChillMainBundle/Controller/PostalCodeAPIController.php +++ b/src/Bundle/ChillMainBundle/Controller/PostalCodeAPIController.php @@ -1,20 +1,20 @@ countryRepository = $countryRepository; @@ -92,5 +92,8 @@ final class PostalCodeAPIController extends ApiController $qb->where('e.country = :country') ->setParameter('country', $request->query->get('country')); } + + $qb->andWhere('e.origin = :zero') + ->setParameter('zero', 0); } } diff --git a/src/Bundle/ChillMainBundle/Controller/PostalCodeController.php b/src/Bundle/ChillMainBundle/Controller/PostalCodeController.php index f38e0004b..91de40985 100644 --- a/src/Bundle/ChillMainBundle/Controller/PostalCodeController.php +++ b/src/Bundle/ChillMainBundle/Controller/PostalCodeController.php @@ -1,14 +1,14 @@ addOrderBy('e.id', 'ASC'); + + return parent::orderQuery($action, $query, $request, $paginator); + } +} diff --git a/src/Bundle/ChillMainBundle/Controller/SavedExportController.php b/src/Bundle/ChillMainBundle/Controller/SavedExportController.php new file mode 100644 index 000000000..197fe253d --- /dev/null +++ b/src/Bundle/ChillMainBundle/Controller/SavedExportController.php @@ -0,0 +1,185 @@ +exportManager = $exportManager; + $this->entityManager = $entityManager; + $this->formFactory = $formBuilder; + $this->savedExportRepository = $savedExportRepository; + $this->security = $security; + $this->session = $session; + $this->templating = $templating; + $this->translator = $translator; + $this->urlGenerator = $urlGenerator; + } + + /** + * @Route("/{_locale}/exports/saved/{id}/delete", name="chill_main_export_saved_delete") + */ + public function delete(SavedExport $savedExport, Request $request): Response + { + if (!$this->security->isGranted(SavedExportVoter::DELETE, $savedExport)) { + throw new AccessDeniedHttpException(); + } + + $form = $this->formFactory->create(); + $form->add('submit', SubmitType::class, ['label' => 'Delete']); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $this->entityManager->remove($savedExport); + $this->entityManager->flush(); + + $this->session->getFlashBag()->add('success', $this->translator->trans('saved_export.Export is deleted')); + + return new RedirectResponse( + $this->urlGenerator->generate('chill_main_export_saved_list_my') + ); + } + + return new Response( + $this->templating->render( + '@ChillMain/SavedExport/delete.html.twig', + [ + 'saved_export' => $savedExport, + 'delete_form' => $form->createView(), + ] + ) + ); + } + + /** + * @Route("/{_locale}/exports/saved/{id}/edit", name="chill_main_export_saved_edit") + */ + public function edit(SavedExport $savedExport, Request $request): Response + { + if (!$this->security->isGranted(SavedExportVoter::EDIT, $savedExport)) { + throw new AccessDeniedHttpException(); + } + + $form = $this->formFactory->create(SavedExportType::class, $savedExport); + + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $this->entityManager->flush(); + + $this->session->getFlashBag()->add('success', $this->translator->trans('saved_export.Saved export is saved!')); + + return new RedirectResponse( + $this->urlGenerator->generate('chill_main_export_saved_list_my') + ); + } + + return new Response( + $this->templating->render( + '@ChillMain/SavedExport/edit.html.twig', + [ + 'form' => $form->createView(), + ] + ) + ); + } + + /** + * @Route("/{_locale}/exports/saved/my", name="chill_main_export_saved_list_my") + */ + public function list(): Response + { + $user = $this->security->getUser(); + + if (!$this->security->isGranted('ROLE_USER') || !$user instanceof User) { + throw new AccessDeniedHttpException(); + } + + $exports = $this->savedExportRepository->findByUser($user, ['title' => 'ASC']); + + // group by center + /** @var array $exportsGrouped */ + $exportsGrouped = []; + + foreach ($exports as $savedExport) { + $export = $this->exportManager->getExport($savedExport->getExportAlias()); + + $exportsGrouped[ + $export instanceof GroupedExportInterface + ? $this->translator->trans($export->getGroup()) : '_' + ][] = ['saved' => $savedExport, 'export' => $export]; + } + + ksort($exportsGrouped); + + return new Response( + $this->templating->render( + '@ChillMain/SavedExport/index.html.twig', + [ + 'grouped_exports' => $exportsGrouped, + 'total' => count($exports), + ] + ) + ); + } +} diff --git a/src/Bundle/ChillMainBundle/Controller/ScopeApiController.php b/src/Bundle/ChillMainBundle/Controller/ScopeApiController.php new file mode 100644 index 000000000..85041f0f3 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Controller/ScopeApiController.php @@ -0,0 +1,25 @@ +andWhere($query->expr()->eq('e.active', "'TRUE'")); + } + } +} diff --git a/src/Bundle/ChillMainBundle/Controller/ScopeController.php b/src/Bundle/ChillMainBundle/Controller/ScopeController.php index 5dc85d201..f3b08204d 100644 --- a/src/Bundle/ChillMainBundle/Controller/ScopeController.php +++ b/src/Bundle/ChillMainBundle/Controller/ScopeController.php @@ -1,14 +1,14 @@ getHasAdvancedFormSearchServices(); if (count($advancedSearchProviders) === 1) { - reset($advancedSearchProviders); - return $this->redirectToRoute('chill_main_advanced_search', [ - 'name' => key($advancedSearchProviders), + 'name' => array_key_first($advancedSearchProviders), ]); } diff --git a/src/Bundle/ChillMainBundle/Controller/TimelineCenterController.php b/src/Bundle/ChillMainBundle/Controller/TimelineCenterController.php index 5d50f246c..c51952b3a 100644 --- a/src/Bundle/ChillMainBundle/Controller/TimelineCenterController.php +++ b/src/Bundle/ChillMainBundle/Controller/TimelineCenterController.php @@ -1,14 +1,14 @@ ['read']] ); } + + /** + * @param QueryBuilder $query + */ + protected function customizeQuery(string $action, Request $request, $query): void + { + if ('_index' === $action) { + $query->andWhere($query->expr()->eq('e.enabled', "'TRUE'")); + } + } + + /** + * @param mixed $query + * @param mixed $_format + */ + protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator, $_format) + { + return $query->orderBy('e.label', 'ASC'); + } } diff --git a/src/Bundle/ChillMainBundle/Controller/UserController.php b/src/Bundle/ChillMainBundle/Controller/UserController.php index b6ded6653..ecf8a3c9c 100644 --- a/src/Bundle/ChillMainBundle/Controller/UserController.php +++ b/src/Bundle/ChillMainBundle/Controller/UserController.php @@ -1,14 +1,14 @@ $this->createAddLinkGroupCenterForm($entity, $request)->createView(), 'delete_groupcenter_form' => array_map( - static function (Form $form) { - return $form->createView(); - }, + static fn (Form $form) => $form->createView(), iterator_to_array($this->getDeleteLinkGroupCenterByUser($entity, $request), true) ), ] ); - } elseif ('index' === $action) { + } + if ('index' === $action) { return array_merge( ['allow_change_password' => $this->parameterBag->get('chill_main.access_user_change_password')], $defaultTemplateParameters diff --git a/src/Bundle/ChillMainBundle/Controller/UserExportController.php b/src/Bundle/ChillMainBundle/Controller/UserExportController.php new file mode 100644 index 000000000..dc2674dbb --- /dev/null +++ b/src/Bundle/ChillMainBundle/Controller/UserExportController.php @@ -0,0 +1,145 @@ +security->isGranted('ROLE_ADMIN')) { + throw new AccessDeniedHttpException('Only ROLE_ADMIN can export this list'); + } + + $users = $this->userRepository->findAllAsArray($request->getLocale()); + + $csv = Writer::createFromPath('php://temp', 'r+'); + $csv->insertOne( + array_map( + fn (string $e) => $this->translator->trans('admin.users.export.' . $e), + [ + 'id', + 'username', + 'email', + 'enabled', + 'civility_id', + 'civility_abbreviation', + 'civility_name', + 'label', + 'mainCenter_id' , + 'mainCenter_name', + 'mainScope_id', + 'mainScope_name', + 'userJob_id', + 'userJob_name', + 'currentLocation_id', + 'currentLocation_name', + 'mainLocation_id', + 'mainLocation_name', + 'absenceStart' + ] + ) + ); + $csv->addFormatter(fn (array $row) => null !== ($row['absenceStart'] ?? null) ? array_merge($row, ['absenceStart' => $row['absenceStart']->format('Y-m-d')]) : $row); + $csv->insertAll($users); + + return new StreamedResponse( + function () use ($csv) { + foreach ($csv->chunk(1024) as $chunk) { + echo $chunk; + flush(); + } + }, + Response::HTTP_OK, + [ + 'Content-Encoding' => 'none', + 'Content-Type' => 'text/csv; charset=UTF-8', + 'Content-Disposition' => 'attachment; users.csv', + ] + ); + } + + /** + * @return StreamedResponse + * @throws \League\Csv\CannotInsertRecord + * @throws \League\Csv\Exception + * @throws \League\Csv\UnavailableStream + * + * @Route("/{_locale}/admin/main/users/export/permissions.{_format}", requirements={"_format": "csv"}, name="chill_main_users_export_permissions") + */ + public function userPermissionsList(string $_format = 'csv'): StreamedResponse + { + if (!$this->security->isGranted('ROLE_ADMIN')) { + throw new AccessDeniedHttpException('Only ROLE_ADMIN can export this list'); + } + + $userPermissions = $this->userRepository->findAllUserACLAsArray(); + + $csv = Writer::createFromPath('php://temp', 'r+'); + $csv->insertOne( + array_map( + fn (string $e) => $this->translator->trans('admin.users.export.' . $e), + [ + 'id', + 'username', + 'email', + 'label', + 'enabled', + 'center_id', + 'center_name', + 'permissionsGroup_id', + 'permissionsGroup_name', + ] + ) + ); + $csv->insertAll($userPermissions); + + return new StreamedResponse( + function () use ($csv) { + foreach ($csv->chunk(1024) as $chunk) { + echo $chunk; + flush(); + } + }, + Response::HTTP_OK, + [ + 'Content-Encoding' => 'none', + 'Content-Type' => 'text/csv; charset=UTF-8', + 'Content-Disposition' => 'attachment; users.csv', + ] + ); + } + +} diff --git a/src/Bundle/ChillMainBundle/Controller/UserJobApiController.php b/src/Bundle/ChillMainBundle/Controller/UserJobApiController.php new file mode 100644 index 000000000..1b0dbbf5f --- /dev/null +++ b/src/Bundle/ChillMainBundle/Controller/UserJobApiController.php @@ -0,0 +1,25 @@ +andWhere($query->expr()->eq('e.active', "'TRUE'")); + } + } +} diff --git a/src/Bundle/ChillMainBundle/Controller/UserJobController.php b/src/Bundle/ChillMainBundle/Controller/UserJobController.php index 0c1fd4b3d..098b6ff29 100644 --- a/src/Bundle/ChillMainBundle/Controller/UserJobController.php +++ b/src/Bundle/ChillMainBundle/Controller/UserJobController.php @@ -1,14 +1,14 @@ security->isGranted('ROLE_USER') || !$this->security->getUser() instanceof User) { + throw new AccessDeniedException(); + } + + $total = $this->entityWorkflowRepository->countByCc($this->security->getUser()); + + if ($request->query->getBoolean('countOnly', false)) { + return new JsonResponse( + $this->serializer->serialize(new Counter($total), 'json'), + JsonResponse::HTTP_OK, + [], + true + ); + } + + $paginator = $this->paginatorFactory->create($total); + + $workflows = $this->entityWorkflowRepository->findByCc( + $this->security->getUser(), + ['id' => 'DESC'], + $paginator->getItemsPerPage(), + $paginator->getCurrentPageFirstItemNumber() + ); + + return new JsonResponse( + $this->serializer->serialize(new Collection($workflows, $paginator), 'json', ['groups' => ['read']]), + JsonResponse::HTTP_OK, + [], + true + ); + } + /** * @Route("/api/1.0/main/workflow/{id}/subscribe", methods={"POST"}) */ diff --git a/src/Bundle/ChillMainBundle/Controller/WorkflowController.php b/src/Bundle/ChillMainBundle/Controller/WorkflowController.php index 2803ff254..cfd28160c 100644 --- a/src/Bundle/ChillMainBundle/Controller/WorkflowController.php +++ b/src/Bundle/ChillMainBundle/Controller/WorkflowController.php @@ -1,14 +1,14 @@ entityWorkflowManager = $entityWorkflowManager; $this->entityWorkflowRepository = $entityWorkflowRepository; @@ -61,6 +64,7 @@ class WorkflowController extends AbstractController $this->registry = $registry; $this->entityManager = $entityManager; $this->translator = $translator; + $this->security = $security; } /** @@ -85,7 +89,6 @@ class WorkflowController extends AbstractController ->setRelatedEntityClass($request->query->get('entityClass')) ->setRelatedEntityId($request->query->getInt('entityId')) ->setWorkflowName($request->query->get('workflow')) - ->addSubscriberToStep($this->getUser()) ->addSubscriberToFinal($this->getUser()); $errors = $this->validator->validate($entityWorkflow, null, ['creation']); @@ -225,6 +228,33 @@ class WorkflowController extends AbstractController ); } + /** + * @Route("/{_locale}/main/workflow/list/cc", name="chill_main_workflow_list_cc") + */ + public function myWorkflowsCc(Request $request): Response + { + $this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED'); + + $total = $this->entityWorkflowRepository->countByDest($this->getUser()); + $paginator = $this->paginatorFactory->create($total); + + $workflows = $this->entityWorkflowRepository->findByCc( + $this->getUser(), + ['createdAt' => 'DESC'], + $paginator->getItemsPerPage(), + $paginator->getCurrentPageFirstItemNumber() + ); + + return $this->render( + '@ChillMain/Workflow/list.html.twig', + [ + 'workflows' => $this->buildHandler($workflows), + 'paginator' => $paginator, + 'step' => 'cc', + ] + ); + } + /** * @Route("/{_locale}/main/workflow/list/dest", name="chill_main_workflow_list_dest") */ @@ -292,10 +322,22 @@ class WorkflowController extends AbstractController if (count($workflow->getEnabledTransitions($entityWorkflow)) > 0) { // possible transition + + $usersInvolved = $entityWorkflow->getUsersInvolved(); + $currentUserFound = array_search($this->security->getUser(), $usersInvolved, true); + + if (false !== $currentUserFound) { + unset($usersInvolved[$currentUserFound]); + } + $transitionForm = $this->createForm( WorkflowStepType::class, $entityWorkflow->getCurrentStep(), - ['transition' => true, 'entity_workflow' => $entityWorkflow] + [ + 'transition' => true, + 'entity_workflow' => $entityWorkflow, + 'suggested_users' => $usersInvolved + ] ); $transitionForm->handleRequest($request); @@ -303,12 +345,10 @@ class WorkflowController extends AbstractController if ($transitionForm->isSubmitted() && $transitionForm->isValid()) { if (!$workflow->can($entityWorkflow, $transition = $transitionForm['transition']->getData()->getName())) { $blockers = $workflow->buildTransitionBlockerList($entityWorkflow, $transition); - $msgs = array_map(function (TransitionBlocker $tb) { - return $this->translator->trans( - $tb->getMessage(), - $tb->getParameters() - ); - }, iterator_to_array($blockers)); + $msgs = array_map(fn (TransitionBlocker $tb) => $this->translator->trans( + $tb->getMessage(), + $tb->getParameters() + ), iterator_to_array($blockers)); throw $this->createAccessDeniedException( sprintf( @@ -318,19 +358,13 @@ class WorkflowController extends AbstractController ); } - $entityWorkflow->futureDestUsers = $transitionForm['future_dest_users']->getData(); - $entityWorkflow->futureDestEmails = $transitionForm['future_dest_emails']->getData(); + // TODO symfony 5: add those "future" on context ($workflow->apply($entityWorkflow, $transition, $context) + $entityWorkflow->futureCcUsers = $transitionForm['future_cc_users']->getData() ?? []; + $entityWorkflow->futureDestUsers = $transitionForm['future_dest_users']->getData() ?? []; + $entityWorkflow->futureDestEmails = $transitionForm['future_dest_emails']->getData() ?? []; $workflow->apply($entityWorkflow, $transition); - foreach ($transitionForm['future_dest_users']->getData() as $user) { - $entityWorkflow->getCurrentStep()->addDestUser($user); - } - - foreach ($transitionForm['future_dest_emails']->getData() as $email) { - $entityWorkflow->getCurrentStep()->addDestEmail($email); - } - $this->entityManager->flush(); return $this->redirectToRoute('chill_main_workflow_show', ['id' => $entityWorkflow->getId()]); diff --git a/src/Bundle/ChillMainBundle/Cron/CronJobInterface.php b/src/Bundle/ChillMainBundle/Cron/CronJobInterface.php new file mode 100644 index 000000000..4e1ca9ff6 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Cron/CronJobInterface.php @@ -0,0 +1,23 @@ + + */ + private iterable $jobs; + + private LoggerInterface $logger; + + /** + * @param CronJobInterface[] $jobs + */ + public function __construct( + CronJobExecutionRepositoryInterface $cronJobExecutionRepository, + EntityManagerInterface $entityManager, + iterable $jobs, + LoggerInterface $logger + ) { + $this->cronJobExecutionRepository = $cronJobExecutionRepository; + $this->entityManager = $entityManager; + $this->jobs = $jobs; + $this->logger = $logger; + } + + public function run(?string $forceJob = null): void + { + if (null !== $forceJob) { + $this->runForce($forceJob); + + return; + } + + [$orderedJobs, $lasts] = $this->getOrderedJobs(); + + foreach ($orderedJobs as $job) { + if ($job->canRun($lasts[$job->getKey()] ?? null)) { + if (array_key_exists($job->getKey(), $lasts)) { + $this->entityManager + ->createQuery(self::UPDATE_BEFORE_EXEC) + ->setParameters([ + 'now' => new DateTimeImmutable('now'), + 'key' => $job->getKey(), + ]) + ->execute(); + } else { + $execution = new CronJobExecution($job->getKey()); + $this->entityManager->persist($execution); + $this->entityManager->flush(); + } + $this->entityManager->clear(); + + try { + $this->logger->info(sprintf('%sWill run job', self::LOG_PREFIX), ['job' => $job->getKey()]); + $job->run(); + + $this->entityManager + ->createQuery(self::UPDATE_AFTER_EXEC) + ->setParameters([ + 'now' => new DateTimeImmutable('now'), + 'status' => CronJobExecution::SUCCESS, + 'key' => $job->getKey(), + ]) + ->execute(); + + $this->logger->info(sprintf('%sSuccessfully run job', self::LOG_PREFIX), ['job' => $job->getKey()]); + + return; + } catch (Exception $e) { + $this->logger->error(sprintf('%sRunning job failed', self::LOG_PREFIX), ['job' => $job->getKey()]); + $this->entityManager + ->createQuery(self::UPDATE_AFTER_EXEC) + ->setParameters([ + 'now' => new DateTimeImmutable('now'), + 'status' => CronJobExecution::FAILURE, + 'key' => $job->getKey(), + ]) + ->execute(); + + return; + } + } + } + } + + /** + * @return array<0: CronJobInterface[], 1: array> + */ + private function getOrderedJobs(): array + { + /** @var array $lasts */ + $lasts = []; + + foreach ($this->cronJobExecutionRepository->findAll() as $execution) { + $lasts[$execution->getKey()] = $execution; + } + + // order by last, NULL first + $orderedJobs = iterator_to_array($this->jobs); + usort( + $orderedJobs, + static function (CronJobInterface $a, CronJobInterface $b) use ($lasts): int { + if ( + (!array_key_exists($a->getKey(), $lasts) && !array_key_exists($b->getKey(), $lasts)) + ) { + return 0; + } + + if (!array_key_exists($a->getKey(), $lasts) && array_key_exists($b->getKey(), $lasts)) { + return -1; + } + + if (!array_key_exists($b->getKey(), $lasts) && array_key_exists($a->getKey(), $lasts)) { + return 1; + } + + return $lasts[$a->getKey()]->getLastStart() <=> $lasts[$b->getKey()]->getLastStart(); + } + ); + + return [$orderedJobs, $lasts]; + } + + private function runForce(string $forceJob): void + { + foreach ($this->jobs as $job) { + if ($job->getKey() === $forceJob) { + $job->run(); + } + } + } +} diff --git a/src/Bundle/ChillMainBundle/Cron/CronManagerInterface.php b/src/Bundle/ChillMainBundle/Cron/CronManagerInterface.php new file mode 100644 index 000000000..d2292d455 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Cron/CronManagerInterface.php @@ -0,0 +1,20 @@ +setRefId($this->faker->numerify('ref-id-######')); $ar->setStreet($this->faker->streetName); - $ar->setStreetNumber((string) mt_rand(0, 199)); + $ar->setStreetNumber((string) random_int(0, 199)); $ar->setPoint($this->getRandomPoint()); $ar->setPostcode($this->getReference( LoadPostalCodes::$refs[array_rand(LoadPostalCodes::$refs)] @@ -89,8 +89,8 @@ class LoadAddressReferences extends AbstractFixture implements ContainerAwareInt { $lonBrussels = 4.35243; $latBrussels = 50.84676; - $lon = $lonBrussels + 0.01 * mt_rand(-5, 5); - $lat = $latBrussels + 0.01 * mt_rand(-5, 5); + $lon = $lonBrussels + 0.01 * random_int(-5, 5); + $lat = $latBrussels + 0.01 * random_int(-5, 5); return Point::fromLonLat($lon, $lat); } diff --git a/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadCenters.php b/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadCenters.php index e4cd7d565..d835aad38 100644 --- a/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadCenters.php +++ b/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadCenters.php @@ -1,14 +1,14 @@ getLanguageNames() as $code => $language) { if ( - !in_array($code, $this->regionalVersionToInclude, true) - && !in_array($code, $this->ancientToExclude, true) + !in_array($code, $this->regionalVersionToInclude, true) + && !in_array($code, $this->ancientToExclude, true) ) { $lang = (new Language()) ->setId($code) diff --git a/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadLocationType.php b/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadLocationType.php index 3e24d034c..37a3b605f 100644 --- a/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadLocationType.php +++ b/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadLocationType.php @@ -1,14 +1,14 @@ setParameter( + 'chill_main.add_address', + $config['add_address'] + ); + $container->setParameter( 'chill_main.routing.resources', $config['routing']['resources'] @@ -196,8 +214,11 @@ class ChillMainExtension extends Extension implements $loader->load('services/search.yaml'); $loader->load('services/serializer.yaml'); $loader->load('services/mailer.yaml'); + $loader->load('services/short_message.yaml'); $this->configureCruds($container, $config['cruds'], $config['apis'], $loader); + $container->setParameter('chill_main.short_messages', $config['short_messages']); + //$this->configureSms($config['short_messages'], $container, $loader); } public function prepend(ContainerBuilder $container) @@ -211,6 +232,7 @@ class ChillMainExtension extends Extension implements 'installation' => [ 'name' => $config['installation_name'], ], 'available_languages' => $config['available_languages'], + 'add_address' => $config['add_address'], ], 'form_themes' => ['@ChillMain/Form/fields.html.twig'], ]; @@ -228,6 +250,7 @@ class ChillMainExtension extends Extension implements 'GET_JSON_FIELD_BY_KEY' => GetJsonFieldByKey::class, 'AGGREGATE' => JsonAggregate::class, 'REPLACE' => Replace::class, + 'JSON_EXTRACT' => JsonExtract::class, ], 'numeric_functions' => [ 'JSONB_EXISTS_IN_ARRAY' => JsonbExistsInArray::class, @@ -236,6 +259,15 @@ class ChillMainExtension extends Extension implements 'STRICT_WORD_SIMILARITY_OPS' => StrictWordSimilarityOPS::class, 'ST_CONTAINS' => STContains::class, 'JSONB_ARRAY_LENGTH' => JsonbArrayLength::class, + 'ST_X' => STX::class, + 'ST_Y' => STY::class, + 'GREATEST' => Greatest::class, + 'LEAST' => LEAST::class, + ], + 'datetime_functions' => [ + 'EXTRACT' => Extract::class, + 'TO_CHAR' => ToChar::class, + 'AGE' => Age::class, ], ], 'hydrators' => [ @@ -471,6 +503,27 @@ class ChillMainExtension extends Extension implements ], ], ], + [ + 'class' => Regroupment::class, + 'name' => 'regroupment', + 'base_path' => '/admin/regroupment', + 'form_class' => RegroupmentType::class, + 'controller' => RegroupmentController::class, + 'actions' => [ + 'index' => [ + 'role' => 'ROLE_ADMIN', + 'template' => '@ChillMain/Admin/Regroupment/index.html.twig', + ], + 'new' => [ + 'role' => 'ROLE_ADMIN', + 'template' => '@ChillMain/Admin/Regroupment/new.html.twig', + ], + 'edit' => [ + 'role' => 'ROLE_ADMIN', + 'template' => '@ChillMain/Admin/Regroupment/edit.html.twig', + ], + ], + ], ], 'apis' => [ [ @@ -501,6 +554,7 @@ class ChillMainExtension extends Extension implements 'name' => 'user_job', 'base_path' => '/api/1.0/main/user-job', 'base_role' => 'ROLE_USER', + 'controller' => UserJobApiController::class, 'actions' => [ '_index' => [ 'methods' => [ @@ -602,6 +656,7 @@ class ChillMainExtension extends Extension implements ], [ 'class' => \Chill\MainBundle\Entity\Scope::class, + 'controller' => \Chill\MainBundle\Controller\ScopeApiController::class, 'name' => 'scope', 'base_path' => '/api/1.0/main/scope', 'base_role' => 'ROLE_USER', @@ -678,6 +733,20 @@ class ChillMainExtension extends Extension implements ], ], ], + [ + 'class' => GeographicalUnitLayer::class, + 'name' => 'geographical-unit-layer', + 'base_path' => '/api/1.0/main/geographical-unit-layer', + 'base_role' => 'ROLE_USER', + 'actions' => [ + '_index' => [ + 'methods' => [ + Request::METHOD_GET => true, + Request::METHOD_HEAD => true, + ], + ], + ], + ] ], ]); } diff --git a/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/ACLFlagsCompilerPass.php b/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/ACLFlagsCompilerPass.php index b8b3b4d85..40baccfc0 100644 --- a/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/ACLFlagsCompilerPass.php +++ b/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/ACLFlagsCompilerPass.php @@ -1,14 +1,14 @@ has('Chill\MainBundle\Export\ExportManager')) { - throw new LogicException('service Chill\MainBundle\Export\ExportManager ' + if (!$container->has(ExportManager::class)) { + throw new LogicException('service ' . ExportManager::class . ' ' . 'is not defined. It is required by ExportsCompilerPass'); } $chillManagerDefinition = $container->findDefinition( - 'Chill\MainBundle\Export\ExportManager' + ExportManager::class ); - $this->compileExports($chillManagerDefinition, $container); - $this->compileFilters($chillManagerDefinition, $container); - $this->compileAggregators($chillManagerDefinition, $container); $this->compileFormatters($chillManagerDefinition, $container); $this->compileExportElementsProvider($chillManagerDefinition, $container); } - private function compileAggregators( - Definition $chillManagerDefinition, - ContainerBuilder $container - ) { - $taggedServices = $container->findTaggedServiceIds( - 'chill.export_aggregator' - ); - - $knownAliases = []; - - foreach ($taggedServices as $id => $tagAttributes) { - foreach ($tagAttributes as $attributes) { - if (!isset($attributes['alias'])) { - throw new LogicException("the 'alias' attribute is missing in your " . - "service '{$id}' definition"); - } - - if (array_search($attributes['alias'], $knownAliases, true)) { - throw new LogicException('There is already a chill.export_aggregator service with alias ' - . $attributes['alias'] . '. Choose another alias.'); - } - $knownAliases[] = $attributes['alias']; - - $chillManagerDefinition->addMethodCall( - 'addAggregator', - [new Reference($id), $attributes['alias']] - ); - } - } - } - private function compileExportElementsProvider( Definition $chillManagerDefinition, ContainerBuilder $container @@ -108,68 +75,6 @@ class ExportsCompilerPass implements CompilerPassInterface } } - private function compileExports( - Definition $chillManagerDefinition, - ContainerBuilder $container - ) { - $taggedServices = $container->findTaggedServiceIds( - 'chill.export' - ); - - $knownAliases = []; - - foreach ($taggedServices as $id => $tagAttributes) { - foreach ($tagAttributes as $attributes) { - if (!isset($attributes['alias'])) { - throw new LogicException("the 'alias' attribute is missing in your " . - "service '{$id}' definition"); - } - - if (array_search($attributes['alias'], $knownAliases, true)) { - throw new LogicException('There is already a chill.export service with alias ' - . $attributes['alias'] . '. Choose another alias.'); - } - $knownAliases[] = $attributes['alias']; - - $chillManagerDefinition->addMethodCall( - 'addExport', - [new Reference($id), $attributes['alias']] - ); - } - } - } - - private function compileFilters( - Definition $chillManagerDefinition, - ContainerBuilder $container - ) { - $taggedServices = $container->findTaggedServiceIds( - 'chill.export_filter' - ); - - $knownAliases = []; - - foreach ($taggedServices as $id => $tagAttributes) { - foreach ($tagAttributes as $attributes) { - if (!isset($attributes['alias'])) { - throw new LogicException("the 'alias' attribute is missing in your " . - "service '{$id}' definition"); - } - - if (array_search($attributes['alias'], $knownAliases, true)) { - throw new LogicException('There is already a chill.export_filter service with alias ' - . $attributes['alias'] . '. Choose another alias.'); - } - $knownAliases[] = $attributes['alias']; - - $chillManagerDefinition->addMethodCall( - 'addFilter', - [new Reference($id), $attributes['alias']] - ); - } - } - } - private function compileFormatters( Definition $chillManagerDefinition, ContainerBuilder $container diff --git a/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/GroupingCenterCompilerPass.php b/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/GroupingCenterCompilerPass.php deleted file mode 100644 index 9b5d0903c..000000000 --- a/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/GroupingCenterCompilerPass.php +++ /dev/null @@ -1,37 +0,0 @@ -hasDefinition('chill.main.form.pick_centers_type')) { - throw new LogicException('The service chill.main.form.pick_centers_type does ' - . 'not exists in container'); - } - - $pickCenterType = $container->getDefinition('chill.main.form.pick_centers_type'); - - foreach ($container->findTaggedServiceIds('chill.grouping_center') as $serviceId => $tagged) { - $pickCenterType->addMethodCall( - 'addGroupingCenter', - [new Reference($serviceId)] - ); - } - } -} diff --git a/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/MenuCompilerPass.php b/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/MenuCompilerPass.php index 7a63412d5..d6a27c943 100644 --- a/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/MenuCompilerPass.php +++ b/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/MenuCompilerPass.php @@ -1,14 +1,14 @@ $a['priority'] <=> $b['priority']); foreach ($services as $service) { $class = $container->getDefinition($service['id'])->getClass(); diff --git a/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/NotificationCounterCompilerPass.php b/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/NotificationCounterCompilerPass.php index 36435400c..ed8c2cf03 100644 --- a/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/NotificationCounterCompilerPass.php +++ b/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/NotificationCounterCompilerPass.php @@ -1,14 +1,14 @@ resolveEnvPlaceholders($container->getParameter('chill_main.short_messages'), true); + // weird fix for special characters + $config['dsn'] = str_replace(['%%'], ['%'], $config['dsn']); + $dsn = parse_url($config['dsn']); + parse_str($dsn['query'] ?? '', $dsn['queries']); + + if ('null' === $dsn['scheme'] || false === $config['enabled']) { + $defaultTransporter = new Reference(NullShortMessageSender::class); + } elseif ('ovh' === $dsn['scheme']) { + if (!class_exists('\\' . \Ovh\Api::class)) { + throw new RuntimeException('Class \\Ovh\\Api not found'); + } + + foreach (['user', 'host', 'pass'] as $component) { + if (!array_key_exists($component, $dsn)) { + throw new RuntimeException(sprintf('The component %s does not exist in dsn. Please provide a dsn ' . + 'like ovh://applicationKey:applicationSecret@endpoint?consumerKey=xxxx&sender=yyyy&service_name=zzzz', $component)); + } + + $container->setParameter('chill_main.short_messages.ovh_config_' . $component, $dsn[$component]); + } + + foreach (['consumer_key', 'sender', 'service_name'] as $param) { + if (!array_key_exists($param, $dsn['queries'])) { + throw new RuntimeException(sprintf('The parameter %s does not exist in dsn. Please provide a dsn ' . + 'like ovh://applicationKey:applicationSecret@endpoint?consumerKey=xxxx&sender=yyyy&service_name=zzzz', $param)); + } + $container->setParameter('chill_main.short_messages.ovh_config_' . $param, $dsn['queries'][$param]); + } + + $ovh = new Definition(); + $ovh + ->setClass('\\' . \Ovh\Api::class) + ->setArgument(0, $dsn['user']) + ->setArgument(1, $dsn['pass']) + ->setArgument(2, $dsn['host']) + ->setArgument(3, $dsn['queries']['consumer_key']); + $container->setDefinition(\Ovh\Api::class, $ovh); + + $ovhSender = new Definition(); + $ovhSender + ->setClass(OvhShortMessageSender::class) + ->setArgument(0, new Reference(\Ovh\Api::class)) + ->setArgument(1, $dsn['queries']['service_name']) + ->setArgument(2, $dsn['queries']['sender']) + ->setArgument(3, new Reference(LoggerInterface::class)) + ->setArgument(4, new Reference(PhoneNumberUtil::class)); + $container->setDefinition(OvhShortMessageSender::class, $ovhSender); + + $defaultTransporter = new Reference(OvhShortMessageSender::class); + } else { + throw new RuntimeException(sprintf('Cannot find a sender for this dsn: %s', $config['dsn'])); + } + + $container->getDefinition(ShortMessageTransporter::class) + ->setArgument(0, $defaultTransporter); + } +} diff --git a/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/TimelineCompilerClass.php b/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/TimelineCompilerClass.php index 8b9b83fb6..56b4f0ef3 100644 --- a/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/TimelineCompilerClass.php +++ b/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/TimelineCompilerClass.php @@ -1,14 +1,14 @@ getRootNode('chill_main'); + $rootNode = $treeBuilder->getRootNode(); $rootNode ->children() @@ -102,6 +102,14 @@ class Configuration implements ConfigurationInterface ->end() ->end() ->end() + ->arrayNode('short_messages') + ->canBeEnabled() + ->children() + ->scalarNode('dsn')->cannotBeEmpty()->defaultValue('null://null') + ->info('the dsn for sending short message. Example: ovh://applicationKey:secret@endpoint') + ->end() + ->end() + ->end() // end for 'short_messages' ->arrayNode('acl') ->addDefaultsIfNotSet() ->children() @@ -266,7 +274,17 @@ class Configuration implements ConfigurationInterface ->end() ->end() // end of root/children ->end() // end of root -; + ; + + $rootNode->children() + ->arrayNode('add_address')->addDefaultsIfNotSet()->children() + ->scalarNode('default_country')->cannotBeEmpty()->defaultValue('BE')->end() + ->arrayNode('map_center')->children() + ->scalarNode('x')->cannotBeEmpty()->defaultValue(50.8443)->end() + ->scalarNode('y')->cannotBeEmpty()->defaultValue(4.3523)->end() + ->scalarNode('z')->cannotBeEmpty()->defaultValue(15)->end() + ->end() + ->end(); return $treeBuilder; } diff --git a/src/Bundle/ChillMainBundle/DependencyInjection/MissingBundleException.php b/src/Bundle/ChillMainBundle/DependencyInjection/MissingBundleException.php index 54961699a..f50a97636 100644 --- a/src/Bundle/ChillMainBundle/DependencyInjection/MissingBundleException.php +++ b/src/Bundle/ChillMainBundle/DependencyInjection/MissingBundleException.php @@ -1,14 +1,14 @@ widgetServices[$alias]; // get the config (under the key which equals to widget_alias - $config = isset($param[$factory->getWidgetAlias()]) ? - $param[$factory->getWidgetAlias()] : []; + $config = $param[$factory->getWidgetAlias()] ?? []; // register the service into the container $serviceId = $this->registerServiceIntoContainer( $container, diff --git a/src/Bundle/ChillMainBundle/DependencyInjection/Widget/AddWidgetConfigurationTrait.php b/src/Bundle/ChillMainBundle/DependencyInjection/Widget/AddWidgetConfigurationTrait.php index 2b2d0f001..41ec16818 100644 --- a/src/Bundle/ChillMainBundle/DependencyInjection/Widget/AddWidgetConfigurationTrait.php +++ b/src/Bundle/ChillMainBundle/DependencyInjection/Widget/AddWidgetConfigurationTrait.php @@ -1,14 +1,14 @@ getRootNode($place) + $root = $treeBuilder->getRootNode() ->canBeUnset() ->info('register widgets on place "' . $place . '"'); @@ -174,7 +170,7 @@ trait AddWidgetConfigurationTrait // adding the possible config on each widget under the widget_alias foreach ($this->filterWidgetByPlace($place) as $factory) { $builder = new TreeBuilder($factory->getWidgetAlias()); - $widgetOptionsRoot = $builder->getRootNode($factory->getWidgetAlias()); + $widgetOptionsRoot = $builder->getRootNode(); $widgetOptionsRoot->canBeUnset() ->info(sprintf( 'the configuration for the widget "%s" (only required if this widget is set in widget_alias)', @@ -215,7 +211,7 @@ trait AddWidgetConfigurationTrait * * @throws InvalidConfigurationException if a service's tag does not have the "alias" key * - * @return type + * @return array */ protected function getWidgetAliasesbyPlace($place, ContainerBuilder $containerBuilder) { diff --git a/src/Bundle/ChillMainBundle/DependencyInjection/Widget/Factory/AbstractWidgetFactory.php b/src/Bundle/ChillMainBundle/DependencyInjection/Widget/Factory/AbstractWidgetFactory.php index 242b46e9c..b0c14f27e 100644 --- a/src/Bundle/ChillMainBundle/DependencyInjection/Widget/Factory/AbstractWidgetFactory.php +++ b/src/Bundle/ChillMainBundle/DependencyInjection/Widget/Factory/AbstractWidgetFactory.php @@ -1,14 +1,14 @@ value2) { + return sprintf( + 'AGE(%s, %s)', + $this->value1->dispatch($sqlWalker), + $this->value2->dispatch($sqlWalker) + ); + } + + return sprintf( + 'AGE(%s)', + $this->value1->dispatch($sqlWalker), + ); + } + + public function parse(Parser $parser) + { + $parser->match(Lexer::T_IDENTIFIER); + $parser->match(Lexer::T_OPEN_PARENTHESIS); + + $this->value1 = $parser->SimpleArithmeticExpression(); + + $parser->match(Lexer::T_COMMA); + + $this->value2 = $parser->SimpleArithmeticExpression(); + + $parser->match(Lexer::T_CLOSE_PARENTHESIS); + } +} diff --git a/src/Bundle/ChillMainBundle/Doctrine/DQL/Extract.php b/src/Bundle/ChillMainBundle/Doctrine/DQL/Extract.php new file mode 100644 index 000000000..e224c2a0c --- /dev/null +++ b/src/Bundle/ChillMainBundle/Doctrine/DQL/Extract.php @@ -0,0 +1,61 @@ + EXTRACT(field FROM interval) + */ +class Extract extends FunctionNode +{ + private string $field; + + private $value; + //private PathExpression $value; + //private FunctionNode $value; + //private DateDiffFunction $value; + + public function getSql(SqlWalker $sqlWalker) + { + return sprintf( + 'EXTRACT(%s FROM %s)', + $this->field, + $this->value->dispatch($sqlWalker) + ); + } + + public function parse(Parser $parser) + { + $parser->match(Lexer::T_IDENTIFIER); + $parser->match(Lexer::T_OPEN_PARENTHESIS); + + $parser->match(Lexer::T_IDENTIFIER); + $this->field = $parser->getLexer()->token['value']; + + $parser->match(Lexer::T_FROM); + + //$this->value = $parser->ScalarExpression(); + $this->value = $parser->ArithmeticPrimary(); + + $parser->match(Lexer::T_CLOSE_PARENTHESIS); + } +} diff --git a/src/Bundle/ChillMainBundle/Doctrine/DQL/GetJsonFieldByKey.php b/src/Bundle/ChillMainBundle/Doctrine/DQL/GetJsonFieldByKey.php index d57cc866d..69b26e982 100644 --- a/src/Bundle/ChillMainBundle/Doctrine/DQL/GetJsonFieldByKey.php +++ b/src/Bundle/ChillMainBundle/Doctrine/DQL/GetJsonFieldByKey.php @@ -1,14 +1,14 @@ $expr->dispatch($sqlWalker), $this->exprs)) . ')'; + } + + public function parse(Parser $parser) + { + $this->exprs = []; + + $lexer = $parser->getLexer(); + $parser->match(Lexer::T_IDENTIFIER); + $parser->match(Lexer::T_OPEN_PARENTHESIS); + $this->exprs[] = $parser->ArithmeticPrimary(); + + while (Lexer::T_COMMA === $lexer->lookahead['type']) { + $parser->match(Lexer::T_COMMA); + $this->exprs[] = $parser->ArithmeticPrimary(); + } + + $parser->match(Lexer::T_CLOSE_PARENTHESIS); + } +} diff --git a/src/Bundle/ChillMainBundle/Doctrine/DQL/JsonAggregate.php b/src/Bundle/ChillMainBundle/Doctrine/DQL/JsonAggregate.php index a678f8c27..c9a138c20 100644 --- a/src/Bundle/ChillMainBundle/Doctrine/DQL/JsonAggregate.php +++ b/src/Bundle/ChillMainBundle/Doctrine/DQL/JsonAggregate.php @@ -1,14 +1,14 @@ >%s', $this->element->dispatch($sqlWalker), $this->keyToExtract->dispatch($sqlWalker)); + } + + public function parse(Parser $parser) + { + $parser->match(Lexer::T_IDENTIFIER); + $parser->match(Lexer::T_OPEN_PARENTHESIS); + + $this->element = $parser->ArithmeticPrimary(); + + $parser->match(Lexer::T_COMMA); + + $this->keyToExtract = $parser->ArithmeticExpression(); + + $parser->match(Lexer::T_CLOSE_PARENTHESIS); + } +} diff --git a/src/Bundle/ChillMainBundle/Doctrine/DQL/JsonbArrayLength.php b/src/Bundle/ChillMainBundle/Doctrine/DQL/JsonbArrayLength.php index 680d5c6f4..2fc7c7e17 100644 --- a/src/Bundle/ChillMainBundle/Doctrine/DQL/JsonbArrayLength.php +++ b/src/Bundle/ChillMainBundle/Doctrine/DQL/JsonbArrayLength.php @@ -1,14 +1,14 @@ expr1->dispatch($sqlWalker), - $sqlWalker->walkInputParameter($this->expr2) + $this->expr2->dispatch($sqlWalker) ); } diff --git a/src/Bundle/ChillMainBundle/Doctrine/DQL/Least.php b/src/Bundle/ChillMainBundle/Doctrine/DQL/Least.php new file mode 100644 index 000000000..9befa5f40 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Doctrine/DQL/Least.php @@ -0,0 +1,55 @@ + $expr->dispatch($sqlWalker), $this->exprs)) . ')'; + } + + public function parse(Parser $parser) + { + $this->exprs = []; + + $lexer = $parser->getLexer(); + $parser->match(Lexer::T_IDENTIFIER); + $parser->match(Lexer::T_OPEN_PARENTHESIS); + $this->exprs[] = $parser->ArithmeticPrimary(); + + while (Lexer::T_COMMA === $lexer->lookahead['type']) { + $parser->match(Lexer::T_COMMA); + $this->exprs[] = $parser->ArithmeticPrimary(); + } + + $parser->match(Lexer::T_CLOSE_PARENTHESIS); + } +} diff --git a/src/Bundle/ChillMainBundle/Doctrine/DQL/OverlapsI.php b/src/Bundle/ChillMainBundle/Doctrine/DQL/OverlapsI.php index 52b39e97f..29642dc15 100644 --- a/src/Bundle/ChillMainBundle/Doctrine/DQL/OverlapsI.php +++ b/src/Bundle/ChillMainBundle/Doctrine/DQL/OverlapsI.php @@ -1,14 +1,14 @@ field->dispatch($sqlWalker)); + } + + public function parse(Parser $parser) + { + $parser->match(Lexer::T_IDENTIFIER); + $parser->match(Lexer::T_OPEN_PARENTHESIS); + + $this->field = $parser->ArithmeticExpression(); + + $parser->match(Lexer::T_CLOSE_PARENTHESIS); + } +} diff --git a/src/Bundle/ChillMainBundle/Doctrine/DQL/STY.php b/src/Bundle/ChillMainBundle/Doctrine/DQL/STY.php new file mode 100644 index 000000000..e827da543 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Doctrine/DQL/STY.php @@ -0,0 +1,37 @@ +field->dispatch($sqlWalker)); + } + + public function parse(Parser $parser) + { + $parser->match(Lexer::T_IDENTIFIER); + $parser->match(Lexer::T_OPEN_PARENTHESIS); + + $this->field = $parser->ArithmeticExpression(); + + $parser->match(Lexer::T_CLOSE_PARENTHESIS); + } +} diff --git a/src/Bundle/ChillMainBundle/Doctrine/DQL/Similarity.php b/src/Bundle/ChillMainBundle/Doctrine/DQL/Similarity.php index d6a3c1238..e8b2bff1e 100644 --- a/src/Bundle/ChillMainBundle/Doctrine/DQL/Similarity.php +++ b/src/Bundle/ChillMainBundle/Doctrine/DQL/Similarity.php @@ -1,14 +1,14 @@ walkArithmeticPrimary($this->datetime), + $sqlWalker->walkArithmeticPrimary($this->fmt) + ); + } + + public function parse(Parser $parser) + { + $parser->match(Lexer::T_IDENTIFIER); + $parser->match(Lexer::T_OPEN_PARENTHESIS); + $this->datetime = $parser->ArithmeticExpression(); + $parser->match(Lexer::T_COMMA); + $this->fmt = $parser->StringExpression(); + $parser->match(Lexer::T_CLOSE_PARENTHESIS); + } +} diff --git a/src/Bundle/ChillMainBundle/Doctrine/DQL/Unaccent.php b/src/Bundle/ChillMainBundle/Doctrine/DQL/Unaccent.php index 799b208bb..552f1e557 100644 --- a/src/Bundle/ChillMainBundle/Doctrine/DQL/Unaccent.php +++ b/src/Bundle/ChillMainBundle/Doctrine/DQL/Unaccent.php @@ -1,14 +1,14 @@ getObject(); - if ( - $object instanceof TrackCreationInterface - && $this->security->getUser() instanceof User - ) { - $object->setCreatedBy($this->security->getUser()); + if ($object instanceof TrackCreationInterface) { $object->setCreatedAt(new DateTimeImmutable('now')); + + if ($this->security->getUser() instanceof User) { + $object->setCreatedBy($this->security->getUser()); + } } $this->onUpdate($object); @@ -61,12 +61,12 @@ class TrackCreateUpdateSubscriber implements EventSubscriber protected function onUpdate(object $object): void { - if ( - $object instanceof TrackUpdateInterface - && $this->security->getUser() instanceof User - ) { - $object->setUpdatedBy($this->security->getUser()); + if ($object instanceof TrackUpdateInterface) { $object->setUpdatedAt(new DateTimeImmutable('now')); + + if ($this->security->getUser() instanceof User) { + $object->setUpdatedBy($this->security->getUser()); + } } } } diff --git a/src/Bundle/ChillMainBundle/Doctrine/Migrations/VersionComparator.php b/src/Bundle/ChillMainBundle/Doctrine/Migrations/VersionComparator.php index aafa852f6..96fc12558 100644 --- a/src/Bundle/ChillMainBundle/Doctrine/Migrations/VersionComparator.php +++ b/src/Bundle/ChillMainBundle/Doctrine/Migrations/VersionComparator.php @@ -1,14 +1,14 @@ toArrayGeoJson(); - return json_encode($array); + return json_encode($array, JSON_THROW_ON_ERROR); } public function toWKT(): string diff --git a/src/Bundle/ChillMainBundle/Doctrine/Model/PointException.php b/src/Bundle/ChillMainBundle/Doctrine/Model/PointException.php index 5186e8168..e99e84908 100644 --- a/src/Bundle/ChillMainBundle/Doctrine/Model/PointException.php +++ b/src/Bundle/ChillMainBundle/Doctrine/Model/PointException.php @@ -1,14 +1,14 @@ format(self::FORMAT); } - throw ConversionException::conversionFailedInvalidType($value, $this->getName(), ['null', 'DateInterval']); + throw ConversionException::conversionFailedInvalidType($value, 'string', ['null', 'DateInterval']); } public function convertToPHPValue($value, AbstractPlatform $platform) @@ -80,7 +81,7 @@ class NativeDateIntervalType extends DateIntervalType protected function createConversionException($value, $exception = null) { - return ConversionException::conversionFailedFormat($value, $this->getName(), 'xx year xx mons xx days 01:02:03', $exception); + return ConversionException::conversionFailedFormat($value, 'string', 'xx year xx mons xx days 01:02:03', $exception); } private function convertEntry(&$strings) @@ -125,5 +126,7 @@ class NativeDateIntervalType extends DateIntervalType return $intervalSpec; } + + throw new LogicException(); } } diff --git a/src/Bundle/ChillMainBundle/Doctrine/Type/PointType.php b/src/Bundle/ChillMainBundle/Doctrine/Type/PointType.php index 1d44b2358..3f74c62a3 100644 --- a/src/Bundle/ChillMainBundle/Doctrine/Type/PointType.php +++ b/src/Bundle/ChillMainBundle/Doctrine/Type/PointType.php @@ -1,14 +1,14 @@ |GeographicalUnit[] + * @readonly + * @ORM\ManyToMany(targetEntity=GeographicalUnit::class) + * @ORM\JoinTable( + * name="view_chill_main_address_geographical_unit", + * joinColumns={@ORM\JoinColumn(name="address_id")}, + * inverseJoinColumns={@ORM\JoinColumn(name="geographical_unit_id")} + * ) + */ + private Collection $geographicalUnits; /** * @var int @@ -104,18 +128,17 @@ class Address * @ORM\Column(name="id", type="integer") * @ORM\GeneratedValue(strategy="AUTO") * @Groups({"write"}) + * @readonly */ - private $id; + private ?int $id = null; /** * True if the address is a "no address", aka homeless person, ... * * @Groups({"write"}) - * @ORM\Column(type="boolean") - * - * @var bool + * @ORM\Column(type="boolean", options={"default": false}) */ - private $isNoAddress = false; + private bool $isNoAddress = false; /** * A ThirdParty reference for person's addresses that are linked to a third party. @@ -126,7 +149,7 @@ class Address * @Groups({"write"}) * @ORM\JoinColumn(nullable=true, onDelete="SET NULL") */ - private $linkedToThirdParty; + private ?ThirdParty $linkedToThirdParty = null; /** * A geospatial field storing the coordinates of the Address. @@ -136,7 +159,7 @@ class Address * @ORM\Column(type="point", nullable=true) * @Groups({"write"}) */ - private $point; + private ?Point $point = null; /** * @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\PostalCode") @@ -146,28 +169,36 @@ class Address private ?PostalCode $postcode = null; /** - * @var string|null - * - * @ORM\Column(type="string", length=255, nullable=true) - * @Groups({"write"}) + * @var self::ADDR_REFERENCE_STATUS_* + * @ORM\Column(type="text", nullable=false, options={"default": self::ADDR_REFERENCE_STATUS_MATCH}) */ - private $steps; + private string $refStatus = self::ADDR_REFERENCE_STATUS_MATCH; /** - * @var string - * - * @ORM\Column(type="string", length=255) - * @Groups({"write"}) + * @ORM\Column(type="datetime_immutable", nullable=false, options={"default": "CURRENT_TIMESTAMP"}) */ - private $street = ''; + private \DateTimeImmutable $refStatusLastUpdate; /** - * @var string * - * @ORM\Column(type="string", length=255) + * @ORM\Column(type="text", nullable=false, options={"default": ""}) * @Groups({"write"}) */ - private $streetNumber = ''; + private string $steps = ''; + + /** + * + * @ORM\Column(type="text", nullable=false, options={"default": ""}) + * @Groups({"write"}) + */ + private string $street = ''; + + /** + * + * @ORM\Column(type="text", nullable=false, options={"default": ""}) + * @Groups({"write"}) + */ + private string $streetNumber = ''; /** * Indicates when the address starts validation. Used to build an history @@ -190,6 +221,8 @@ class Address public function __construct() { $this->validFrom = new DateTime(); + $this->refStatusLastUpdate = new \DateTimeImmutable('now'); + $this->geographicalUnits = new ArrayCollection(); } public static function createFromAddress(Address $original): Address @@ -199,7 +232,6 @@ class Address ->setBuildingName($original->getBuildingName()) ->setConfidential($original->getConfidential()) ->setCorridor($original->getCorridor()) - ->setCustoms($original->getCustoms()) ->setDistribution($original->getDistribution()) ->setExtra($original->getExtra()) ->setFlat($original->getFlat()) @@ -218,11 +250,20 @@ class Address public static function createFromAddressReference(AddressReference $original): Address { return (new Address()) - ->setPoint($original->getPoint()) - ->setPostcode($original->getPostcode()) - ->setStreet($original->getStreet()) - ->setStreetNumber($original->getStreetNumber()) - ->setAddressReference($original); + ->syncWithReference($original); + } + + public function syncWithReference(AddressReference $addressReference): Address + { + $this + ->setPoint($addressReference->getPoint()) + ->setPostcode($addressReference->getPostcode()) + ->setStreet($addressReference->getStreet()) + ->setStreetNumber($addressReference->getStreetNumber()) + ->setRefStatus(self::ADDR_REFERENCE_STATUS_MATCH) + ->setAddressReference($addressReference); + + return $this; } public function getAddressReference(): ?AddressReference @@ -230,7 +271,7 @@ class Address return $this->addressReference; } - public function getBuildingName(): ?string + public function getBuildingName(): string { return $this->buildingName; } @@ -240,39 +281,39 @@ class Address return $this->confidential; } - public function getCorridor(): ?string + public function getCorridor(): string { return $this->corridor; } - /** - * Get customs informations in the address. - */ - public function getCustoms(): array - { - return $this->customs; - } - - public function getDistribution(): ?string + public function getDistribution(): string { return $this->distribution; } - public function getExtra(): ?string + public function getExtra(): string { return $this->extra; } - public function getFlat(): ?string + public function getFlat(): string { return $this->flat; } - public function getFloor(): ?string + public function getFloor(): string { return $this->floor; } + /** + * @return Collection|GeographicalUnit[] + */ + public function getGeographicalUnits(): Collection + { + return $this->geographicalUnits; + } + /** * Get id. * @@ -311,12 +352,22 @@ class Address return $this->postcode; } - public function getSteps(): ?string + public function getRefStatus(): string + { + return $this->refStatus; + } + + public function getRefStatusLastUpdate(): \DateTimeImmutable + { + return $this->refStatusLastUpdate; + } + + public function getSteps(): string { return $this->steps; } - public function getStreet(): ?string + public function getStreet(): string { return $this->street; } @@ -325,6 +376,7 @@ class Address * Get streetAddress1 (legacy function). * * @return string + * @deprecated */ public function getStreetAddress1() { @@ -335,13 +387,14 @@ class Address * Get streetAddress2 (legacy function). * * @return string + * @deprecated */ public function getStreetAddress2() { return $this->streetNumber; } - public function getStreetNumber(): ?string + public function getStreetNumber(): string { return $this->streetNumber; } @@ -349,7 +402,7 @@ class Address /** * @return DateTime */ - public function getValidFrom() + public function getValidFrom(): DateTime { return $this->validFrom; } @@ -359,6 +412,11 @@ class Address return $this->validTo; } + public function hasAddressReference(): bool + { + return null !== $this->getAddressReference(); + } + public function isNoAddress(): bool { return $this->getIsNoAddress(); @@ -373,7 +431,7 @@ class Address public function setBuildingName(?string $buildingName): self { - $this->buildingName = $buildingName; + $this->buildingName = (string) $buildingName; return $this; } @@ -387,47 +445,35 @@ class Address public function setCorridor(?string $corridor): self { - $this->corridor = $corridor; - - return $this; - } - - /** - * Store custom informations in the address. - * - * @return $this - */ - public function setCustoms(array $customs): self - { - $this->customs = $customs; + $this->corridor = (string) $corridor; return $this; } public function setDistribution(?string $distribution): self { - $this->distribution = $distribution; + $this->distribution = (string) $distribution; return $this; } public function setExtra(?string $extra): self { - $this->extra = $extra; + $this->extra = (string) $extra; return $this; } public function setFlat(?string $flat): self { - $this->flat = $flat; + $this->flat = (string) $flat; return $this; } public function setFloor(?string $floor): self { - $this->floor = $floor; + $this->floor = (string) $floor; return $this; } @@ -474,19 +520,44 @@ class Address return $this; } + /** + * Update the ref status + * + * <<<<<<< HEAD + * @param Address::ADDR_REFERENCE_STATUS_* $refStatus + * @param bool|null $updateLastUpdate Also update the "refStatusLastUpdate" + * ======= + * The refstatuslast update is also updated + * >>>>>>> 31152616d (Feature: Provide api endpoint for reviewing addresses) + */ + public function setRefStatus(string $refStatus, ?bool $updateLastUpdate = true): self + { + $this->refStatus = $refStatus; + + if ($updateLastUpdate) { + $this->setRefStatusLastUpdate(new \DateTimeImmutable('now')); + } + + return $this; + } + + public function setRefStatusLastUpdate(\DateTimeImmutable $refStatusLastUpdate): self + { + $this->refStatusLastUpdate = $refStatusLastUpdate; + + return $this; + } + public function setSteps(?string $steps): self { - $this->steps = $steps; + $this->steps = (string) $steps; return $this; } public function setStreet(?string $street): self { - if (null === $street) { - $street = ''; - } - $this->street = $street; + $this->street = (string) $street; return $this; } @@ -497,10 +568,11 @@ class Address * @param string $streetAddress1 * * @return Address + * @deprecated */ - public function setStreetAddress1($streetAddress1) + public function setStreetAddress1(?string $streetAddress1): self { - $this->street = null === $streetAddress1 ? '' : $streetAddress1; + $this->street = (string) $streetAddress1; return $this; } @@ -509,22 +581,19 @@ class Address * Set streetAddress2 (legacy function). * * @param string $streetAddress2 - * + * @deprecated * @return Address */ - public function setStreetAddress2($streetAddress2) + public function setStreetAddress2(?string $streetAddress2): self { - $this->streetNumber = null === $streetAddress2 ? '' : $streetAddress2; + $this->streetNumber = (string) $streetAddress2; return $this; } public function setStreetNumber(?string $streetNumber): self { - if (null === $streetNumber) { - $streetNumber = ''; - } - $this->streetNumber = $streetNumber; + $this->streetNumber = (string) $streetNumber; return $this; } @@ -571,7 +640,7 @@ class Address return; } - if (empty($this->getStreetAddress1())) { + if ('' === $this->getStreet()) { $context ->buildViolation('address.street1-should-be-set') ->atPath('streetAddress1') diff --git a/src/Bundle/ChillMainBundle/Entity/AddressReference.php b/src/Bundle/ChillMainBundle/Entity/AddressReference.php index fc4339fe0..e75402c5f 100644 --- a/src/Bundle/ChillMainBundle/Entity/AddressReference.php +++ b/src/Bundle/ChillMainBundle/Entity/AddressReference.php @@ -1,14 +1,14 @@ id; } - public function getMunicipalityCode(): ?string + public function getMunicipalityCode(): string { return $this->municipalityCode; } @@ -138,27 +141,27 @@ class AddressReference * * @return PostalCode */ - public function getPostcode() + public function getPostcode(): ?PostalCode { return $this->postcode; } - public function getRefId(): ?string + public function getRefId(): string { return $this->refId; } - public function getSource(): ?string + public function getSource(): string { return $this->source; } - public function getStreet(): ?string + public function getStreet(): string { return $this->street; } - public function getStreetNumber(): ?string + public function getStreetNumber(): string { return $this->streetNumber; } @@ -168,6 +171,11 @@ class AddressReference return $this->updatedAt; } + public function hasPoint(): bool + { + return null !== $this->getPoint(); + } + public function setCreatedAt(?DateTimeImmutable $createdAt): self { $this->createdAt = $createdAt; @@ -184,7 +192,7 @@ class AddressReference public function setMunicipalityCode(?string $municipalityCode): self { - $this->municipalityCode = $municipalityCode; + $this->municipalityCode = (string) $municipalityCode; return $this; } @@ -219,21 +227,21 @@ class AddressReference public function setSource(?string $source): self { - $this->source = $source; + $this->source = (string) $source; return $this; } public function setStreet(?string $street): self { - $this->street = $street; + $this->street = (string) $street; return $this; } public function setStreetNumber(?string $streetNumber): self { - $this->streetNumber = $streetNumber; + $this->streetNumber = (string) $streetNumber; return $this; } diff --git a/src/Bundle/ChillMainBundle/Entity/Center.php b/src/Bundle/ChillMainBundle/Entity/Center.php index 5b25ee119..5ca051ec0 100644 --- a/src/Bundle/ChillMainBundle/Entity/Center.php +++ b/src/Bundle/ChillMainBundle/Entity/Center.php @@ -1,14 +1,14 @@ * * @ORM\Column(type="json") * @groups({"read", "docgen:read"}) * @Context({"is-translatable": true}, groups={"docgen:read"}) */ - private $name; + private array $name = []; - /** - * @return string - */ - public function __toString() - { - return $this->getName(); - } - - /** - * @return the string - */ - public function getCountryCode() + public function getCountryCode(): string { return $this->countryCode; } - /** - * Get id. - * - * @return int - */ - public function getId() + public function getId(): ?int { return $this->id; } @@ -80,31 +64,23 @@ class Country /** * Get name. * - * @return string */ - public function getName() + public function getName(): array { return $this->name; } - /** - * @param string $countryCode - */ - public function setCountryCode($countryCode) + public function setCountryCode(?string $countryCode): self { - $this->countryCode = $countryCode; + $this->countryCode = (string) $countryCode; return $this; } /** - * Set name. - * - * @param string $name - * - * @return Country + * @param array $name */ - public function setName($name) + public function setName(array $name): self { $this->name = $name; diff --git a/src/Bundle/ChillMainBundle/Entity/CronJobExecution.php b/src/Bundle/ChillMainBundle/Entity/CronJobExecution.php new file mode 100644 index 000000000..0cacffac9 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Entity/CronJobExecution.php @@ -0,0 +1,95 @@ +key = $key; + $this->lastStart = new DateTimeImmutable('now'); + } + + public function getKey(): string + { + return $this->key; + } + + public function getLastEnd(): DateTimeImmutable + { + return $this->lastEnd; + } + + public function getLastStart(): DateTimeImmutable + { + return $this->lastStart; + } + + public function getLastStatus(): ?int + { + return $this->lastStatus; + } + + public function setLastEnd(?DateTimeImmutable $lastEnd): CronJobExecution + { + $this->lastEnd = $lastEnd; + + return $this; + } + + public function setLastStart(DateTimeImmutable $lastStart): CronJobExecution + { + $this->lastStart = $lastStart; + + return $this; + } + + public function setLastStatus(?int $lastStatus): CronJobExecution + { + $this->lastStatus = $lastStatus; + + return $this; + } +} diff --git a/src/Bundle/ChillMainBundle/Entity/Embeddable/CommentEmbeddable.php b/src/Bundle/ChillMainBundle/Entity/Embeddable/CommentEmbeddable.php index a29ad8f29..f404d5931 100644 --- a/src/Bundle/ChillMainBundle/Entity/Embeddable/CommentEmbeddable.php +++ b/src/Bundle/ChillMainBundle/Entity/Embeddable/CommentEmbeddable.php @@ -1,14 +1,14 @@ date; } - /** - * @return interger $userId - */ - public function getUserId() + public function getUserId(): ?int { return $this->userId; } diff --git a/src/Bundle/ChillMainBundle/Entity/Embeddable/PrivateCommentEmbeddable.php b/src/Bundle/ChillMainBundle/Entity/Embeddable/PrivateCommentEmbeddable.php index 00e0f29d7..44e53671d 100644 --- a/src/Bundle/ChillMainBundle/Entity/Embeddable/PrivateCommentEmbeddable.php +++ b/src/Bundle/ChillMainBundle/Entity/Embeddable/PrivateCommentEmbeddable.php @@ -1,14 +1,14 @@ getComments() ? [] : $this->getComments(); + $currentComments = $this->getComments() ?? []; $mergedComments = $newComment->getComments() + $currentComments; diff --git a/src/Bundle/ChillMainBundle/Entity/GeographicalUnit.php b/src/Bundle/ChillMainBundle/Entity/GeographicalUnit.php new file mode 100644 index 000000000..ea017f053 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Entity/GeographicalUnit.php @@ -0,0 +1,93 @@ +id; + } + + public function getLayer(): ?GeographicalUnitLayer + { + return $this->layer; + } + + public function getUnitName(): ?string + { + return $this->unitName; + } + + public function setLayer(?GeographicalUnitLayer $layer): GeographicalUnit + { + $this->layer = $layer; + + return $this; + } + + public function setUnitName(?string $unitName): self + { + $this->unitName = $unitName; + + return $this; + } + + public function setUnitRefId(string $unitRefId): GeographicalUnit + { + $this->unitRefId = $unitRefId; + + return $this; + } + + protected function setId(int $id): self + { + $this->id = $id; + + return $this; + } +} diff --git a/src/Bundle/ChillMainBundle/Entity/GeographicalUnit/SimpleGeographicalUnitDTO.php b/src/Bundle/ChillMainBundle/Entity/GeographicalUnit/SimpleGeographicalUnitDTO.php new file mode 100644 index 000000000..7add53066 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Entity/GeographicalUnit/SimpleGeographicalUnitDTO.php @@ -0,0 +1,58 @@ +id = $id; + $this->unitName = $unitName; + $this->unitRefId = $unitRefId; + $this->layerId = $layerId; + } +} diff --git a/src/Bundle/ChillMainBundle/Entity/GeographicalUnitLayer.php b/src/Bundle/ChillMainBundle/Entity/GeographicalUnitLayer.php new file mode 100644 index 000000000..b856c7a21 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Entity/GeographicalUnitLayer.php @@ -0,0 +1,83 @@ +units = new ArrayCollection(); + } + + public function getId(): ?int + { + return $this->id; + } + + public function getName(): array + { + return $this->name; + } + + public function getRefId(): string + { + return $this->refId; + } + + public function getUnits(): Collection + { + return $this->units; + } + + public function setName(array $name): GeographicalUnitLayer + { + $this->name = $name; + + return $this; + } +} diff --git a/src/Bundle/ChillMainBundle/Entity/GroupCenter.php b/src/Bundle/ChillMainBundle/Entity/GroupCenter.php index ccf000b9d..01b450394 100644 --- a/src/Bundle/ChillMainBundle/Entity/GroupCenter.php +++ b/src/Bundle/ChillMainBundle/Entity/GroupCenter.php @@ -1,14 +1,14 @@ */ - private $users; + private Collection $users; /** * GroupCenter constructor. */ public function __construct() { - $this->permissionsGroup = new ArrayCollection(); $this->users = new ArrayCollection(); } - /** - * @return Center - */ - public function getCenter() + public function getCenter(): ?Center { return $this->center; } - /** - * @return int - */ - public function getId() + public function getId(): ?int { return $this->id; } - /** - * @return PermissionGroup - */ - public function getPermissionsGroup() + public function getPermissionsGroup(): ?PermissionsGroup { return $this->permissionsGroup; } /** - * @return ArrayCollection|Collection + * @return Collection */ - public function getUsers() + public function getUsers(): Collection { return $this->users; } - /** - * @return \Chill\MainBundle\Entity\GroupCenter - */ - public function setCenter(Center $center) + public function setCenter(Center $center): self { $this->center = $center; return $this; } - /** - * @return \Chill\MainBundle\Entity\GroupCenter - */ - public function setPermissionsGroup(PermissionsGroup $permissionsGroup) + public function setPermissionsGroup(PermissionsGroup $permissionsGroup): self { $this->permissionsGroup = $permissionsGroup; diff --git a/src/Bundle/ChillMainBundle/Entity/HasCenterInterface.php b/src/Bundle/ChillMainBundle/Entity/HasCenterInterface.php index 5ffe6dc87..78412899c 100644 --- a/src/Bundle/ChillMainBundle/Entity/HasCenterInterface.php +++ b/src/Bundle/ChillMainBundle/Entity/HasCenterInterface.php @@ -1,14 +1,14 @@ updatedBy; } + public function hasAddress(): bool + { + return null !== $this->getAddress(); + } + public function setActive(bool $active): self { $this->active = $active; diff --git a/src/Bundle/ChillMainBundle/Entity/LocationType.php b/src/Bundle/ChillMainBundle/Entity/LocationType.php index 2fd4dbfea..42d1aa960 100644 --- a/src/Bundle/ChillMainBundle/Entity/LocationType.php +++ b/src/Bundle/ChillMainBundle/Entity/LocationType.php @@ -1,14 +1,14 @@ recentlyPersisted = true; } diff --git a/src/Bundle/ChillMainBundle/Entity/PermissionsGroup.php b/src/Bundle/ChillMainBundle/Entity/PermissionsGroup.php index f066178dc..6c0d0be5b 100644 --- a/src/Bundle/ChillMainBundle/Entity/PermissionsGroup.php +++ b/src/Bundle/ChillMainBundle/Entity/PermissionsGroup.php @@ -1,14 +1,14 @@ getId(); - }, + static fn (RoleScope $roleScope) => $roleScope->getId(), $this->getRoleScopes()->toArray() ); $countedIds = array_count_values($roleScopesId); diff --git a/src/Bundle/ChillMainBundle/Entity/PostalCode.php b/src/Bundle/ChillMainBundle/Entity/PostalCode.php index 4b79f58e8..d016f7bc6 100644 --- a/src/Bundle/ChillMainBundle/Entity/PostalCode.php +++ b/src/Bundle/ChillMainBundle/Entity/PostalCode.php @@ -1,17 +1,22 @@ centers = new ArrayCollection(); + } + + public function getCenters(): Collection + { + return $this->centers; + } + + public function getId(): ?int + { + return $this->id; + } + + public function getIsActive(): bool + { + return $this->isActive; + } + + public function getName(): string + { + return $this->name; + } + + public function setCenters(?Collection $centers): self + { + $this->centers = $centers; + + return $this; + } + + public function setIsActive(bool $isActive): self + { + $this->isActive = $isActive; + + return $this; + } + + public function setName(string $name): self + { + $this->name = $name; + + return $this; + } +} diff --git a/src/Bundle/ChillMainBundle/Entity/RoleScope.php b/src/Bundle/ChillMainBundle/Entity/RoleScope.php index a9a65799d..b2afe9117 100644 --- a/src/Bundle/ChillMainBundle/Entity/RoleScope.php +++ b/src/Bundle/ChillMainBundle/Entity/RoleScope.php @@ -1,14 +1,14 @@ id = Uuid::uuid4(); + } + + public function getDescription(): string + { + return $this->description; + } + + public function getExportAlias(): string + { + return $this->exportAlias; + } + + public function getId(): UuidInterface + { + return $this->id; + } + + public function getOptions(): array + { + return $this->options; + } + + public function getTitle(): string + { + return $this->title; + } + + public function getUser(): User + { + return $this->user; + } + + public function setDescription(?string $description): SavedExport + { + $this->description = (string) $description; + + return $this; + } + + public function setExportAlias(string $exportAlias): SavedExport + { + $this->exportAlias = $exportAlias; + + return $this; + } + + public function setOptions(array $options): SavedExport + { + $this->options = $options; + + return $this; + } + + public function setTitle(?string $title): SavedExport + { + $this->title = (string) $title; + + return $this; + } + + public function setUser(User $user): SavedExport + { + $this->user = $user; + + return $this; + } +} diff --git a/src/Bundle/ChillMainBundle/Entity/Scope.php b/src/Bundle/ChillMainBundle/Entity/Scope.php index 72ac0ad01..df4f75e4c 100644 --- a/src/Bundle/ChillMainBundle/Entity/Scope.php +++ b/src/Bundle/ChillMainBundle/Entity/Scope.php @@ -1,14 +1,14 @@ roleScopes; } + public function isActive(): bool + { + return $this->active; + } + + public function setActive(bool $active): Scope + { + $this->active = $active; + + return $this; + } + /** * @param $name * diff --git a/src/Bundle/ChillMainBundle/Entity/User.php b/src/Bundle/ChillMainBundle/Entity/User.php index 80848e2cf..35b73786b 100644 --- a/src/Bundle/ChillMainBundle/Entity/User.php +++ b/src/Bundle/ChillMainBundle/Entity/User.php @@ -1,24 +1,25 @@ absenceStart; + } + /** * Get attributes. * @@ -216,7 +225,7 @@ class User implements AdvancedUserInterface } /** - * @return GroupCenter + * @return Collection */ public function getGroupCenters() { @@ -225,10 +234,8 @@ class User implements AdvancedUserInterface /** * Get id. - * - * @return int */ - public function getId() + public function getId(): ?int { return $this->id; } @@ -295,6 +302,11 @@ class User implements AdvancedUserInterface return $this->usernameCanonical; } + public function isAbsent(): bool + { + return null !== $this->getAbsenceStart() && $this->getAbsenceStart() <= new DateTimeImmutable('now'); + } + /** * @return bool */ @@ -359,16 +371,26 @@ class User implements AdvancedUserInterface } } - /** - * Set attributes. - * - * @param array $attributes - * - * @return Report - */ - public function setAttributes($attributes) + public function setAbsenceStart(?DateTimeImmutable $absenceStart): void { - $this->attributes = $attributes; + $this->absenceStart = $absenceStart; + } + + public function setAttributeByDomain(string $domain, string $key, $value): self + { + $this->attributes[$domain][$key] = $value; + + return $this; + } + + /** + * Merge the attributes with existing attributes. + * + * Only the key provided will be created or updated. For a two-level array, use @see{User::setAttributeByDomain} + */ + public function setAttributes(array $attributes): self + { + $this->attributes = array_merge($this->attributes, $attributes); return $this; } @@ -482,13 +504,13 @@ class User implements AdvancedUserInterface * * @param string $name * - * @return Agent + * @return User */ - public function setUsername($name) + public function setUsername(?string $name) { - $this->username = $name; + $this->username = (string) $name; - if (empty($this->getLabel())) { + if ("" === trim($this->getLabel())) { $this->setLabel($name); } @@ -506,4 +528,11 @@ class User implements AdvancedUserInterface return $this; } + + public function unsetAttribute($key): self + { + unset($this->attributes[$key]); + + return $this; + } } diff --git a/src/Bundle/ChillMainBundle/Entity/UserJob.php b/src/Bundle/ChillMainBundle/Entity/UserJob.php index 99db5d4f1..ed8bc43f3 100644 --- a/src/Bundle/ChillMainBundle/Entity/UserJob.php +++ b/src/Bundle/ChillMainBundle/Entity/UserJob.php @@ -1,14 +1,14 @@ transitionningStep; } + /** + * @return User[] + */ + public function getUsersInvolved(): array + { + $usersInvolved = []; + $usersInvolved[spl_object_hash($this->getCreatedBy())] = $this->getCreatedBy(); + + foreach ($this->steps as $step) { + foreach ($step->getDestUser() as $u) { + $usersInvolved[spl_object_hash($u)] = $u; + } + } + + return $usersInvolved; + } + public function getWorkflowName(): string { return $this->workflowName; diff --git a/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflowComment.php b/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflowComment.php index 9f4e7f096..67093d5ce 100644 --- a/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflowComment.php +++ b/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflowComment.php @@ -1,14 +1,14 @@ ccUser = new ArrayCollection(); $this->destUser = new ArrayCollection(); $this->destUserByAccessKey = new ArrayCollection(); $this->accessKey = bin2hex(openssl_random_pseudo_bytes(32)); } + public function addCcUser(User $user): self + { + if (!$this->ccUser->contains($user)) { + $this->ccUser[] = $user; + } + + return $this; + } + public function addDestEmail(string $email): self { if (!in_array($email, $this->destEmail, true)) { @@ -132,9 +148,6 @@ class EntityWorkflowStep { if (!$this->destUser->contains($user)) { $this->destUser[] = $user; - $this->getEntityWorkflow() - ->addSubscriberToFinal($user) - ->addSubscriberToStep($user); } return $this; @@ -144,9 +157,6 @@ class EntityWorkflowStep { if (!$this->destUserByAccessKey->contains($user) && !$this->destUser->contains($user)) { $this->destUserByAccessKey[] = $user; - $this->getEntityWorkflow() - ->addSubscriberToFinal($user) - ->addSubscriberToStep($user); } return $this; @@ -173,6 +183,11 @@ class EntityWorkflowStep ); } + public function getCcUser(): Collection + { + return $this->ccUser; + } + public function getComment(): string { return $this->comment; @@ -267,11 +282,16 @@ class EntityWorkflowStep return true; } + public function removeCcUser(User $user): self + { + $this->ccUser->removeElement($user); + + return $this; + } + public function removeDestEmail(string $email): self { - $this->destEmail = array_filter($this->destEmail, static function (string $existing) use ($email) { - return $email !== $existing; - }); + $this->destEmail = array_filter($this->destEmail, static fn (string $existing) => $email !== $existing); return $this; } diff --git a/src/Bundle/ChillMainBundle/Export/AggregatorInterface.php b/src/Bundle/ChillMainBundle/Export/AggregatorInterface.php index 244d3ba14..850d29838 100644 --- a/src/Bundle/ChillMainBundle/Export/AggregatorInterface.php +++ b/src/Bundle/ChillMainBundle/Export/AggregatorInterface.php @@ -1,14 +1,14 @@ getName(); }` + * @return callable(null|string|int|float|'_header' $value): string|int|\DateTimeInterface where the first argument is the value, and the function should return the label to show in the formatted file. Example : `function($countryCode) use ($countries) { return $countries[$countryCode]->getName(); }` */ public function getLabels($key, array $values, $data); @@ -103,7 +104,7 @@ interface ExportInterface extends ExportElementInterface /** * Return the results of the query builder. * - * @param \Doctrine\ORM\NativeQuery|QueryBuilder $query + * @param Q $query * @param mixed[] $data the data from the export's fomr (added by self::buildForm) * * @return mixed[] an array of results @@ -133,16 +134,14 @@ interface ExportInterface extends ExportElementInterface * @param array $acl an array where each row has a `center` key containing the Chill\MainBundle\Entity\Center, and `circles` keys containing the reachable circles. Example: `array( array('center' => $centerA, 'circles' => array($circleA, $circleB) ) )` * @param array $data the data from the form, if any * - * @return \Doctrine\ORM\NativeQuery|QueryBuilder the query to execute. + * @return Q the query to execute. */ public function initiateQuery(array $requiredModifiers, array $acl, array $data = []); /** * Return the required Role to execute the Export. - * - * @return \Symfony\Component\Security\Core\Role\Role */ - public function requiredRole(); + public function requiredRole(): string; /** * Inform which ModifiersInterface (i.e. AggregatorInterface, FilterInterface) diff --git a/src/Bundle/ChillMainBundle/Export/ExportManager.php b/src/Bundle/ChillMainBundle/Export/ExportManager.php index f9964ea3b..f39926083 100644 --- a/src/Bundle/ChillMainBundle/Export/ExportManager.php +++ b/src/Bundle/ChillMainBundle/Export/ExportManager.php @@ -1,23 +1,21 @@ logger = $logger; - $this->em = $em; $this->authorizationChecker = $authorizationChecker; $this->authorizationHelper = $authorizationHelper; $this->user = $tokenStorage->getToken()->getUser(); + $this->exports = iterator_to_array($exports); + $this->aggregators = iterator_to_array($aggregators); + $this->filters = iterator_to_array($filters); + // NOTE: PHP crashes on the next line (exit error code 11). This is desactivated until further investigation + //$this->formatters = iterator_to_array($formatters); + + //foreach ($exportElementProvider as $prefix => $provider) { + // $this->addExportElementsProvider($provider, $prefix); + //} } /** @@ -155,52 +149,17 @@ class ExportManager } } - /** - * add an aggregator. - * - * @internal used by DI - * - * @param string $alias - */ - public function addAggregator(AggregatorInterface $aggregator, $alias) - { - $this->aggregators[$alias] = $aggregator; - } - - /** - * add an export. - * - * @internal used by DI - * - * @param DirectExportInterface|ExportInterface $export - * @param type $alias - */ - public function addExport($export, $alias) - { - if ($export instanceof ExportInterface || $export instanceof DirectExportInterface) { - $this->exports[$alias] = $export; - } else { - throw new InvalidArgumentException(sprintf( - 'The export with alias %s ' - . 'does not implements %s or %s.', - $alias, - ExportInterface::class, - DirectExportInterface::class - )); - } - } - public function addExportElementsProvider(ExportElementsProviderInterface $provider, $prefix) { foreach ($provider->getExportElements() as $suffix => $element) { $alias = $prefix . '_' . $suffix; if ($element instanceof ExportInterface) { - $this->addExport($element, $alias); + $this->exports[$alias] = $element; } elseif ($element instanceof FilterInterface) { - $this->addFilter($element, $alias); + $this->filters[$alias] = $element; } elseif ($element instanceof AggregatorInterface) { - $this->addAggregator($element, $alias); + $this->aggregators[$alias] = $element; } elseif ($element instanceof FormatterInterface) { $this->addFormatter($element, $alias); } else { @@ -210,24 +169,12 @@ class ExportManager } } - /** - * add a Filter. - * - * @internal Normally used by the dependency injection - * - * @param string $alias - */ - public function addFilter(FilterInterface $filter, $alias) - { - $this->filters[$alias] = $filter; - } - /** * add a formatter. * * @internal used by DI * - * @param type $alias + * @param string $alias */ public function addFormatter(FormatterInterface $formatter, $alias) { @@ -245,7 +192,6 @@ class ExportManager public function generate($exportAlias, array $pickedCentersData, array $data, array $formatterData) { $export = $this->getExport($exportAlias); - //$qb = $this->em->createQueryBuilder(); $centers = $this->getPickedCenters($pickedCentersData); if ($export instanceof DirectExportInterface) { @@ -277,8 +223,8 @@ class ExportManager //handle aggregators $this->handleAggregators($export, $query, $data[ExportType::AGGREGATOR_KEY], $centers); - $this->logger->debug('current query is ' . $query->getDQL(), [ - 'class' => self::class, 'function' => __FUNCTION__, + $this->logger->notice('[export] will execute this qb in export', [ + 'dql' => $query->getDQL(), ]); } else { throw new UnexpectedValueException('The method `intiateQuery` should return ' @@ -547,25 +493,22 @@ class ExportManager . 'an ExportInterface.'); } - if (null === $centers) { - $centers = $this->authorizationHelper->getReachableCenters( + if (null === $centers || [] !== $centers) { + // we want to try if at least one center is reachabler + return [] !== $this->authorizationHelper->getReachableCenters( $this->user, - $role->getRole(), + $role ); } - if (count($centers) === 0) { - return false; - } - foreach ($centers as $center) { - if ($this->authorizationChecker->isGranted($role->getRole(), $center) === false) { + if (false === $this->authorizationChecker->isGranted($role, $center)) { //debugging $this->logger->debug('user has no access to element', [ 'method' => __METHOD__, 'type' => get_class($element), 'center' => $center->getName(), - 'role' => $role->getRole(), + 'role' => $role, ]); return false; @@ -590,7 +533,7 @@ class ExportManager 'center' => $center, 'circles' => $this->authorizationHelper->getReachableScopes( $this->user, - $element->requiredRole()->getRole(), + $element->requiredRole(), $center ), ]; diff --git a/src/Bundle/ChillMainBundle/Export/FilterInterface.php b/src/Bundle/ChillMainBundle/Export/FilterInterface.php index 0b27f1cf1..5e398c30d 100644 --- a/src/Bundle/ChillMainBundle/Export/FilterInterface.php +++ b/src/Bundle/ChillMainBundle/Export/FilterInterface.php @@ -1,14 +1,14 @@ result); + $values = array_map(static fn ($v) => $v[$key], $this->result); // store the label in the labelsCache property $this->labelsCache[$key] = $export->getLabels($key, $values, $this->exportData); } diff --git a/src/Bundle/ChillMainBundle/Export/Formatter/CSVPivotedListFormatter.php b/src/Bundle/ChillMainBundle/Export/Formatter/CSVPivotedListFormatter.php index d523b5b33..63d691443 100644 --- a/src/Bundle/ChillMainBundle/Export/Formatter/CSVPivotedListFormatter.php +++ b/src/Bundle/ChillMainBundle/Export/Formatter/CSVPivotedListFormatter.php @@ -1,14 +1,14 @@ result); + $values = array_map(static fn ($v) => $v[$key], $this->result); // store the label in the labelsCache property $this->labelsCache[$key] = $export->getLabels($key, $values, $this->exportData); } diff --git a/src/Bundle/ChillMainBundle/Export/Formatter/SpreadSheetFormatter.php b/src/Bundle/ChillMainBundle/Export/Formatter/SpreadSheetFormatter.php index a2bce1201..dc09c55a1 100644 --- a/src/Bundle/ChillMainBundle/Export/Formatter/SpreadSheetFormatter.php +++ b/src/Bundle/ChillMainBundle/Export/Formatter/SpreadSheetFormatter.php @@ -1,14 +1,14 @@ fromArray( $sortedResults, null, - 'A' . $line + 'A' . $line, + true ); return $line + count($sortedResults) + 1; @@ -434,7 +435,7 @@ class SpreadSheetFormatter implements FormatterInterface * Get the displayable result. * * @param string $key - * @param string $value + * @param mixed $value * * @return string */ @@ -444,12 +445,16 @@ class SpreadSheetFormatter implements FormatterInterface $this->initializeCache($key); } + $value ??= ''; + return call_user_func($this->cacheDisplayableResult[$key], $value); } protected function getTitle() { - return $this->translator->trans($this->export->getTitle()); + $title = $this->translator->trans($this->export->getTitle()); + + return substr($title, 0, 30) . '…'; } protected function initializeCache($key) @@ -493,8 +498,13 @@ class SpreadSheetFormatter implements FormatterInterface // 3. iterate on `keysExportElementAssociation` to store the callable // in cache foreach ($keysExportElementAssociation as $key => [$element, $data]) { - $this->cacheDisplayableResult[$key] = - $element->getLabels($key, array_unique($allValues[$key]), $data); + // handle the case when there is not results lines (query is empty) + if ([] === $allValues) { + $this->cacheDisplayableResult[$key] = $element->getLabels($key, ['_header'], $data); + } else { + $this->cacheDisplayableResult[$key] = + $element->getLabels($key, array_unique($allValues[$key]), $data); + } } // the cache is initialized ! diff --git a/src/Bundle/ChillMainBundle/Export/Formatter/SpreadsheetListFormatter.php b/src/Bundle/ChillMainBundle/Export/Formatter/SpreadsheetListFormatter.php index 4380cd877..0e5e339ea 100644 --- a/src/Bundle/ChillMainBundle/Export/Formatter/SpreadsheetListFormatter.php +++ b/src/Bundle/ChillMainBundle/Export/Formatter/SpreadsheetListFormatter.php @@ -1,14 +1,14 @@ formatterData['numerotation']) { $worksheet->setCellValue('A' . ($i + 1), (string) $i); } @@ -155,13 +154,22 @@ class SpreadsheetListFormatter implements FormatterInterface foreach ($row as $key => $value) { $row = $a . ($i + 1); - if ($value instanceof DateTimeInterface) { - $worksheet->setCellValue($row, Date::PHPToExcel($value)); - $worksheet->getStyle($row) - ->getNumberFormat() - ->setFormatCode(NumberFormat::FORMAT_DATE_DDMMYYYY); + $formattedValue = $this->getLabel($key, $value); + + if ($formattedValue instanceof DateTimeInterface) { + $worksheet->setCellValue($row, Date::PHPToExcel($formattedValue)); + + if ($formattedValue->format('His') === '000000') { + $worksheet->getStyle($row) + ->getNumberFormat() + ->setFormatCode(NumberFormat::FORMAT_DATE_DDMMYYYY); + } else { + $worksheet->getStyle($row) + ->getNumberFormat() + ->setFormatCode(NumberFormat::FORMAT_DATE_DATETIME); + } } else { - $worksheet->setCellValue($row, $this->getLabel($key, $value)); + $worksheet->setCellValue($row, $formattedValue); } ++$a; } @@ -259,6 +267,10 @@ class SpreadsheetListFormatter implements FormatterInterface foreach ($keys as $key) { // get an array with all values for this key if possible $values = array_map(static function ($v) use ($key) { + if (!array_key_exists($key, $v)) { + throw new RuntimeException(sprintf('This key does not exists: %s. Available keys are %s', $key, implode(', ', array_keys($v)))); + } + return $v[$key]; }, $this->result); // store the label in the labelsCache property diff --git a/src/Bundle/ChillMainBundle/Export/FormatterInterface.php b/src/Bundle/ChillMainBundle/Export/FormatterInterface.php index 2e4e569fc..1f9df225a 100644 --- a/src/Bundle/ChillMainBundle/Export/FormatterInterface.php +++ b/src/Bundle/ChillMainBundle/Export/FormatterInterface.php @@ -1,14 +1,14 @@ translator = $translator; + } + + public function getLabel($header): callable + { + return function ($value) use ($header) { + if ('_header' === $value) { + return $this->translator->trans($header); + } + + if (null === $value) { + return ''; + } + + if ($value instanceof DateTimeInterface) { + return $value; + } + + // warning: won't work with DateTimeImmutable as we reset time a few lines later + $date = DateTime::createFromFormat('Y-m-d', $value); + $hasTime = false; + + if (false === $date) { + $date = DateTime::createFromFormat('Y-m-d H:i:s', $value); + $hasTime = true; + } + + // check that the creation could occurs. + if (false === $date) { + throw new Exception(sprintf('The value %s could ' + . 'not be converted to %s', $value, DateTime::class)); + } + + if (!$hasTime) { + $date->setTime(0, 0, 0); + } + + return $date; + }; + } +} diff --git a/src/Bundle/ChillMainBundle/Export/Helper/ExportAddressHelper.php b/src/Bundle/ChillMainBundle/Export/Helper/ExportAddressHelper.php new file mode 100644 index 000000000..fca338a6f --- /dev/null +++ b/src/Bundle/ChillMainBundle/Export/Helper/ExportAddressHelper.php @@ -0,0 +1,424 @@ + self::F_COUNTRY, + 'postal_code' => self::F_POSTAL_CODE, + 'street' => self::F_STREET, + 'building' => self::F_BUILDING, + 'string' => self::F_AS_STRING, + 'geom' => self::F_GEOM, + 'attributes' => self::F_ATTRIBUTES, + 'geographical_units' => self::F_GEOGRAPHICAL_UNITS, + ]; + + private const COLUMN_MAPPING = [ + 'country' => ['country'], + 'postal_code' => ['postcode_code', 'postcode_name'], + 'street' => ['street', 'streetNumber'], + 'building' => ['buildingName', 'corridor', 'distribution', 'extra', 'flat', 'floor', 'steps'], + 'string' => ['_as_string'], + 'attributes' => ['isNoAddress', 'confidential', 'id'], + 'geom' => ['_lat', '_lon'], + 'geographical_units' => ['_unit_names', '_unit_refs'], + ]; + + private AddressRender $addressRender; + + private AddressRepository $addressRepository; + + private GeographicalUnitLayerRepositoryInterface $geographicalUnitLayerRepository; + + private TranslatableStringHelperInterface $translatableStringHelper; + + /** + * @var array>|null + */ + private ?array $unitNamesKeysCache = []; + + /** + * @var array>|null + */ + private ?array $unitRefsKeysCache = []; + + public function __construct( + AddressRender $addressRender, + AddressRepository $addressRepository, + GeographicalUnitLayerRepositoryInterface $geographicalUnitLayerRepository, + TranslatableStringHelperInterface $translatableStringHelper + ) { + $this->addressRepository = $addressRepository; + $this->geographicalUnitLayerRepository = $geographicalUnitLayerRepository; + $this->translatableStringHelper = $translatableStringHelper; + $this->addressRender = $addressRender; + } + + public function addSelectClauses(int $params, QueryBuilder $queryBuilder, $entityName = 'address', $prefix = 'add') + { + foreach (self::ALL as $key => $bitmask) { + if (($params & $bitmask) === $bitmask) { + foreach (self::COLUMN_MAPPING[$key] as $field) { + switch ($field) { + case 'id': + case '_as_string': + $queryBuilder->addSelect(sprintf('%s.id AS %s%s', $entityName, $prefix, $field)); + + break; + + case 'street': + case 'streetNumber': + case 'floor': + case 'corridor': + case 'steps': + case 'buildingName': + case 'flat': + case 'distribution': + case 'extra': + $queryBuilder->addSelect(sprintf('%s.%s AS %s%s', $entityName, $field, $prefix, $field)); + + break; + + case 'country': + case 'postcode_name': + case 'postcode_code': + $postCodeAlias = sprintf('%spostcode_t', $prefix); + + if (!in_array($postCodeAlias, $queryBuilder->getAllAliases(), true)) { + $queryBuilder->leftJoin($entityName . '.postcode', $postCodeAlias); + } + + if ('postcode_name' === $field) { + $queryBuilder->addSelect(sprintf('%s.%s AS %s%s', $postCodeAlias, 'name', $prefix, $field)); + + break; + } + + if ('postcode_code' === $field) { + $queryBuilder->addSelect(sprintf('%s.%s AS %s%s', $postCodeAlias, 'code', $prefix, $field)); + + break; + } + + $countryAlias = sprintf('%scountry_t', $prefix); + + if (!in_array($countryAlias, $queryBuilder->getAllAliases(), true)) { + $queryBuilder->leftJoin(sprintf('%s.country', $postCodeAlias), $countryAlias); + } + + $queryBuilder->addSelect(sprintf('%s.%s AS %s%s', $countryAlias, 'name', $prefix, $field)); + + break; + + case 'isNoAddress': + case 'confidential': + $queryBuilder->addSelect(sprintf('CASE WHEN %s.%s = \'TRUE\' THEN 1 ELSE 0 END AS %s%s', $entityName, $field, $prefix, $field)); + + break; + + case '_lat': + $queryBuilder->addSelect(sprintf('ST_Y(%s.point) AS %s%s', $entityName, $prefix, $field)); + + break; + + case '_lon': + $queryBuilder->addSelect(sprintf('ST_X(%s.point) AS %s%s', $entityName, $prefix, $field)); + + break; + + case '_unit_names': + foreach ($this->generateKeysForUnitsNames($prefix) as $alias => $layer) { + $queryBuilder + ->addSelect( + sprintf( + '(SELECT AGGREGATE(u_n_%s_%s.unitName) FROM %s u_n_%s_%s WHERE u_n_%s_%s MEMBER OF %s.geographicalUnits AND u_n_%s_%s.layer = :layer_%s_%s) AS %s', + $prefix, + $layer->getId(), + GeographicalUnit::class, + $prefix, + $layer->getId(), + $prefix, + $layer->getId(), + $entityName, + $prefix, + $layer->getId(), + $prefix, + $layer->getId(), + $alias + ) + ) + ->setParameter(sprintf('layer_%s_%s', $prefix, $layer->getId()), $layer); + } + + break; + + case '_unit_refs': + foreach ($this->generateKeysForUnitsRefs($prefix) as $alias => $layer) { + $queryBuilder + ->addSelect( + sprintf( + '(SELECT AGGREGATE(u_r_%s_%s.unitRefId) FROM %s u_r_%s_%s WHERE u_r_%s_%s MEMBER OF %s.geographicalUnits AND u_r_%s_%s.layer = :layer_%s_%s) AS %s', + $prefix, + $layer->getId(), + GeographicalUnit::class, + $prefix, + $layer->getId(), + $prefix, + $layer->getId(), + $entityName, + $prefix, + $layer->getId(), + $prefix, + $layer->getId(), + $alias + ) + ) + ->setParameter(sprintf('layer_%s_%s', $prefix, $layer->getId()), $layer); + } + + break; + + default: + throw new LogicException(sprintf('This key is not supported: %s, field %s', $key, $field)); + } + } + } + } + } + + /** + * @param self::F_* $params + * + * @return array|string[] + */ + public function getKeys(int $params, string $prefix = ''): array + { + $prefixes = []; + + foreach (self::ALL as $key => $bitmask) { + if (($params & $bitmask) === $bitmask) { + if ('geographical_units' === $key) { + // geographical unit generate keys dynamically, depending on layers + $prefixes = [...$prefixes, ...array_keys($this->generateKeysForUnitsNames($prefix)), ...array_keys($this->generateKeysForUnitsRefs($prefix))]; + + continue; + } + + $prefixes = array_merge( + $prefixes, + array_map( + static fn ($item) => $prefix . $item, + self::COLUMN_MAPPING[$key] + ) + ); + } + } + + return $prefixes; + } + + public function getLabel($key, array $values, $data, string $prefix = '', string $translationPrefix = 'export.address_helper.'): callable + { + $sanitizedKey = substr($key, strlen($prefix)); + + switch ($sanitizedKey) { + case 'id': + case 'street': + case 'streetNumber': + case 'buildingName': + case 'corridor': + case 'distribution': + case 'extra': + case 'flat': + case 'floor': + case '_lat': + case '_lon': + case 'steps': + case 'postcode_code': + case 'postcode_name': + return static function ($value) use ($sanitizedKey, $translationPrefix) { + if ('_header' === $value) { + return $translationPrefix . $sanitizedKey; + } + + if (null === $value) { + return ''; + } + + return $value; + }; + + case 'country': + return function ($value) use ($key) { + if ('_header' === $value) { + return 'export.list.acp' . $key; + } + + if (null === $value) { + return ''; + } + + return $this->translatableStringHelper->localize(json_decode($value, true, 512, JSON_THROW_ON_ERROR)); + }; + + case 'isNoAddress': + case 'confidential': + return static function ($value) use ($sanitizedKey, $translationPrefix) { + if ('_header' === $value) { + return $translationPrefix . $sanitizedKey; + } + + switch ($value) { + case null: + return ''; + + case true: + return 1; + + case false: + return 0; + + default: + throw new LogicException('this value is not supported for ' . $sanitizedKey . ': ' . $value); + } + }; + + case '_as_string': + return function ($value) use ($sanitizedKey, $translationPrefix) { + if ('_header' === $value) { + return $translationPrefix . $sanitizedKey; + } + + if (null === $value) { + return ''; + } + + $address = $this->addressRepository->find($value); + + return $this->addressRender->renderString($address, []); + }; + + default: + $layerNamesKeys = array_merge($this->generateKeysForUnitsNames($prefix), $this->generateKeysForUnitsRefs($prefix)); + + if (array_key_exists($key, $layerNamesKeys)) { + return function ($value) use ($key, $layerNamesKeys) { + if ('_header' === $value) { + $header = $this->translatableStringHelper->localize($layerNamesKeys[$key]->getName()); + + if (str_contains($key, 'unit_ref')) { + $header .= ' (id)'; + } + + return $header; + } + + if (null === $value) { + return ''; + } + + $decodedValues = json_decode($value, true, 512, JSON_THROW_ON_ERROR); + + switch (count($decodedValues)) { + case 0: + return ''; + + case 1: + return $decodedValues[0]; + + default: + return implode('|', $decodedValues); + } + }; + } + + throw new LogicException('this key is not supported: ' . $sanitizedKey); + } + } + + /** + * @return array + */ + private function generateKeysForUnitsNames(string $prefix): array + { + if (array_key_exists($prefix, $this->unitNamesKeysCache)) { + return $this->unitNamesKeysCache[$prefix]; + } + + $keys = []; + + foreach ($this->geographicalUnitLayerRepository->findAllHavingUnits() as $layer) { + $keys[$prefix . 'unit_names_' . $layer->getId()] = $layer; + } + + return $this->unitNamesKeysCache[$prefix] = $keys; + } + + /** + * @return array + */ + private function generateKeysForUnitsRefs(string $prefix): array + { + if (array_key_exists($prefix, $this->unitRefsKeysCache)) { + return $this->unitRefsKeysCache[$prefix]; + } + + $keys = []; + + foreach ($this->geographicalUnitLayerRepository->findAllHavingUnits() as $layer) { + $keys[$prefix . 'unit_refs_' . $layer->getId()] = $layer; + } + + return $this->unitRefsKeysCache[$prefix] = $keys; + } +} diff --git a/src/Bundle/ChillMainBundle/Export/Helper/TranslatableStringExportLabelHelper.php b/src/Bundle/ChillMainBundle/Export/Helper/TranslatableStringExportLabelHelper.php new file mode 100644 index 000000000..9b49d476b --- /dev/null +++ b/src/Bundle/ChillMainBundle/Export/Helper/TranslatableStringExportLabelHelper.php @@ -0,0 +1,70 @@ +translatableStringHelper = $translatableStringHelper; + } + + public function getLabel(string $key, array $values, string $header) + { + return function ($value) use ($header) { + if ('_header' === $value) { + return $header; + } + + if (null === $value) { + return ''; + } + + return $this->translatableStringHelper->localize(json_decode($value, true, 512, JSON_THROW_ON_ERROR)); + }; + } + + public function getLabelMulti(string $key, array $values, string $header) + { + return function ($value) use ($header) { + if ('_header' === $value) { + return $header; + } + + if (null === $value) { + return ''; + } + + $decoded = json_decode($value, true, 512, JSON_THROW_ON_ERROR); + + return implode( + '|', + array_unique( + array_map( + fn (array $translatableString) => $this->translatableStringHelper->localize($translatableString), + array_filter($decoded, static fn ($elem) => null !== $elem) + ) + ) + ); + }; + } +} diff --git a/src/Bundle/ChillMainBundle/Export/Helper/UserHelper.php b/src/Bundle/ChillMainBundle/Export/Helper/UserHelper.php new file mode 100644 index 000000000..2821bf1a6 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Export/Helper/UserHelper.php @@ -0,0 +1,84 @@ +userRender = $userRender; + $this->userRepository = $userRepository; + } + + public function getLabel($key, array $values, string $header): callable + { + return function ($value) use ($header) { + if ('_header' === $value) { + return $header; + } + + if (null === $value || null === $user = $this->userRepository->find($value)) { + return ''; + } + + return $this->userRender->renderString($user, []); + }; + } + + public function getLabelMulti($key, array $values, string $header): callable + { + return function ($value) use ($header) { + if ('_header' === $value) { + return $header; + } + + if (null === $value) { + return ''; + } + + $decoded = json_decode($value, null, 512, JSON_THROW_ON_ERROR); + + if (0 === count($decoded)) { + return ''; + } + + return + implode( + '|', + array_map( + function (int $userId) { + $user = $this->userRepository->find($userId); + + if (null === $user) { + return ''; + } + + return $this->userRender->renderString($user, []); + }, + array_unique( + array_filter($decoded, static fn (?int $userId) => null !== $userId), + SORT_NUMERIC + ) + ) + ); + }; + } +} diff --git a/src/Bundle/ChillMainBundle/Export/ListInterface.php b/src/Bundle/ChillMainBundle/Export/ListInterface.php index 496415e3b..53442f0e7 100644 --- a/src/Bundle/ChillMainBundle/Export/ListInterface.php +++ b/src/Bundle/ChillMainBundle/Export/ListInterface.php @@ -1,14 +1,14 @@ searchProvider = $searchProvider; - } - public function buildForm(FormBuilderInterface $builder, array $options) { - $this->searchProvider - ->getHasAdvancedFormByName($options['search_service']) - ->createSearchForm($builder); + $builder + ->add('absenceStart', ChillDateType::class, [ + 'required' => true, + 'input' => 'datetime_immutable', + 'label' => 'absence.Absence start', + ]); } public function configureOptions(OptionsResolver $resolver) { - $resolver - ->setRequired('search_service') - ->setAllowedTypes('search_service', ['string']); + $resolver->setDefaults([ + 'data_class' => User::class, + ]); } } diff --git a/src/Bundle/ChillMainBundle/Form/CenterType.php b/src/Bundle/ChillMainBundle/Form/CenterType.php index 494df50e9..ff758ca49 100644 --- a/src/Bundle/ChillMainBundle/Form/CenterType.php +++ b/src/Bundle/ChillMainBundle/Form/CenterType.php @@ -1,14 +1,14 @@ setDefaults([ - 'data_class' => 'Chill\MainBundle\Entity\Center', + 'data_class' => \Chill\MainBundle\Entity\Center::class, ]); } diff --git a/src/Bundle/ChillMainBundle/Form/ChoiceLoader/PostalCodeChoiceLoader.php b/src/Bundle/ChillMainBundle/Form/ChoiceLoader/PostalCodeChoiceLoader.php index ed0426b8b..174fb3304 100644 --- a/src/Bundle/ChillMainBundle/Form/ChoiceLoader/PostalCodeChoiceLoader.php +++ b/src/Bundle/ChillMainBundle/Form/ChoiceLoader/PostalCodeChoiceLoader.php @@ -1,14 +1,14 @@ lazyLoadedPostalCodes, - static function (?PostalCode $pc = null) use ($value) { - return call_user_func($value, $pc); - } + static fn (?PostalCode $pc = null) => call_user_func($value, $pc) ); } diff --git a/src/Bundle/ChillMainBundle/Form/CivilityType.php b/src/Bundle/ChillMainBundle/Form/CivilityType.php index 96469b216..c83d3e7ee 100644 --- a/src/Bundle/ChillMainBundle/Form/CivilityType.php +++ b/src/Bundle/ChillMainBundle/Form/CivilityType.php @@ -1,14 +1,14 @@ setData($address->getStreetAddress1()); break; case 'streetAddress2': + /** @phpstan-ignore-next-line */ $form->setData($address->getStreetAddress2()); break; @@ -110,11 +112,13 @@ class AddressDataMapper implements DataMapperInterface return; } + /** @phpstan-ignore-next-line */ $address->setStreetAddress1($form->getData()); break; case 'streetAddress2': + /** @phpstan-ignore-next-line */ $address->setStreetAddress2($form->getData()); break; diff --git a/src/Bundle/ChillMainBundle/Form/DataMapper/ExportPickCenterDataMapper.php b/src/Bundle/ChillMainBundle/Form/DataMapper/ExportPickCenterDataMapper.php new file mode 100644 index 000000000..01303bfbb --- /dev/null +++ b/src/Bundle/ChillMainBundle/Form/DataMapper/ExportPickCenterDataMapper.php @@ -0,0 +1,73 @@ + $form */ + $form = iterator_to_array($forms); + + $pickedRegroupment = []; + + foreach ($this->regroupmentRepository->findAll() as $regroupment) { + /** @phpstan-ignore-next-line */ + [$contained, $notContained] = $regroupment->getCenters()->partition(static fn (Center $center): bool => false); + + if (0 === count($notContained)) { + $pickedRegroupment[] = $regroupment; + } + } + + $form['regroupment']->setData($pickedRegroupment); + $form['centers']->setData($data); + } + + public function mapFormsToData($forms, &$data): void + { + /** @var array $forms */ + $forms = iterator_to_array($forms); + + $centers = []; + + foreach ($forms['center']->getData() as $center) { + $centers[spl_object_hash($center)] = $center; + } + + if (array_key_exists('regroupment', $forms)) { + /** @var Regroupment $regroupment */ + foreach ($forms['regroupment']->getData() as $regroupment) { + foreach ($regroupment->getCenters() as $center) { + $centers[spl_object_hash($center)] = $center; + } + } + } + + $data = array_values($centers); + } +} diff --git a/src/Bundle/ChillMainBundle/Form/DataMapper/PrivateCommentDataMapper.php b/src/Bundle/ChillMainBundle/Form/DataMapper/PrivateCommentDataMapper.php index 2c03c59f1..e8c7e6c5a 100644 --- a/src/Bundle/ChillMainBundle/Form/DataMapper/PrivateCommentDataMapper.php +++ b/src/Bundle/ChillMainBundle/Form/DataMapper/PrivateCommentDataMapper.php @@ -1,14 +1,14 @@ setData($viewData->getRoll()); + $forms['fixedDate']->setData($viewData->getFixedDate()); + } + + public function mapFormsToData($forms, &$viewData): void + { + $forms = iterator_to_array($forms); + + $viewData = new RollingDate( + ($forms['roll']->getData() ?? RollingDate::T_TODAY), + $forms['fixedDate']->getData() + ); + } +} diff --git a/src/Bundle/ChillMainBundle/Form/DataMapper/ScopePickerDataMapper.php b/src/Bundle/ChillMainBundle/Form/DataMapper/ScopePickerDataMapper.php index 7284b9b3a..ebae9f2b9 100644 --- a/src/Bundle/ChillMainBundle/Form/DataMapper/ScopePickerDataMapper.php +++ b/src/Bundle/ChillMainBundle/Form/DataMapper/ScopePickerDataMapper.php @@ -1,14 +1,14 @@ repository = $repository; + $this->multiple = $multiple; + $this->getId = $getId ?? static fn (object $o) => $o->getId(); + } + + /** + * @param string $value + * + * @return array|object[]|T[]|T|object + */ + public function reverseTransform($value) + { + if ($this->multiple) { + if (null === $value | '' === $value) { + return []; + } + + return array_map( + fn (string $id): ?object => $this->repository->findOneBy(['id' => (int) $id]), + explode(',', $value) + ); + } + + if (null === $value | '' === $value) { + return null; + } + + $object = $this->repository->findOneBy(['id' => (int) $value]); + + if (null === $object) { + throw new TransformationFailedException('could not find any object by object id'); + } + + return $object; + } + + /** + * @param object|T|object[]|T[] $value + */ + public function transform($value): string + { + if ($this->multiple) { + $ids = []; + + foreach ($value as $v) { + $ids[] = $id = call_user_func($this->getId, $v); + + if (null === $id) { + throw new TransformationFailedException('id is null'); + } + } + + return implode(',', $ids); + } + + if (null === $value) { + return ''; + } + + $id = call_user_func($this->getId, $value); + + if (null === $id) { + throw new TransformationFailedException('id is null'); + } + + return (string) $id; + } +} diff --git a/src/Bundle/ChillMainBundle/Form/DataTransformer/IdToLocationDataTransformer.php b/src/Bundle/ChillMainBundle/Form/DataTransformer/IdToLocationDataTransformer.php new file mode 100644 index 000000000..6dc6c0399 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Form/DataTransformer/IdToLocationDataTransformer.php @@ -0,0 +1,29 @@ +add('locationType', EntityType::class, [ 'class' => EntityLocationType::class, - 'choice_attr' => static function (EntityLocationType $entity) { - return [ - 'data-address' => $entity->getAddressRequired(), - 'data-contact' => $entity->getContactData(), - ]; - }, - 'choice_label' => function (EntityLocationType $entity) { - return $this->translatableStringHelper->localize($entity->getTitle()); - }, + 'choice_attr' => static fn (EntityLocationType $entity) => [ + 'data-address' => $entity->getAddressRequired(), + 'data-contact' => $entity->getContactData(), + ], + 'choice_label' => fn (EntityLocationType $entity) => $this->translatableStringHelper->localize($entity->getTitle()), ]) ->add('name', TextType::class) ->add('phonenumber1', ChillPhoneNumberType::class, ['required' => false]) @@ -75,7 +71,7 @@ final class LocationFormType extends AbstractType public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ - 'data_class' => 'Chill\MainBundle\Entity\Location', + 'data_class' => \Chill\MainBundle\Entity\Location::class, ]); } diff --git a/src/Bundle/ChillMainBundle/Form/LocationTypeType.php b/src/Bundle/ChillMainBundle/Form/LocationTypeType.php index 2ecaa56c8..d75c40716 100644 --- a/src/Bundle/ChillMainBundle/Form/LocationTypeType.php +++ b/src/Bundle/ChillMainBundle/Form/LocationTypeType.php @@ -1,14 +1,14 @@ setDefaults([ - 'data_class' => 'Chill\MainBundle\Entity\PermissionsGroup', + 'data_class' => \Chill\MainBundle\Entity\PermissionsGroup::class, ]); } diff --git a/src/Bundle/ChillMainBundle/Form/RegroupmentType.php b/src/Bundle/ChillMainBundle/Form/RegroupmentType.php new file mode 100644 index 000000000..bc8e6684f --- /dev/null +++ b/src/Bundle/ChillMainBundle/Form/RegroupmentType.php @@ -0,0 +1,47 @@ +add('name', TextType::class, [ + 'label' => 'Nom', + ]) + ->add('centers', EntityType::class, [ + 'class' => Center::class, + 'multiple' => true, + 'attr' => ['class' => 'select2'], + ]) + ->add('isActive', CheckboxType::class, [ + 'label' => 'Actif ?', + 'required' => false, + ]); + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver + ->setDefault('class', Regroupment::class); + } +} diff --git a/src/Bundle/ChillMainBundle/Form/SavedExportType.php b/src/Bundle/ChillMainBundle/Form/SavedExportType.php new file mode 100644 index 000000000..16aa4e42e --- /dev/null +++ b/src/Bundle/ChillMainBundle/Form/SavedExportType.php @@ -0,0 +1,40 @@ +add('title', TextType::class, [ + 'required' => true, + ]) + ->add('description', ChillTextareaType::class, [ + 'required' => false, + ]); + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults([ + 'class' => SavedExport::class, + ]); + } +} diff --git a/src/Bundle/ChillMainBundle/Form/ScopeType.php b/src/Bundle/ChillMainBundle/Form/ScopeType.php index c0c08ffbf..c5a7657b0 100644 --- a/src/Bundle/ChillMainBundle/Form/ScopeType.php +++ b/src/Bundle/ChillMainBundle/Form/ScopeType.php @@ -1,18 +1,19 @@ add('name', TranslatableStringFormType::class); + ->add('name', TranslatableStringFormType::class) + ->add('active', ChoiceType::class, [ + 'choices' => [ + 'Active' => true, + 'Inactive' => false, + ], ]); } /** @@ -30,7 +36,7 @@ class ScopeType extends AbstractType public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ - 'data_class' => 'Chill\MainBundle\Entity\Scope', + 'data_class' => \Chill\MainBundle\Entity\Scope::class, ]); } diff --git a/src/Bundle/ChillMainBundle/Form/Type/AddressDateType.php b/src/Bundle/ChillMainBundle/Form/Type/AddressDateType.php index 299728d6e..020a434f6 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/AddressDateType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/AddressDateType.php @@ -1,14 +1,14 @@ setRequired(['center', 'role']) - ->setAllowedTypes('center', 'Chill\MainBundle\Entity\Center') - ->setAllowedTypes('role', 'Symfony\Component\Security\Core\Role\Role'); + ->setAllowedTypes('center', \Chill\MainBundle\Entity\Center::class) + ->setAllowedTypes('role', 'string'); } /** diff --git a/src/Bundle/ChillMainBundle/Form/Type/ChillCollectionType.php b/src/Bundle/ChillMainBundle/Form/Type/ChillCollectionType.php index 88d5d3ce0..b38b453f3 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/ChillCollectionType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/ChillCollectionType.php @@ -1,14 +1,14 @@ setDefault('date_widget', 'single_text') - ->setDefault('date_format', 'dd-MM-yyyy') ->setDefault('time_widget', 'choice') ->setDefault('minutes', range(0, 59, 5)) ->setDefault('hours', range(8, 22)) diff --git a/src/Bundle/ChillMainBundle/Form/Type/ChillDateType.php b/src/Bundle/ChillMainBundle/Form/Type/ChillDateType.php index 1b7f51115..95fc9daad 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/ChillDateType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/ChillDateType.php @@ -1,14 +1,14 @@ vars = array_replace( - $view->vars, - [ - 'hideLabel' => true, - ] - ); + $view->vars['fullWidth'] = true; } public function configureOptions(OptionsResolver $resolver) diff --git a/src/Bundle/ChillMainBundle/Form/Type/ComposedGroupCenterType.php b/src/Bundle/ChillMainBundle/Form/Type/ComposedGroupCenterType.php index 11b079f4a..48bd37dcc 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/ComposedGroupCenterType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/ComposedGroupCenterType.php @@ -1,14 +1,14 @@ add('permissionsgroup', EntityType::class, [ - 'class' => 'Chill\MainBundle\Entity\PermissionsGroup', - 'choice_label' => static function (PermissionsGroup $group) { - return $group->getName(); - }, + 'class' => \Chill\MainBundle\Entity\PermissionsGroup::class, + 'choice_label' => static fn (PermissionsGroup $group) => $group->getName(), ])->add('center', EntityType::class, [ - 'class' => 'Chill\MainBundle\Entity\Center', - 'choice_label' => static function (Center $center) { - return $center->getName(); - }, + 'class' => \Chill\MainBundle\Entity\Center::class, + 'choice_label' => static fn (Center $center) => $center->getName(), ]); } public function configureOptions(OptionsResolver $resolver) { - $resolver->setDefault('data_class', 'Chill\MainBundle\Entity\GroupCenter'); + $resolver->setDefault('data_class', \Chill\MainBundle\Entity\GroupCenter::class); } public function getBlockPrefix() diff --git a/src/Bundle/ChillMainBundle/Form/Type/ComposedRoleScopeType.php b/src/Bundle/ChillMainBundle/Form/Type/ComposedRoleScopeType.php index 6646cd2b5..38f1475d5 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/ComposedRoleScopeType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/ComposedRoleScopeType.php @@ -1,14 +1,14 @@ '1']; }, - 'group_by' => function ($role, $key, $index) { - return $this->roleProvider->getRoleTitle($role); - }, + 'group_by' => fn ($role, $key, $index) => $this->roleProvider->getRoleTitle($role), ]) ->add('scope', EntityType::class, [ 'class' => Scope::class, - 'choice_label' => static function (Scope $scope) use ($translatableStringHelper) { - return $translatableStringHelper->localize($scope->getName()); - }, + 'choice_label' => static fn (Scope $scope) => $translatableStringHelper->localize($scope->getName()), + 'placeholder' => 'Choose amongst scopes', 'required' => false, 'data' => null, ]); @@ -98,6 +95,6 @@ class ComposedRoleScopeType extends AbstractType public function configureOptions(OptionsResolver $resolver) { - $resolver->setDefault('data_class', 'Chill\MainBundle\Entity\RoleScope'); + $resolver->setDefault('data_class', \Chill\MainBundle\Entity\RoleScope::class); } } diff --git a/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/AddressToIdDataTransformer.php b/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/AddressToIdDataTransformer.php index 76f217df6..844834177 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/AddressToIdDataTransformer.php +++ b/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/AddressToIdDataTransformer.php @@ -1,14 +1,14 @@ multiple) { if (null === $denormalized) { @@ -51,15 +55,11 @@ class EntityToJsonTransformer implements DataTransformerInterface } return array_map( - function ($item) { return $this->denormalizeOne($item); }, + fn ($item) => $this->denormalizeOne($item), $denormalized ); } - if ('' === $value) { - return null; - } - return $this->denormalizeOne($denormalized); } diff --git a/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/MultipleObjectsToIdTransformer.php b/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/MultipleObjectsToIdTransformer.php index 48ef5cdf0..a21f49b2c 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/MultipleObjectsToIdTransformer.php +++ b/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/MultipleObjectsToIdTransformer.php @@ -1,14 +1,14 @@ postalCodeRepository = $postalCodeRepository; + } + + public function reverseTransform($value) + { + if (null === $value || trim('') === $value) { + return null; + } + + if (!is_int((int) $value)) { + throw new TransformationFailedException('Cannot transform ' . gettype($value)); + } + + return $this->postalCodeRepository->find((int) $value); + } + + public function transform($value) + { + if (null === $value) { + return null; + } + + if ($value instanceof PostalCode) { + return $value->getId(); + } + + throw new TransformationFailedException('Could not reverseTransform ' . gettype($value)); + } +} diff --git a/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/ScopeTransformer.php b/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/ScopeTransformer.php index 5f6c3ed65..5ae26ec79 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/ScopeTransformer.php +++ b/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/ScopeTransformer.php @@ -1,14 +1,14 @@ exportManager = $exportManager; $this->user = $tokenStorage->getToken()->getUser(); $this->authorizationHelper = $authorizationHelper; - } - - public function addGroupingCenter(GroupingCenterInterface $grouping) - { - $this->groupingCenters[md5($grouping->getName())] = $grouping; + $this->regroupmentRepository = $regroupmentRepository; } public function buildForm(FormBuilderInterface $builder, array $options) { - - $export = $this->exportManager->getExport($options['export_alias']); $centers = $this->authorizationHelper->getReachableCenters( $this->user, - (string) $export->requiredRole() + $export->requiredRole() ); - $builder->add(self::CENTERS_IDENTIFIERS, EntityType::class, [ + $builder->add('center', EntityType::class, [ 'class' => Center::class, - 'query_builder' => static function (EntityRepository $er) use ($centers) { - $qb = $er->createQueryBuilder('c'); - $ids = array_map( - static function (Center $el) { - return $el->getId(); - }, - $centers - ); - - return $qb->where($qb->expr()->in('c.id', $ids)); - }, + 'label' => 'center', + 'choices' => $centers, 'multiple' => true, 'expanded' => true, - 'choice_label' => static function (Center $c) { - return $c->getName(); - }, - 'data' => count($this->groupingCenters) > 0 ? null : $centers, + 'choice_label' => static fn (Center $c) => $c->getName(), + 'data' => $centers, ]); - if (count($this->groupingCenters) > 0) { - $groupingBuilder = $builder->create('g', null, [ - 'compound' => true, + if (count($this->regroupmentRepository->findAllActive()) > 0) { + $builder->add('regroupment', EntityType::class, [ + 'class' => Regroupment::class, + 'label' => 'regroupment', + 'multiple' => true, + 'expanded' => true, + 'choices' => $this->regroupmentRepository->findAllActive(), + 'choice_label' => static fn (Regroupment $r) => $r->getName(), ]); - - foreach ($this->groupingCenters as $key => $gc) { - $choices = $this->buildChoices($centers, $gc); - - if (count($choices) > 0) { - $groupingBuilder->add($key, ChoiceType::class, [ - 'choices' => $choices, - 'multiple' => true, - 'expanded' => true, - 'label' => $gc->getName(), - 'required' => false, - ]); - } - } - - if ($groupingBuilder->count() > 0) { - $builder->add($groupingBuilder); - } } - $builder->addModelTransformer(new CallbackTransformer( - function ($data) use ($centers) { - return $this->transform($data, $centers); - }, - function ($data) use ($centers) { - return $this->reverseTransform($data, $centers); - } - )); + $builder->setDataMapper(new ExportPickCenterDataMapper()); } public function configureOptions(OptionsResolver $resolver) { $resolver->setRequired('export_alias'); } - - protected function buildChoices($reachablesCenters, GroupingCenterInterface $gc) - { - $result = []; - - foreach ($gc->getGroups() as $group) { - foreach ($gc->getCentersForGroup($group) as $center) { - if (in_array($center, $reachablesCenters, true)) { - $result[$group] = $group; - } - } - } - - return $result; - } - - protected function reverseTransform($data, $centers) - { - $picked = $data[self::CENTERS_IDENTIFIERS] - instanceof \Doctrine\Common\Collections\Collection ? - $data[self::CENTERS_IDENTIFIERS]->toArray() - : - $data[self::CENTERS_IDENTIFIERS]; - - if (array_key_exists('g', $data)) { - foreach ($data['g'] as $gcid => $group) { - $picked = - array_merge( - array_intersect( - $this->groupingCenters[$gcid]->getCentersForGroup($group), - $centers - ), - $picked - ); - } - } - - return array_unique($picked); - } - - protected function transform($data, $centers) - { - return $data; - } } diff --git a/src/Bundle/ChillMainBundle/Form/Type/Export/PickFormatterType.php b/src/Bundle/ChillMainBundle/Form/Type/Export/PickFormatterType.php index 885be3c27..ce442231e 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/Export/PickFormatterType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/Export/PickFormatterType.php @@ -1,14 +1,14 @@ add($checkboxesBuilder); } + if (0 < count($helper->getDateRanges())) { + $dateRangesBuilder = $builder->create('dateRanges', null, ['compound' => true]); + + foreach ($helper->getDateRanges() as $name => $opts) { + $rangeBuilder = $dateRangesBuilder->create($name, null, [ + 'compound' => true, + 'label' => null === $opts['label'] ? false : $opts['label'] ?? $name, + ]); + + $rangeBuilder->add( + 'from', + ChillDateType::class, + ['input' => 'datetime_immutable', 'required' => false] + ); + $rangeBuilder->add( + 'to', + ChillDateType::class, + ['input' => 'datetime_immutable', 'required' => false] + ); + + $dateRangesBuilder->add($rangeBuilder); + } + + $builder->add($dateRangesBuilder); + } + foreach ($this->requestStack->getCurrentRequest()->query->getIterator() as $key => $value) { switch ($key) { case 'q': case 'checkboxes' . $key: + case $key . '_from': + case $key . '_to': break; case 'page': diff --git a/src/Bundle/ChillMainBundle/Form/Type/PickAddressType.php b/src/Bundle/ChillMainBundle/Form/Type/PickAddressType.php index 20933563a..50d6f4eb7 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/PickAddressType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/PickAddressType.php @@ -1,14 +1,14 @@ $data]; }, - static function ($data) { - return $data['center']; - } + static fn ($data) => $data['center'] )); } diff --git a/src/Bundle/ChillMainBundle/Form/Type/PickCivilityType.php b/src/Bundle/ChillMainBundle/Form/Type/PickCivilityType.php index 0381bfd0d..adb0f2a67 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/PickCivilityType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/PickCivilityType.php @@ -1,14 +1,14 @@ setDefault('label', 'Civility') ->setDefault( 'choice_label', - function (Civility $civility): string { - return $this->translatableStringHelper->localize($civility->getName()); - } + fn (Civility $civility): string => $this->translatableStringHelper->localize($civility->getName()) ) ->setDefault( 'query_builder', - static function (EntityRepository $er): QueryBuilder { - return $er->createQueryBuilder('c') - ->where('c.active = true') - ->orderBy('c.order'); - }, + static fn (EntityRepository $er): QueryBuilder => $er->createQueryBuilder('c') + ->where('c.active = true') + ->orderBy('c.order'), ) ->setDefault('placeholder', 'choose civility') ->setDefault('class', Civility::class); diff --git a/src/Bundle/ChillMainBundle/Form/Type/PickLocationTypeType.php b/src/Bundle/ChillMainBundle/Form/Type/PickLocationTypeType.php new file mode 100644 index 000000000..3d993c9f1 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Form/Type/PickLocationTypeType.php @@ -0,0 +1,48 @@ +translatableStringHelper = $translatableStringHelper; + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver + ->setDefaults([ + 'class' => LocationType::class, + 'choice_label' => fn (LocationType $type) => $this->translatableStringHelper->localize($type->getTitle()), + 'placeholder' => 'Pick a location type', + 'required' => false, + 'attr' => ['class' => 'select2'], + 'label' => 'Location type', + 'multiple' => false, + ]) + ->setAllowedTypes('multiple', ['bool']); + } + + public function getParent(): string + { + return EntityType::class; + } +} diff --git a/src/Bundle/ChillMainBundle/Form/Type/PickPostalCodeType.php b/src/Bundle/ChillMainBundle/Form/Type/PickPostalCodeType.php new file mode 100644 index 000000000..d1feacd6a --- /dev/null +++ b/src/Bundle/ChillMainBundle/Form/Type/PickPostalCodeType.php @@ -0,0 +1,49 @@ +postalCodeToIdTransformer = $postalCodeToIdTransformer; + } + + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder->addViewTransformer($this->postalCodeToIdTransformer); + } + + public function buildView(FormView $view, FormInterface $form, array $options) + { + $view->vars['uniqid'] = $view->vars['attr']['data-input-postal-code'] = uniqid('input_pick_postal_code_'); + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver + ->setDefault('class', PostalCode::class) + ->setDefault('multiple', false) + ->setAllowedTypes('multiple', ['bool']) + ->setDefault('compound', false); + } +} diff --git a/src/Bundle/ChillMainBundle/Form/Type/PickRollingDateType.php b/src/Bundle/ChillMainBundle/Form/Type/PickRollingDateType.php new file mode 100644 index 000000000..a4492526f --- /dev/null +++ b/src/Bundle/ChillMainBundle/Form/Type/PickRollingDateType.php @@ -0,0 +1,73 @@ +add('roll', ChoiceType::class, [ + 'choices' => array_combine( + array_map(static fn (string $item) => 'rolling_date.' . $item, RollingDate::ALL_T), + RollingDate::ALL_T + ), + 'multiple' => false, + 'expanded' => false, + 'label' => 'rolling_date.roll_movement', + ]) + ->add('fixedDate', ChillDateType::class, [ + 'input' => 'datetime_immutable', + 'label' => 'rolling_date.fixed_date_date', + ]); + + $builder->setDataMapper(new RollingDateDataMapper()); + } + + public function buildView(FormView $view, FormInterface $form, array $options) + { + $view->vars['uniqid'] = uniqid('rollingdate-'); + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults([ + 'class' => RollingDate::class, + 'empty_data' => new RollingDate(RollingDate::T_TODAY), + 'constraints' => [ + new Callback([$this, 'validate']), + ], + ]); + } + + public function validate($data, ExecutionContextInterface $context, $payload): void + { + /** @var RollingDate $data */ + if (RollingDate::T_FIXED_DATE === $data->getRoll() && null === $data->getFixedDate()) { + $context + ->buildViolation('rolling_date.When fixed date is selected, you must provide a date') + ->atPath('fixedDate') + ->addViolation(); + } + } +} diff --git a/src/Bundle/ChillMainBundle/Form/Type/PickUserDynamicType.php b/src/Bundle/ChillMainBundle/Form/Type/PickUserDynamicType.php index 3bc4df436..f6c2b7f4a 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/PickUserDynamicType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/PickUserDynamicType.php @@ -1,14 +1,14 @@ denormalizer = $denormalizer; $this->serializer = $serializer; + $this->normalizer = $normalizer; } public function buildForm(FormBuilderInterface $builder, array $options) @@ -46,6 +50,11 @@ class PickUserDynamicType extends AbstractType $view->vars['multiple'] = $options['multiple']; $view->vars['types'] = ['user']; $view->vars['uniqid'] = uniqid('pick_user_dyn'); + $view->vars['suggested'] = []; + + foreach ($options['suggested'] as $user) { + $view->vars['suggested'][] = $this->normalizer->normalize($user, 'json', ['groups' => 'read']); + } } public function configureOptions(OptionsResolver $resolver) @@ -53,7 +62,8 @@ class PickUserDynamicType extends AbstractType $resolver ->setDefault('multiple', false) ->setAllowedTypes('multiple', ['bool']) - ->setDefault('compound', false); + ->setDefault('compound', false) + ->setDefault('suggested', []); } public function getBlockPrefix() diff --git a/src/Bundle/ChillMainBundle/Form/Type/PickUserLocationType.php b/src/Bundle/ChillMainBundle/Form/Type/PickUserLocationType.php new file mode 100644 index 000000000..d78957f29 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Form/Type/PickUserLocationType.php @@ -0,0 +1,55 @@ +translatableStringHelper = $translatableStringHelper; + $this->locationRepository = $locationRepository; + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver + ->setDefaults([ + 'class' => Location::class, + 'choices' => $this->locationRepository->findByPublicLocations(), + 'choice_label' => fn (Location $entity) => $entity->getName() ? + $entity->getName() . ' (' . $this->translatableStringHelper->localize($entity->getLocationType()->getTitle()) . ')' : + $this->translatableStringHelper->localize($entity->getLocationType()->getTitle()), + 'placeholder' => 'Pick a location', + 'required' => false, + 'attr' => ['class' => 'select2'], + 'label' => 'Current location', + 'multiple' => false, + ]) + ->setAllowedTypes('multiple', ['bool']); + } + + public function getParent(): string + { + return EntityType::class; + } +} diff --git a/src/Bundle/ChillMainBundle/Form/Type/PostalCodeType.php b/src/Bundle/ChillMainBundle/Form/Type/PostalCodeType.php index 456ff753f..51a321be6 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/PostalCodeType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/PostalCodeType.php @@ -1,14 +1,14 @@ translatableStringHelper; $resolver ->setDefault('class', PostalCode::class) - ->setDefault('choice_label', static function (PostalCode $code) use ($helper) { - return $code->getCode() . ' ' . $code->getName() . ' [' . - $helper->localize($code->getCountry()->getName()) . ']'; - }) + ->setDefault('choice_label', static fn (PostalCode $code) => $code->getCode() . ' ' . $code->getName() . ' [' . + $helper->localize($code->getCountry()->getName()) . ']') ->setDefault('choice_loader', $this->choiceLoader) ->setDefault('placeholder', 'Select a postal code'); } diff --git a/src/Bundle/ChillMainBundle/Form/Type/PrivateCommentType.php b/src/Bundle/ChillMainBundle/Form/Type/PrivateCommentType.php index d4c0d1611..5c29f4d42 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/PrivateCommentType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/PrivateCommentType.php @@ -1,14 +1,14 @@ add('comments', ChillTextareaType::class, [ 'disable_editor' => $options['disable_editor'], - 'label' => false, + 'label' => $options['label'], ]) ->setDataMapper($this->dataMapper); } public function buildView(FormView $view, FormInterface $form, array $options) { - $view->vars['hideLabel'] = true; + $view->vars['fullWidth'] = true; } public function configureOptions(OptionsResolver $resolver) diff --git a/src/Bundle/ChillMainBundle/Form/Type/ScopePickerType.php b/src/Bundle/ChillMainBundle/Form/Type/ScopePickerType.php index 92a1b26c3..c86684a8a 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/ScopePickerType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/ScopePickerType.php @@ -1,23 +1,23 @@ authorizationHelper = $authorizationHelper; - $this->tokenStorage = $tokenStorage; - $this->scopeRepository = $scopeRepository; $this->security = $security; $this->translatableStringHelper = $translatableStringHelper; } public function buildForm(FormBuilderInterface $builder, array $options) { - $items = $this->authorizationHelper->getReachableScopes( - $this->security->getUser(), - $options['role'] instanceof Role ? $options['role']->getRole() : $options['role'], - $options['center'] + $items = array_values( + array_filter( + $this->authorizationHelper->getReachableScopes( + $this->security->getUser(), + $options['role'] instanceof Role ? $options['role']->getRole() : $options['role'], + $options['center'] + ), + static fn (Scope $s) => $s->isActive() + ) ); + if (0 === count($items)) { + throw new RuntimeException('no scopes are reachable. This form should not be shown to user'); + } + if (1 !== count($items)) { $builder->add('scope', EntityType::class, [ 'class' => Scope::class, 'placeholder' => 'Choose the circle', - 'choice_label' => function (Scope $c) { - return $this->translatableStringHelper->localize($c->getName()); - }, + 'choice_label' => fn (Scope $c) => $this->translatableStringHelper->localize($c->getName()), 'choices' => $items, ]); $builder->setDataMapper(new ScopePickerDataMapper()); @@ -105,12 +93,7 @@ class ScopePickerType extends AbstractType public function buildView(FormView $view, FormInterface $form, array $options) { - $view->vars = array_replace( - $view->vars, - [ - 'hideLabel' => true, - ] - ); + $view->vars['fullWidth'] = true; } public function configureOptions(OptionsResolver $resolver) @@ -123,35 +106,4 @@ class ScopePickerType extends AbstractType ->setRequired('role') ->setAllowedTypes('role', ['string', Role::class]); } - - /** - * @param array|Center|Center[] $center - * @param string $role - * - * @return \Doctrine\ORM\QueryBuilder - */ - protected function buildAccessibleScopeQuery($center, $role) - { - $roles = $this->authorizationHelper->getParentRoles($role); - $roles[] = $role; - $centers = $center instanceof Center ? [$center] : $center; - - $qb = $this->scopeRepository->createQueryBuilder('s'); - $qb - // jointure to center - ->join('s.roleScopes', 'rs') - ->join('rs.permissionsGroups', 'pg') - ->join('pg.groupCenters', 'gc') - // add center constraint - ->where($qb->expr()->in('IDENTITY(gc.center)', ':centers')) - ->setParameter('centers', array_map(static fn (Center $c) => $c->getId(), $centers)) - // role constraints - ->andWhere($qb->expr()->in('rs.role', ':roles')) - ->setParameter('roles', $roles) - // user contraint - ->andWhere(':user MEMBER OF gc.users') - ->setParameter('user', $this->tokenStorage->getToken()->getUser()); - - return $qb; - } } diff --git a/src/Bundle/ChillMainBundle/Form/Type/Select2ChoiceType.php b/src/Bundle/ChillMainBundle/Form/Type/Select2ChoiceType.php index ae5ab075e..b427c603b 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/Select2ChoiceType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/Select2ChoiceType.php @@ -1,14 +1,14 @@ setDefaults([ - 'class' => 'Chill\MainBundle\Entity\Country', + 'class' => \Chill\MainBundle\Entity\Country::class, 'choices' => array_combine(array_values($choices), array_keys($choices)), 'preferred_choices' => array_combine(array_values($preferredChoices), array_keys($preferredChoices)), ]); diff --git a/src/Bundle/ChillMainBundle/Form/Type/Select2EntityType.php b/src/Bundle/ChillMainBundle/Form/Type/Select2EntityType.php index b4db6393e..51e7261e4 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/Select2EntityType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/Select2EntityType.php @@ -1,14 +1,14 @@ setDefaults([ - 'class' => 'Chill\MainBundle\Entity\Language', + 'class' => \Chill\MainBundle\Entity\Language::class, 'choices' => array_combine(array_values($choices), array_keys($choices)), 'preferred_choices' => array_combine(array_values($preferredChoices), array_keys($preferredChoices)), ]); diff --git a/src/Bundle/ChillMainBundle/Form/Type/TranslatableStringFormType.php b/src/Bundle/ChillMainBundle/Form/Type/TranslatableStringFormType.php index 822ff0101..ee39ef75e 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/TranslatableStringFormType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/TranslatableStringFormType.php @@ -1,14 +1,14 @@ setAllowedTypes('having_permissions_group_flag', ['string', 'null']) ->setDefault('class', User::class) ->setDefault('placeholder', 'Choose an user') - ->setDefault('choice_label', function (User $u) { - return $this->userRender->renderString($u, []); - }) + ->setDefault('choice_label', fn (User $u) => $this->userRender->renderString($u, [])) ->setDefault('scope', null) ->setAllowedTypes('scope', [Scope::class, 'array', 'null']) ->setNormalizer('choices', function (Options $options) { diff --git a/src/Bundle/ChillMainBundle/Form/UserCurrentLocationType.php b/src/Bundle/ChillMainBundle/Form/UserCurrentLocationType.php index e7ae27b3a..bf6a5d172 100644 --- a/src/Bundle/ChillMainBundle/Form/UserCurrentLocationType.php +++ b/src/Bundle/ChillMainBundle/Form/UserCurrentLocationType.php @@ -1,49 +1,24 @@ translatableStringHelper = $translatableStringHelper; - $this->locationRepository = $locationRepository; - } - public function buildForm(FormBuilderInterface $builder, array $options) { - $builder - ->add('currentLocation', EntityType::class, [ - 'class' => Location::class, - 'choices' => $this->locationRepository->findByPublicLocations(), - 'choice_label' => function (Location $entity) { - return $entity->getName() ? - $entity->getName() . ' (' . $this->translatableStringHelper->localize($entity->getLocationType()->getTitle()) . ')' : - $this->translatableStringHelper->localize($entity->getLocationType()->getTitle()); - }, - 'placeholder' => 'Pick a location', - 'required' => false, - 'attr' => ['class' => 'select2'], - ]); + $builder->add('currentLocation', PickUserLocationType::class); } } diff --git a/src/Bundle/ChillMainBundle/Form/UserJobType.php b/src/Bundle/ChillMainBundle/Form/UserJobType.php index 74d69bd07..32ddc3179 100644 --- a/src/Bundle/ChillMainBundle/Form/UserJobType.php +++ b/src/Bundle/ChillMainBundle/Form/UserJobType.php @@ -1,14 +1,14 @@ false, 'placeholder' => 'Choose a main scope', 'class' => Scope::class, - 'choice_label' => function (Scope $c) { - return $this->translatableStringHelper->localize($c->getName()); - }, + 'choice_label' => fn (Scope $c) => $this->translatableStringHelper->localize($c->getName()), ]) ->add('userJob', EntityType::class, [ 'label' => 'user job', 'required' => false, 'placeholder' => 'choose a job', 'class' => UserJob::class, - 'choice_label' => function (UserJob $c) { - return $this->translatableStringHelper->localize($c->getLabel()); - }, + 'choice_label' => fn (UserJob $c) => $this->translatableStringHelper->localize($c->getLabel()), 'query_builder' => static function (EntityRepository $er) { $qb = $er->createQueryBuilder('uj'); $qb->where('uj.active = TRUE'); @@ -100,9 +97,7 @@ class UserType extends AbstractType 'required' => false, 'placeholder' => 'choose a location', 'class' => Location::class, - 'choice_label' => function (Location $l) { - return $this->translatableStringHelper->localize($l->getLocationType()->getTitle()) . ' - ' . $l->getName(); - }, + 'choice_label' => fn (Location $l) => $this->translatableStringHelper->localize($l->getLocationType()->getTitle()) . ' - ' . $l->getName(), 'query_builder' => static function (EntityRepository $er) { $qb = $er->createQueryBuilder('l'); $qb->orderBy('l.locationType'); @@ -110,6 +105,11 @@ class UserType extends AbstractType return $qb; }, + ]) + ->add('absenceStart', ChillDateType::class, [ + 'required' => false, + 'input' => 'datetime_immutable', + 'label' => 'absence.Absence start', ]); // @phpstan-ignore-next-line @@ -161,7 +161,7 @@ class UserType extends AbstractType public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ - 'data_class' => 'Chill\MainBundle\Entity\User', + 'data_class' => \Chill\MainBundle\Entity\User::class, ]); $resolver diff --git a/src/Bundle/ChillMainBundle/Form/Utils/PermissionsGroupFlagProvider.php b/src/Bundle/ChillMainBundle/Form/Utils/PermissionsGroupFlagProvider.php index f4a91f100..5fcf3b824 100644 --- a/src/Bundle/ChillMainBundle/Form/Utils/PermissionsGroupFlagProvider.php +++ b/src/Bundle/ChillMainBundle/Form/Utils/PermissionsGroupFlagProvider.php @@ -1,14 +1,14 @@ getName(); - }, + static fn (Transition $transition) => $transition->getName(), $transitions ), $transitions @@ -88,14 +86,10 @@ class WorkflowStepType extends AbstractType 'backward' => 'backward', 'neutral' => 'neutral', ], - 'choice_label' => function (string $key) use ($inputLabels) { - return $this->translatableStringHelper->localize($inputLabels[$key]); - }, - 'choice_attr' => static function (string $key) { - return [ - $key => $key, - ]; - }, + 'choice_label' => fn (string $key) => $this->translatableStringHelper->localize($inputLabels[$key]), + 'choice_attr' => static fn (string $key) => [ + $key => $key, + ], 'mapped' => false, 'expanded' => true, 'data' => 'forward', @@ -137,8 +131,8 @@ class WorkflowStepType extends AbstractType $meta = $workflow->getMetadataStore()->getPlaceMetadata($to); if ( - !array_key_exists('isFinal', $meta) || false === $meta['isFinal'] - ) { + !array_key_exists('isFinal', $meta) || false === $meta['isFinal'] + ) { $toFinal = false; } } @@ -154,6 +148,14 @@ class WorkflowStepType extends AbstractType 'label' => 'workflow.dest for next steps', 'multiple' => true, 'mapped' => false, + 'suggested' => $options['suggested_users'], + ]) + ->add('future_cc_users', PickUserDynamicType::class, [ + 'label' => 'workflow.cc for next steps', + 'multiple' => true, + 'mapped' => false, + 'required' => false, + 'suggested' => $options['suggested_users'], ]) ->add('future_dest_emails', ChillCollectionType::class, [ 'label' => 'workflow.dest by email', @@ -195,11 +197,12 @@ class WorkflowStepType extends AbstractType public function configureOptions(OptionsResolver $resolver) { $resolver - ->setDefined('class', EntityWorkflowStep::class) + ->setDefined('class') ->setRequired('transition') ->setAllowedTypes('transition', 'bool') ->setRequired('entity_workflow') ->setAllowedTypes('entity_workflow', EntityWorkflow::class) + ->setDefault('suggested_users', []) ->setDefault('constraints', [ new Callback( function ($step, ExecutionContextInterface $context, $payload) { @@ -234,6 +237,20 @@ class WorkflowStepType extends AbstractType } } ), + new Callback( + function ($step, ExecutionContextInterface $context, $payload) { + $form = $context->getObject(); + + foreach ($form->get('future_dest_users')->getData() as $u) { + if (in_array($u, $form->get('future_cc_users')->getData(), true)) { + $context + ->buildViolation('workflow.The user in cc cannot be a dest user in the same workflow step') + ->atPath('ccUsers') + ->addViolation(); + } + } + } + ) ]); } } diff --git a/src/Bundle/ChillMainBundle/Form/WorkflowTransitionType.php b/src/Bundle/ChillMainBundle/Form/WorkflowTransitionType.php index 1692928dd..bf2646e60 100644 --- a/src/Bundle/ChillMainBundle/Form/WorkflowTransitionType.php +++ b/src/Bundle/ChillMainBundle/Form/WorkflowTransitionType.php @@ -1,14 +1,14 @@ getId(); } - public function onEditNotificationComment(NotificationComment $notificationComment, LifecycleEventArgs $eventArgs): void + public function onEditNotificationComment(NotificationComment $notificationComment, PostPersistEventArgs $eventArgs): void { $this->resetCacheForNotification($notificationComment->getNotification()); } diff --git a/src/Bundle/ChillMainBundle/Notification/Email/NotificationMailer.php b/src/Bundle/ChillMainBundle/Notification/Email/NotificationMailer.php index 69fcf66f0..5290492b1 100644 --- a/src/Bundle/ChillMainBundle/Notification/Email/NotificationMailer.php +++ b/src/Bundle/ChillMainBundle/Notification/Email/NotificationMailer.php @@ -1,19 +1,21 @@ translator = $translator; } - public function postPersistComment(NotificationComment $comment, LifecycleEventArgs $eventArgs): void + public function postPersistComment(NotificationComment $comment, PostPersistEventArgs $eventArgs): void { foreach ( array_merge( @@ -72,13 +74,13 @@ class NotificationMailer /** * Send a email after a notification is persisted. */ - public function postPersistNotification(Notification $notification, LifecycleEventArgs $eventArgs): void + public function postPersistNotification(Notification $notification, PostPersistEventArgs $eventArgs): void { $this->sendNotificationEmailsToAddresses($notification); $this->sendNotificationEmailsToAddressesEmails($notification); } - public function postUpdateNotification(Notification $notification, LifecycleEventArgs $eventArgs): void + public function postUpdateNotification(Notification $notification, PostUpdateEventArgs $eventArgs): void { $this->sendNotificationEmailsToAddressesEmails($notification); } diff --git a/src/Bundle/ChillMainBundle/Notification/EventListener/PersistNotificationOnTerminateEventSubscriber.php b/src/Bundle/ChillMainBundle/Notification/EventListener/PersistNotificationOnTerminateEventSubscriber.php index cb9d2db92..f9e050769 100644 --- a/src/Bundle/ChillMainBundle/Notification/EventListener/PersistNotificationOnTerminateEventSubscriber.php +++ b/src/Bundle/ChillMainBundle/Notification/EventListener/PersistNotificationOnTerminateEventSubscriber.php @@ -1,14 +1,14 @@ logger = $logger; $this->twig = $twig; $this->mailer = $mailer; - //$this->forcedMailer = new \Swift_Mailer($mailerTransporter); $this->router = $router; $this->translator = $translator; $this->routeParameters = $routeParameters; @@ -115,20 +106,6 @@ class Mailer return $content; } - /** - * @param $force - * - * @throws \Symfony\Component\Mailer\Exception\TransportExceptionInterface - */ - public function sendMessage(Swift_Message $message, $force) - { - if ($force) { - $this->forcedMailer->send($message); - } else { - $this->mailer->send($message); - } - } - /** * Envoie une notification à un utilisateur. * @@ -155,23 +132,25 @@ class Mailer $subject[2] ?? null ); - $message = (new Swift_Message($subjectI18n)) - ->setFrom($fromEmail, $fromName) - ->setTo($to); + $email = new Email(); + $email->addTo($to)->subject($subjectI18n); foreach ($bodies as $contentType => $content) { - $message->setBody($content, $contentType); + match ($contentType) { + 'text/plain' => $email->text($content), + default => $email->text($content), + }; } if (null !== $callback) { - call_user_func($callback, $message); + call_user_func($callback, $email); } $this->logger->info('[notification] Sending notification', [ - 'to' => $message->getTo(), - 'subject' => $message->getSubject(), + 'to' => $email->getTo(), + 'subject' => $email->getSubject() ]); - $this->sendMessage($message, $force); + $this->mailer->send($email); } } diff --git a/src/Bundle/ChillMainBundle/Notification/NotificationHandlerInterface.php b/src/Bundle/ChillMainBundle/Notification/NotificationHandlerInterface.php index 774609512..58ee8372c 100644 --- a/src/Bundle/ChillMainBundle/Notification/NotificationHandlerInterface.php +++ b/src/Bundle/ChillMainBundle/Notification/NotificationHandlerInterface.php @@ -1,14 +1,14 @@ render('@ChillMain/Notification/extension_list_notifications_for.html.twig', [ - 'notifications' => $notifications, 'appendCommentForms' => $appendCommentForms, + 'notifications' => $notifications, + 'appendCommentForms' => $appendCommentForms, ]); } } diff --git a/src/Bundle/ChillMainBundle/Pagination/ChillItemsPerPageTwig.php b/src/Bundle/ChillMainBundle/Pagination/ChillItemsPerPageTwig.php index 3a236f5c1..97d4629c7 100644 --- a/src/Bundle/ChillMainBundle/Pagination/ChillItemsPerPageTwig.php +++ b/src/Bundle/ChillMainBundle/Pagination/ChillItemsPerPageTwig.php @@ -1,14 +1,14 @@ urlGenerator = $urlGenerator; $this->number = $number; @@ -74,24 +73,24 @@ class Page implements PageInterface $this->totalItems = $totalItems; } - public function generateUrl() + public function generateUrl(): string { return $this->urlGenerator->generate($this->route, $this->routeParameters); } - public function getFirstItemNumber() + public function getFirstItemNumber(): int { return ($this->number - 1) * $this->itemPerPage; } - public function getLastItemNumber() + public function getLastItemNumber(): int { $last = $this->number * $this->itemPerPage - 1; return $last < $this->totalItems ? $last : $this->totalItems; } - public function getNumber() + public function getNumber(): int { return $this->number; } diff --git a/src/Bundle/ChillMainBundle/Pagination/PageGenerator.php b/src/Bundle/ChillMainBundle/Pagination/PageGenerator.php index d7da19708..4a10fde2a 100644 --- a/src/Bundle/ChillMainBundle/Pagination/PageGenerator.php +++ b/src/Bundle/ChillMainBundle/Pagination/PageGenerator.php @@ -1,14 +1,14 @@ paginator = $paginator; } - public function current() + public function current(): Page { - return $this->paginator->getPage($current); + return $this->paginator->getPage($this->current); } - public function key() + public function key(): int { return $this->current; } - public function next() + public function next(): void { ++$this->current; } - public function rewind() + public function rewind(): void { $this->current = 1; } - public function valid() + public function valid(): bool { return 0 < $this->current && $this->paginator->countPages() >= $this->current; diff --git a/src/Bundle/ChillMainBundle/Pagination/PageInterface.php b/src/Bundle/ChillMainBundle/Pagination/PageInterface.php index acca7301c..e6589b2f9 100644 --- a/src/Bundle/ChillMainBundle/Pagination/PageInterface.php +++ b/src/Bundle/ChillMainBundle/Pagination/PageInterface.php @@ -1,14 +1,14 @@ totalItems = $totalItems; $this->itemPerPage = $itemPerPage; @@ -98,12 +98,12 @@ class Paginator implements PaginatorInterface $this->itemPerPageKey = $itemPerPageKey; } - public function count() + public function count(): int { return $this->countPages(); } - public function countPages() + public function countPages(): int { if (0 === $this->itemPerPage) { return 1; @@ -122,20 +122,17 @@ class Paginator implements PaginatorInterface return 0 === $nb ? 1 : (int) $nb; } - /** - * @return \Chill\MainBundle\Pagination\Page - */ - public function getCurrentPage() + public function getCurrentPage(): Page { return $this->getPage($this->currentPageNumber); } - public function getCurrentPageFirstItemNumber() + public function getCurrentPageFirstItemNumber(): int { return $this->getCurrentPage()->getFirstItemNumber(); } - public function getItemsPerPage() + public function getItemsPerPage(): int { return $this->itemPerPage; } @@ -145,7 +142,7 @@ class Paginator implements PaginatorInterface * * @return \Chill\MainBundle\Pagination\Page */ - public function getNextPage() + public function getNextPage(): Page { if (!$this->hasNextPage()) { throw new RuntimeException('this page has no next page'); @@ -155,11 +152,10 @@ class Paginator implements PaginatorInterface } /** - * @param type $number * * @return \Chill\MainBundle\Pagination\Page */ - public function getPage($number) + public function getPage(int $number): Page { if (!$this->hasPage($number)) { throw new RuntimeException("The page with number {$number} does not " @@ -179,7 +175,7 @@ class Paginator implements PaginatorInterface ); } - public function getPagesGenerator() + public function getPagesGenerator(): iterable { for ($i = 1; $this->countPages() >= $i; ++$i) { yield $this->getPage($i); @@ -191,7 +187,7 @@ class Paginator implements PaginatorInterface * * @return \Chill\MainBundle\Pagination\Page */ - public function getPreviousPage() + public function getPreviousPage(): PageInterface { if (!$this->hasPreviousPage()) { throw new RuntimeException('this page has no previous page'); @@ -200,7 +196,7 @@ class Paginator implements PaginatorInterface return $this->getPage($this->currentPageNumber - 1); } - public function getTotalItems() + public function getTotalItems(): int { return $this->totalItems; } @@ -208,12 +204,12 @@ class Paginator implements PaginatorInterface /** * @return bool */ - public function hasNextPage() + public function hasNextPage(): bool { return $this->hasPage($this->currentPageNumber + 1); } - public function hasPage($number) + public function hasPage($number): bool { if (0 === $this->totalItems) { return 1 === $number; @@ -226,18 +222,18 @@ class Paginator implements PaginatorInterface /** * @return bool */ - public function hasPreviousPage() + public function hasPreviousPage(): bool { return $this->hasPage($this->currentPageNumber - 1); } - public function isCurrentPage(PageInterface $page) + public function isCurrentPage(PageInterface $page): bool { return $page->getNumber() === $this->currentPageNumber; } - public function setItemsPerPage($itemPerPage) + public function setItemsPerPage(int $itemsPerPage) { - $this->itemPerPage = $itemPerPage; + $this->itemPerPage = $itemsPerPage; } } diff --git a/src/Bundle/ChillMainBundle/Pagination/PaginatorFactory.php b/src/Bundle/ChillMainBundle/Pagination/PaginatorFactory.php index 5b3684d3c..7f06bf216 100644 --- a/src/Bundle/ChillMainBundle/Pagination/PaginatorFactory.php +++ b/src/Bundle/ChillMainBundle/Pagination/PaginatorFactory.php @@ -1,14 +1,14 @@ getCurrentItemsPerPage(), $this->getCurrentPageNumber(), - null === $route ? $this->getCurrentRoute() : $route, - null === $routeParameters ? $this->getCurrentRouteParameters() : - $routeParameters, + $route ?? $this->getCurrentRoute(), + $routeParameters ?? $this->getCurrentRouteParameters(), $this->router, self::DEFAULT_CURRENT_PAGE_KEY, self::DEFAULT_ITEM_PER_NUMBER_KEY @@ -124,12 +123,12 @@ class PaginatorFactory return array_merge( $this->router->getContext()->getParameters(), // get the route parameters - $this->requestStack - ->getCurrentRequest() - ->attributes->get('_route_params'), + $this->requestStack + ->getCurrentRequest() + ->attributes->get('_route_params'), // get the query parameters - $this->requestStack - ->getCurrentRequest()->query->all() + $this->requestStack + ->getCurrentRequest()->query->all() ); } } diff --git a/src/Bundle/ChillMainBundle/Pagination/PaginatorInterface.php b/src/Bundle/ChillMainBundle/Pagination/PaginatorInterface.php index bf7130516..cbdc12e6b 100644 --- a/src/Bundle/ChillMainBundle/Pagination/PaginatorInterface.php +++ b/src/Bundle/ChillMainBundle/Pagination/PaginatorInterface.php @@ -1,14 +1,14 @@ cachePool->getItem('pnum_' . $filtered); @@ -220,7 +224,7 @@ final class PhonenumberHelper implements PhoneNumberHelperInterface return null; } - $validation = json_decode($response->getBody()->getContents())->carrier->type; + $validation = json_decode($response->getBody()->getContents(), null, 512, JSON_THROW_ON_ERROR)->carrier->type; $item ->set($validation) diff --git a/src/Bundle/ChillMainBundle/Phonenumber/Templating.php b/src/Bundle/ChillMainBundle/Phonenumber/Templating.php index c07d8c5a8..b6304e0de 100644 --- a/src/Bundle/ChillMainBundle/Phonenumber/Templating.php +++ b/src/Bundle/ChillMainBundle/Phonenumber/Templating.php @@ -1,14 +1,14 @@ repository->find($id, $lockMode, $lockVersion); } + public function findActive(): array + { + return $this->findAll(); + } + /** * @return Center[] */ diff --git a/src/Bundle/ChillMainBundle/Repository/CenterRepositoryInterface.php b/src/Bundle/ChillMainBundle/Repository/CenterRepositoryInterface.php new file mode 100644 index 000000000..b933a34b7 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Repository/CenterRepositoryInterface.php @@ -0,0 +1,27 @@ +repository = $entityManager->getRepository($this->getClassName()); + } + + public function find($id): ?Civility + { + return $this->repository->find($id); + } + + public function findAll(): array + { + return $this->repository->findAll(); + } + + public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array + { + return $this->repository->findBy($criteria, $orderBy, $limit, $offset); + } + + public function findOneBy(array $criteria): ?Civility + { + return $this->repository->findOneBy($criteria); + } + + public function getClassName(): string + { + return Civility::class; } } diff --git a/src/Bundle/ChillMainBundle/Repository/CivilityRepositoryInterface.php b/src/Bundle/ChillMainBundle/Repository/CivilityRepositoryInterface.php new file mode 100644 index 000000000..5d687ac7e --- /dev/null +++ b/src/Bundle/ChillMainBundle/Repository/CivilityRepositoryInterface.php @@ -0,0 +1,34 @@ +repository = $entityManager->getRepository(Country::class); } + public function createQueryBuilder(string $alias, ?string $indexBy = null): QueryBuilder + { + return $this->repository->createQueryBuilder($alias, $indexBy); + } + public function find($id, $lockMode = null, $lockVersion = null): ?Country { return $this->repository->find($id, $lockMode, $lockVersion); diff --git a/src/Bundle/ChillMainBundle/Repository/CronJobExecutionRepository.php b/src/Bundle/ChillMainBundle/Repository/CronJobExecutionRepository.php new file mode 100644 index 000000000..a3c495d7d --- /dev/null +++ b/src/Bundle/ChillMainBundle/Repository/CronJobExecutionRepository.php @@ -0,0 +1,57 @@ +repository = $entityManager->getRepository($this->getClassName()); + } + + public function find($id): ?CronJobExecution + { + return $this->repository->find($id); + } + + /** + * @return array|CronJobExecution[] + */ + public function findAll(): array + { + return $this->repository->findAll(); + } + + /** + * @return array|CronJobExecution[] + */ + public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array + { + return $this->repository->findBy($criteria, $orderBy, $limit, $offset); + } + + public function findOneBy(array $criteria): ?CronJobExecution + { + return $this->repository->findOneBy($criteria); + } + + public function getClassName(): string + { + return CronJobExecution::class; + } +} diff --git a/src/Bundle/ChillMainBundle/Repository/CronJobExecutionRepositoryInterface.php b/src/Bundle/ChillMainBundle/Repository/CronJobExecutionRepositoryInterface.php new file mode 100644 index 000000000..df894bbfb --- /dev/null +++ b/src/Bundle/ChillMainBundle/Repository/CronJobExecutionRepositoryInterface.php @@ -0,0 +1,34 @@ +repository = $em->getRepository($this->getClassName()); + } + + public function find($id): ?GeographicalUnitLayer + { + return $this->repository->find($id); + } + + /** + * @return array|GeographicalUnitLayer[] + */ + public function findAll(): array + { + return $this->repository->findAll(); + } + + public function findAllHavingUnits(): array + { + $qb = $this->repository->createQueryBuilder('l'); + + return $qb->where($qb->expr()->gt('SIZE(l.units)', 0)) + ->getQuery() + ->getResult(); + } + + /** + * @return array|GeographicalUnitLayer[] + */ + public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array + { + return $this->repository->findBy($criteria, $orderBy, $limit, $offset); + } + + public function findOneBy(array $criteria): ?GeographicalUnitLayer + { + return $this->repository->findOneBy($criteria); + } + + public function getClassName(): string + { + return GeographicalUnitLayer::class; + } +} diff --git a/src/Bundle/ChillMainBundle/Repository/GeographicalUnitLayerRepositoryInterface.php b/src/Bundle/ChillMainBundle/Repository/GeographicalUnitLayerRepositoryInterface.php new file mode 100644 index 000000000..7d436b928 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Repository/GeographicalUnitLayerRepositoryInterface.php @@ -0,0 +1,23 @@ +repository = $em->getRepository($this->getClassName()); + } + + + public function countGeographicalUnitContainingAddress(Address $address): int + { + $qb = $this->buildQueryGeographicalUnitContainingAddress($address); + + return $qb + ->select('COUNT(gu)') + ->getQuery() + ->getSingleScalarResult(); + } + + public function findGeographicalUnitContainingAddress(Address $address, int $offset = 0, int $limit = 50): array + { + $qb = $this->buildQueryGeographicalUnitContainingAddress($address); + + return $qb + ->select(sprintf('NEW %s(gu.id, gu.unitName, gu.unitRefId, IDENTITY(gu.layer))', GeographicalUnit\SimpleGeographicalUnitDTO::class)) + ->addOrderBy('IDENTITY(gu.layer)') + ->addOrderBy(('gu.unitName')) + ->getQuery() + ->setFirstResult($offset) + ->setMaxResults($limit) + ->getResult(); + } + + private function buildQueryGeographicalUnitContainingAddress(Address $address): QueryBuilder + { + $qb = $this->repository + ->createQueryBuilder('gu') + ; + return $qb + ->select(sprintf('NEW %s(gu.id, gu.unitName, gu.unitRefId, IDENTITY(gu.layer))', GeographicalUnit\SimpleGeographicalUnitDTO::class)) + ->innerJoin(Address::class, 'address', Join::WITH, 'ST_CONTAINS(gu.geom, address.point) = TRUE') + ->where($qb->expr()->eq('address', ':address')) + ->setParameter('address', $address) + ; + } + + public function find($id): ?GeographicalUnit + { + return $this->repository->find($id); + } + + /** + * Will return only partial object, where the @see{GeographicalUnit::geom} property is not loaded. + * + * @return array|GeographicalUnit[] + */ + public function findAll(): array + { + return $this->repository + ->createQueryBuilder('gu') + ->select(sprintf('NEW %s(gu.id, gu.unitName, gu.unitRefId, IDENTITY(gu.layer))', GeographicalUnit\SimpleGeographicalUnitDTO::class)) + ->addOrderBy('IDENTITY(gu.layer)') + ->addOrderBy(('gu.unitName')) + ->getQuery() + ->getResult(); + } + + public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): ?GeographicalUnit + { + return $this->repository->findBy($criteria, $orderBy, $limit, $offset); + } + + public function findOneBy(array $criteria): ?GeographicalUnit + { + return $this->repository->findOneBy($criteria); + } + + public function getClassName(): string + { + return GeographicalUnit::class; + } +} diff --git a/src/Bundle/ChillMainBundle/Repository/GeographicalUnitRepositoryInterface.php b/src/Bundle/ChillMainBundle/Repository/GeographicalUnitRepositoryInterface.php new file mode 100644 index 000000000..f2c102407 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Repository/GeographicalUnitRepositoryInterface.php @@ -0,0 +1,33 @@ +repository = $entityManager->getRepository(Language::class); + $this->repository = $entityManager->getRepository($this->getClassName()); } public function find($id, $lockMode = null, $lockVersion = null): ?Language @@ -54,7 +53,7 @@ final class LanguageRepository implements ObjectRepository return $this->repository->findOneBy($criteria, $orderBy); } - public function getClassName() + public function getClassName(): string { return Language::class; } diff --git a/src/Bundle/ChillMainBundle/Repository/LanguageRepositoryInterface.php b/src/Bundle/ChillMainBundle/Repository/LanguageRepositoryInterface.php new file mode 100644 index 000000000..397b264e4 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Repository/LanguageRepositoryInterface.php @@ -0,0 +1,37 @@ +repository->findAll(); } + /** + * @return list + */ + public function findAllOrderedAlphabetically(): array + { + $qb = $this->repository->createQueryBuilder('pg'); + + return $qb->select(['pg', 'pg.name AS HIDDEN sort_name']) + ->orderBy('sort_name') + ->getQuery() + ->getResult(); + } + /** * @param mixed|null $limit * @param mixed|null $offset diff --git a/src/Bundle/ChillMainBundle/Repository/PostalCodeRepository.php b/src/Bundle/ChillMainBundle/Repository/PostalCodeRepository.php index 1c4a69366..32c1322be 100644 --- a/src/Bundle/ChillMainBundle/Repository/PostalCodeRepository.php +++ b/src/Bundle/ChillMainBundle/Repository/PostalCodeRepository.php @@ -1,14 +1,14 @@ repository = $entityManager->getRepository(PostalCode::class); + $this->repository = $entityManager->getRepository($this->getClassName()); $this->entityManager = $entityManager; } @@ -51,20 +50,11 @@ final class PostalCodeRepository implements ObjectRepository return $this->repository->find($id, $lockMode, $lockVersion); } - /** - * @return PostalCode[] - */ public function findAll(): array { return $this->repository->findAll(); } - /** - * @param mixed|null $limit - * @param mixed|null $offset - * - * @return PostalCode[] - */ public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array { return $this->repository->findBy($criteria, $orderBy, $limit, $offset); @@ -95,7 +85,7 @@ final class PostalCodeRepository implements ObjectRepository return $this->repository->findOneBy($criteria, $orderBy); } - public function getClassName() + public function getClassName(): string { return PostalCode::class; } diff --git a/src/Bundle/ChillMainBundle/Repository/PostalCodeRepositoryInterface.php b/src/Bundle/ChillMainBundle/Repository/PostalCodeRepositoryInterface.php new file mode 100644 index 000000000..fe3dee195 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Repository/PostalCodeRepositoryInterface.php @@ -0,0 +1,42 @@ +repository = $entityManager->getRepository(Regroupment::class); + } + + public function find($id, $lockMode = null, $lockVersion = null): ?Regroupment + { + return $this->repository->find($id, $lockMode, $lockVersion); + } + + /** + * @return Regroupment[] + */ + public function findAll(): array + { + return $this->repository->findAll(); + } + + public function findAllActive(): array + { + return $this->repository->findBy(['isActive' => true], ['name' => 'ASC']); + } + + /** + * @param mixed|null $limit + * @param mixed|null $offset + * + * @return Regroupment[] + */ + public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array + { + return $this->repository->findBy($criteria, $orderBy, $limit, $offset); + } + + public function findOneBy(array $criteria, ?array $orderBy = null): ?Regroupment + { + return $this->repository->findOneBy($criteria, $orderBy); + } + + public function getClassName() + { + return Regroupment::class; + } +} diff --git a/src/Bundle/ChillMainBundle/Repository/RoleScopeRepository.php b/src/Bundle/ChillMainBundle/Repository/RoleScopeRepository.php index 2475f9497..49162310c 100644 --- a/src/Bundle/ChillMainBundle/Repository/RoleScopeRepository.php +++ b/src/Bundle/ChillMainBundle/Repository/RoleScopeRepository.php @@ -1,14 +1,14 @@ + */ +class SavedExportRepository implements SavedExportRepositoryInterface +{ + private EntityRepository $repository; + + public function __construct(EntityManagerInterface $entityManager) + { + $this->repository = $entityManager->getRepository($this->getClassName()); + } + + public function find($id): ?SavedExport + { + return $this->repository->find($id); + } + + /** + * @return array|SavedExport[] + */ + public function findAll(): array + { + return $this->repository->findAll(); + } + + public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array + { + return $this->repository->findBy($criteria, $orderBy, $limit, $offset); + } + + public function findByUser(User $user, ?array $orderBy = [], ?int $limit = null, ?int $offset = null): array + { + $qb = $this->repository->createQueryBuilder('se'); + + $qb + ->where($qb->expr()->eq('se.user', ':user')) + ->setParameter('user', $user); + + if (null !== $limit) { + $qb->setMaxResults($limit); + } + + if (null !== $offset) { + $qb->setFirstResult($offset); + } + + foreach ($orderBy as $field => $order) { + $qb->addOrderBy('se.' . $field, $order); + } + + return $qb->getQuery()->getResult(); + } + + public function findOneBy(array $criteria): ?SavedExport + { + return $this->repository->findOneBy($criteria); + } + + public function getClassName(): string + { + return SavedExport::class; + } +} diff --git a/src/Bundle/ChillMainBundle/Repository/SavedExportRepositoryInterface.php b/src/Bundle/ChillMainBundle/Repository/SavedExportRepositoryInterface.php new file mode 100644 index 000000000..3b168505f --- /dev/null +++ b/src/Bundle/ChillMainBundle/Repository/SavedExportRepositoryInterface.php @@ -0,0 +1,40 @@ + + */ +interface SavedExportRepositoryInterface extends ObjectRepository +{ + public function find($id): ?SavedExport; + + /** + * @return array|SavedExport[] + */ + public function findAll(): array; + + public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array; + + /** + * @return array|SavedExport[] + */ + public function findByUser(User $user, ?array $orderBy = [], ?int $limit = null, ?int $offset = null): array; + + public function findOneBy(array $criteria): ?SavedExport; + + public function getClassName(): string; +} diff --git a/src/Bundle/ChillMainBundle/Repository/ScopeRepository.php b/src/Bundle/ChillMainBundle/Repository/ScopeRepository.php index 5b3efe658..158678fb6 100644 --- a/src/Bundle/ChillMainBundle/Repository/ScopeRepository.php +++ b/src/Bundle/ChillMainBundle/Repository/ScopeRepository.php @@ -1,22 +1,22 @@ repository = $entityManager->getRepository(Scope::class); } - public function createQueryBuilder($alias, $indexBy = null) + public function createQueryBuilder($alias, $indexBy = null): QueryBuilder { return $this->repository->createQueryBuilder($alias, $indexBy); } @@ -43,6 +43,15 @@ final class ScopeRepository implements ObjectRepository return $this->repository->findAll(); } + public function findAllActive(): array + { + $qb = $this->repository->createQueryBuilder('s'); + + $qb->where('s.active = \'TRUE\''); + + return $qb->getQuery()->getResult(); + } + /** * @param mixed|null $limit * @param mixed|null $offset @@ -59,7 +68,7 @@ final class ScopeRepository implements ObjectRepository return $this->repository->findOneBy($criteria, $orderBy); } - public function getClassName() + public function getClassName(): string { return Scope::class; } diff --git a/src/Bundle/ChillMainBundle/Repository/ScopeRepositoryInterface.php b/src/Bundle/ChillMainBundle/Repository/ScopeRepositoryInterface.php new file mode 100644 index 000000000..3cfb6042d --- /dev/null +++ b/src/Bundle/ChillMainBundle/Repository/ScopeRepositoryInterface.php @@ -0,0 +1,45 @@ +repository->findAll(); } + public function findAllActive(): array + { + return $this->repository->findBy(['active' => true]); + } + /** * @param mixed|null $limit * @param mixed|null $offset @@ -49,12 +53,12 @@ class UserJobRepository implements ObjectRepository return $this->repository->findBy($criteria, $orderBy, $limit, $offset); } - public function findOneBy(array $criteria) + public function findOneBy(array $criteria): ?UserJob { return $this->repository->findOneBy($criteria); } - public function getClassName() + public function getClassName(): string { return UserJob::class; } diff --git a/src/Bundle/ChillMainBundle/Repository/UserJobRepositoryInterface.php b/src/Bundle/ChillMainBundle/Repository/UserJobRepositoryInterface.php new file mode 100644 index 000000000..a752f452b --- /dev/null +++ b/src/Bundle/ChillMainBundle/Repository/UserJobRepositoryInterface.php @@ -0,0 +1,42 @@ +countBy(['enabled' => true]); } + public function countByNotHavingAttribute(string $key): int + { + $rsm = new ResultSetMapping(); + $rsm->addScalarResult('count', 'count'); + + $sql = 'SELECT count(*) FROM users u WHERE NOT attributes ?? :key OR attributes IS NULL AND enabled IS TRUE'; + + return $this->entityManager->createNativeQuery($sql, $rsm)->setParameter(':key', $key)->getSingleScalarResult(); + } + public function countByUsernameOrEmail(string $pattern): int { $qb = $this->queryByUsernameOrEmail($pattern); @@ -64,6 +77,81 @@ final class UserRepository implements ObjectRepository return $this->repository->findAll(); } + /** + * @param string $lang + */ + public function findAllAsArray(string $lang): iterable + { + $dql = sprintf(<<<'DQL' + SELECT + u.id AS id, + u.username AS username, + u.email, + u.enabled, + IDENTITY(u.civility) AS civility_id, + JSON_EXTRACT(civility.abbreviation, :lang) AS civility_abbreviation, + JSON_EXTRACT(civility.name, :lang) AS civility_name, + u.label, + mainCenter.id AS mainCenter_id, + mainCenter.name AS mainCenter_name, + IDENTITY(u.mainScope) AS mainScope_id, + JSON_EXTRACT(mainScope.name, :lang) AS mainScope_name, + IDENTITY(u.userJob) AS userJob_id, + JSON_EXTRACT(userJob.label, :lang) AS userJob_name, + currentLocation.id AS currentLocation_id, + currentLocation.name AS currentLocation_name, + mainLocation.id AS mainLocation_id, + mainLocation.name AS mainLocation_name, + u.absenceStart + FROM Chill\MainBundle\Entity\User u + LEFT JOIN u.civility civility + LEFT JOIN u.currentLocation currentLocation + LEFT JOIN u.mainLocation mainLocation + LEFT JOIN u.mainCenter mainCenter + LEFT JOIN u.mainScope mainScope + LEFT JOIN u.userJob userJob + ORDER BY u.label + DQL); + + $query = $this->entityManager->createQuery($dql) + ->setHydrationMode(AbstractQuery::HYDRATE_ARRAY) + ->setParameter('lang', $lang) + ; + + foreach ($query->toIterable() as $u) { + yield $u; + } + } + + public function findAllUserACLAsArray(): iterable + { + $sql = <<<'SQL' + SELECT + u.id, + u.username, + u.email, + u.label, + u.enabled, + c.id AS center_id, + c.name AS center_name, + pg.id AS permissionsGroup_id, + pg.name AS permissionsGroup_name + FROM users u + LEFT JOIN user_groupcenter ON u.id = user_groupcenter.user_id + LEFT JOIN group_centers ON user_groupcenter.groupcenter_id = group_centers.id + LEFT JOIN centers c on group_centers.center_id = c.id + LEFT JOIN permission_groups pg on group_centers.permissionsgroup_id = pg.id + ORDER BY u.username, c.name, pg.name + SQL; + + $query = $this->entityManager->getConnection()->executeQuery($sql); + + foreach ($query->iterateAssociative() as $u) { + yield $u; + } + } + + /** * @param mixed|null $limit * @param mixed|null $offset @@ -83,6 +171,29 @@ final class UserRepository implements ObjectRepository return $this->findBy(['enabled' => true], $orderBy, $limit, $offset); } + /** + * Find users which does not have a key on attribute column. + * + * @return array|User[] + */ + public function findByNotHavingAttribute(string $key, ?int $limit = null, ?int $offset = null): array + { + $rsm = new ResultSetMappingBuilder($this->entityManager); + $rsm->addRootEntityFromClassMetadata(User::class, 'u'); + + $sql = 'SELECT ' . $rsm->generateSelectClause() . ' FROM users u WHERE NOT attributes ?? :key OR attributes IS NULL AND enabled IS TRUE'; + + if (null !== $limit) { + $sql .= " LIMIT {$limit}"; + } + + if (null !== $offset) { + $sql .= " OFFSET {$offset}"; + } + + return $this->entityManager->createNativeQuery($sql, $rsm)->setParameter(':key', $key)->getResult(); + } + public function findByUsernameOrEmail(string $pattern, ?array $orderBy = [], ?int $limit = null, ?int $offset = null): array { $qb = $this->queryByUsernameOrEmail($pattern); @@ -109,11 +220,15 @@ final class UserRepository implements ObjectRepository return $this->repository->findOneBy($criteria, $orderBy); } - public function findOneByUsernameOrEmail(string $pattern) + public function findOneByUsernameOrEmail(string $pattern): ?User { - $qb = $this->queryByUsernameOrEmail($pattern); + $qb = $this->queryByUsernameOrEmail($pattern)->select('u'); - return $qb->getQuery()->getSingleResult(); + try { + return $qb->getQuery()->getSingleResult(); + } catch (NoResultException $e) { + return null; + } } /** @@ -171,7 +286,7 @@ final class UserRepository implements ObjectRepository return $qb->getQuery()->getResult(); } - public function getClassName() + public function getClassName(): string { return User::class; } diff --git a/src/Bundle/ChillMainBundle/Repository/UserRepositoryInterface.php b/src/Bundle/ChillMainBundle/Repository/UserRepositoryInterface.php new file mode 100644 index 000000000..b405e2d23 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Repository/UserRepositoryInterface.php @@ -0,0 +1,77 @@ + + */ +interface UserRepositoryInterface extends ObjectRepository +{ + public function countBy(array $criteria): int; + + public function countByActive(): int; + + public function countByNotHavingAttribute(string $key): int; + + public function countByUsernameOrEmail(string $pattern): int; + + /** + * Find a list of all users. + * + * The main purpose for this method is to provide a lightweight list of all users in the database. + * + * @param string $lang The lang to display all the translatable string (no fallback if not present) + * @return iterable + */ + public function findAllAsArray(string $lang): iterable; + + /** + * Find a list of permissions associated to each users. + * + * The main purpose for this method is to provide a lightweight list of all permissions group and center + * associated to each user. + * + * @return iterable + */ + public function findAllUserACLAsArray(): iterable; + + /** + * @return array|User[] + */ + public function findByActive(?array $orderBy = null, ?int $limit = null, ?int $offset = null): array; + + /** + * Find users which does not have a key on attribute column. + * + * @return array|User[] + */ + public function findByNotHavingAttribute(string $key, ?int $limit = null, ?int $offset = null): array; + + public function findByUsernameOrEmail(string $pattern, ?array $orderBy = [], ?int $limit = null, ?int $offset = null): array; + + public function findOneByUsernameOrEmail(string $pattern): ?User; + + /** + * Get the users having a specific flags. + * + * If provided, only the users amongst "filtered users" are searched. This + * allows to make a first search amongst users based on role and center + * and, then filter those users having some flags. + * + * @param \Chill\MainBundle\Entity\User[] $amongstUsers + * @param mixed $flag + */ + public function findUsersHavingFlags($flag, array $amongstUsers = []): array; +} diff --git a/src/Bundle/ChillMainBundle/Repository/Workflow/EntityWorkflowRepository.php b/src/Bundle/ChillMainBundle/Repository/Workflow/EntityWorkflowRepository.php index a73da74b0..a304ff6d7 100644 --- a/src/Bundle/ChillMainBundle/Repository/Workflow/EntityWorkflowRepository.php +++ b/src/Bundle/ChillMainBundle/Repository/Workflow/EntityWorkflowRepository.php @@ -1,14 +1,14 @@ repository = $entityManager->getRepository(EntityWorkflow::class); } + public function countByCc(User $user): int + { + $qb = $this->buildQueryByCc($user)->select('count(ew)'); + + return (int) $qb->getQuery()->getSingleScalarResult(); + } + public function countByDest(User $user): int { $qb = $this->buildQueryByDest($user)->select('count(ew)'); @@ -103,6 +110,19 @@ class EntityWorkflowRepository implements ObjectRepository return $this->repository->findBy($criteria, $orderBy, $limit, $offset); } + public function findByCc(User $user, ?array $orderBy = null, $limit = null, $offset = null): array + { + $qb = $this->buildQueryByCc($user)->select('ew'); + + foreach ($orderBy as $key => $sort) { + $qb->addOrderBy('ew.' . $key, $sort); + } + + $qb->setMaxResults($limit)->setFirstResult($offset); + + return $qb->getQuery()->getResult(); + } + public function findByDest(User $user, ?array $orderBy = null, $limit = null, $offset = null): array { $qb = $this->buildQueryByDest($user)->select('ew'); @@ -165,6 +185,25 @@ class EntityWorkflowRepository implements ObjectRepository return EntityWorkflow::class; } + private function buildQueryByCc(User $user): QueryBuilder + { + $qb = $this->repository->createQueryBuilder('ew'); + + $qb->join('ew.steps', 'step'); + + $qb->where( + $qb->expr()->andX( + $qb->expr()->isMemberOf(':user', 'step.ccUser'), + $qb->expr()->isNull('step.transitionAfter'), + $qb->expr()->eq('step.isFinal', "'FALSE'") + ) + ); + + $qb->setParameter('user', $user); + + return $qb; + } + private function buildQueryByDest(User $user): QueryBuilder { $qb = $this->repository->createQueryBuilder('ew'); diff --git a/src/Bundle/ChillMainBundle/Repository/Workflow/EntityWorkflowStepRepository.php b/src/Bundle/ChillMainBundle/Repository/Workflow/EntityWorkflowStepRepository.php index e4f0aa45d..d8c3619c3 100644 --- a/src/Bundle/ChillMainBundle/Repository/Workflow/EntityWorkflowStepRepository.php +++ b/src/Bundle/ChillMainBundle/Repository/Workflow/EntityWorkflowStepRepository.php @@ -1,14 +1,14 @@ { +export const dateToISO = (date: Date|null): string|null => { if (null === date) { return null; } @@ -29,7 +29,7 @@ const dateToISO = (date) => { * * **Experimental** */ -const ISOToDate = (str) => { +export const ISOToDate = (str: string|null): Date|null => { if (null === str) { return null; } @@ -38,25 +38,25 @@ const ISOToDate = (str) => { } let - [year, month, day] = str.split('-'); + [year, month, day] = str.split('-').map(p => parseInt(p)); - return new Date(year, month-1, day); + return new Date(year, month-1, day, 0, 0, 0, 0); } /** * Return a date object from iso string formatted as YYYY-mm-dd:HH:MM:ss+01:00 * */ -const ISOToDatetime = (str) => { +export const ISOToDatetime = (str: string|null): Date|null => { if (null === str) { return null; } let [cal, times] = str.split('T'), - [year, month, date] = cal.split('-'), + [year, month, date] = cal.split('-').map(s => parseInt(s)), [time, timezone] = times.split(times.charAt(8)), - [hours, minutes, seconds] = time.split(':') + [hours, minutes, seconds] = time.split(':').map(s => parseInt(s)); ; return new Date(year, month-1, date, hours, minutes, seconds); @@ -66,7 +66,7 @@ const ISOToDatetime = (str) => { * Convert a date to ISO8601, valid for usage in api * */ -const datetimeToISO = (date) => { +export const datetimeToISO = (date: Date): string => { let cal, time, offset; cal = [ date.getFullYear(), @@ -92,7 +92,7 @@ const datetimeToISO = (date) => { return x; }; -const intervalDaysToISO = (days) => { +export const intervalDaysToISO = (days: number|string|null): string => { if (null === days) { return 'P0D'; } @@ -100,7 +100,7 @@ const intervalDaysToISO = (days) => { return `P${days}D`; } -const intervalISOToDays = (str) => { +export const intervalISOToDays = (str: string|null): number|null => { if (null === str) { return null } @@ -154,12 +154,3 @@ const intervalISOToDays = (str) => { return days; } - -export { - dateToISO, - ISOToDate, - ISOToDatetime, - datetimeToISO, - intervalISOToDays, - intervalDaysToISO, -}; diff --git a/src/Bundle/ChillMainBundle/Resources/public/chill/scss/flex_table.scss b/src/Bundle/ChillMainBundle/Resources/public/chill/scss/flex_table.scss index 7ed607535..673d5129a 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/chill/scss/flex_table.scss +++ b/src/Bundle/ChillMainBundle/Resources/public/chill/scss/flex_table.scss @@ -99,11 +99,14 @@ div.flex-table { div.item-bloc { flex-direction: column; - &:nth-child(even) { - background-color: $gray-200; + &:not(.no-altern) { // class to avoid even/odd - .chill-user-quote { - background-color: shade-color($gray-200, 5%) + &:nth-child(even) { + background-color: $gray-200; + + .chill-user-quote { + background-color: shade-color($gray-200, 5%) + } } } diff --git a/src/Bundle/ChillMainBundle/Resources/public/chill/scss/record_actions.scss b/src/Bundle/ChillMainBundle/Resources/public/chill/scss/record_actions.scss index d72b77e04..5158a826e 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/chill/scss/record_actions.scss +++ b/src/Bundle/ChillMainBundle/Resources/public/chill/scss/record_actions.scss @@ -1,3 +1,11 @@ +/// mixin to set sticky area on bottom when scrolling +@mixin sticky-bottom { + position: sticky; + bottom: 0; + margin-top: 4em; + z-index: 1000; +} + ul.record_actions { display: flex; flex-direction: row; @@ -5,6 +13,10 @@ ul.record_actions { justify-content: flex-end; padding: 0.5em 0; + &.inline { + display: inline-block; + } + &.column { flex-direction: column; } @@ -18,6 +30,13 @@ ul.record_actions { padding-right: 1em; } + &.small { + .btn { + padding: .25rem .5rem; + font-size: .75rem; + } + } + li { display: inline-block; list-style-type: none; @@ -42,16 +61,30 @@ ul.record_actions { } } } + +.sticky-form { + background-color: $white; + padding-top: 1.25em; + margin: -1em; + box-shadow: 0 -20px 20px -20px rgba($chill-gray, .5); + @include sticky-bottom; + + .sticky-form-buttons { + position: initial; + bottom: unset; + margin-top: unset; + z-index: unset; + } +} + .sticky-form-buttons { - margin-top: 4em; - background-color: $beige; - position: sticky; - bottom: 0.3em; - text-align: center; - display: flex; - padding: 0.8em 1.6em; - border-radius: 0; - z-index: 1000; + display: flex; + background-color: $beige; + text-align: center; + padding: 0.8em 1.6em; + border-radius: 0; + @include sticky-bottom; + bottom: 0.3em; } /// EXCEPTIONS diff --git a/src/Bundle/ChillMainBundle/Resources/public/chill/scss/render_box.scss b/src/Bundle/ChillMainBundle/Resources/public/chill/scss/render_box.scss index 4d0bfcf8e..57fa17648 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/chill/scss/render_box.scss +++ b/src/Bundle/ChillMainBundle/Resources/public/chill/scss/render_box.scss @@ -64,6 +64,7 @@ section.chill-entity { margin: 0.7em 0; p { display: block; + margin-bottom: 0; } } &.delimiter { @@ -106,18 +107,5 @@ section.chill-entity { // used for comment-embeddable &.entity-comment-embeddable { width: 100%; - - /* already defined !! - div.metadata { - font-size: smaller; - color: $gray-600; - span.user, span.date { - text-decoration: underline dotted; - &:hover { - color: $gray-700; - } - } - } - */ } } diff --git a/src/Bundle/ChillMainBundle/Resources/public/lib/api/address.ts b/src/Bundle/ChillMainBundle/Resources/public/lib/api/address.ts new file mode 100644 index 000000000..1499f4a6a --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/lib/api/address.ts @@ -0,0 +1,35 @@ +import {Address, GeographicalUnitLayer, SimpleGeographicalUnit} from "../../types"; +import {fetchResults, makeFetch} from "./apiMethods"; + +export const getAddressById = async (address_id: number): Promise
    => +{ + const url = `/api/1.0/main/address/${address_id}.json`; + + const response = await fetch(url); + + if (response.ok) { + return response.json(); + } + + throw Error('Error with request resource response'); +}; + +export const getGeographicalUnitsByAddress = async (address: Address): Promise => { + return fetchResults(`/api/1.0/main/geographical-unit/by-address/${address.address_id}.json`); +} + +export const getAllGeographicalUnitLayers = async (): Promise => { + return fetchResults(`/api/1.0/main/geographical-unit-layer.json`); +} + +export const syncAddressWithReference = async (address: Address): Promise
    => { + return makeFetch("POST", `/api/1.0/main/address/reference-match/${address.address_id}/sync-with-reference`); +} + +export const markAddressReviewed = async (address: Address): Promise
    => { + return makeFetch("POST", `/api/1.0/main/address/reference-match/${address.address_id}/set/reviewed`); +} + +export const markAddressToReview = async (address: Address): Promise
    => { + return makeFetch("POST", `/api/1.0/main/address/reference-match/${address.address_id}/set/to_review`); +} diff --git a/src/Bundle/ChillMainBundle/Resources/public/lib/api/apiMethods.d.ts.backup b/src/Bundle/ChillMainBundle/Resources/public/lib/api/apiMethods.d.ts.backup new file mode 100644 index 000000000..07ce99e1a --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/lib/api/apiMethods.d.ts.backup @@ -0,0 +1,4 @@ +export function fetchResults(uri: string, params: {item_per_page?: number}): Promise; + +export function makeFetch(method: "GET"|"POST"|"PATCH"|"DELETE", url: string, body: B, options: {[key: string]: string}): Promise; + diff --git a/src/Bundle/ChillMainBundle/Resources/public/lib/api/apiMethods.js b/src/Bundle/ChillMainBundle/Resources/public/lib/api/apiMethods.js deleted file mode 100644 index 80e59005f..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/lib/api/apiMethods.js +++ /dev/null @@ -1,110 +0,0 @@ -/** - * Generic api method that can be adapted to any fetch request - */ -const makeFetch = (method, url, body, options) => { - let opts = { - method: method, - headers: { - 'Content-Type': 'application/json;charset=utf-8' - }, - body: (body !== null) ? JSON.stringify(body) : null - }; - - if (typeof options !== 'undefined') { - opts = Object.assign(opts, options); - } - - return fetch(url, opts) - .then(response => { - if (response.ok) { - return response.json(); - } - - if (response.status === 422) { - return response.json().then(response => { - throw ValidationException(response) - }); - } - - if (response.status === 403) { - throw AccessException(response); - } - - throw { - name: 'Exception', - sta: response.status, - txt: response.statusText, - err: new Error(), - violations: response.body - }; - }); -} - -/** - * Fetch results with certain parameters - */ -const _fetchAction = (page, uri, params) => { - const item_per_page = 50; - if (params === undefined) { - params = {}; - } - let url = uri + '?' + new URLSearchParams({ item_per_page, page, ...params }); - - return fetch(url, { - method: 'GET', - headers: { - 'Content-Type': 'application/json;charset=utf-8' - }, - }).then(response => { - if (response.ok) { return response.json(); } - throw Error({ m: response.statusText }); - }); -}; - -const fetchResults = async (uri, params) => { - let promises = [], - page = 1; - let firstData = await _fetchAction(page, uri, params); - - promises.push(Promise.resolve(firstData.results)); - - if (firstData.pagination.more) { - do { - page = ++page; - promises.push(_fetchAction(page, uri, params).then(r => Promise.resolve(r.results))); - } while (page * firstData.pagination.items_per_page < firstData.count) - } - - return Promise.all(promises).then(values => values.flat()); -}; - -const fetchScopes = () => { - return fetchResults('/api/1.0/main/scope.json'); -}; - - -/** - * Error objects to be thrown - */ -const ValidationException = (response) => { - const error = {}; - error.name = 'ValidationException'; - error.violations = response.violations.map((violation) => `${violation.title}: ${violation.propertyPath}`); - error.titles = response.violations.map((violation) => violation.title); - error.propertyPaths = response.violations.map((violation) => violation.propertyPath); - return error; -} - -const AccessException = (response) => { - const error = {}; - error.name = 'AccessException'; - error.violations = ['You are not allowed to perform this action']; - - return error; -} - -export { - makeFetch, - fetchResults, - fetchScopes -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/lib/api/apiMethods.ts b/src/Bundle/ChillMainBundle/Resources/public/lib/api/apiMethods.ts new file mode 100644 index 000000000..17ac8879e --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/lib/api/apiMethods.ts @@ -0,0 +1,222 @@ +import {Scope} from '../../types'; + +export type body = {[key: string]: boolean|string|number|null}; +export type fetchOption = {[key: string]: boolean|string|number|null}; + +export interface Params { + [key: string]: number|string +} + +export interface PaginationResponse { + pagination: { + more: boolean; + items_per_page: number; + }; + results: T[]; + count: number; +} + +export interface FetchParams { + [K: string]: string|number|null; +}; + +export interface TransportExceptionInterface { + name: string; +} + +export interface ValidationExceptionInterface extends TransportExceptionInterface { + name: 'ValidationException'; + error: object; + violations: string[]; + titles: string[]; + propertyPaths: string[]; +} + +export interface ValidationErrorResponse extends TransportExceptionInterface { + violations: { + title: string; + propertyPath: string; + }[]; +} + +export interface AccessExceptionInterface extends TransportExceptionInterface { + name: 'AccessException'; + violations: string[]; +} + +export interface NotFoundExceptionInterface extends TransportExceptionInterface { + name: 'NotFoundException'; +} + +export interface ServerExceptionInterface extends TransportExceptionInterface { + name: 'ServerException'; + message: string; + code: number; + body: string; +} + + +/** + * Generic api method that can be adapted to any fetch request + */ +export const makeFetch = (method: 'POST'|'GET'|'PUT'|'PATCH'|'DELETE', url: string, body?: body | Input | null, options?: FetchParams): Promise => { + let opts = { + method: method, + headers: { + 'Content-Type': 'application/json;charset=utf-8' + }, + }; + + if (body !== null && typeof body !== 'undefined') { + Object.assign(opts, {body: JSON.stringify(body)}) + } + + if (typeof options !== 'undefined') { + opts = Object.assign(opts, options); + } + return fetch(url, opts) + .then(response => { + if (response.ok) { + return response.json(); + } + + if (response.status === 422) { + return response.json().then(response => { + throw ValidationException(response) + }); + } + + if (response.status === 403) { + throw AccessException(response); + } + + throw { + name: 'Exception', + sta: response.status, + txt: response.statusText, + err: new Error(), + violations: response.body + }; + }); +} + +/** + * Fetch results with certain parameters + */ +function _fetchAction(page: number, uri: string, params?: FetchParams): Promise> { + const item_per_page: number = 50; + + let searchParams = new URLSearchParams(); + searchParams.append('item_per_page', item_per_page.toString()); + searchParams.append('page', page.toString()); + + if (params !== undefined) { + Object.keys(params).forEach(key => { + let v = params[key]; + if (typeof v === 'string') { + searchParams.append(key, v); + } else if (typeof v === 'number') { + searchParams.append(key, v.toString()); + } else if (v === null) { + searchParams.append(key, ''); + } + }); + } + + let url = uri + '?' + searchParams.toString(); + + return fetch(url, { + method: 'GET', + headers: { + 'Content-Type': 'application/json;charset=utf-8' + }, + }).then((response) => { + if (response.ok) { return response.json(); } + + if (response.status === 404) { + throw NotFoundException(response); + } + + if (response.status === 422) { + return response.json().then(response => { + throw ValidationException(response) + }); + } + + if (response.status === 403) { + throw AccessException(response); + } + + if (response.status >= 500) { + return response.text().then(body => { + throw ServerException(response.status, body); + }); + } + + throw new Error("other network error"); + }).catch((reason: any) => { + console.error(reason); + throw new Error(reason); + }); +}; + +export const fetchResults = async (uri: string, params?: FetchParams): Promise => { + let promises: Promise[] = [], + page = 1; + let firstData: PaginationResponse = await _fetchAction(page, uri, params) as PaginationResponse; + + promises.push(Promise.resolve(firstData.results)); + + if (firstData.pagination.more) { + do { + page = ++page; + promises.push( + _fetchAction(page, uri, params) + .then(r => Promise.resolve(r.results)) + ); + } while (page * firstData.pagination.items_per_page < firstData.count) + } + + return Promise.all(promises).then((values) => values.flat()); +}; + +export const fetchScopes = (): Promise => { + return fetchResults('/api/1.0/main/scope.json'); +}; + + +/** + * Error objects to be thrown + */ +const ValidationException = (response: ValidationErrorResponse): ValidationExceptionInterface => { + const error = {} as ValidationExceptionInterface; + error.name = 'ValidationException'; + error.violations = response.violations.map((violation) => `${violation.title}: ${violation.propertyPath}`); + error.titles = response.violations.map((violation) => violation.title); + error.propertyPaths = response.violations.map((violation) => violation.propertyPath); + return error; +} + +const AccessException = (response: Response): AccessExceptionInterface => { + const error = {} as AccessExceptionInterface; + error.name = 'AccessException'; + error.violations = ['You are not allowed to perform this action']; + + return error; +} + +const NotFoundException = (response: Response): NotFoundExceptionInterface => { + const error = {} as NotFoundExceptionInterface; + error.name = 'NotFoundException'; + + return error; +} + +const ServerException = (code: number, body: string): ServerExceptionInterface => { + const error = {} as ServerExceptionInterface; + error.name = 'ServerException'; + error.code = code; + error.body = body; + + return error; +} diff --git a/src/Bundle/ChillMainBundle/Resources/public/lib/api/locations.ts b/src/Bundle/ChillMainBundle/Resources/public/lib/api/locations.ts new file mode 100644 index 000000000..d5ab081c3 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/lib/api/locations.ts @@ -0,0 +1,6 @@ +import {fetchResults} from "./apiMethods"; +import {Location, LocationType} from "../../types"; + +export const getLocations = (): Promise => fetchResults('/api/1.0/main/location.json'); + +export const getLocationTypes = (): Promise => fetchResults('/api/1.0/main/location-type.json'); diff --git a/src/Bundle/ChillMainBundle/Resources/public/lib/api/user.ts b/src/Bundle/ChillMainBundle/Resources/public/lib/api/user.ts new file mode 100644 index 000000000..730e6de66 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/lib/api/user.ts @@ -0,0 +1,25 @@ +import {User} from "../../types"; +import {makeFetch} from "./apiMethods"; + +export const whoami = (): Promise => { + const url = `/api/1.0/main/whoami.json`; + return fetch(url) + .then(response => { + if (response.ok) { + return response.json(); + } + throw { + msg: 'Error while getting whoami.', + sta: response.status, + txt: response.statusText, + err: new Error(), + body: response.body + }; + }); +}; + +export const whereami = (): Promise => { + const url = `/api/1.0/main/user-current-location.json`; + + return makeFetch("GET", url); +} diff --git a/src/Bundle/ChillMainBundle/Resources/public/lib/download-report/download-report.js b/src/Bundle/ChillMainBundle/Resources/public/lib/download-report/download-report.js index c7b7bb59a..041e94c45 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/lib/download-report/download-report.js +++ b/src/Bundle/ChillMainBundle/Resources/public/lib/download-report/download-report.js @@ -1,4 +1,4 @@ -/* +/* * Copyright (C) 2018 Champs Libres Cooperative * * This program is free software: you can redistribute it and/or modify @@ -15,12 +15,12 @@ * along with this program. If not, see . */ -var mime = require('mime-types') +var mime = require('mime') var download_report = (url, container) => { var download_text = container.dataset.downloadText, alias = container.dataset.alias; - + window.fetch(url, { credentials: 'same-origin' }) .then(response => { if (!response.ok) { @@ -29,21 +29,21 @@ var download_report = (url, container) => { return response.blob(); }).then(blob => { - + var content = URL.createObjectURL(blob), link = document.createElement("a"), type = blob.type, hasForcedType = 'mimeType' in container.dataset, extension; - + if (hasForcedType) { // force a type type = container.dataset.mimeType; blob = new Blob([ blob ], { 'type': type }); content = URL.createObjectURL(blob); } - - extension = mime.extension(type); + + extension = mime.getExtension(type); link.appendChild(document.createTextNode(download_text)); link.classList.add("btn", "btn-action"); @@ -56,7 +56,7 @@ var download_report = (url, container) => { container.appendChild(link); }).catch(function(error) { console.log(error); - var problem_text = + var problem_text = document.createTextNode("Problem during download"); container @@ -64,4 +64,4 @@ var download_report = (url, container) => { }); }; -module.exports = download_report; \ No newline at end of file +module.exports = download_report; diff --git a/src/Bundle/ChillMainBundle/Resources/public/lib/export-list/export-list.scss b/src/Bundle/ChillMainBundle/Resources/public/lib/export-list/export-list.scss deleted file mode 100644 index 25b42a822..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/lib/export-list/export-list.scss +++ /dev/null @@ -1,20 +0,0 @@ -.container-export { - margin-left: 1rem; - margin-right: 1rem; - - .export-list { - display: flex; - flex-direction: row; - flex-wrap: wrap; - - .export-list__element { - min-width: 18rem; - max-width: 20rem; - padding: 1rem; - margin: 1rem; - flex-grow: 1; - - border: 1px solid var(--chill-gray); - } - } -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/lib/export-list/index.js b/src/Bundle/ChillMainBundle/Resources/public/lib/export-list/index.js deleted file mode 100644 index 49e1a5b20..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/lib/export-list/index.js +++ /dev/null @@ -1 +0,0 @@ -require('./export-list.scss'); diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/address-details/index.ts b/src/Bundle/ChillMainBundle/Resources/public/module/address-details/index.ts new file mode 100644 index 000000000..9bc2409ee --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/module/address-details/index.ts @@ -0,0 +1,39 @@ +import AddressDetailsButton from "../../vuejs/_components/AddressDetails/AddressDetailsButton.vue"; +import {createApp} from "vue"; +import {createI18n} from "vue-i18n"; +import {_createI18n} from "../../vuejs/_js/i18n"; +import {Address} from "../../types"; + +const i18n = _createI18n({}); + +document.querySelectorAll('span[data-address-details]').forEach((el) => { + const dataset = el.dataset as { + addressId: string, + addressRefStatus: string, + }; + + const app = createApp({ + components: {AddressDetailsButton}, + data() { + return { + addressId: Number.parseInt(dataset.addressId), + addressRefStatus: dataset.addressRefStatus, + } + }, + template: '', + methods: { + onUpdateAddress: (address: Address): void => { + if (address.refStatus === 'to_review' || address.refStatus === 'reviewed') { + // in this two case, the address content do not change + return; + } + if (window.confirm("L'adresse a été modifiée. Vous pouvez continuer votre travail. Cependant, pour afficher les données immédiatement, veuillez recharger la page. \n\n Voulez-vous recharger la page immédiatement ?")) { + window.location.reload(); + } + } + } + }); + + app.use(i18n); + app.mount(el); +}); diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/bootstrap/_shared.scss b/src/Bundle/ChillMainBundle/Resources/public/module/bootstrap/_shared.scss index 21e1ceca0..96da20779 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/module/bootstrap/_shared.scss +++ b/src/Bundle/ChillMainBundle/Resources/public/module/bootstrap/_shared.scss @@ -1,14 +1,25 @@ -// Configuration +/* + * This shared file is used in Chill sass sheets and Vue sass styles to use some bootstrap/chill variables. + * Search on @import 'ChillMainAssets/module/bootstrap/shared'; + */ + +// 1. Include functions first (so you can manipulate colors, SVGs, calc, etc) @import "bootstrap/scss/functions"; -/* -* Replaced by CHILL variables -* it is necessary to keep the possibility of making a diff with original ! -* original: "bootstrap/scss/variables"; -*/ +// 2. Include any default variable overrides here @import "custom/_variables"; +// 3. Include remainder of required Bootstrap stylesheets +@import "bootstrap/scss/variables"; + +// 4. Include any default map overrides here +@import "custom/_maps"; +@import "bootstrap/scss/maps"; + +// 5. Include remainder of required parts @import "bootstrap/scss/mixins"; @import "bootstrap/scss/utilities"; +@import "bootstrap/scss/root"; + diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/bootstrap/bootstrap.scss b/src/Bundle/ChillMainBundle/Resources/public/module/bootstrap/bootstrap.scss index cf36a9a54..a8d5f2f13 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/module/bootstrap/bootstrap.scss +++ b/src/Bundle/ChillMainBundle/Resources/public/module/bootstrap/bootstrap.scss @@ -1,18 +1,8 @@ -/*! - * Bootstrap v5.0.1 (https://getbootstrap.com/) - * Copyright 2011-2021 The Bootstrap Authors - * Copyright 2011-2021 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - * - * Enable / disable bootstrap assets - */ - -// Bootstrap configuration files are shared with chill entrypoint +// Some Bootstrap variables and configuration files are shared with chill entrypoint @import "shared"; -// scss-docs-start import-stack -// Layout & components -@import "bootstrap/scss/root"; +// 6. Optionally include any other parts as needed +@import "bootstrap/scss/utilities"; @import "bootstrap/scss/reboot"; @import "bootstrap/scss/type"; @import "bootstrap/scss/images"; @@ -42,14 +32,11 @@ @import "bootstrap/scss/carousel"; @import "bootstrap/scss/spinners"; @import "bootstrap/scss/offcanvas"; - -// Helpers @import "bootstrap/scss/helpers"; -// Utilities +// 7. Optionally include utilities API last to generate classes based on the Sass map in @import "bootstrap/scss/utilities/api"; -// scss-docs-end import-stack -// CHILL custom +// 8. Add additional custom code here @import "custom"; @import "custom/_debug"; diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/bootstrap/custom/_maps.scss b/src/Bundle/ChillMainBundle/Resources/public/module/bootstrap/custom/_maps.scss new file mode 100644 index 000000000..a2f996fb9 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/module/bootstrap/custom/_maps.scss @@ -0,0 +1,69 @@ +//// CHILL OVERRIDE DEFAULT BOOTSTRAP COLORS MAP + +// 'beige' variations +$beige-100: tint-color($beige, 80%); +$beige-200: tint-color($beige, 60%); +$beige-300: tint-color($beige, 40%); +$beige-400: tint-color($beige, 20%); +$beige-500: $beige; +$beige-600: shade-color($beige, 20%); +$beige-700: shade-color($beige, 40%); +$beige-800: shade-color($beige, 60%); +$beige-900: shade-color($beige, 80%); + +$chill-blue: $blue; +$chill-green: $green; +$chill-green-dark: $green-600; +$chill-orange: $orange; +$chill-yellow: $yellow; +$chill-red: $red; +$chill-beige: $beige; +$chill-pink: $pink; +$chill-dark-gray: $gray-800; +$chill-gray: $gray-600; +$chill-l-gray: $gray-400; +$chill-ll-gray: $gray-300; +$chill-light-gray: $gray-200; +$chill-llight-gray: $gray-100; + +$colors: ( + "blue": $blue, + "green": $green, + "green-dark": $green-600, + "yellow": $yellow, + "orange": $orange, + "red": $red, + "pink": $pink, + "beige": $beige, + "white": $white, + "gray": $gray-600, + "dark": $gray-800, + "black": $black +); + +$chill-colors: ( + "chill-blue": $chill-blue, + "chill-green": $chill-green, + "chill-green-dark": $chill-green-dark, + "chill-orange": $chill-orange, + "chill-yellow": $chill-yellow, + "chill-red": $chill-red, + "chill-beige": $chill-beige, + "chill-pink": $chill-pink, + "chill-dark-gray": $chill-dark-gray, + "chill-gray": $chill-gray, + "chill-l-gray": $chill-l-gray, + "chill-ll-gray": $chill-ll-gray, + "chill-light-gray": $chill-light-gray, + "chill-llight-gray": $chill-llight-gray, +); + +// Merge the maps +$theme-colors: map-merge($theme-colors, $chill-colors); + +// Chill color classes +@each $color, $value in $chill-colors { + .#{$color} { + color: $value; + } +} \ No newline at end of file diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/bootstrap/custom/_variables.scss b/src/Bundle/ChillMainBundle/Resources/public/module/bootstrap/custom/_variables.scss index f9cd33f5f..26bf517ed 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/module/bootstrap/custom/_variables.scss +++ b/src/Bundle/ChillMainBundle/Resources/public/module/bootstrap/custom/_variables.scss @@ -1,144 +1,32 @@ -// Variables -// -// Variables should follow the `$component-state-property-size` formula for -// consistent naming. Ex: $nav-link-disabled-color and $modal-content-box-shadow-xs. - -// Color system - -// scss-docs-start gray-color-variables -$white: #fff !default; -$gray-100: #f3f3f3 !default; -$gray-200: #e6e6e6 !default; -$gray-300: #dee2e6 !default; -$gray-400: #ced4da !default; -$gray-500: #b2b2b2 !default; -$gray-600: #6c757d !default; -$gray-700: #495057 !default; -$gray-800: #2c2d2f !default; -$gray-900: #212529 !default; -$black: #111 !default; -// scss-docs-end gray-color-variables - -// fusv-disable -// scss-docs-start gray-colors-map -$grays: ( - "100": $gray-100, - "200": $gray-200, /* = $chill-light-gray, $chill-gray */ - "300": $gray-300, - "400": $gray-400, - "500": $gray-500, /* = $chill-light-gray */ - "600": $gray-600, - "700": $gray-700, - "800": $gray-800, /* = $chill-dark-gray */ - "900": $gray-900 -) !default; -// scss-docs-end gray-colors-map -// fusv-enable - /* * CHILL Theme colors - * (apply chill colors, add missing colors, trust in bootstrap grey scale) */ -// scss-docs-start color-variables -$blue: #334d5c !default; -$green: #43b29d !default; -$yellow: #eec84a !default; -$orange: #e2793d !default; -$red: #df4949 !default; -$pink: #dd506d !default; -$beige: #cabb9f !default; -// scss-docs-end color-variables -// fusv-disable -$blue-100: tint-color($blue, 80%) !default; -$blue-200: tint-color($blue, 60%) !default; -$blue-300: tint-color($blue, 40%) !default; -$blue-400: tint-color($blue, 20%) !default; -$blue-500: $blue !default; -$blue-600: shade-color($blue, 20%) !default; -$blue-700: shade-color($blue, 40%) !default; -$blue-800: shade-color($blue, 60%) !default; -$blue-900: shade-color($blue, 80%) !default; +//// CHILL OVERRIDE DEFAULT BOOTSTRAP COLORS VARIABLES -$beige-100: tint-color($beige, 80%) !default; -$beige-200: tint-color($beige, 60%) !default; -$beige-300: tint-color($beige, 40%) !default; -$beige-400: tint-color($beige, 20%) !default; -$beige-500: $beige !default; -$beige-600: shade-color($beige, 20%) !default; -$beige-700: shade-color($beige, 40%) !default; -$beige-800: shade-color($beige, 60%) !default; -$beige-900: shade-color($beige, 80%) !default; +$white: #fff; +$gray-100: #f3f3f3; +$gray-200: #e6e6e6; +$gray-300: #dee2e6; +$gray-400: #ced4da; +$gray-500: #b2b2b2; +$gray-600: #6c757d; +$gray-700: #495057; +$gray-800: #2c2d2f; +$gray-900: #212529; +$black: #111; -$pink-100: tint-color($pink, 80%) !default; -$pink-200: tint-color($pink, 60%) !default; -$pink-300: tint-color($pink, 40%) !default; -$pink-400: tint-color($pink, 20%) !default; -$pink-500: $pink !default; -$pink-600: shade-color($pink, 20%) !default; -$pink-700: shade-color($pink, 40%) !default; -$pink-800: shade-color($pink, 60%) !default; -$pink-900: shade-color($pink, 80%) !default; +// override main colors +// don't use indigo, purple, teal +// add 'beige' +$blue: #334d5c; +$green: #43b29d; +$yellow: #eec84a; +$orange: #e2793d; +$red: #df4949; +$pink: #dd506d; +$beige: #cabb9f; -$red-100: tint-color($red, 80%) !default; -$red-200: tint-color($red, 60%) !default; -$red-300: tint-color($red, 40%) !default; -$red-400: tint-color($red, 20%) !default; -$red-500: $red !default; -$red-600: shade-color($red, 20%) !default; -$red-700: shade-color($red, 40%) !default; -$red-800: shade-color($red, 60%) !default; -$red-900: shade-color($red, 80%) !default; - -$orange-100: tint-color($orange, 80%) !default; -$orange-200: tint-color($orange, 60%) !default; -$orange-300: tint-color($orange, 40%) !default; -$orange-400: tint-color($orange, 20%) !default; -$orange-500: $orange !default; -$orange-600: shade-color($orange, 20%) !default; -$orange-700: shade-color($orange, 40%) !default; -$orange-800: shade-color($orange, 60%) !default; -$orange-900: shade-color($orange, 80%) !default; - -$yellow-100: tint-color($yellow, 80%) !default; -$yellow-200: tint-color($yellow, 60%) !default; -$yellow-300: tint-color($yellow, 40%) !default; -$yellow-400: tint-color($yellow, 20%) !default; -$yellow-500: $yellow !default; -$yellow-600: shade-color($yellow, 20%) !default; -$yellow-700: shade-color($yellow, 40%) !default; -$yellow-800: shade-color($yellow, 60%) !default; -$yellow-900: shade-color($yellow, 80%) !default; - -$green-100: tint-color($green, 80%) !default; -$green-200: tint-color($green, 60%) !default; -$green-300: tint-color($green, 40%) !default; -$green-400: tint-color($green, 20%) !default; -$green-500: $green !default; -$green-600: shade-color($green, 20%) !default; -$green-700: shade-color($green, 40%) !default; -$green-800: shade-color($green, 60%) !default; -$green-900: shade-color($green, 80%) !default; -// fusv-enable - -// scss-docs-start colors-map -$colors: ( - "blue": $blue, - "green": $green, - "green-dark": $green-600, - "yellow": $yellow, - "orange": $orange, - "red": $red, - "pink": $pink, - "beige": $beige, - "white": $white, - "gray": $gray-600, - "dark": $gray-800, - "black": $black -) !default; -// scss-docs-end colors-map - -// scss-docs-start theme-color-variables $primary: $blue; $secondary: $gray-500; $success: $green; @@ -149,1331 +37,29 @@ $light: $gray-100; $dark: $gray-800; $black: $black; -$chill-blue: $blue; -$chill-green: $green; -$chill-green-dark: $green-600; -$chill-orange: $orange; -$chill-yellow: $yellow; -$chill-red: $red; -$chill-beige: $beige; -$chill-pink: $pink; -$chill-dark-gray: $gray-800; -$chill-gray: $gray-600; -$chill-l-gray: $gray-400; -$chill-ll-gray: $gray-300; -$chill-light-gray: $gray-200; -$chill-llight-gray: $gray-100; -// scss-docs-end theme-color-variables -// scss-docs-start theme-colors-map -$theme-colors: ( - "primary": $primary, - "secondary": $secondary, - "success": $success, - "info": $info, - "warning": $warning, - "danger": $danger, - "light": $light, - "dark": $dark, -) !default; -// scss-docs-end theme-colors-map +//// CHILL OVERRIDE DEFAULT BOOTSTRAP VARIABLES -$chill-colors: ( - "chill-blue": $chill-blue, - "chill-green": $chill-green, - "chill-green-dark": $chill-green-dark, - "chill-orange": $chill-orange, - "chill-yellow": $chill-yellow, - "chill-red": $chill-red, - "chill-beige": $chill-beige, - "chill-pink": $chill-pink, - "chill-dark-gray": $chill-dark-gray, - "chill-gray": $chill-gray, - "chill-l-gray": $chill-l-gray, - "chill-ll-gray": $chill-ll-gray, - "chill-light-gray": $chill-light-gray, - "chill-llight-gray": $chill-llight-gray, -); +$border-radius: .25rem; //.25rem !default; +$font-family-sans-serif: 'Open Sans'; //system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji" !default; -// Merge the maps -$theme-colors: map-merge($theme-colors, $chill-colors); +$font-size-base-chill: 1rem; //$font-size-base: 1rem !default; +$h1-font-size: $font-size-base-chill * 2.0; //$font-size-base * 2.5 !default; +$h2-font-size: $font-size-base-chill * 1.5; //$font-size-base * 2 !default; +$h3-font-size: $font-size-base-chill * 1.25; //$font-size-base * 1.75 !default; +$h4-font-size: $font-size-base-chill * 1.15; //$font-size-base * 1.5 !default; +$h5-font-size: $font-size-base-chill * 1.05; //$font-size-base * 1.25 !default; -// Chill color classes -@each $color, $value in $chill-colors { - .#{$color} { - color: $value; - } -} +$headings-font-weight: 600; //500 !default; +$table-striped-bg-factor: .10; //.05 !default; +$table-active-bg-factor: .15; //.1 !default; +$table-hover-bg-factor: .125; //.075 !default; +$table-border-factor: .15; //.1 !default; +$btn-border-radius: 0; //$border-radius !default; +$btn-border-radius-sm: 0; //$border-radius-sm !default; +$btn-border-radius-lg: 0; //$border-radius-lg !default; -// The contrast ratio to reach against white, to determine if color changes from "light" to "dark". Acceptable values for WCAG 2.0 are 3, 4.5 and 7. -// See https://www.w3.org/TR/WCAG20/#visual-audio-contrast-contrast -$min-contrast-ratio: 4.5 !default; - -// Customize the light and dark text colors for use in our color contrast function. -$color-contrast-dark: $black !default; -$color-contrast-light: $white !default; - -// Characters which are escaped by the escape-svg function -$escaped-characters: ( - ("<", "%3c"), - (">", "%3e"), - ("#", "%23"), - ("(", "%28"), - (")", "%29"), -) !default; - -// Options -// -// Quickly modify global styling by enabling or disabling optional features. - -$enable-caret: true !default; -$enable-rounded: true !default; -$enable-shadows: false !default; -$enable-gradients: false !default; -$enable-transitions: true !default; -$enable-reduced-motion: true !default; -$enable-smooth-scroll: true !default; -$enable-grid-classes: true !default; -$enable-button-pointers: true !default; -$enable-rfs: true !default; -$enable-validation-icons: true !default; -$enable-negative-margins: false !default; -$enable-deprecation-messages: true !default; -$enable-important-utilities: true !default; - -// Prefix for :root CSS variables - -$variable-prefix: bs- !default; - -// Gradient -// -// The gradient which is added to components if `$enable-gradients` is `true` -// This gradient is also added to elements with `.bg-gradient` -// scss-docs-start variable-gradient -$gradient: linear-gradient(180deg, rgba($white, .15), rgba($white, 0)) !default; -// scss-docs-end variable-gradient - -// Spacing -// -// Control the default styling of most Bootstrap elements by modifying these -// variables. Mostly focused on spacing. -// You can add more entries to the $spacers map, should you need more variation. - -// scss-docs-start spacer-variables-maps -$spacer: 1rem !default; -$spacers: ( - 0: 0, - 1: $spacer / 4, - 2: $spacer / 2, - 3: $spacer, - 4: $spacer * 1.5, - 5: $spacer * 3, -) !default; - -$negative-spacers: if($enable-negative-margins, negativify-map($spacers), null) !default; -// scss-docs-end spacer-variables-maps - -// Position -// -// Define the edge positioning anchors of the position utilities. - -// scss-docs-start position-map -$position-values: ( - 0: 0, - 50: 50%, - 100: 100% -) !default; -// scss-docs-end position-map - -// Body -// -// Settings for the `` element. - -$body-bg: $white !default; -$body-color: $gray-900 !default; -$body-text-align: null !default; - - -// Links -// -// Style anchor elements. - -$link-color: $primary !default; -$link-decoration: underline !default; -$link-shade-percentage: 20% !default; -$link-hover-color: shift-color($link-color, $link-shade-percentage) !default; -$link-hover-decoration: null !default; - -$stretched-link-pseudo-element: after !default; -$stretched-link-z-index: 1 !default; - -// Paragraphs -// -// Style p element. - -$paragraph-margin-bottom: 1rem !default; - - -// Grid breakpoints -// -// Define the minimum dimensions at which your layout will change, -// adapting to different screen sizes, for use in media queries. - -// scss-docs-start grid-breakpoints -$grid-breakpoints: ( - xs: 0, - sm: 576px, - md: 768px, - lg: 992px, - xl: 1200px, - xxl: 1400px -) !default; -// scss-docs-end grid-breakpoints - -@include _assert-ascending($grid-breakpoints, "$grid-breakpoints"); -@include _assert-starts-at-zero($grid-breakpoints, "$grid-breakpoints"); - - -// Grid containers -// -// Define the maximum width of `.container` for different screen sizes. - -// scss-docs-start container-max-widths -$container-max-widths: ( - sm: 540px, - md: 720px, - lg: 960px, - xl: 1140px, - xxl: 1320px -) !default; -// scss-docs-end container-max-widths - -@include _assert-ascending($container-max-widths, "$container-max-widths"); - - -// Grid columns -// -// Set the number of columns and specify the width of the gutters. - -$grid-columns: 12 !default; -$grid-gutter-width: 1.5rem !default; -$grid-row-columns: 6 !default; - -$gutters: $spacers !default; - -// Container padding - -$container-padding-x: $grid-gutter-width / 2 !default; - - -// Components -// -// Define common padding and border radius sizes and more. - -// scss-docs-start border-variables -$border-width: 1px !default; -$border-widths: ( - 1: 1px, - 2: 2px, - 3: 3px, - 4: 4px, - 5: 5px -) !default; - -$border-color: $gray-300 !default; -// scss-docs-end border-variables - -// scss-docs-start border-radius-variables -$border-radius: .25rem !default; // <== -$border-radius-sm: .2rem !default; -$border-radius-lg: .3rem !default; -$border-radius-pill: 50rem !default; -// scss-docs-end border-radius-variables - -// scss-docs-start box-shadow-variables -$box-shadow: 0 .5rem 1rem rgba($black, .15) !default; -$box-shadow-sm: 0 .125rem .25rem rgba($black, .075) !default; -$box-shadow-lg: 0 1rem 3rem rgba($black, .175) !default; -$box-shadow-inset: inset 0 1px 2px rgba($black, .075) !default; -// scss-docs-end box-shadow-variables - -$component-active-color: $white !default; -$component-active-bg: $primary !default; - -// scss-docs-start caret-variables -$caret-width: .3em !default; -$caret-vertical-align: $caret-width * .85 !default; -$caret-spacing: $caret-width * .85 !default; -// scss-docs-end caret-variables - -$transition-base: all .2s ease-in-out !default; -$transition-fade: opacity .15s linear !default; -// scss-docs-start collapse-transition -$transition-collapse: height .35s ease !default; -// scss-docs-end collapse-transition - -// stylelint-disable function-disallowed-list -// scss-docs-start aspect-ratios -$aspect-ratios: ( - "1x1": 100%, - "4x3": calc(3 / 4 * 100%), - "16x9": calc(9 / 16 * 100%), - "21x9": calc(9 / 21 * 100%) -) !default; -// scss-docs-end aspect-ratios -// stylelint-enable function-disallowed-list - -// Typography -// -// Font, line-height, and color for body text, headings, and more. - -// scss-docs-start font-variables -// stylelint-disable value-keyword-case -$font-family-sans-serif: 'Open Sans'; -$font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace !default; -// stylelint-enable value-keyword-case -$font-family-base: var(--#{$variable-prefix}font-sans-serif) !default; -$font-family-code: var(--#{$variable-prefix}font-monospace) !default; - -// $font-size-root effects the value of `rem`, which is used for as well font sizes, paddings and margins -// $font-size-base effects the font size of the body text -$font-size-root: null !default; -$font-size-base: 1rem !default; // Assumes the browser default, typically `16px` -$font-size-sm: $font-size-base * .875 !default; -$font-size-lg: $font-size-base * 1.25 !default; - -$font-weight-lighter: lighter !default; -$font-weight-light: 300 !default; -$font-weight-normal: 400 !default; -$font-weight-bold: 700 !default; -$font-weight-bolder: bolder !default; - -$font-weight-base: $font-weight-normal !default; - -$line-height-base: 1.5 !default; -$line-height-sm: 1.25 !default; -$line-height-lg: 2 !default; - -$h1-font-size: $font-size-base * 2.0 !default; -$h2-font-size: $font-size-base * 1.5 !default; -$h3-font-size: $font-size-base * 1.25 !default; -$h4-font-size: $font-size-base * 1.15 !default; -$h5-font-size: $font-size-base * 1.05 !default; -$h6-font-size: $font-size-base !default; -// scss-docs-end font-variables - -// scss-docs-start font-sizes -$font-sizes: ( - 1: $h1-font-size, - 2: $h2-font-size, - 3: $h3-font-size, - 4: $h4-font-size, - 5: $h5-font-size, - 6: $h6-font-size -) !default; -// scss-docs-end font-sizes - -// scss-docs-start headings-variables -$headings-margin-bottom: $spacer / 2 !default; -$headings-font-family: null !default; -$headings-font-style: null !default; -$headings-font-weight: 600 !default; -$headings-line-height: 1.2 !default; -$headings-color: null !default; -// scss-docs-end headings-variables - -// scss-docs-start display-headings -$display-font-sizes: ( - 1: 5rem, - 2: 4.5rem, - 3: 4rem, - 4: 3.5rem, - 5: 3rem, - 6: 2.5rem -) !default; - -$display-font-weight: 300 !default; -$display-line-height: $headings-line-height !default; -// scss-docs-end display-headings - -// scss-docs-start type-variables -$lead-font-size: $font-size-base * 1.25 !default; -$lead-font-weight: 300 !default; - -$small-font-size: .875em !default; - -$sub-sup-font-size: .75em !default; - -$text-muted: $gray-600 !default; - -$initialism-font-size: $small-font-size !default; - -$blockquote-margin-y: $spacer !default; -$blockquote-font-size: $font-size-base * 1.25 !default; -$blockquote-footer-color: $gray-600 !default; -$blockquote-footer-font-size: $small-font-size !default; - -$hr-margin-y: $spacer !default; -$hr-color: inherit !default; -$hr-height: $border-width !default; -$hr-opacity: .25 !default; - -$legend-margin-bottom: .5rem !default; -$legend-font-size: 1.5rem !default; -$legend-font-weight: null !default; - -$mark-padding: .2em !default; - -$dt-font-weight: $font-weight-bold !default; - -$nested-kbd-font-weight: $font-weight-bold !default; - -$list-inline-padding: .5rem !default; - -$mark-bg: #fcf8e3 !default; -// scss-docs-end type-variables - - -// Tables -// -// Customizes the `.table` component with basic values, each used across all table variations. - -// scss-docs-start table-variables -$table-cell-padding-y: .5rem !default; -$table-cell-padding-x: .5rem !default; -$table-cell-padding-y-sm: .25rem !default; -$table-cell-padding-x-sm: .25rem !default; - -$table-cell-vertical-align: top !default; - -$table-color: $body-color !default; -$table-bg: transparent !default; - -$table-th-font-weight: null !default; - -$table-striped-color: $table-color !default; -$table-striped-bg-factor: .10 !default; -$table-striped-bg: rgba($black, $table-striped-bg-factor) !default; - -$table-active-color: $table-color !default; -$table-active-bg-factor: .15 !default; -$table-active-bg: rgba($black, $table-active-bg-factor) !default; - -$table-hover-color: $table-color !default; -$table-hover-bg-factor: .125 !default; -$table-hover-bg: rgba($black, $table-hover-bg-factor) !default; - -$table-border-factor: .15 !default; -$table-border-width: $border-width !default; -$table-border-color: $border-color !default; - -$table-striped-order: odd !default; - -$table-group-separator-color: currentColor !default; - -$table-caption-color: $text-muted !default; - -$table-bg-scale: -80% !default; -// scss-docs-end table-variables - -// scss-docs-start table-loop -$table-variants: ( - "primary": shift-color($primary, $table-bg-scale), - "secondary": shift-color($secondary, $table-bg-scale), - "success": shift-color($success, $table-bg-scale), - "info": shift-color($info, $table-bg-scale), - "warning": shift-color($warning, $table-bg-scale), - "danger": shift-color($danger, $table-bg-scale), - "light": $light, - "dark": $dark, -) !default; -// scss-docs-end table-loop - - -// Buttons + Forms -// -// Shared variables that are reassigned to `$input-` and `$btn-` specific variables. - -// scss-docs-start input-btn-variables -$input-btn-padding-y: .375rem !default; -$input-btn-padding-x: .75rem !default; -$input-btn-font-family: null !default; -$input-btn-font-size: $font-size-base !default; -$input-btn-line-height: $line-height-base !default; - -$input-btn-focus-width: .25rem !default; -$input-btn-focus-color-opacity: .25 !default; -$input-btn-focus-color: rgba($component-active-bg, $input-btn-focus-color-opacity) !default; -$input-btn-focus-blur: 0 !default; -$input-btn-focus-box-shadow: 0 0 $input-btn-focus-blur $input-btn-focus-width $input-btn-focus-color !default; - -$input-btn-padding-y-sm: .25rem !default; -$input-btn-padding-x-sm: .5rem !default; -$input-btn-font-size-sm: $font-size-sm !default; - -$input-btn-padding-y-lg: .5rem !default; -$input-btn-padding-x-lg: 1rem !default; -$input-btn-font-size-lg: $font-size-lg !default; - -$input-btn-border-width: $border-width !default; -// scss-docs-end input-btn-variables - - -// Buttons -// -// For each of Bootstrap's buttons, define text, background, and border color. - -// scss-docs-start btn-variables -$btn-padding-y: $input-btn-padding-y !default; -$btn-padding-x: $input-btn-padding-x !default; -$btn-font-family: $input-btn-font-family !default; -$btn-font-size: $input-btn-font-size !default; -$btn-line-height: $input-btn-line-height !default; -$btn-white-space: null !default; // Set to `nowrap` to prevent text wrapping - -$btn-padding-y-sm: $input-btn-padding-y-sm !default; -$btn-padding-x-sm: $input-btn-padding-x-sm !default; -$btn-font-size-sm: $input-btn-font-size-sm !default; - -$btn-padding-y-lg: $input-btn-padding-y-lg !default; -$btn-padding-x-lg: $input-btn-padding-x-lg !default; -$btn-font-size-lg: $input-btn-font-size-lg !default; - -$btn-border-width: $input-btn-border-width !default; - -$btn-font-weight: $font-weight-normal !default; -$btn-box-shadow: inset 0 1px 0 rgba($white, .15), 0 1px 1px rgba($black, .075) !default; -$btn-focus-width: $input-btn-focus-width !default; -$btn-focus-box-shadow: $input-btn-focus-box-shadow !default; -$btn-disabled-opacity: .65 !default; -$btn-active-box-shadow: inset 0 3px 5px rgba($black, .125) !default; - -$btn-link-color: $link-color !default; -$btn-link-hover-color: $link-hover-color !default; -$btn-link-disabled-color: $gray-600 !default; - -// Allows for customizing button radius independently from global border radius -$btn-border-radius: 0 !default; // $border-radius !default; (only disabled for button) -$btn-border-radius-sm: 0 !default; // $border-radius-sm !default; -$btn-border-radius-lg: 0 !default; // $border-radius-lg !default; - -$btn-transition: color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out !default; - -$btn-hover-bg-shade-amount: 15% !default; -$btn-hover-bg-tint-amount: 15% !default; -$btn-hover-border-shade-amount: 20% !default; -$btn-hover-border-tint-amount: 10% !default; -$btn-active-bg-shade-amount: 20% !default; -$btn-active-bg-tint-amount: 20% !default; -$btn-active-border-shade-amount: 25% !default; -$btn-active-border-tint-amount: 10% !default; -// scss-docs-end btn-variables - - -// Forms - -// scss-docs-start form-text-variables -$form-text-margin-top: .25rem !default; -$form-text-font-size: $small-font-size !default; -$form-text-font-style: null !default; -$form-text-font-weight: null !default; -$form-text-color: $text-muted !default; -// scss-docs-end form-text-variables - -// scss-docs-start form-label-variables -$form-label-margin-bottom: .5rem !default; -$form-label-font-size: null !default; -$form-label-font-style: null !default; -$form-label-font-weight: null !default; -$form-label-color: null !default; -// scss-docs-end form-label-variables - -// scss-docs-start form-input-variables -$input-padding-y: $input-btn-padding-y !default; -$input-padding-x: $input-btn-padding-x !default; -$input-font-family: $input-btn-font-family !default; -$input-font-size: $input-btn-font-size !default; -$input-font-weight: $font-weight-base !default; -$input-line-height: $input-btn-line-height !default; - -$input-padding-y-sm: $input-btn-padding-y-sm !default; -$input-padding-x-sm: $input-btn-padding-x-sm !default; -$input-font-size-sm: $input-btn-font-size-sm !default; - -$input-padding-y-lg: $input-btn-padding-y-lg !default; -$input-padding-x-lg: $input-btn-padding-x-lg !default; -$input-font-size-lg: $input-btn-font-size-lg !default; - -$input-bg: $white !default; -$input-disabled-bg: $gray-200 !default; -$input-disabled-border-color: null !default; - -$input-color: $body-color !default; -$input-border-color: $gray-400 !default; -$input-border-width: $input-btn-border-width !default; -$input-box-shadow: $box-shadow-inset !default; - -$input-border-radius: $border-radius !default; -$input-border-radius-sm: $border-radius-sm !default; -$input-border-radius-lg: $border-radius-lg !default; - -$input-focus-bg: $input-bg !default; -$input-focus-border-color: tint-color($component-active-bg, 50%) !default; -$input-focus-color: $input-color !default; -$input-focus-width: $input-btn-focus-width !default; -$input-focus-box-shadow: $input-btn-focus-box-shadow !default; - -$input-placeholder-color: $gray-600 !default; -$input-plaintext-color: $body-color !default; - -$input-height-border: $input-border-width * 2 !default; - -$input-height-inner: add($input-line-height * 1em, $input-padding-y * 2) !default; -$input-height-inner-half: add($input-line-height * .5em, $input-padding-y) !default; -$input-height-inner-quarter: add($input-line-height * .25em, $input-padding-y / 2) !default; - -$input-height: add($input-line-height * 1em, add($input-padding-y * 2, $input-height-border, false)) !default; -$input-height-sm: add($input-line-height * 1em, add($input-padding-y-sm * 2, $input-height-border, false)) !default; -$input-height-lg: add($input-line-height * 1em, add($input-padding-y-lg * 2, $input-height-border, false)) !default; - -$input-transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out !default; -// scss-docs-end form-input-variables - -// scss-docs-start form-check-variables -$form-check-input-width: 1em !default; -$form-check-min-height: $font-size-base * $line-height-base !default; -$form-check-padding-start: $form-check-input-width + .5em !default; -$form-check-margin-bottom: .125rem !default; -$form-check-label-color: null !default; -$form-check-label-cursor: null !default; -$form-check-transition: null !default; - -$form-check-input-active-filter: brightness(90%) !default; - -$form-check-input-bg: $input-bg !default; -$form-check-input-border: 1px solid rgba($black, .25) !default; -$form-check-input-border-radius: .25em !default; -$form-check-radio-border-radius: 50% !default; -$form-check-input-focus-border: $input-focus-border-color !default; -$form-check-input-focus-box-shadow: $input-btn-focus-box-shadow !default; - -$form-check-input-checked-color: $component-active-color !default; -$form-check-input-checked-bg-color: $component-active-bg !default; -$form-check-input-checked-border-color: $form-check-input-checked-bg-color !default; -$form-check-input-checked-bg-image: url("data:image/svg+xml,") !default; -$form-check-radio-checked-bg-image: url("data:image/svg+xml,") !default; - -$form-check-input-indeterminate-color: $component-active-color !default; -$form-check-input-indeterminate-bg-color: $component-active-bg !default; -$form-check-input-indeterminate-border-color: $form-check-input-indeterminate-bg-color !default; -$form-check-input-indeterminate-bg-image: url("data:image/svg+xml,") !default; - -$form-check-input-disabled-opacity: .5 !default; -$form-check-label-disabled-opacity: $form-check-input-disabled-opacity !default; -$form-check-btn-check-disabled-opacity: $btn-disabled-opacity !default; - -$form-check-inline-margin-end: 1rem !default; -// scss-docs-end form-check-variables - -// scss-docs-start form-switch-variables -$form-switch-color: rgba(0, 0, 0, .25) !default; -$form-switch-width: 2em !default; -$form-switch-padding-start: $form-switch-width + .5em !default; -$form-switch-bg-image: url("data:image/svg+xml,") !default; -$form-switch-border-radius: $form-switch-width !default; -$form-switch-transition: background-position .15s ease-in-out !default; - -$form-switch-focus-color: $input-focus-border-color !default; -$form-switch-focus-bg-image: url("data:image/svg+xml,") !default; - -$form-switch-checked-color: $component-active-color !default; -$form-switch-checked-bg-image: url("data:image/svg+xml,") !default; -$form-switch-checked-bg-position: right center !default; -// scss-docs-end form-switch-variables - -// scss-docs-start input-group-variables -$input-group-addon-padding-y: $input-padding-y !default; -$input-group-addon-padding-x: $input-padding-x !default; -$input-group-addon-font-weight: $input-font-weight !default; -$input-group-addon-color: $input-color !default; -$input-group-addon-bg: $gray-200 !default; -$input-group-addon-border-color: $input-border-color !default; -// scss-docs-end input-group-variables - -// scss-docs-start form-select-variables -$form-select-padding-y: $input-padding-y !default; -$form-select-padding-x: $input-padding-x !default; -$form-select-font-family: $input-font-family !default; -$form-select-font-size: $input-font-size !default; -$form-select-indicator-padding: $form-select-padding-x * 3 !default; // Extra padding for background-image -$form-select-font-weight: $input-font-weight !default; -$form-select-line-height: $input-line-height !default; -$form-select-color: $input-color !default; -$form-select-bg: $input-bg !default; -$form-select-disabled-color: null !default; -$form-select-disabled-bg: $gray-200 !default; -$form-select-disabled-border-color: $input-disabled-border-color !default; -$form-select-bg-position: right $form-select-padding-x center !default; -$form-select-bg-size: 16px 12px !default; // In pixels because image dimensions -$form-select-indicator-color: $gray-800 !default; -$form-select-indicator: url("data:image/svg+xml,") !default; - -$form-select-feedback-icon-padding-end: $form-select-padding-x * 2.5 + $form-select-indicator-padding !default; -$form-select-feedback-icon-position: center right $form-select-indicator-padding !default; -$form-select-feedback-icon-size: $input-height-inner-half $input-height-inner-half !default; - -$form-select-border-width: $input-border-width !default; -$form-select-border-color: $input-border-color !default; -$form-select-border-radius: $border-radius !default; -$form-select-box-shadow: $box-shadow-inset !default; - -$form-select-focus-border-color: $input-focus-border-color !default; -$form-select-focus-width: $input-focus-width !default; -$form-select-focus-box-shadow: 0 0 0 $form-select-focus-width $input-btn-focus-color !default; - -$form-select-padding-y-sm: $input-padding-y-sm !default; -$form-select-padding-x-sm: $input-padding-x-sm !default; -$form-select-font-size-sm: $input-font-size-sm !default; - -$form-select-padding-y-lg: $input-padding-y-lg !default; -$form-select-padding-x-lg: $input-padding-x-lg !default; -$form-select-font-size-lg: $input-font-size-lg !default; -// scss-docs-end form-select-variables - -// scss-docs-start form-range-variables -$form-range-track-width: 100% !default; -$form-range-track-height: .5rem !default; -$form-range-track-cursor: pointer !default; -$form-range-track-bg: $gray-300 !default; -$form-range-track-border-radius: 1rem !default; -$form-range-track-box-shadow: $box-shadow-inset !default; - -$form-range-thumb-width: 1rem !default; -$form-range-thumb-height: $form-range-thumb-width !default; -$form-range-thumb-bg: $component-active-bg !default; -$form-range-thumb-border: 0 !default; -$form-range-thumb-border-radius: 1rem !default; -$form-range-thumb-box-shadow: 0 .1rem .25rem rgba($black, .1) !default; -$form-range-thumb-focus-box-shadow: 0 0 0 1px $body-bg, $input-focus-box-shadow !default; -$form-range-thumb-focus-box-shadow-width: $input-focus-width !default; // For focus box shadow issue in Edge -$form-range-thumb-active-bg: tint-color($component-active-bg, 70%) !default; -$form-range-thumb-disabled-bg: $gray-500 !default; -$form-range-thumb-transition: background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out !default; -// scss-docs-end form-range-variables - -// scss-docs-start form-file-variables -$form-file-button-color: $input-color !default; -$form-file-button-bg: $input-group-addon-bg !default; -$form-file-button-hover-bg: shade-color($form-file-button-bg, 5%) !default; -// scss-docs-end form-file-variables - -// scss-docs-start form-floating-variables -$form-floating-height: add(3.5rem, $input-height-border) !default; -$form-floating-padding-x: $input-padding-x !default; -$form-floating-padding-y: 1rem !default; -$form-floating-input-padding-t: 1.625rem !default; -$form-floating-input-padding-b: .625rem !default; -$form-floating-label-opacity: .65 !default; -$form-floating-label-transform: scale(.85) translateY(-.5rem) translateX(.15rem) !default; -$form-floating-transition: opacity .1s ease-in-out, transform .1s ease-in-out !default; -// scss-docs-end form-floating-variables - -// Form validation - -// scss-docs-start form-feedback-variables -$form-feedback-margin-top: $form-text-margin-top !default; -$form-feedback-font-size: $form-text-font-size !default; -$form-feedback-font-style: $form-text-font-style !default; -$form-feedback-valid-color: $success !default; -$form-feedback-invalid-color: $danger !default; - -$form-feedback-icon-valid-color: $form-feedback-valid-color !default; -$form-feedback-icon-valid: url("data:image/svg+xml,") !default; -$form-feedback-icon-invalid-color: $form-feedback-invalid-color !default; -$form-feedback-icon-invalid: url("data:image/svg+xml,") !default; -// scss-docs-end form-feedback-variables - -// scss-docs-start form-validation-states -$form-validation-states: ( - "valid": ( - "color": $form-feedback-valid-color, - "icon": $form-feedback-icon-valid - ), - "invalid": ( - "color": $form-feedback-invalid-color, - "icon": $form-feedback-icon-invalid - ) -) !default; -// scss-docs-end form-validation-states - -// Z-index master list -// -// Warning: Avoid customizing these values. They're used for a bird's eye view -// of components dependent on the z-axis and are designed to all work together. - -// scss-docs-start zindex-stack -$zindex-dropdown: 1000 !default; -$zindex-sticky: 1020 !default; -$zindex-fixed: 1030 !default; -$zindex-modal-backdrop: 1040 !default; -$zindex-offcanvas: 1050 !default; -$zindex-modal: 1060 !default; -$zindex-popover: 1070 !default; -$zindex-tooltip: 1080 !default; -// scss-docs-end zindex-stack - - -// Navs - -// scss-docs-start nav-variables -$nav-link-padding-y: .5rem !default; -$nav-link-padding-x: 1rem !default; -$nav-link-font-size: null !default; -$nav-link-font-weight: null !default; -$nav-link-color: $link-color !default; -$nav-link-hover-color: $link-hover-color !default; -$nav-link-transition: color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out !default; -$nav-link-disabled-color: $gray-600 !default; - -$nav-tabs-border-color: $gray-300 !default; -$nav-tabs-border-width: $border-width !default; -$nav-tabs-border-radius: $border-radius !default; -$nav-tabs-link-hover-border-color: $gray-200 $gray-200 $nav-tabs-border-color !default; -$nav-tabs-link-active-color: $gray-700 !default; -$nav-tabs-link-active-bg: $body-bg !default; -$nav-tabs-link-active-border-color: $gray-300 $gray-300 $nav-tabs-link-active-bg !default; - -$nav-pills-border-radius: $border-radius !default; -$nav-pills-link-active-color: $component-active-color !default; -$nav-pills-link-active-bg: $component-active-bg !default; -// scss-docs-end nav-variables - - -// Navbar - -// scss-docs-start navbar-variables -$navbar-padding-y: $spacer / 2 !default; -$navbar-padding-x: null !default; - -$navbar-nav-link-padding-x: .5rem !default; - -$navbar-brand-font-size: $font-size-lg !default; -// Compute the navbar-brand padding-y so the navbar-brand will have the same height as navbar-text and nav-link -$nav-link-height: $font-size-base * $line-height-base + $nav-link-padding-y * 2 !default; -$navbar-brand-height: $navbar-brand-font-size * $line-height-base !default; -$navbar-brand-padding-y: ($nav-link-height - $navbar-brand-height) / 2 !default; -$navbar-brand-margin-end: 1rem !default; - -$navbar-toggler-padding-y: .25rem !default; -$navbar-toggler-padding-x: .75rem !default; -$navbar-toggler-font-size: $font-size-lg !default; -$navbar-toggler-border-radius: $btn-border-radius !default; -$navbar-toggler-focus-width: $btn-focus-width !default; -$navbar-toggler-transition: box-shadow .15s ease-in-out !default; -// scss-docs-end navbar-variables - -// scss-docs-start navbar-theme-variables -$navbar-dark-color: rgba($white, .55) !default; -$navbar-dark-hover-color: rgba($white, .75) !default; -$navbar-dark-active-color: $white !default; -$navbar-dark-disabled-color: rgba($white, .25) !default; -$navbar-dark-toggler-icon-bg: url("data:image/svg+xml,") !default; -$navbar-dark-toggler-border-color: rgba($white, .1) !default; - -$navbar-light-color: rgba($black, .55) !default; -$navbar-light-hover-color: rgba($black, .7) !default; -$navbar-light-active-color: rgba($black, .9) !default; -$navbar-light-disabled-color: rgba($black, .3) !default; -$navbar-light-toggler-icon-bg: url("data:image/svg+xml,") !default; -$navbar-light-toggler-border-color: rgba($black, .1) !default; - -$navbar-light-brand-color: $navbar-light-active-color !default; -$navbar-light-brand-hover-color: $navbar-light-active-color !default; -$navbar-dark-brand-color: $navbar-dark-active-color !default; -$navbar-dark-brand-hover-color: $navbar-dark-active-color !default; -// scss-docs-end navbar-theme-variables - - -// Dropdowns -// -// Dropdown menu container and contents. - -// scss-docs-start dropdown-variables -$dropdown-min-width: 10rem !default; -$dropdown-padding-x: 0 !default; -$dropdown-padding-y: .5rem !default; -$dropdown-spacer: .125rem !default; -$dropdown-font-size: $font-size-base !default; -$dropdown-color: $body-color !default; -$dropdown-bg: $white !default; -$dropdown-border-color: rgba($black, .15) !default; -$dropdown-border-radius: $border-radius !default; -$dropdown-border-width: $border-width !default; -$dropdown-inner-border-radius: subtract($dropdown-border-radius, $dropdown-border-width) !default; -$dropdown-divider-bg: $dropdown-border-color !default; -$dropdown-divider-margin-y: $spacer / 2 !default; -$dropdown-box-shadow: $box-shadow !default; - -$dropdown-link-color: $gray-900 !default; -$dropdown-link-hover-color: shade-color($gray-900, 10%) !default; -$dropdown-link-hover-bg: $gray-200 !default; - -$dropdown-link-active-color: $component-active-color !default; -$dropdown-link-active-bg: $component-active-bg !default; - -$dropdown-link-disabled-color: $gray-500 !default; - -$dropdown-item-padding-y: $spacer / 4 !default; -$dropdown-item-padding-x: $spacer !default; - -$dropdown-header-color: $gray-600 !default; -$dropdown-header-padding: $dropdown-padding-y $dropdown-item-padding-x !default; -// scss-docs-end dropdown-variables - -// scss-docs-start dropdown-dark-variables -$dropdown-dark-color: $gray-300 !default; -$dropdown-dark-bg: $gray-800 !default; -$dropdown-dark-border-color: $dropdown-border-color !default; -$dropdown-dark-divider-bg: $dropdown-divider-bg !default; -$dropdown-dark-box-shadow: null !default; -$dropdown-dark-link-color: $dropdown-dark-color !default; -$dropdown-dark-link-hover-color: $white !default; -$dropdown-dark-link-hover-bg: rgba($white, .15) !default; -$dropdown-dark-link-active-color: $dropdown-link-active-color !default; -$dropdown-dark-link-active-bg: $dropdown-link-active-bg !default; -$dropdown-dark-link-disabled-color: $gray-500 !default; -$dropdown-dark-header-color: $gray-500 !default; -// scss-docs-end dropdown-dark-variables - - -// Pagination - -// scss-docs-start pagination-variables -$pagination-padding-y: .375rem !default; -$pagination-padding-x: .75rem !default; -$pagination-padding-y-sm: .25rem !default; -$pagination-padding-x-sm: .5rem !default; -$pagination-padding-y-lg: .75rem !default; -$pagination-padding-x-lg: 1.5rem !default; - -$pagination-color: $link-color !default; -$pagination-bg: $white !default; -$pagination-border-width: $border-width !default; -$pagination-border-radius: $border-radius !default; -$pagination-margin-start: -$pagination-border-width !default; -$pagination-border-color: $gray-300 !default; - -$pagination-focus-color: $link-hover-color !default; -$pagination-focus-bg: $gray-200 !default; -$pagination-focus-box-shadow: $input-btn-focus-box-shadow !default; -$pagination-focus-outline: 0 !default; - -$pagination-hover-color: $link-hover-color !default; -$pagination-hover-bg: $gray-200 !default; -$pagination-hover-border-color: $gray-300 !default; - -$pagination-active-color: $component-active-color !default; -$pagination-active-bg: $component-active-bg !default; -$pagination-active-border-color: $pagination-active-bg !default; - -$pagination-disabled-color: $gray-600 !default; -$pagination-disabled-bg: $white !default; -$pagination-disabled-border-color: $gray-300 !default; - -$pagination-transition: color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out !default; - -$pagination-border-radius-sm: $border-radius-sm !default; -$pagination-border-radius-lg: $border-radius-lg !default; -// scss-docs-end pagination-variables - - -// Cards - -// scss-docs-start card-variables -$card-spacer-y: $spacer !default; -$card-spacer-x: $spacer !default; -$card-title-spacer-y: $spacer / 2 !default; -$card-border-width: $border-width !default; -$card-border-radius: $border-radius !default; -$card-border-color: rgba($black, .125) !default; -$card-inner-border-radius: subtract($card-border-radius, $card-border-width) !default; -$card-cap-padding-y: $card-spacer-y / 2 !default; -$card-cap-padding-x: $card-spacer-x !default; -$card-cap-bg: rgba($black, .03) !default; -$card-cap-color: null !default; -$card-height: null !default; -$card-color: null !default; -$card-bg: $white !default; -$card-img-overlay-padding: $spacer !default; -$card-group-margin: $grid-gutter-width / 2 !default; -// scss-docs-end card-variables - -// Accordion - -// scss-docs-start accordion-variables -$accordion-padding-y: 1rem !default; -$accordion-padding-x: 1.25rem !default; -$accordion-color: $body-color !default; -$accordion-bg: $body-bg !default; -$accordion-border-width: $border-width !default; -$accordion-border-color: rgba($black, .125) !default; -$accordion-border-radius: $border-radius !default; -$accordion-inner-border-radius: subtract($accordion-border-radius, $accordion-border-width) !default; - -$accordion-body-padding-y: $accordion-padding-y !default; -$accordion-body-padding-x: $accordion-padding-x !default; - -$accordion-button-padding-y: $accordion-padding-y !default; -$accordion-button-padding-x: $accordion-padding-x !default; -$accordion-button-color: $accordion-color !default; -$accordion-button-bg: $accordion-bg !default; -$accordion-transition: $btn-transition, border-radius .15s ease !default; -$accordion-button-active-bg: tint-color($component-active-bg, 90%) !default; -$accordion-button-active-color: shade-color($primary, 10%) !default; - -$accordion-button-focus-border-color: $input-focus-border-color !default; -$accordion-button-focus-box-shadow: $btn-focus-box-shadow !default; - -$accordion-icon-width: 1.25rem !default; -$accordion-icon-color: $accordion-color !default; -$accordion-icon-active-color: $accordion-button-active-color !default; -$accordion-icon-transition: transform .2s ease-in-out !default; -$accordion-icon-transform: rotate(-180deg) !default; - -$accordion-button-icon: url("data:image/svg+xml,") !default; -$accordion-button-active-icon: url("data:image/svg+xml,") !default; -// scss-docs-end accordion-variables - -// Tooltips - -// scss-docs-start tooltip-variables -$tooltip-font-size: $font-size-sm !default; -$tooltip-max-width: 200px !default; -$tooltip-color: $white !default; -$tooltip-bg: $black !default; -$tooltip-border-radius: $border-radius !default; -$tooltip-opacity: .9 !default; -$tooltip-padding-y: $spacer / 4 !default; -$tooltip-padding-x: $spacer / 2 !default; -$tooltip-margin: 0 !default; - -$tooltip-arrow-width: .8rem !default; -$tooltip-arrow-height: .4rem !default; -$tooltip-arrow-color: $tooltip-bg !default; -// scss-docs-end tooltip-variables - -// Form tooltips must come after regular tooltips -// scss-docs-start tooltip-feedback-variables -$form-feedback-tooltip-padding-y: $tooltip-padding-y !default; -$form-feedback-tooltip-padding-x: $tooltip-padding-x !default; -$form-feedback-tooltip-font-size: $tooltip-font-size !default; -$form-feedback-tooltip-line-height: null !default; -$form-feedback-tooltip-opacity: $tooltip-opacity !default; -$form-feedback-tooltip-border-radius: $tooltip-border-radius !default; -// scss-docs-start tooltip-feedback-variables - - -// Popovers - -// scss-docs-start popover-variables -$popover-font-size: $font-size-sm !default; -$popover-bg: $white !default; -$popover-max-width: 276px !default; -$popover-border-width: $border-width !default; -$popover-border-color: rgba($black, .2) !default; -$popover-border-radius: $border-radius-lg !default; -$popover-inner-border-radius: subtract($popover-border-radius, $popover-border-width) !default; -$popover-box-shadow: $box-shadow !default; - -$popover-header-bg: shade-color($popover-bg, 6%) !default; -$popover-header-color: $headings-color !default; -$popover-header-padding-y: .5rem !default; -$popover-header-padding-x: $spacer !default; - -$popover-body-color: $body-color !default; -$popover-body-padding-y: $spacer !default; -$popover-body-padding-x: $spacer !default; - -$popover-arrow-width: 1rem !default; -$popover-arrow-height: .5rem !default; -$popover-arrow-color: $popover-bg !default; - -$popover-arrow-outer-color: fade-in($popover-border-color, .05) !default; -// scss-docs-end popover-variables - - -// Toasts - -// scss-docs-start toast-variables -$toast-max-width: 350px !default; -$toast-padding-x: .75rem !default; -$toast-padding-y: .5rem !default; -$toast-font-size: .875rem !default; -$toast-color: null !default; -$toast-background-color: rgba($white, .85) !default; -$toast-border-width: 1px !default; -$toast-border-color: rgba(0, 0, 0, .1) !default; -$toast-border-radius: $border-radius !default; -$toast-box-shadow: $box-shadow !default; -$toast-spacing: $container-padding-x !default; - -$toast-header-color: $gray-600 !default; -$toast-header-background-color: rgba($white, .85) !default; -$toast-header-border-color: rgba(0, 0, 0, .05) !default; -// scss-docs-end toast-variables - - -// Badges - -// scss-docs-start badge-variables -$badge-font-size: .75em !default; -$badge-font-weight: $font-weight-bold !default; -$badge-color: $white !default; -$badge-padding-y: .35em !default; -$badge-padding-x: .65em !default; -$badge-border-radius: $border-radius !default; -// scss-docs-end badge-variables - - -// Modals - -// scss-docs-start modal-variables -$modal-inner-padding: $spacer !default; - -$modal-footer-margin-between: .5rem !default; - -$modal-dialog-margin: .5rem !default; -$modal-dialog-margin-y-sm-up: 1.75rem !default; - -$modal-title-line-height: $line-height-base !default; - -$modal-content-color: null !default; -$modal-content-bg: $white !default; -$modal-content-border-color: rgba($black, .2) !default; -$modal-content-border-width: $border-width !default; -$modal-content-border-radius: $border-radius-lg !default; -$modal-content-inner-border-radius: subtract($modal-content-border-radius, $modal-content-border-width) !default; -$modal-content-box-shadow-xs: $box-shadow-sm !default; -$modal-content-box-shadow-sm-up: $box-shadow !default; - -$modal-backdrop-bg: $black !default; -$modal-backdrop-opacity: .5 !default; -$modal-header-border-color: $border-color !default; -$modal-footer-border-color: $modal-header-border-color !default; -$modal-header-border-width: $modal-content-border-width !default; -$modal-footer-border-width: $modal-header-border-width !default; -$modal-header-padding-y: $modal-inner-padding !default; -$modal-header-padding-x: $modal-inner-padding !default; -$modal-header-padding: $modal-header-padding-y $modal-header-padding-x !default; // Keep this for backwards compatibility - -$modal-sm: 300px !default; -$modal-md: 500px !default; -$modal-lg: 800px !default; -$modal-xl: 1140px !default; - -$modal-fade-transform: translate(0, -50px) !default; -$modal-show-transform: none !default; -$modal-transition: transform .3s ease-out !default; -$modal-scale-transform: scale(1.02) !default; -// scss-docs-end modal-variables - - -// Alerts -// -// Define alert colors, border radius, and padding. - -// scss-docs-start alert-variables -$alert-padding-y: $spacer !default; -$alert-padding-x: $spacer !default; -$alert-margin-bottom: 1rem !default; -$alert-border-radius: 0 !default; //$border-radius !default; -$alert-link-font-weight: $font-weight-bold !default; -$alert-border-width: 0 !default; //$border-width !default; -$alert-bg-scale: -80% !default; -$alert-border-scale: -70% !default; -$alert-color-scale: 40% !default; -$alert-dismissible-padding-r: $alert-padding-x * 3 !default; // 3x covers width of x plus default padding on either side -// scss-docs-end alert-variables - - -// Progress bars - -// scss-docs-start progress-variables -$progress-height: 1rem !default; -$progress-font-size: $font-size-base * .75 !default; -$progress-bg: $gray-200 !default; -$progress-border-radius: $border-radius !default; -$progress-box-shadow: $box-shadow-inset !default; -$progress-bar-color: $white !default; -$progress-bar-bg: $primary !default; -$progress-bar-animation-timing: 1s linear infinite !default; -$progress-bar-transition: width .6s ease !default; -// scss-docs-end progress-variables - - -// List group - -// scss-docs-start list-group-variables -$list-group-color: $gray-900 !default; -$list-group-bg: $white !default; -$list-group-border-color: rgba($black, .125) !default; -$list-group-border-width: $border-width !default; -$list-group-border-radius: $border-radius !default; - -$list-group-item-padding-y: $spacer / 2 !default; -$list-group-item-padding-x: $spacer !default; -$list-group-item-bg-scale: -80% !default; -$list-group-item-color-scale: 40% !default; - -$list-group-hover-bg: $gray-100 !default; -$list-group-active-color: $component-active-color !default; -$list-group-active-bg: $component-active-bg !default; -$list-group-active-border-color: $list-group-active-bg !default; - -$list-group-disabled-color: $gray-600 !default; -$list-group-disabled-bg: $list-group-bg !default; - -$list-group-action-color: $gray-700 !default; -$list-group-action-hover-color: $list-group-action-color !default; - -$list-group-action-active-color: $body-color !default; -$list-group-action-active-bg: $gray-200 !default; -// scss-docs-end list-group-variables - - -// Image thumbnails - -// scss-docs-start thumbnail-variables -$thumbnail-padding: .25rem !default; -$thumbnail-bg: $body-bg !default; -$thumbnail-border-width: $border-width !default; -$thumbnail-border-color: $gray-300 !default; -$thumbnail-border-radius: $border-radius !default; -$thumbnail-box-shadow: $box-shadow-sm !default; -// scss-docs-end thumbnail-variables - - -// Figures - -// scss-docs-start figure-variables -$figure-caption-font-size: $small-font-size !default; -$figure-caption-color: $gray-600 !default; -// scss-docs-end figure-variables - - -// Breadcrumbs - -// scss-docs-start breadcrumb-variables -$breadcrumb-font-size: null !default; -$breadcrumb-padding-y: 0 !default; -$breadcrumb-padding-x: 0 !default; -$breadcrumb-item-padding-x: .5rem !default; -$breadcrumb-margin-bottom: 1rem !default; -$breadcrumb-bg: null !default; -$breadcrumb-divider-color: $gray-600 !default; -$breadcrumb-active-color: $gray-600 !default; -$breadcrumb-divider: quote("/") !default; -$breadcrumb-divider-flipped: $breadcrumb-divider !default; -$breadcrumb-border-radius: null !default; -// scss-docs-end breadcrumb-variables - -// Carousel - -// scss-docs-start carousel-variables -$carousel-control-color: $white !default; -$carousel-control-width: 15% !default; -$carousel-control-opacity: .5 !default; -$carousel-control-hover-opacity: .9 !default; -$carousel-control-transition: opacity .15s ease !default; - -$carousel-indicator-width: 30px !default; -$carousel-indicator-height: 3px !default; -$carousel-indicator-hit-area-height: 10px !default; -$carousel-indicator-spacer: 3px !default; -$carousel-indicator-opacity: .5 !default; -$carousel-indicator-active-bg: $white !default; -$carousel-indicator-active-opacity: 1 !default; -$carousel-indicator-transition: opacity .6s ease !default; - -$carousel-caption-width: 70% !default; -$carousel-caption-color: $white !default; -$carousel-caption-padding-y: 1.25rem !default; -$carousel-caption-spacer: 1.25rem !default; - -$carousel-control-icon-width: 2rem !default; - -$carousel-control-prev-icon-bg: url("data:image/svg+xml,") !default; -$carousel-control-next-icon-bg: url("data:image/svg+xml,") !default; - -$carousel-transition-duration: .6s !default; -$carousel-transition: transform $carousel-transition-duration ease-in-out !default; // Define transform transition first if using multiple transitions (e.g., `transform 2s ease, opacity .5s ease-out`) - -$carousel-dark-indicator-active-bg: $black !default; -$carousel-dark-caption-color: $black !default; -$carousel-dark-control-icon-filter: invert(1) grayscale(100) !default; -// scss-docs-end carousel-variables - - -// Spinners - -// scss-docs-start spinner-variables -$spinner-width: 2rem !default; -$spinner-height: $spinner-width !default; -$spinner-vertical-align: -.125em !default; -$spinner-border-width: .25em !default; -$spinner-animation-speed: .75s !default; - -$spinner-width-sm: 1rem !default; -$spinner-height-sm: $spinner-width-sm !default; -$spinner-border-width-sm: .2em !default; -// scss-docs-end spinner-variables - - -// Close - -// scss-docs-start close-variables -$btn-close-width: 1em !default; -$btn-close-height: $btn-close-width !default; -$btn-close-padding-x: .25em !default; -$btn-close-padding-y: $btn-close-padding-x !default; -$btn-close-color: $black !default; -$btn-close-bg: url("data:image/svg+xml,") !default; -$btn-close-focus-shadow: $input-btn-focus-box-shadow !default; -$btn-close-opacity: .5 !default; -$btn-close-hover-opacity: .75 !default; -$btn-close-focus-opacity: 1 !default; -$btn-close-disabled-opacity: .25 !default; -$btn-close-white-filter: invert(1) grayscale(100%) brightness(200%) !default; -// scss-docs-end close-variables - - -// Offcanvas - -// scss-docs-start offcanvas-variables -$offcanvas-padding-y: $modal-inner-padding !default; -$offcanvas-padding-x: $modal-inner-padding !default; -$offcanvas-horizontal-width: 400px !default; -$offcanvas-vertical-height: 30vh !default; -$offcanvas-transition-duration: .3s !default; -$offcanvas-border-color: $modal-content-border-color !default; -$offcanvas-border-width: $modal-content-border-width !default; -$offcanvas-title-line-height: $modal-title-line-height !default; -$offcanvas-bg-color: $modal-content-bg !default; -$offcanvas-color: $modal-content-color !default; -$offcanvas-box-shadow: $modal-content-box-shadow-xs !default; -// scss-docs-end offcanvas-variables - -// Code - -$code-font-size: $small-font-size !default; -$code-color: $pink !default; - -$kbd-padding-y: .2rem !default; -$kbd-padding-x: .4rem !default; -$kbd-font-size: $code-font-size !default; -$kbd-color: $white !default; -$kbd-bg: $gray-900 !default; - -$pre-color: null !default; +$alert-border-radius: 0; //$border-radius !default; +$alert-border-width: 0; //$border-width !default; \ No newline at end of file diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/pick-entity/index.js b/src/Bundle/ChillMainBundle/Resources/public/module/pick-entity/index.js index 511d2126e..99f83e1e3 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/module/pick-entity/index.js +++ b/src/Bundle/ChillMainBundle/Resources/public/module/pick-entity/index.js @@ -6,6 +6,7 @@ import { appMessages } from 'ChillMainAssets/vuejs/PickEntity/i18n'; const i18n = _createI18n(appMessages); let appsOnPage = new Map(); +let appsPerInput = new Map(); function loadDynamicPicker(element) { @@ -17,12 +18,13 @@ function loadDynamicPicker(element) { isMultiple = parseInt(el.dataset.multiple) === 1, uniqId = el.dataset.uniqid, input = element.querySelector('[data-input-uniqid="'+ el.dataset.uniqid +'"]'), + // the "picked" will always be an array, even if multiple is false picked = isMultiple ? JSON.parse(input.value) : ( (input.value === '[]' || input.value === '') ? null : [ JSON.parse(input.value) ] ) - ; + suggested = JSON.parse(el.dataset.suggested) if (!isMultiple) { if (input.value === '[]'){ @@ -36,6 +38,7 @@ function loadDynamicPicker(element) { ':types="types" ' + ':picked="picked" ' + ':uniqid="uniqid" ' + + ':suggested="notPickedSuggested" ' + '@addNewEntity="addNewEntity" ' + '@removeEntity="removeEntity">', components: { @@ -47,16 +50,27 @@ function loadDynamicPicker(element) { types: JSON.parse(el.dataset.types), picked: picked === null ? [] : picked, uniqid: el.dataset.uniqid, + suggested: suggested } }, + computed: { + notPickedSuggested() { + const pickedIds = new Set(); + for (const p of this.picked) { + pickedIds.add(`${p.type}${p.id}`); + } + return this.suggested.filter(e => !pickedIds.has(`${e.type}${e.id}`)) + } + }, methods: { - addNewEntity(entity) { + addNewEntity({entity}) { if (this.multiple) { if (!this.picked.some(el => { return el.type === entity.type && el.id === entity.id; })) { this.picked.push(entity); input.value = JSON.stringify(this.picked); + console.log(entity) } } else { if (!this.picked.some(el => { @@ -68,9 +82,16 @@ function loadDynamicPicker(element) { } } }, - removeEntity(entity) { + removeEntity({entity}) { + if (-1 === this.suggested.findIndex(e => e.type === entity.type && e.id === entity.id)) { + this.suggested.push(entity); + } this.picked = this.picked.filter(e => !(e.type === entity.type && e.id === entity.id)); - input.value = JSON.stringify(this.picked); + if (this.multiple) { + input.value = JSON.stringify(this.picked); + } else { + input.value = ""; + } }, } }) @@ -78,13 +99,14 @@ function loadDynamicPicker(element) { .mount(el); appsOnPage.set(uniqId, app); + appsPerInput.set(input.name, app); }); } document.addEventListener('show-hide-show', function(e) { loadDynamicPicker(e.detail.container) -}) +}); document.addEventListener('show-hide-hide', function(e) { console.log('hiding event caught') @@ -95,13 +117,25 @@ document.addEventListener('show-hide-hide', function(e) { appsOnPage.delete(uniqId); } }) -}) +}); + +document.addEventListener('pick-entity-type-action', function (e) { + console.log('pick entity event', e); + if (!appsPerInput.has(e.detail.name)) { + console.error('no app with this name'); + return; + } + const app = appsPerInput.get(e.detail.name); + if (e.detail.action === 'add') { + app.addNewEntity(e.detail.entity); + } else if (e.detail.action === 'remove') { + app.removeEntity(e.detail.entity); + } else { + console.error('action not supported: '+e.detail.action); + } +}); document.addEventListener('DOMContentLoaded', function(e) { loadDynamicPicker(document) }) - - - - diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/pick-postal-code/index.js b/src/Bundle/ChillMainBundle/Resources/public/module/pick-postal-code/index.js new file mode 100644 index 000000000..14fd164e0 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/module/pick-postal-code/index.js @@ -0,0 +1,60 @@ +import { createApp } from 'vue'; +import PickPostalCode from 'ChillMainAssets/vuejs/PickPostalCode/PickPostalCode'; +import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n'; +import { appMessages } from 'ChillMainAssets/vuejs/PickEntity/i18n'; +import { makeFetch } from 'ChillMainAssets/lib/api/apiMethods'; + +const i18n = _createI18n(appMessages); + + +function loadOnePicker(el, input, uniqId, city) { + const app = createApp({ + template: '', + components: { + PickPostalCode, + }, + data() { + return { + city: city, + } + }, + methods: { + onCitySelected(city) { + this.city = city; + input.value = city.id; + }, + onCityRemoved(city) { + this.city = null; + input.value = ''; + } + } + }) + .use(i18n) + .mount(el); +} + +function loadDynamicPickers(element) { + + let apps = element.querySelectorAll('[data-module="pick-postal-code"]'); + + apps.forEach(function(el) { + + const + uniqId = el.dataset.uniqid, + input = document.querySelector(`input[data-input-uniqid="${uniqId}"]`), + cityIdValue = input.value === '' ? null : input.value + ; + + if (cityIdValue !== null) { + makeFetch('GET', `/api/1.0/main/postal-code/${cityIdValue}.json`).then(city => { + loadOnePicker(el, input, uniqId, city); + }) + } else { + loadOnePicker(el, input, uniqId, null); + } + }); +} + +document.addEventListener('DOMContentLoaded', function(e) { + loadDynamicPickers(document) +}) diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/pick-rolling-date/index.js b/src/Bundle/ChillMainBundle/Resources/public/module/pick-rolling-date/index.js new file mode 100644 index 000000000..2600ea40b --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/module/pick-rolling-date/index.js @@ -0,0 +1,28 @@ +import {ShowHide} from 'ChillMainAssets/lib/show_hide/index'; + +document.addEventListener('DOMContentLoaded', function(_e) { + console.log('pick-rolling-date'); + document.querySelectorAll('div[data-rolling-date]').forEach( (picker) => { + const + roll_wrapper = picker.querySelector('div.roll-wrapper'), + fixed_wrapper = picker.querySelector('div.fixed-wrapper'); + + new ShowHide({ + froms: [roll_wrapper], + container: [fixed_wrapper], + test: function (elems) { + console.log('testing'); + console.log('elems', elems); + for (let el of elems) { + for (let select_roll of el.querySelectorAll('select[data-roll-picker]')) { + console.log('select_roll', select_roll); + console.log('value', select_roll.value); + return select_roll.value === 'fixed_date'; + } + } + return false; + } + }) + }); +}); + diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/_custom.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/_custom.scss deleted file mode 100644 index e44fdebcc..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/_custom.scss +++ /dev/null @@ -1,190 +0,0 @@ -/* - * NOTE 2021.04 - * scss/chill.scss is the main sass file for the new chill.2 - * scratch will be replaced by bootstrap, please avoid to edit in modules/scratch/_custom.scss - */ - -// YOUR CUSTOM SCSS -@import 'custom/config/colors'; -@import 'custom/config/variables'; -@import 'custom/fonts'; -@import 'custom/timeline'; -@import 'custom/mixins/entity'; -@import 'custom/report'; -@import 'custom/person'; -@import 'custom/pagination'; -@import 'custom/custom-fields'; -@import 'custom/address'; -@import 'custom/record_actions'; -@import 'custom/flash_messages'; -@import 'custom/box'; - - -html,body { - min-height:100%; - font-family: 'Open Sans'; -} - -header { - position: relative; -} - -#content_conainter { - position: relative; - min-height: calc(100% - 145px); -} - -#content_conainter:before { - bottom: 0; - content: ""; - left: 0; - opacity: 0.1; - position: absolute; - right: 0; - top: 0; - z-index: -1; - //background-image: url('./../../img/background/desert.jpg'); - background-attachment: fixed; - background-repeat: no-repeat; - background-size: cover; - background-position: center; -} - -/* CUSTOM FIELDS -> */ -.cf-title { - font-size: 2em; -} - -.cf-subtitle { - font-size: 1.5em; -} -/* <- CUSTOM FIELDS */ - -@each $len in 11, 15 { - ul.submenu.width-#{$len}-em { - min-width: #{$len}em; - } -} - -.content { - padding-top: 1em; - padding-bottom: 1em; -} - -.select2 { - width: 100%; -} - -ul.custom_fields.choice li { - list-style:none; -} - -.errors { - color: $red; -} - -.footer { - p { - font-family: 'Open Sans'; - font-weight: 300; - } - - a { - color: white; - text-decoration: underline; - } -} - -// inline time input -.time_compound { - input[type=text], select { - width: 4em; - display: inline-block; - text-align: center; - } - - .separator { - margin-left: 0.2em; - margin-right: 0.2em; - } -} - -.open_sansbold { - font-family: 'Open Sans'; - font-weight: bold; - -} - - -dd { - margin-left: 0; -} - -dt { - font-family: 'Open Sans'; - font-weight: 600; -} - - -/* INPUT CLASS -> */ -div.input_with_post_text { - display: flex; - align-items: center; -} - -div.input_with_post_text span.post_text { - flex: 1; - margin-left: 0.5em; -} -div.input_with_post_text input { - width: 70%; - display: inline-block; - flex: 2; -} -/* <- INPUT CLASS */ - -dl.chill_report_view_data, -dl.chill_view_data { - - dt { - margin-top: 1.5em; - color: $chill-blue; - } - - dd { - padding-left: 1.5em; - margin-top: 0.2em; - - ul { - padding-left: 0; - } - } - -} - -blockquote.chill-user-quote, -div.chill-user-quote { - border-left: 10px solid $chill-yellow; - margin: 1.5em 10px; - padding: 0.5em 10px; - quotes: "\201C""\201D""\2018""\2019"; - background-color: $chill-llight-gray; - - blockquote { - margin: 1.5em 10px; - padding: 0.5em 10px; - } - - blockquote:before { - color: #ccc; - content: open-quote; - font-size: 4em; - line-height: 0.1em; - margin-right: 0.25em; - vertical-align: -0.4em; - } -} - -.chill-no-data-statement { - font-style: italic; -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/config/_buttons.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/config/_buttons.scss deleted file mode 100644 index 00b61968c..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/config/_buttons.scss +++ /dev/null @@ -1,7 +0,0 @@ -// Buttons -$button-font-size : 15px !default; -$button-text-color : #000 !default; -$button-text-weight : 300 !default; -$button-padding : 8px 12px !default; -$button-background : #eee !default; -$button-margin : 5px !default; \ No newline at end of file diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/config/_colors.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/config/_colors.scss deleted file mode 100644 index 9c200db0f..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/config/_colors.scss +++ /dev/null @@ -1,64 +0,0 @@ -// BODY -$body-background : #fff !default; - -// PLAIN TEXT -$text-color : #555 !default; - -// HEADINGS -$headings-color : #404040 !default; - -// LINKS -$link-color : #6998C9 !default; -$link-visited-color : #808080 !default; -$link-active-color : shade(red,5%) !default; -$link-hover-color : #007ED5 !default; -$link-focus-color : $link-color !default; - -$white : #fff !default; -$black : #000 !default; -$orange : #FF622C !default; -$red : #C83D3D !default; -$green : #27806f !default; -$blue : #2980b9 !default; -$yellow : #FFC82C !default; - -$light-blue : #4995C7; -$llight-blue : #72B0D9; -$dark-blue : #096EB0 !default; -$ddark-blue : #07507F !default; - -$light-green : #419484 !default; -$llight-green : #6CB3A5 !default; - -$warning-bg : $orange !default; -$caution-bg : $red !default; -$error-bg : $red !default; -$success-bg : $green !default; -$info-bg : $blue !default; - -$grey-95 : lighten($black, 5%) !default; -$grey-90 : lighten($black, 10%) !default; -$grey-85 : lighten($black, 15%) !default; -$grey-80 : lighten($black, 20%) !default; -$grey-75 : lighten($black, 25%) !default; -$grey-70 : lighten($black, 30%) !default; -$grey-65 : lighten($black, 35%) !default; -$grey-60 : lighten($black, 40%) !default; -$grey-55 : lighten($black, 45%) !default; -$grey-50 : lighten($black, 50%) !default; -$grey-45 : lighten($black, 55%) !default; -$grey-40 : lighten($black, 60%) !default; -$grey-35 : lighten($black, 65%) !default; -$grey-30 : lighten($black, 70%) !default; -$grey-25 : lighten($black, 75%) !default; -$grey-20 : lighten($black, 80%) !default; -$grey-15 : lighten($black, 85%) !default; -$grey-10 : lighten($black, 90%) !default; -$grey-5 : lighten($black, 95%) !default; - - -$dark-grey: $grey-80; //#333; -$medium-grey: $grey-50; //#999; -$light-grey: $grey-20; //#DDD; - -@import "../custom/config/colors"; \ No newline at end of file diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/config/_variables.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/config/_variables.scss deleted file mode 100644 index 09840d957..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/config/_variables.scss +++ /dev/null @@ -1,115 +0,0 @@ -// Typography -//$sans-serif: $helvetica; -//$serif: $georgia; -//$base-font-family: $sans-serif; -//$header-font-family: $base-font-family; - -// Font Sizes -$base-font-size: 1em; -$h1-font-size: $base-font-size * 2.25; -$h2-font-size: $base-font-size * 2; -$h3-font-size: $base-font-size * 1.75; -$h4-font-size: $base-font-size * 1.5; -$h5-font-size: $base-font-size * 1.25; -$h6-font-size: $base-font-size; - -// Line height -$base-line-height: 1.5; -$header-line-height: 1.25; - -// Other Sizes -$base-border-radius: 3px; -$base-spacing: $base-line-height * 1em; -$base-z-index: 0; - -// Background Color -$base-background-color: white; - -// Font Colors -$base-font-color: $dark-grey; -$base-accent-color: $blue; - -// Link Colors -$base-link-color: $base-accent-color; -$hover-link-color: darken($base-accent-color, 15); -$base-button-color: $base-link-color; -$hover-button-color: $hover-link-color; - -/* -// Flash Colors -$alert-color: $light-yellow; -$error-color: $light-red; -$notice-color: lighten($base-accent-color, 40); -$success-color: $light-green; -*/ - -// Border color -$base-border-color: $light-grey; -$base-border: 1px solid $base-border-color; - -// Footer -$footer-background: $grey-50;//-blue;// desaturate(darken($base-accent-color, 20), 30); -$footer-color: white; -$footer-link-color: transparentize($footer-color, .6); -$footer-disclaimer-color: transparentize($footer-color, .6); -$footer-vertical-padding: 0; - -// Forms -$form-border-size: 1px; -$form-border-color: $base-border-color; -$form-border-color-hover: darken($base-border-color, 10); -$form-border-color-focus: $base-accent-color; -$form-border-radius: $base-border-radius; -$form-box-shadow: inset 0 1px 3px rgba(black,0.06); -$form-box-shadow-focus: $form-box-shadow, 0 0 5px rgba(darken($form-border-color-focus, 5), 0.7); -$form-font-size: $base-font-size; -//$form-font-family: $base-font-family; - -// Navigation -$navigation-background: $dark-grey; -$navigation-color: transparentize(white, 0.3); -$navigation-color-hover: white; -$navigation-height: 60px; -$navigation-active-link-color: transparentize(white, 0.5); -$navigation-submenu-padding: 1em; -$navigation-submenu-width: 12em; -$navigation-border-radius: $base-border-radius; -$navigation-first-padding-top: 1em; -$navigation-last-padding-bottom: 0.7em; -$navigation-more-pin: '\25BE'; -$navigation-more-pin-color: $navigation-color; -$navigation-ul-submenu-top: 1.5em; -$navigation-ul-submenu-padding-left: 0; -$navigation-search-padding: .85em .6em; -$navigation-border-bottom: 1px solid darken($navigation-background, 10); - -//Table -$table-width: 100%; - -$table-head-bg-color: $orange; -$table-head-td-border: unset; -$table-head-td-text-align: center; -$table-head-td-padding: 0.3em; -$table-head-text-color: $white; - -$table-body-tr-bg-color-even: $grey-5; -$table-body-tr-bg-color-odd: unset; -$table-body-td-border: unset; -$table-body-td-text-align: left; -$table-body-td-padding: 0.3em; -$table-body-text-color: unset; - -//Tabs -$tabs-nav-margin-bottom: none; -$tabs-nav-title-bg-color: $blue; -$tabs-nav-title-text-color: $white; -$tabs-nav-title-padding: 0.5em 0.5em 0.5em 1em; -$tabs-nav-bg-color: none; -$tabs-nav-text-color: inherit; -$tabs-new-border: 3px solid transparent; -$tabs-nav-hover-border: 3px solid $orange; -$tabs-nav-hover-text-color: inherit; -$tabs-nav-font-family: unset; -$tabs-nav-padding: 0.5em 0.5em 0.5em 1.5em; - -@import "../custom/config/variables"; \ No newline at end of file diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/_bourbon-deprecated-upcoming.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/_bourbon-deprecated-upcoming.scss deleted file mode 100644 index e6d1b8cec..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/_bourbon-deprecated-upcoming.scss +++ /dev/null @@ -1,411 +0,0 @@ -// The following features have been deprecated and will be removed in the next MAJOR version release - -@mixin inline-block { - display: inline-block; - - @warn "The inline-block mixin is deprecated and will be removed in the next major version release"; -} - -@mixin button ($style: simple, $base-color: #4294f0, $text-size: inherit, $padding: 7px 18px) { - - @if type-of($style) == string and type-of($base-color) == color { - @include buttonstyle($style, $base-color, $text-size, $padding); - } - - @if type-of($style) == string and type-of($base-color) == number { - $padding: $text-size; - $text-size: $base-color; - $base-color: #4294f0; - - @if $padding == inherit { - $padding: 7px 18px; - } - - @include buttonstyle($style, $base-color, $text-size, $padding); - } - - @if type-of($style) == color and type-of($base-color) == color { - $base-color: $style; - $style: simple; - @include buttonstyle($style, $base-color, $text-size, $padding); - } - - @if type-of($style) == color and type-of($base-color) == number { - $padding: $text-size; - $text-size: $base-color; - $base-color: $style; - $style: simple; - - @if $padding == inherit { - $padding: 7px 18px; - } - - @include buttonstyle($style, $base-color, $text-size, $padding); - } - - @if type-of($style) == number { - $padding: $base-color; - $text-size: $style; - $base-color: #4294f0; - $style: simple; - - @if $padding == #4294f0 { - $padding: 7px 18px; - } - - @include buttonstyle($style, $base-color, $text-size, $padding); - } - - &:disabled { - cursor: not-allowed; - opacity: 0.5; - } - - @warn "The button mixin is deprecated and will be removed in the next major version release"; -} - -// Selector Style Button -@mixin buttonstyle($type, $b-color, $t-size, $pad) { - // Grayscale button - @if $type == simple and $b-color == grayscale($b-color) { - @include simple($b-color, true, $t-size, $pad); - } - - @if $type == shiny and $b-color == grayscale($b-color) { - @include shiny($b-color, true, $t-size, $pad); - } - - @if $type == pill and $b-color == grayscale($b-color) { - @include pill($b-color, true, $t-size, $pad); - } - - @if $type == flat and $b-color == grayscale($b-color) { - @include flat($b-color, true, $t-size, $pad); - } - - // Colored button - @if $type == simple { - @include simple($b-color, false, $t-size, $pad); - } - - @else if $type == shiny { - @include shiny($b-color, false, $t-size, $pad); - } - - @else if $type == pill { - @include pill($b-color, false, $t-size, $pad); - } - - @else if $type == flat { - @include flat($b-color, false, $t-size, $pad); - } -} - -// Simple Button -@mixin simple($base-color, $grayscale: false, $textsize: inherit, $padding: 7px 18px) { - $color: hsl(0, 0, 100%); - $border: adjust-color($base-color, $saturation: 9%, $lightness: -14%); - $inset-shadow: adjust-color($base-color, $saturation: -8%, $lightness: 15%); - $stop-gradient: adjust-color($base-color, $saturation: 9%, $lightness: -11%); - $text-shadow: adjust-color($base-color, $saturation: 15%, $lightness: -18%); - - @if is-light($base-color) { - $color: hsl(0, 0, 20%); - $text-shadow: adjust-color($base-color, $saturation: 10%, $lightness: 4%); - } - - @if $grayscale == true { - $border: grayscale($border); - $inset-shadow: grayscale($inset-shadow); - $stop-gradient: grayscale($stop-gradient); - $text-shadow: grayscale($text-shadow); - } - - border: 1px solid $border; - border-radius: 3px; - box-shadow: inset 0 1px 0 0 $inset-shadow; - color: $color; - display: inline-block; - font-size: $textsize; - font-weight: bold; - @include linear-gradient ($base-color, $stop-gradient); - padding: $padding; - text-decoration: none; - text-shadow: 0 1px 0 $text-shadow; - background-clip: padding-box; - - &:hover:not(:disabled) { - $base-color-hover: adjust-color($base-color, $saturation: -4%, $lightness: -5%); - $inset-shadow-hover: adjust-color($base-color, $saturation: -7%, $lightness: 5%); - $stop-gradient-hover: adjust-color($base-color, $saturation: 8%, $lightness: -14%); - - @if $grayscale == true { - $base-color-hover: grayscale($base-color-hover); - $inset-shadow-hover: grayscale($inset-shadow-hover); - $stop-gradient-hover: grayscale($stop-gradient-hover); - } - - @include linear-gradient ($base-color-hover, $stop-gradient-hover); - - box-shadow: inset 0 1px 0 0 $inset-shadow-hover; - cursor: pointer; - } - - &:active:not(:disabled), - &:focus:not(:disabled) { - $border-active: adjust-color($base-color, $saturation: 9%, $lightness: -14%); - $inset-shadow-active: adjust-color($base-color, $saturation: 7%, $lightness: -17%); - - @if $grayscale == true { - $border-active: grayscale($border-active); - $inset-shadow-active: grayscale($inset-shadow-active); - } - - border: 1px solid $border-active; - box-shadow: inset 0 0 8px 4px $inset-shadow-active, inset 0 0 8px 4px $inset-shadow-active; - } -} - -// Shiny Button -@mixin shiny($base-color, $grayscale: false, $textsize: inherit, $padding: 7px 18px) { - $color: hsl(0, 0, 100%); - $border: adjust-color($base-color, $red: -117, $green: -111, $blue: -81); - $border-bottom: adjust-color($base-color, $red: -126, $green: -127, $blue: -122); - $fourth-stop: adjust-color($base-color, $red: -79, $green: -70, $blue: -46); - $inset-shadow: adjust-color($base-color, $red: 37, $green: 29, $blue: 12); - $second-stop: adjust-color($base-color, $red: -56, $green: -50, $blue: -33); - $text-shadow: adjust-color($base-color, $red: -140, $green: -141, $blue: -114); - $third-stop: adjust-color($base-color, $red: -86, $green: -75, $blue: -48); - - @if is-light($base-color) { - $color: hsl(0, 0, 20%); - $text-shadow: adjust-color($base-color, $saturation: 10%, $lightness: 4%); - } - - @if $grayscale == true { - $border: grayscale($border); - $border-bottom: grayscale($border-bottom); - $fourth-stop: grayscale($fourth-stop); - $inset-shadow: grayscale($inset-shadow); - $second-stop: grayscale($second-stop); - $text-shadow: grayscale($text-shadow); - $third-stop: grayscale($third-stop); - } - - @include linear-gradient(top, $base-color 0%, $second-stop 50%, $third-stop 50%, $fourth-stop 100%); - - border: 1px solid $border; - border-bottom: 1px solid $border-bottom; - border-radius: 5px; - box-shadow: inset 0 1px 0 0 $inset-shadow; - color: $color; - display: inline-block; - font-size: $textsize; - font-weight: bold; - padding: $padding; - text-align: center; - text-decoration: none; - text-shadow: 0 -1px 1px $text-shadow; - - &:hover:not(:disabled) { - $first-stop-hover: adjust-color($base-color, $red: -13, $green: -15, $blue: -18); - $second-stop-hover: adjust-color($base-color, $red: -66, $green: -62, $blue: -51); - $third-stop-hover: adjust-color($base-color, $red: -93, $green: -85, $blue: -66); - $fourth-stop-hover: adjust-color($base-color, $red: -86, $green: -80, $blue: -63); - - @if $grayscale == true { - $first-stop-hover: grayscale($first-stop-hover); - $second-stop-hover: grayscale($second-stop-hover); - $third-stop-hover: grayscale($third-stop-hover); - $fourth-stop-hover: grayscale($fourth-stop-hover); - } - - @include linear-gradient(top, $first-stop-hover 0%, - $second-stop-hover 50%, - $third-stop-hover 50%, - $fourth-stop-hover 100%); - cursor: pointer; - } - - &:active:not(:disabled), - &:focus:not(:disabled) { - $inset-shadow-active: adjust-color($base-color, $red: -111, $green: -116, $blue: -122); - - @if $grayscale == true { - $inset-shadow-active: grayscale($inset-shadow-active); - } - - box-shadow: inset 0 0 20px 0 $inset-shadow-active; - } -} - -// Pill Button -@mixin pill($base-color, $grayscale: false, $textsize: inherit, $padding: 7px 18px) { - $color: hsl(0, 0, 100%); - $border-bottom: adjust-color($base-color, $hue: 8, $saturation: -11%, $lightness: -26%); - $border-sides: adjust-color($base-color, $hue: 4, $saturation: -21%, $lightness: -21%); - $border-top: adjust-color($base-color, $hue: -1, $saturation: -30%, $lightness: -15%); - $inset-shadow: adjust-color($base-color, $hue: -1, $saturation: -1%, $lightness: 7%); - $stop-gradient: adjust-color($base-color, $hue: 8, $saturation: 14%, $lightness: -10%); - $text-shadow: adjust-color($base-color, $hue: 5, $saturation: -19%, $lightness: -15%); - - @if is-light($base-color) { - $color: hsl(0, 0, 20%); - $text-shadow: adjust-color($base-color, $saturation: 10%, $lightness: 4%); - } - - @if $grayscale == true { - $border-bottom: grayscale($border-bottom); - $border-sides: grayscale($border-sides); - $border-top: grayscale($border-top); - $inset-shadow: grayscale($inset-shadow); - $stop-gradient: grayscale($stop-gradient); - $text-shadow: grayscale($text-shadow); - } - - border: 1px solid $border-top; - border-color: $border-top $border-sides $border-bottom; - border-radius: 16px; - box-shadow: inset 0 1px 0 0 $inset-shadow; - color: $color; - display: inline-block; - font-size: $textsize; - font-weight: normal; - line-height: 1; - @include linear-gradient ($base-color, $stop-gradient); - padding: $padding; - text-align: center; - text-decoration: none; - text-shadow: 0 -1px 1px $text-shadow; - background-clip: padding-box; - - &:hover:not(:disabled) { - $base-color-hover: adjust-color($base-color, $lightness: -4.5%); - $border-bottom: adjust-color($base-color, $hue: 8, $saturation: 13.5%, $lightness: -32%); - $border-sides: adjust-color($base-color, $hue: 4, $saturation: -2%, $lightness: -27%); - $border-top: adjust-color($base-color, $hue: -1, $saturation: -17%, $lightness: -21%); - $inset-shadow-hover: adjust-color($base-color, $saturation: -1%, $lightness: 3%); - $stop-gradient-hover: adjust-color($base-color, $hue: 8, $saturation: -4%, $lightness: -15.5%); - $text-shadow-hover: adjust-color($base-color, $hue: 5, $saturation: -5%, $lightness: -22%); - - @if $grayscale == true { - $base-color-hover: grayscale($base-color-hover); - $border-bottom: grayscale($border-bottom); - $border-sides: grayscale($border-sides); - $border-top: grayscale($border-top); - $inset-shadow-hover: grayscale($inset-shadow-hover); - $stop-gradient-hover: grayscale($stop-gradient-hover); - $text-shadow-hover: grayscale($text-shadow-hover); - } - - @include linear-gradient ($base-color-hover, $stop-gradient-hover); - - background-clip: padding-box; - border: 1px solid $border-top; - border-color: $border-top $border-sides $border-bottom; - box-shadow: inset 0 1px 0 0 $inset-shadow-hover; - cursor: pointer; - text-shadow: 0 -1px 1px $text-shadow-hover; - } - - &:active:not(:disabled), - &:focus:not(:disabled) { - $active-color: adjust-color($base-color, $hue: 4, $saturation: -12%, $lightness: -10%); - $border-active: adjust-color($base-color, $hue: 6, $saturation: -2.5%, $lightness: -30%); - $border-bottom-active: adjust-color($base-color, $hue: 11, $saturation: 6%, $lightness: -31%); - $inset-shadow-active: adjust-color($base-color, $hue: 9, $saturation: 2%, $lightness: -21.5%); - $text-shadow-active: adjust-color($base-color, $hue: 5, $saturation: -12%, $lightness: -21.5%); - - @if $grayscale == true { - $active-color: grayscale($active-color); - $border-active: grayscale($border-active); - $border-bottom-active: grayscale($border-bottom-active); - $inset-shadow-active: grayscale($inset-shadow-active); - $text-shadow-active: grayscale($text-shadow-active); - } - - background: $active-color; - border: 1px solid $border-active; - border-bottom: 1px solid $border-bottom-active; - box-shadow: inset 0 0 6px 3px $inset-shadow-active; - text-shadow: 0 -1px 1px $text-shadow-active; - } -} - -// Flat Button -@mixin flat($base-color, $grayscale: false, $textsize: inherit, $padding: 7px 18px) { - $color: hsl(0, 0, 100%); - - @if is-light($base-color) { - $color: hsl(0, 0, 20%); - } - - background-color: $base-color; - border-radius: 3px; - border: 0; - color: $color; - display: inline-block; - font-size: $textsize; - font-weight: bold; - padding: $padding; - text-decoration: none; - background-clip: padding-box; - - &:hover:not(:disabled){ - $base-color-hover: adjust-color($base-color, $saturation: 4%, $lightness: 5%); - - @if $grayscale == true { - $base-color-hover: grayscale($base-color-hover); - } - - background-color: $base-color-hover; - cursor: pointer; - } - - &:active:not(:disabled), - &:focus:not(:disabled) { - $base-color-active: adjust-color($base-color, $saturation: -4%, $lightness: -5%); - - @if $grayscale == true { - $base-color-active: grayscale($base-color-active); - } - - background-color: $base-color-active; - cursor: pointer; - } -} - -// Flexible grid -@function flex-grid($columns, $container-columns: $fg-max-columns) { - $width: $columns * $fg-column + ($columns - 1) * $fg-gutter; - $container-width: $container-columns * $fg-column + ($container-columns - 1) * $fg-gutter; - @return percentage($width / $container-width); - - @warn "The flex-grid function is deprecated and will be removed in the next major version release"; -} - -// Flexible gutter -@function flex-gutter($container-columns: $fg-max-columns, $gutter: $fg-gutter) { - $container-width: $container-columns * $fg-column + ($container-columns - 1) * $fg-gutter; - @return percentage($gutter / $container-width); - - @warn "The flex-gutter function is deprecated and will be removed in the next major version release"; -} - -@function grid-width($n) { - @return $n * $gw-column + ($n - 1) * $gw-gutter; - - @warn "The grid-width function is deprecated and will be removed in the next major version release"; -} - -@function golden-ratio($value, $increment) { - @return modular-scale($increment, $value, $ratio: $golden); - - @warn "The golden-ratio function is deprecated and will be removed in the next major version release. Please use the modular-scale function, instead."; -} - -@mixin box-sizing($box) { - @include prefixer(box-sizing, $box, webkit moz spec); - - @warn "The box-sizing mixin is deprecated and will be removed in the next major version release. This property can now be used un-prefixed."; -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/_bourbon.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/_bourbon.scss deleted file mode 100644 index 509fcc10e..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/_bourbon.scss +++ /dev/null @@ -1,87 +0,0 @@ -// Bourbon 4.2.6 -// http://bourbon.io -// Copyright 2011-2015 thoughtbot, inc. -// MIT License - -@import "settings/prefixer"; -@import "settings/px-to-em"; -@import "settings/asset-pipeline"; - -@import "functions/assign-inputs"; -@import "functions/contains"; -@import "functions/contains-falsy"; -@import "functions/is-length"; -@import "functions/is-light"; -@import "functions/is-number"; -@import "functions/is-size"; -@import "functions/px-to-em"; -@import "functions/px-to-rem"; -@import "functions/shade"; -@import "functions/strip-units"; -@import "functions/tint"; -@import "functions/transition-property-name"; -@import "functions/unpack"; -@import "functions/modular-scale"; - -@import "helpers/convert-units"; -@import "helpers/directional-values"; -@import "helpers/font-source-declaration"; -@import "helpers/gradient-positions-parser"; -@import "helpers/linear-angle-parser"; -@import "helpers/linear-gradient-parser"; -@import "helpers/linear-positions-parser"; -@import "helpers/linear-side-corner-parser"; -@import "helpers/radial-arg-parser"; -@import "helpers/radial-positions-parser"; -@import "helpers/radial-gradient-parser"; -@import "helpers/render-gradients"; -@import "helpers/shape-size-stripper"; -@import "helpers/str-to-num"; - -@import "css3/animation"; -@import "css3/appearance"; -@import "css3/backface-visibility"; -@import "css3/background"; -@import "css3/background-image"; -@import "css3/border-image"; -@import "css3/calc"; -@import "css3/columns"; -@import "css3/filter"; -@import "css3/flex-box"; -@import "css3/font-face"; -@import "css3/font-feature-settings"; -@import "css3/hidpi-media-query"; -@import "css3/hyphens"; -@import "css3/image-rendering"; -@import "css3/keyframes"; -@import "css3/linear-gradient"; -@import "css3/perspective"; -@import "css3/placeholder"; -@import "css3/radial-gradient"; -@import "css3/selection"; -@import "css3/text-decoration"; -@import "css3/transform"; -@import "css3/transition"; -@import "css3/user-select"; - -@import "addons/border-color"; -@import "addons/border-radius"; -@import "addons/border-style"; -@import "addons/border-width"; -@import "addons/buttons"; -@import "addons/clearfix"; -@import "addons/ellipsis"; -@import "addons/font-stacks"; -@import "addons/hide-text"; -@import "addons/margin"; -@import "addons/padding"; -@import "addons/position"; -@import "addons/prefixer"; -@import "addons/retina-image"; -@import "addons/size"; -@import "addons/text-inputs"; -@import "addons/timing-functions"; -@import "addons/triangle"; -@import "addons/word-wrap"; - -@import "bourbon-deprecated-upcoming"; diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_border-color.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_border-color.scss deleted file mode 100644 index 6f6ab36c4..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_border-color.scss +++ /dev/null @@ -1,26 +0,0 @@ -@charset "UTF-8"; - -/// Provides a quick method for targeting `border-color` on specific sides of a box. Use a `null` value to “skip” a side. -/// -/// @param {Arglist} $vals -/// List of arguments -/// -/// @example scss - Usage -/// .element { -/// @include border-color(#a60b55 #76cd9c null #e8ae1a); -/// } -/// -/// @example css - CSS Output -/// .element { -/// border-left-color: #e8ae1a; -/// border-right-color: #76cd9c; -/// border-top-color: #a60b55; -/// } -/// -/// @require {mixin} directional-property -/// -/// @output `border-color` - -@mixin border-color($vals...) { - @include directional-property(border, color, $vals...); -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_border-radius.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_border-radius.scss deleted file mode 100644 index 1f6586335..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_border-radius.scss +++ /dev/null @@ -1,48 +0,0 @@ -@charset "UTF-8"; - -/// Provides a quick method for targeting `border-radius` on both corners on the side of a box. -/// -/// @param {Number} $radii -/// List of arguments -/// -/// @example scss - Usage -/// .element-one { -/// @include border-top-radius(5px); -/// } -/// -/// .element-two { -/// @include border-left-radius(3px); -/// } -/// -/// @example css - CSS Output -/// .element-one { -/// border-top-left-radius: 5px; -/// border-top-right-radius: 5px; -/// } -/// -/// .element-two { -/// border-bottom-left-radius: 3px; -/// border-top-left-radius: 3px; -/// } -/// -/// @output `border-radius` - -@mixin border-top-radius($radii) { - border-top-left-radius: $radii; - border-top-right-radius: $radii; -} - -@mixin border-right-radius($radii) { - border-bottom-right-radius: $radii; - border-top-right-radius: $radii; -} - -@mixin border-bottom-radius($radii) { - border-bottom-left-radius: $radii; - border-bottom-right-radius: $radii; -} - -@mixin border-left-radius($radii) { - border-bottom-left-radius: $radii; - border-top-left-radius: $radii; -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_border-style.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_border-style.scss deleted file mode 100644 index d86ee79d9..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_border-style.scss +++ /dev/null @@ -1,25 +0,0 @@ -@charset "UTF-8"; - -/// Provides a quick method for targeting `border-style` on specific sides of a box. Use a `null` value to “skip” a side. -/// -/// @param {Arglist} $vals -/// List of arguments -/// -/// @example scss - Usage -/// .element { -/// @include border-style(dashed null solid); -/// } -/// -/// @example css - CSS Output -/// .element { -/// border-bottom-style: solid; -/// border-top-style: dashed; -/// } -/// -/// @require {mixin} directional-property -/// -/// @output `border-style` - -@mixin border-style($vals...) { - @include directional-property(border, style, $vals...); -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_border-width.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_border-width.scss deleted file mode 100644 index 0ea2d4b71..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_border-width.scss +++ /dev/null @@ -1,25 +0,0 @@ -@charset "UTF-8"; - -/// Provides a quick method for targeting `border-width` on specific sides of a box. Use a `null` value to “skip” a side. -/// -/// @param {Arglist} $vals -/// List of arguments -/// -/// @example scss - Usage -/// .element { -/// @include border-width(1em null 20px); -/// } -/// -/// @example css - CSS Output -/// .element { -/// border-bottom-width: 20px; -/// border-top-width: 1em; -/// } -/// -/// @require {mixin} directional-property -/// -/// @output `border-width` - -@mixin border-width($vals...) { - @include directional-property(border, width, $vals...); -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_buttons.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_buttons.scss deleted file mode 100644 index debeabc53..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_buttons.scss +++ /dev/null @@ -1,64 +0,0 @@ -@charset "UTF-8"; - -/// Generates variables for all buttons. Please note that you must use interpolation on the variable: `#{$all-buttons}`. -/// -/// @example scss - Usage -/// #{$all-buttons} { -/// background-color: #f00; -/// } -/// -/// #{$all-buttons-focus}, -/// #{$all-buttons-hover} { -/// background-color: #0f0; -/// } -/// -/// #{$all-buttons-active} { -/// background-color: #00f; -/// } -/// -/// @example css - CSS Output -/// button, -/// input[type="button"], -/// input[type="reset"], -/// input[type="submit"] { -/// background-color: #f00; -/// } -/// -/// button:focus, -/// input[type="button"]:focus, -/// input[type="reset"]:focus, -/// input[type="submit"]:focus, -/// button:hover, -/// input[type="button"]:hover, -/// input[type="reset"]:hover, -/// input[type="submit"]:hover { -/// background-color: #0f0; -/// } -/// -/// button:active, -/// input[type="button"]:active, -/// input[type="reset"]:active, -/// input[type="submit"]:active { -/// background-color: #00f; -/// } -/// -/// @require assign-inputs -/// -/// @type List -/// -/// @todo Remove double assigned variables (Lines 59–62) in v5.0.0 - -$buttons-list: 'button', - 'input[type="button"]', - 'input[type="reset"]', - 'input[type="submit"]'; - -$all-buttons: assign-inputs($buttons-list); -$all-buttons-active: assign-inputs($buttons-list, active); -$all-buttons-focus: assign-inputs($buttons-list, focus); -$all-buttons-hover: assign-inputs($buttons-list, hover); - -$all-button-inputs: $all-buttons; -$all-button-inputs-active: $all-buttons-active; -$all-button-inputs-focus: $all-buttons-focus; -$all-button-inputs-hover: $all-buttons-hover; diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_clearfix.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_clearfix.scss deleted file mode 100644 index 11313d66f..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_clearfix.scss +++ /dev/null @@ -1,25 +0,0 @@ -@charset "UTF-8"; - -/// Provides an easy way to include a clearfix for containing floats. -/// -/// @link http://cssmojo.com/latest_new_clearfix_so_far/ -/// -/// @example scss - Usage -/// .element { -/// @include clearfix; -/// } -/// -/// @example css - CSS Output -/// .element::after { -/// clear: both; -/// content: ""; -/// display: table; -/// } - -@mixin clearfix { - &::after { - clear: both; - content: ""; - display: table; - } -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_ellipsis.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_ellipsis.scss deleted file mode 100644 index a367f651c..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_ellipsis.scss +++ /dev/null @@ -1,30 +0,0 @@ -@charset "UTF-8"; - -/// Truncates text and adds an ellipsis to represent overflow. -/// -/// @param {Number} $width [100%] -/// Max-width for the string to respect before being truncated -/// -/// @example scss - Usage -/// .element { -/// @include ellipsis; -/// } -/// -/// @example css - CSS Output -/// .element { -/// display: inline-block; -/// max-width: 100%; -/// overflow: hidden; -/// text-overflow: ellipsis; -/// white-space: nowrap; -/// word-wrap: normal; -/// } - -@mixin ellipsis($width: 100%) { - display: inline-block; - max-width: $width; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - word-wrap: normal; -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_font-stacks.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_font-stacks.scss deleted file mode 100644 index 57128f422..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_font-stacks.scss +++ /dev/null @@ -1,31 +0,0 @@ -@charset "UTF-8"; - -/// Georgia font stack. -/// -/// @type List - -$georgia: "Georgia", "Cambria", "Times New Roman", "Times", serif; - -/// Helvetica font stack. -/// -/// @type List - -$helvetica: "Helvetica Neue", "Helvetica", "Roboto", "Arial", sans-serif; - -/// Lucida Grande font stack. -/// -/// @type List - -$lucida-grande: "Lucida Grande", "Tahoma", "Verdana", "Arial", sans-serif; - -/// Monospace font stack. -/// -/// @type List - -$monospace: "Bitstream Vera Sans Mono", "Consolas", "Courier", monospace; - -/// Verdana font stack. -/// -/// @type List - -$verdana: "Verdana", "Geneva", sans-serif; diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_hide-text.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_hide-text.scss deleted file mode 100644 index 4caf20ed5..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_hide-text.scss +++ /dev/null @@ -1,27 +0,0 @@ -/// Hides the text in an element, commonly used to show an image. Some elements will need block-level styles applied. -/// -/// @link http://zeldman.com/2012/03/01/replacing-the-9999px-hack-new-image-replacement -/// -/// @example scss - Usage -/// .element { -/// @include hide-text; -/// } -/// -/// @example css - CSS Output -/// .element { -/// overflow: hidden; -/// text-indent: 101%; -/// white-space: nowrap; -/// } -/// -/// @todo Remove height argument in v5.0.0 - -@mixin hide-text($height: null) { - overflow: hidden; - text-indent: 101%; - white-space: nowrap; - - @if $height { - @warn "The `hide-text` mixin has changed and no longer requires a height. The height argument will no longer be accepted in v5.0.0"; - } -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_margin.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_margin.scss deleted file mode 100644 index 674f4e5f6..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_margin.scss +++ /dev/null @@ -1,26 +0,0 @@ -@charset "UTF-8"; - -/// Provides a quick method for targeting `margin` on specific sides of a box. Use a `null` value to “skip” a side. -/// -/// @param {Arglist} $vals -/// List of arguments -/// -/// @example scss - Usage -/// .element { -/// @include margin(null 10px 3em 20vh); -/// } -/// -/// @example css - CSS Output -/// .element { -/// margin-bottom: 3em; -/// margin-left: 20vh; -/// margin-right: 10px; -/// } -/// -/// @require {mixin} directional-property -/// -/// @output `margin` - -@mixin margin($vals...) { - @include directional-property(margin, false, $vals...); -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_padding.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_padding.scss deleted file mode 100644 index 40a5f006b..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_padding.scss +++ /dev/null @@ -1,26 +0,0 @@ -@charset "UTF-8"; - -/// Provides a quick method for targeting `padding` on specific sides of a box. Use a `null` value to “skip” a side. -/// -/// @param {Arglist} $vals -/// List of arguments -/// -/// @example scss - Usage -/// .element { -/// @include padding(12vh null 10px 5%); -/// } -/// -/// @example css - CSS Output -/// .element { -/// padding-bottom: 10px; -/// padding-left: 5%; -/// padding-top: 12vh; -/// } -/// -/// @require {mixin} directional-property -/// -/// @output `padding` - -@mixin padding($vals...) { - @include directional-property(padding, false, $vals...); -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_position.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_position.scss deleted file mode 100644 index e460f3ffd..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_position.scss +++ /dev/null @@ -1,48 +0,0 @@ -@charset "UTF-8"; - -/// Provides a quick method for setting an element’s position. Use a `null` value to “skip” a side. -/// -/// @param {Position} $position [relative] -/// A CSS position value -/// -/// @param {Arglist} $coordinates [null null null null] -/// List of values that correspond to the 4-value syntax for the edges of a box -/// -/// @example scss - Usage -/// .element { -/// @include position(absolute, 0 null null 10em); -/// } -/// -/// @example css - CSS Output -/// .element { -/// left: 10em; -/// position: absolute; -/// top: 0; -/// } -/// -/// @require {function} is-length -/// @require {function} unpack - -@mixin position($position: relative, $coordinates: null null null null) { - @if type-of($position) == list { - $coordinates: $position; - $position: relative; - } - - $coordinates: unpack($coordinates); - - $offsets: ( - top: nth($coordinates, 1), - right: nth($coordinates, 2), - bottom: nth($coordinates, 3), - left: nth($coordinates, 4) - ); - - position: $position; - - @each $offset, $value in $offsets { - @if is-length($value) { - #{$offset}: $value; - } - } -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_prefixer.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_prefixer.scss deleted file mode 100644 index 2b6f73138..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_prefixer.scss +++ /dev/null @@ -1,66 +0,0 @@ -@charset "UTF-8"; - -/// A mixin for generating vendor prefixes on non-standardized properties. -/// -/// @param {String} $property -/// Property to prefix -/// -/// @param {*} $value -/// Value to use -/// -/// @param {List} $prefixes -/// Prefixes to define -/// -/// @example scss - Usage -/// .element { -/// @include prefixer(border-radius, 10px, webkit ms spec); -/// } -/// -/// @example css - CSS Output -/// .element { -/// -webkit-border-radius: 10px; -/// -moz-border-radius: 10px; -/// border-radius: 10px; -/// } -/// -/// @require {variable} $prefix-for-webkit -/// @require {variable} $prefix-for-mozilla -/// @require {variable} $prefix-for-microsoft -/// @require {variable} $prefix-for-opera -/// @require {variable} $prefix-for-spec - -@mixin prefixer($property, $value, $prefixes) { - @each $prefix in $prefixes { - @if $prefix == webkit { - @if $prefix-for-webkit { - -webkit-#{$property}: $value; - } - } @else if $prefix == moz { - @if $prefix-for-mozilla { - -moz-#{$property}: $value; - } - } @else if $prefix == ms { - @if $prefix-for-microsoft { - -ms-#{$property}: $value; - } - } @else if $prefix == o { - @if $prefix-for-opera { - -o-#{$property}: $value; - } - } @else if $prefix == spec { - @if $prefix-for-spec { - #{$property}: $value; - } - } @else { - @warn "Unrecognized prefix: #{$prefix}"; - } - } -} - -@mixin disable-prefix-for-all() { - $prefix-for-webkit: false !global; - $prefix-for-mozilla: false !global; - $prefix-for-microsoft: false !global; - $prefix-for-opera: false !global; - $prefix-for-spec: false !global; -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_retina-image.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_retina-image.scss deleted file mode 100644 index 7febbd751..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_retina-image.scss +++ /dev/null @@ -1,25 +0,0 @@ -@mixin retina-image($filename, $background-size, $extension: png, $retina-filename: null, $retina-suffix: _2x, $asset-pipeline: $asset-pipeline) { - @if $asset-pipeline { - background-image: image-url("#{$filename}.#{$extension}"); - } @else { - background-image: url("#{$filename}.#{$extension}"); - } - - @include hidpi { - @if $asset-pipeline { - @if $retina-filename { - background-image: image-url("#{$retina-filename}.#{$extension}"); - } @else { - background-image: image-url("#{$filename}#{$retina-suffix}.#{$extension}"); - } - } @else { - @if $retina-filename { - background-image: url("#{$retina-filename}.#{$extension}"); - } @else { - background-image: url("#{$filename}#{$retina-suffix}.#{$extension}"); - } - } - - background-size: $background-size; - } -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_size.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_size.scss deleted file mode 100644 index a2992a34c..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_size.scss +++ /dev/null @@ -1,51 +0,0 @@ -@charset "UTF-8"; - -/// Sets the `width` and `height` of the element. -/// -/// @param {List} $size -/// A list of at most 2 size values. -/// -/// If there is only a single value in `$size` it is used for both width and height. All units are supported. -/// -/// @example scss - Usage -/// .first-element { -/// @include size(2em); -/// } -/// -/// .second-element { -/// @include size(auto 10em); -/// } -/// -/// @example css - CSS Output -/// .first-element { -/// width: 2em; -/// height: 2em; -/// } -/// -/// .second-element { -/// width: auto; -/// height: 10em; -/// } -/// -/// @todo Refactor in 5.0.0 to use a comma-separated argument - -@mixin size($value) { - $width: nth($value, 1); - $height: $width; - - @if length($value) > 1 { - $height: nth($value, 2); - } - - @if is-size($height) { - height: $height; - } @else { - @warn "`#{$height}` is not a valid length for the `$height` parameter in the `size` mixin."; - } - - @if is-size($width) { - width: $width; - } @else { - @warn "`#{$width}` is not a valid length for the `$width` parameter in the `size` mixin."; - } -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_text-inputs.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_text-inputs.scss deleted file mode 100644 index 1eb7a5451..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_text-inputs.scss +++ /dev/null @@ -1,113 +0,0 @@ -@charset "UTF-8"; - -/// Generates variables for all text-based inputs. Please note that you must use interpolation on the variable: `#{$all-text-inputs}`. -/// -/// @example scss - Usage -/// #{$all-text-inputs} { -/// border: 1px solid #f00; -/// } -/// -/// #{$all-text-inputs-focus}, -/// #{$all-text-inputs-hover} { -/// border: 1px solid #0f0; -/// } -/// -/// #{$all-text-inputs-active} { -/// border: 1px solid #00f; -/// } -/// -/// @example css - CSS Output -/// input[type="color"], -/// input[type="date"], -/// input[type="datetime"], -/// input[type="datetime-local"], -/// input[type="email"], -/// input[type="month"], -/// input[type="number"], -/// input[type="password"], -/// input[type="search"], -/// input[type="tel"], -/// input[type="text"], -/// input[type="time"], -/// input[type="url"], -/// input[type="week"], -/// textarea { -/// border: 1px solid #f00; -/// } -/// -/// input[type="color"]:focus, -/// input[type="date"]:focus, -/// input[type="datetime"]:focus, -/// input[type="datetime-local"]:focus, -/// input[type="email"]:focus, -/// input[type="month"]:focus, -/// input[type="number"]:focus, -/// input[type="password"]:focus, -/// input[type="search"]:focus, -/// input[type="tel"]:focus, -/// input[type="text"]:focus, -/// input[type="time"]:focus, -/// input[type="url"]:focus, -/// input[type="week"]:focus, -/// textarea:focus, -/// input[type="color"]:hover, -/// input[type="date"]:hover, -/// input[type="datetime"]:hover, -/// input[type="datetime-local"]:hover, -/// input[type="email"]:hover, -/// input[type="month"]:hover, -/// input[type="number"]:hover, -/// input[type="password"]:hover, -/// input[type="search"]:hover, -/// input[type="tel"]:hover, -/// input[type="text"]:hover, -/// input[type="time"]:hover, -/// input[type="url"]:hover, -/// input[type="week"]:hover, -/// textarea:hover { -/// border: 1px solid #0f0; -/// } -/// -/// input[type="color"]:active, -/// input[type="date"]:active, -/// input[type="datetime"]:active, -/// input[type="datetime-local"]:active, -/// input[type="email"]:active, -/// input[type="month"]:active, -/// input[type="number"]:active, -/// input[type="password"]:active, -/// input[type="search"]:active, -/// input[type="tel"]:active, -/// input[type="text"]:active, -/// input[type="time"]:active, -/// input[type="url"]:active, -/// input[type="week"]:active, -/// textarea:active { -/// border: 1px solid #00f; -/// } -/// -/// @require assign-inputs -/// -/// @type List - -$text-inputs-list: 'input[type="color"]', - 'input[type="date"]', - 'input[type="datetime"]', - 'input[type="datetime-local"]', - 'input[type="email"]', - 'input[type="month"]', - 'input[type="number"]', - 'input[type="password"]', - 'input[type="search"]', - 'input[type="tel"]', - 'input[type="text"]', - 'input[type="time"]', - 'input[type="url"]', - 'input[type="week"]', - 'input:not([type])', - 'textarea'; - -$all-text-inputs: assign-inputs($text-inputs-list); -$all-text-inputs-active: assign-inputs($text-inputs-list, active); -$all-text-inputs-focus: assign-inputs($text-inputs-list, focus); -$all-text-inputs-hover: assign-inputs($text-inputs-list, hover); diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_timing-functions.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_timing-functions.scss deleted file mode 100644 index 20e5f1d40..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_timing-functions.scss +++ /dev/null @@ -1,34 +0,0 @@ -@charset "UTF-8"; - -/// CSS cubic-bezier timing functions. Timing functions courtesy of jquery.easie (github.com/jaukia/easie) -/// -/// Timing functions are the same as demoed here: http://jqueryui.com/resources/demos/effect/easing.html -/// -/// @type cubic-bezier - -$ease-in-quad: cubic-bezier(0.550, 0.085, 0.680, 0.530); -$ease-in-cubic: cubic-bezier(0.550, 0.055, 0.675, 0.190); -$ease-in-quart: cubic-bezier(0.895, 0.030, 0.685, 0.220); -$ease-in-quint: cubic-bezier(0.755, 0.050, 0.855, 0.060); -$ease-in-sine: cubic-bezier(0.470, 0.000, 0.745, 0.715); -$ease-in-expo: cubic-bezier(0.950, 0.050, 0.795, 0.035); -$ease-in-circ: cubic-bezier(0.600, 0.040, 0.980, 0.335); -$ease-in-back: cubic-bezier(0.600, -0.280, 0.735, 0.045); - -$ease-out-quad: cubic-bezier(0.250, 0.460, 0.450, 0.940); -$ease-out-cubic: cubic-bezier(0.215, 0.610, 0.355, 1.000); -$ease-out-quart: cubic-bezier(0.165, 0.840, 0.440, 1.000); -$ease-out-quint: cubic-bezier(0.230, 1.000, 0.320, 1.000); -$ease-out-sine: cubic-bezier(0.390, 0.575, 0.565, 1.000); -$ease-out-expo: cubic-bezier(0.190, 1.000, 0.220, 1.000); -$ease-out-circ: cubic-bezier(0.075, 0.820, 0.165, 1.000); -$ease-out-back: cubic-bezier(0.175, 0.885, 0.320, 1.275); - -$ease-in-out-quad: cubic-bezier(0.455, 0.030, 0.515, 0.955); -$ease-in-out-cubic: cubic-bezier(0.645, 0.045, 0.355, 1.000); -$ease-in-out-quart: cubic-bezier(0.770, 0.000, 0.175, 1.000); -$ease-in-out-quint: cubic-bezier(0.860, 0.000, 0.070, 1.000); -$ease-in-out-sine: cubic-bezier(0.445, 0.050, 0.550, 0.950); -$ease-in-out-expo: cubic-bezier(1.000, 0.000, 0.000, 1.000); -$ease-in-out-circ: cubic-bezier(0.785, 0.135, 0.150, 0.860); -$ease-in-out-back: cubic-bezier(0.680, -0.550, 0.265, 1.550); diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_triangle.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_triangle.scss deleted file mode 100644 index 8a1ed9cd0..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_triangle.scss +++ /dev/null @@ -1,63 +0,0 @@ -@mixin triangle($size, $color, $direction) { - $width: nth($size, 1); - $height: nth($size, length($size)); - $foreground-color: nth($color, 1); - $background-color: if(length($color) == 2, nth($color, 2), transparent); - height: 0; - width: 0; - - @if ($direction == up) or ($direction == down) or ($direction == right) or ($direction == left) { - $width: $width / 2; - $height: if(length($size) > 1, $height, $height/2); - - @if $direction == up { - border-bottom: $height solid $foreground-color; - border-left: $width solid $background-color; - border-right: $width solid $background-color; - } @else if $direction == right { - border-bottom: $width solid $background-color; - border-left: $height solid $foreground-color; - border-top: $width solid $background-color; - } @else if $direction == down { - border-left: $width solid $background-color; - border-right: $width solid $background-color; - border-top: $height solid $foreground-color; - } @else if $direction == left { - border-bottom: $width solid $background-color; - border-right: $height solid $foreground-color; - border-top: $width solid $background-color; - } - } @else if ($direction == up-right) or ($direction == up-left) { - border-top: $height solid $foreground-color; - - @if $direction == up-right { - border-left: $width solid $background-color; - } @else if $direction == up-left { - border-right: $width solid $background-color; - } - } @else if ($direction == down-right) or ($direction == down-left) { - border-bottom: $height solid $foreground-color; - - @if $direction == down-right { - border-left: $width solid $background-color; - } @else if $direction == down-left { - border-right: $width solid $background-color; - } - } @else if ($direction == inset-up) { - border-color: $background-color $background-color $foreground-color; - border-style: solid; - border-width: $height $width; - } @else if ($direction == inset-down) { - border-color: $foreground-color $background-color $background-color; - border-style: solid; - border-width: $height $width; - } @else if ($direction == inset-right) { - border-color: $background-color $background-color $background-color $foreground-color; - border-style: solid; - border-width: $width $height; - } @else if ($direction == inset-left) { - border-color: $background-color $foreground-color $background-color $background-color; - border-style: solid; - border-width: $width $height; - } -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_word-wrap.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_word-wrap.scss deleted file mode 100644 index 64856a925..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/addons/_word-wrap.scss +++ /dev/null @@ -1,29 +0,0 @@ -@charset "UTF-8"; - -/// Provides an easy way to change the `word-wrap` property. -/// -/// @param {String} $wrap [break-word] -/// Value for the `word-break` property. -/// -/// @example scss - Usage -/// .wrapper { -/// @include word-wrap(break-word); -/// } -/// -/// @example css - CSS Output -/// .wrapper { -/// overflow-wrap: break-word; -/// word-break: break-all; -/// word-wrap: break-word; -/// } - -@mixin word-wrap($wrap: break-word) { - overflow-wrap: $wrap; - word-wrap: $wrap; - - @if $wrap == break-word { - word-break: break-all; - } @else { - word-break: $wrap; - } -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/css3/_animation.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/css3/_animation.scss deleted file mode 100644 index aac675f5a..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/css3/_animation.scss +++ /dev/null @@ -1,43 +0,0 @@ -// http://www.w3.org/TR/css3-animations/#the-animation-name-property- -// Each of these mixins support comma separated lists of values, which allows different transitions for individual properties to be described in a single style rule. Each value in the list corresponds to the value at that same position in the other properties. - -@mixin animation($animations...) { - @include prefixer(animation, $animations, webkit moz spec); -} - -@mixin animation-name($names...) { - @include prefixer(animation-name, $names, webkit moz spec); -} - -@mixin animation-duration($times...) { - @include prefixer(animation-duration, $times, webkit moz spec); -} - -@mixin animation-timing-function($motions...) { - // ease | linear | ease-in | ease-out | ease-in-out - @include prefixer(animation-timing-function, $motions, webkit moz spec); -} - -@mixin animation-iteration-count($values...) { - // infinite | - @include prefixer(animation-iteration-count, $values, webkit moz spec); -} - -@mixin animation-direction($directions...) { - // normal | alternate - @include prefixer(animation-direction, $directions, webkit moz spec); -} - -@mixin animation-play-state($states...) { - // running | paused - @include prefixer(animation-play-state, $states, webkit moz spec); -} - -@mixin animation-delay($times...) { - @include prefixer(animation-delay, $times, webkit moz spec); -} - -@mixin animation-fill-mode($modes...) { - // none | forwards | backwards | both - @include prefixer(animation-fill-mode, $modes, webkit moz spec); -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/css3/_appearance.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/css3/_appearance.scss deleted file mode 100644 index abddc0204..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/css3/_appearance.scss +++ /dev/null @@ -1,3 +0,0 @@ -@mixin appearance($value) { - @include prefixer(appearance, $value, webkit moz ms o spec); -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/css3/_backface-visibility.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/css3/_backface-visibility.scss deleted file mode 100644 index fc68e2dd0..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/css3/_backface-visibility.scss +++ /dev/null @@ -1,3 +0,0 @@ -@mixin backface-visibility($visibility) { - @include prefixer(backface-visibility, $visibility, webkit spec); -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/css3/_background-image.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/css3/_background-image.scss deleted file mode 100644 index 6ed19ab58..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/css3/_background-image.scss +++ /dev/null @@ -1,42 +0,0 @@ -//************************************************************************// -// Background-image property for adding multiple background images with -// gradients, or for stringing multiple gradients together. -//************************************************************************// - -@mixin background-image($images...) { - $webkit-images: (); - $spec-images: (); - - @each $image in $images { - $webkit-image: (); - $spec-image: (); - - @if (type-of($image) == string) { - $url-str: str-slice($image, 1, 3); - $gradient-type: str-slice($image, 1, 6); - - @if $url-str == "url" { - $webkit-image: $image; - $spec-image: $image; - } - - @else if $gradient-type == "linear" { - $gradients: _linear-gradient-parser($image); - $webkit-image: map-get($gradients, webkit-image); - $spec-image: map-get($gradients, spec-image); - } - - @else if $gradient-type == "radial" { - $gradients: _radial-gradient-parser($image); - $webkit-image: map-get($gradients, webkit-image); - $spec-image: map-get($gradients, spec-image); - } - } - - $webkit-images: append($webkit-images, $webkit-image, comma); - $spec-images: append($spec-images, $spec-image, comma); - } - - background-image: $webkit-images; - background-image: $spec-images; -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/css3/_background.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/css3/_background.scss deleted file mode 100644 index 019db0ed3..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/css3/_background.scss +++ /dev/null @@ -1,55 +0,0 @@ -//************************************************************************// -// Background property for adding multiple backgrounds using shorthand -// notation. -//************************************************************************// - -@mixin background($backgrounds...) { - $webkit-backgrounds: (); - $spec-backgrounds: (); - - @each $background in $backgrounds { - $webkit-background: (); - $spec-background: (); - $background-type: type-of($background); - - @if $background-type == string or $background-type == list { - $background-str: if($background-type == list, nth($background, 1), $background); - - $url-str: str-slice($background-str, 1, 3); - $gradient-type: str-slice($background-str, 1, 6); - - @if $url-str == "url" { - $webkit-background: $background; - $spec-background: $background; - } - - @else if $gradient-type == "linear" { - $gradients: _linear-gradient-parser("#{$background}"); - $webkit-background: map-get($gradients, webkit-image); - $spec-background: map-get($gradients, spec-image); - } - - @else if $gradient-type == "radial" { - $gradients: _radial-gradient-parser("#{$background}"); - $webkit-background: map-get($gradients, webkit-image); - $spec-background: map-get($gradients, spec-image); - } - - @else { - $webkit-background: $background; - $spec-background: $background; - } - } - - @else { - $webkit-background: $background; - $spec-background: $background; - } - - $webkit-backgrounds: append($webkit-backgrounds, $webkit-background, comma); - $spec-backgrounds: append($spec-backgrounds, $spec-background, comma); - } - - background: $webkit-backgrounds; - background: $spec-backgrounds; -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/css3/_border-image.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/css3/_border-image.scss deleted file mode 100644 index cf568ce6d..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/css3/_border-image.scss +++ /dev/null @@ -1,59 +0,0 @@ -@mixin border-image($borders...) { - $webkit-borders: (); - $spec-borders: (); - - @each $border in $borders { - $webkit-border: (); - $spec-border: (); - $border-type: type-of($border); - - @if $border-type == string or list { - $border-str: if($border-type == list, nth($border, 1), $border); - - $url-str: str-slice($border-str, 1, 3); - $gradient-type: str-slice($border-str, 1, 6); - - @if $url-str == "url" { - $webkit-border: $border; - $spec-border: $border; - } - - @else if $gradient-type == "linear" { - $gradients: _linear-gradient-parser("#{$border}"); - $webkit-border: map-get($gradients, webkit-image); - $spec-border: map-get($gradients, spec-image); - } - - @else if $gradient-type == "radial" { - $gradients: _radial-gradient-parser("#{$border}"); - $webkit-border: map-get($gradients, webkit-image); - $spec-border: map-get($gradients, spec-image); - } - - @else { - $webkit-border: $border; - $spec-border: $border; - } - } - - @else { - $webkit-border: $border; - $spec-border: $border; - } - - $webkit-borders: append($webkit-borders, $webkit-border, comma); - $spec-borders: append($spec-borders, $spec-border, comma); - } - - -webkit-border-image: $webkit-borders; - border-image: $spec-borders; - border-style: solid; -} - -//Examples: -// @include border-image(url("image.png")); -// @include border-image(url("image.png") 20 stretch); -// @include border-image(linear-gradient(45deg, orange, yellow)); -// @include border-image(linear-gradient(45deg, orange, yellow) stretch); -// @include border-image(linear-gradient(45deg, orange, yellow) 20 30 40 50 stretch round); -// @include border-image(radial-gradient(top, cover, orange, yellow, orange)); diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/css3/_calc.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/css3/_calc.scss deleted file mode 100644 index 0bfc738dd..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/css3/_calc.scss +++ /dev/null @@ -1,4 +0,0 @@ -@mixin calc($property, $value) { - #{$property}: -webkit-calc(#{$value}); - #{$property}: calc(#{$value}); -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/css3/_columns.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/css3/_columns.scss deleted file mode 100644 index 96117670c..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/css3/_columns.scss +++ /dev/null @@ -1,47 +0,0 @@ -@mixin columns($arg: auto) { - // || - @include prefixer(columns, $arg, webkit moz spec); -} - -@mixin column-count($int: auto) { - // auto || integer - @include prefixer(column-count, $int, webkit moz spec); -} - -@mixin column-gap($length: normal) { - // normal || length - @include prefixer(column-gap, $length, webkit moz spec); -} - -@mixin column-fill($arg: auto) { - // auto || length - @include prefixer(column-fill, $arg, webkit moz spec); -} - -@mixin column-rule($arg) { - // || || - @include prefixer(column-rule, $arg, webkit moz spec); -} - -@mixin column-rule-color($color) { - @include prefixer(column-rule-color, $color, webkit moz spec); -} - -@mixin column-rule-style($style: none) { - // none | hidden | dashed | dotted | double | groove | inset | inset | outset | ridge | solid - @include prefixer(column-rule-style, $style, webkit moz spec); -} - -@mixin column-rule-width ($width: none) { - @include prefixer(column-rule-width, $width, webkit moz spec); -} - -@mixin column-span($arg: none) { - // none || all - @include prefixer(column-span, $arg, webkit moz spec); -} - -@mixin column-width($length: auto) { - // auto || length - @include prefixer(column-width, $length, webkit moz spec); -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/css3/_filter.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/css3/_filter.scss deleted file mode 100644 index b8f8ffb0e..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/css3/_filter.scss +++ /dev/null @@ -1,4 +0,0 @@ -@mixin filter($function: none) { - // [ - @include prefixer(perspective, $depth, webkit moz spec); -} - -@mixin perspective-origin($value: 50% 50%) { - @include prefixer(perspective-origin, $value, webkit moz spec); -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/css3/_placeholder.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/css3/_placeholder.scss deleted file mode 100644 index 5682fd097..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/css3/_placeholder.scss +++ /dev/null @@ -1,8 +0,0 @@ -@mixin placeholder { - $placeholders: ":-webkit-input" ":-moz" "-moz" "-ms-input"; - @each $placeholder in $placeholders { - &:#{$placeholder}-placeholder { - @content; - } - } -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/css3/_radial-gradient.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/css3/_radial-gradient.scss deleted file mode 100644 index 8da076e28..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/css3/_radial-gradient.scss +++ /dev/null @@ -1,39 +0,0 @@ -// Requires Sass 3.1+ -@mixin radial-gradient($g1, $g2, - $g3: null, $g4: null, - $g5: null, $g6: null, - $g7: null, $g8: null, - $g9: null, $g10: null, - $pos: null, - $shape-size: null, - $fallback: null) { - - $data: _radial-arg-parser($g1, $g2, $pos, $shape-size); - $g1: nth($data, 1); - $g2: nth($data, 2); - $pos: nth($data, 3); - $shape-size: nth($data, 4); - - $full: $g1, $g2, $g3, $g4, $g5, $g6, $g7, $g8, $g9, $g10; - - // Strip deprecated cover/contain for spec - $shape-size-spec: _shape-size-stripper($shape-size); - - // Set $g1 as the default fallback color - $first-color: nth($full, 1); - $fallback-color: nth($first-color, 1); - - @if (type-of($fallback) == color) or ($fallback == "transparent") { - $fallback-color: $fallback; - } - - // Add Commas and spaces - $shape-size: if($shape-size, "#{$shape-size}, ", null); - $pos: if($pos, "#{$pos}, ", null); - $pos-spec: if($pos, "at #{$pos}", null); - $shape-size-spec: if(($shape-size-spec != " ") and ($pos == null), "#{$shape-size-spec}, ", "#{$shape-size-spec} "); - - background-color: $fallback-color; - background-image: -webkit-radial-gradient(unquote(#{$pos}#{$shape-size}#{$full})); - background-image: unquote("radial-gradient(#{$shape-size-spec}#{$pos-spec}#{$full})"); -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/css3/_selection.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/css3/_selection.scss deleted file mode 100644 index 23303ab55..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/css3/_selection.scss +++ /dev/null @@ -1,42 +0,0 @@ -@charset "UTF-8"; - -/// Outputs the spec and prefixed versions of the `::selection` pseudo-element. -/// -/// @param {Bool} $current-selector [false] -/// If set to `true`, it takes the current element into consideration. -/// -/// @example scss - Usage -/// .element { -/// @include selection(true) { -/// background-color: #ffbb52; -/// } -/// } -/// -/// @example css - CSS Output -/// .element::-moz-selection { -/// background-color: #ffbb52; -/// } -/// -/// .element::selection { -/// background-color: #ffbb52; -/// } - -@mixin selection($current-selector: false) { - @if $current-selector { - &::-moz-selection { - @content; - } - - &::selection { - @content; - } - } @else { - ::-moz-selection { - @content; - } - - ::selection { - @content; - } - } -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/css3/_text-decoration.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/css3/_text-decoration.scss deleted file mode 100644 index 9222746ce..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/css3/_text-decoration.scss +++ /dev/null @@ -1,19 +0,0 @@ -@mixin text-decoration($value) { -// || || - @include prefixer(text-decoration, $value, moz); -} - -@mixin text-decoration-line($line: none) { -// none || underline || overline || line-through - @include prefixer(text-decoration-line, $line, moz); -} - -@mixin text-decoration-style($style: solid) { -// solid || double || dotted || dashed || wavy - @include prefixer(text-decoration-style, $style, moz webkit); -} - -@mixin text-decoration-color($color: currentColor) { -// currentColor || - @include prefixer(text-decoration-color, $color, moz); -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/css3/_transform.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/css3/_transform.scss deleted file mode 100644 index 8ee6509ff..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/css3/_transform.scss +++ /dev/null @@ -1,15 +0,0 @@ -@mixin transform($property: none) { - // none | - @include prefixer(transform, $property, webkit moz ms o spec); -} - -@mixin transform-origin($axes: 50%) { - // x-axis - left | center | right | length | % - // y-axis - top | center | bottom | length | % - // z-axis - length - @include prefixer(transform-origin, $axes, webkit moz ms o spec); -} - -@mixin transform-style($style: flat) { - @include prefixer(transform-style, $style, webkit moz ms o spec); -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/css3/_transition.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/css3/_transition.scss deleted file mode 100644 index 3c785ed52..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/css3/_transition.scss +++ /dev/null @@ -1,71 +0,0 @@ -// Shorthand mixin. Supports multiple parentheses-deliminated values for each variable. -// Example: @include transition (all 2s ease-in-out); -// @include transition (opacity 1s ease-in 2s, width 2s ease-out); -// @include transition-property (transform, opacity); - -@mixin transition($properties...) { - // Fix for vendor-prefix transform property - $needs-prefixes: false; - $webkit: (); - $moz: (); - $spec: (); - - // Create lists for vendor-prefixed transform - @each $list in $properties { - @if nth($list, 1) == "transform" { - $needs-prefixes: true; - $list1: -webkit-transform; - $list2: -moz-transform; - $list3: (); - - @each $var in $list { - $list3: join($list3, $var); - - @if $var != "transform" { - $list1: join($list1, $var); - $list2: join($list2, $var); - } - } - - $webkit: append($webkit, $list1); - $moz: append($moz, $list2); - $spec: append($spec, $list3); - } @else { - $webkit: append($webkit, $list, comma); - $moz: append($moz, $list, comma); - $spec: append($spec, $list, comma); - } - } - - @if $needs-prefixes { - -webkit-transition: $webkit; - -moz-transition: $moz; - transition: $spec; - } @else { - @if length($properties) >= 1 { - @include prefixer(transition, $properties, webkit moz spec); - } @else { - $properties: all 0.15s ease-out 0s; - @include prefixer(transition, $properties, webkit moz spec); - } - } -} - -@mixin transition-property($properties...) { - -webkit-transition-property: transition-property-names($properties, "webkit"); - -moz-transition-property: transition-property-names($properties, "moz"); - transition-property: transition-property-names($properties, false); -} - -@mixin transition-duration($times...) { - @include prefixer(transition-duration, $times, webkit moz spec); -} - -@mixin transition-timing-function($motions...) { - // ease | linear | ease-in | ease-out | ease-in-out | cubic-bezier() - @include prefixer(transition-timing-function, $motions, webkit moz spec); -} - -@mixin transition-delay($times...) { - @include prefixer(transition-delay, $times, webkit moz spec); -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/css3/_user-select.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/css3/_user-select.scss deleted file mode 100644 index d4e555100..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/css3/_user-select.scss +++ /dev/null @@ -1,3 +0,0 @@ -@mixin user-select($value: none) { - @include prefixer(user-select, $value, webkit moz ms spec); -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/functions/_assign-inputs.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/functions/_assign-inputs.scss deleted file mode 100644 index f8aba9678..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/functions/_assign-inputs.scss +++ /dev/null @@ -1,11 +0,0 @@ -@function assign-inputs($inputs, $pseudo: null) { - $list: (); - - @each $input in $inputs { - $input: unquote($input); - $input: if($pseudo, $input + ":" + $pseudo, $input); - $list: append($list, $input, comma); - } - - @return $list; -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/functions/_contains-falsy.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/functions/_contains-falsy.scss deleted file mode 100644 index c096fdb92..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/functions/_contains-falsy.scss +++ /dev/null @@ -1,20 +0,0 @@ -@charset "UTF-8"; - -/// Checks if a list does not contains a value. -/// -/// @access private -/// -/// @param {List} $list -/// The list to check against. -/// -/// @return {Bool} - -@function contains-falsy($list) { - @each $item in $list { - @if not $item { - @return true; - } - } - - @return false; -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/functions/_contains.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/functions/_contains.scss deleted file mode 100644 index 3dec27db8..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/functions/_contains.scss +++ /dev/null @@ -1,26 +0,0 @@ -@charset "UTF-8"; - -/// Checks if a list contains a value(s). -/// -/// @access private -/// -/// @param {List} $list -/// The list to check against. -/// -/// @param {List} $values -/// A single value or list of values to check for. -/// -/// @example scss - Usage -/// contains($list, $value) -/// -/// @return {Bool} - -@function contains($list, $values...) { - @each $value in $values { - @if type-of(index($list, $value)) != "number" { - @return false; - } - } - - @return true; -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/functions/_is-length.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/functions/_is-length.scss deleted file mode 100644 index 5826e789b..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/functions/_is-length.scss +++ /dev/null @@ -1,11 +0,0 @@ -@charset "UTF-8"; - -/// Checks for a valid CSS length. -/// -/// @param {String} $value - -@function is-length($value) { - @return type-of($value) != "null" and (str-slice($value + "", 1, 4) == "calc" - or index(auto inherit initial 0, $value) - or (type-of($value) == "number" and not(unitless($value)))); -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/functions/_is-light.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/functions/_is-light.scss deleted file mode 100644 index 92d90ac3c..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/functions/_is-light.scss +++ /dev/null @@ -1,21 +0,0 @@ -@charset "UTF-8"; - -/// Programatically determines whether a color is light or dark. -/// -/// @link http://robots.thoughtbot.com/closer-look-color-lightness -/// -/// @param {Color (Hex)} $color -/// -/// @example scss - Usage -/// is-light($color) -/// -/// @return {Bool} - -@function is-light($hex-color) { - $-local-red: red(rgba($hex-color, 1)); - $-local-green: green(rgba($hex-color, 1)); - $-local-blue: blue(rgba($hex-color, 1)); - $-local-lightness: ($-local-red * 0.2126 + $-local-green * 0.7152 + $-local-blue * 0.0722) / 255; - - @return $-local-lightness > 0.6; -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/functions/_is-number.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/functions/_is-number.scss deleted file mode 100644 index a64e0bf21..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/functions/_is-number.scss +++ /dev/null @@ -1,11 +0,0 @@ -@charset "UTF-8"; - -/// Checks for a valid number. -/// -/// @param {Number} $value -/// -/// @require {function} contains - -@function is-number($value) { - @return contains("0" "1" "2" "3" "4" "5" "6" "7" "8" "9" 0 1 2 3 4 5 6 7 8 9, $value); -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/functions/_is-size.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/functions/_is-size.scss deleted file mode 100644 index 661789ab4..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/functions/_is-size.scss +++ /dev/null @@ -1,13 +0,0 @@ -@charset "UTF-8"; - -/// Checks for a valid CSS size. -/// -/// @param {String} $value -/// -/// @require {function} contains -/// @require {function} is-length - -@function is-size($value) { - @return is-length($value) - or contains("fill" "fit-content" "min-content" "max-content", $value); -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/functions/_modular-scale.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/functions/_modular-scale.scss deleted file mode 100644 index 20fa38812..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/functions/_modular-scale.scss +++ /dev/null @@ -1,69 +0,0 @@ -// Scaling Variables -$golden: 1.618; -$minor-second: 1.067; -$major-second: 1.125; -$minor-third: 1.2; -$major-third: 1.25; -$perfect-fourth: 1.333; -$augmented-fourth: 1.414; -$perfect-fifth: 1.5; -$minor-sixth: 1.6; -$major-sixth: 1.667; -$minor-seventh: 1.778; -$major-seventh: 1.875; -$octave: 2; -$major-tenth: 2.5; -$major-eleventh: 2.667; -$major-twelfth: 3; -$double-octave: 4; - -$modular-scale-ratio: $perfect-fourth !default; -$modular-scale-base: em($em-base) !default; - -@function modular-scale($increment, $value: $modular-scale-base, $ratio: $modular-scale-ratio) { - $v1: nth($value, 1); - $v2: nth($value, length($value)); - $value: $v1; - - // scale $v2 to just above $v1 - @while $v2 > $v1 { - $v2: ($v2 / $ratio); // will be off-by-1 - } - @while $v2 < $v1 { - $v2: ($v2 * $ratio); // will fix off-by-1 - } - - // check AFTER scaling $v2 to prevent double-counting corner-case - $double-stranded: $v2 > $v1; - - @if $increment > 0 { - @for $i from 1 through $increment { - @if $double-stranded and ($v1 * $ratio) > $v2 { - $value: $v2; - $v2: ($v2 * $ratio); - } @else { - $v1: ($v1 * $ratio); - $value: $v1; - } - } - } - - @if $increment < 0 { - // adjust $v2 to just below $v1 - @if $double-stranded { - $v2: ($v2 / $ratio); - } - - @for $i from $increment through -1 { - @if $double-stranded and ($v1 / $ratio) < $v2 { - $value: $v2; - $v2: ($v2 / $ratio); - } @else { - $v1: ($v1 / $ratio); - $value: $v1; - } - } - } - - @return $value; -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/functions/_px-to-em.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/functions/_px-to-em.scss deleted file mode 100644 index ae81a44ad..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/functions/_px-to-em.scss +++ /dev/null @@ -1,13 +0,0 @@ -// Convert pixels to ems -// eg. for a relational value of 12px write em(12) when the parent is 16px -// if the parent is another value say 24px write em(12, 24) - -@function em($pxval, $base: $em-base) { - @if not unitless($pxval) { - $pxval: strip-units($pxval); - } - @if not unitless($base) { - $base: strip-units($base); - } - @return ($pxval / $base) * 1em; -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/functions/_px-to-rem.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/functions/_px-to-rem.scss deleted file mode 100644 index 0ac941e76..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/functions/_px-to-rem.scss +++ /dev/null @@ -1,15 +0,0 @@ -// Convert pixels to rems -// eg. for a relational value of 12px write rem(12) -// Assumes $em-base is the font-size of - -@function rem($pxval) { - @if not unitless($pxval) { - $pxval: strip-units($pxval); - } - - $base: $em-base; - @if not unitless($base) { - $base: strip-units($base); - } - @return ($pxval / $base) * 1rem; -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/functions/_shade.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/functions/_shade.scss deleted file mode 100644 index 8aaf2c6d2..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/functions/_shade.scss +++ /dev/null @@ -1,24 +0,0 @@ -@charset "UTF-8"; - -/// Mixes a color with black. -/// -/// @param {Color} $color -/// -/// @param {Number (Percentage)} $percent -/// The amount of black to be mixed in. -/// -/// @example scss - Usage -/// .element { -/// background-color: shade(#ffbb52, 60%); -/// } -/// -/// @example css - CSS Output -/// .element { -/// background-color: #664a20; -/// } -/// -/// @return {Color} - -@function shade($color, $percent) { - @return mix(#000, $color, $percent); -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/functions/_strip-units.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/functions/_strip-units.scss deleted file mode 100644 index 6c5f3e810..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/functions/_strip-units.scss +++ /dev/null @@ -1,17 +0,0 @@ -@charset "UTF-8"; - -/// Strips the unit from a number. -/// -/// @param {Number (With Unit)} $value -/// -/// @example scss - Usage -/// $dimension: strip-units(10em); -/// -/// @example css - CSS Output -/// $dimension: 10; -/// -/// @return {Number (Unitless)} - -@function strip-units($value) { - @return ($value / ($value * 0 + 1)); -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/functions/_tint.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/functions/_tint.scss deleted file mode 100644 index 2e3381488..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/functions/_tint.scss +++ /dev/null @@ -1,24 +0,0 @@ -@charset "UTF-8"; - -/// Mixes a color with white. -/// -/// @param {Color} $color -/// -/// @param {Number (Percentage)} $percent -/// The amount of white to be mixed in. -/// -/// @example scss - Usage -/// .element { -/// background-color: tint(#6ecaa6, 40%); -/// } -/// -/// @example css - CSS Output -/// .element { -/// background-color: #a8dfc9; -/// } -/// -/// @return {Color} - -@function tint($color, $percent) { - @return mix(#fff, $color, $percent); -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/functions/_transition-property-name.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/functions/_transition-property-name.scss deleted file mode 100644 index 18348b93a..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/functions/_transition-property-name.scss +++ /dev/null @@ -1,22 +0,0 @@ -// Return vendor-prefixed property names if appropriate -// Example: transition-property-names((transform, color, background), moz) -> -moz-transform, color, background -//************************************************************************// -@function transition-property-names($props, $vendor: false) { - $new-props: (); - - @each $prop in $props { - $new-props: append($new-props, transition-property-name($prop, $vendor), comma); - } - - @return $new-props; -} - -@function transition-property-name($prop, $vendor: false) { - // put other properties that need to be prefixed here aswell - @if $vendor and $prop == transform { - @return unquote('-'+$vendor+'-'+$prop); - } - @else { - @return $prop; - } -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/functions/_unpack.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/functions/_unpack.scss deleted file mode 100644 index 4367935d5..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/functions/_unpack.scss +++ /dev/null @@ -1,27 +0,0 @@ -@charset "UTF-8"; - -/// Converts shorthand to the 4-value syntax. -/// -/// @param {List} $shorthand -/// -/// @example scss - Usage -/// .element { -/// margin: unpack(1em 2em); -/// } -/// -/// @example css - CSS Output -/// .element { -/// margin: 1em 2em 1em 2em; -/// } - -@function unpack($shorthand) { - @if length($shorthand) == 1 { - @return nth($shorthand, 1) nth($shorthand, 1) nth($shorthand, 1) nth($shorthand, 1); - } @else if length($shorthand) == 2 { - @return nth($shorthand, 1) nth($shorthand, 2) nth($shorthand, 1) nth($shorthand, 2); - } @else if length($shorthand) == 3 { - @return nth($shorthand, 1) nth($shorthand, 2) nth($shorthand, 3) nth($shorthand, 2); - } @else { - @return $shorthand; - } -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/helpers/_convert-units.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/helpers/_convert-units.scss deleted file mode 100644 index e0a65a05c..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/helpers/_convert-units.scss +++ /dev/null @@ -1,21 +0,0 @@ -//************************************************************************// -// Helper function for str-to-num fn. -// Source: http://sassmeister.com/gist/9647408 -//************************************************************************// -@function _convert-units($number, $unit) { - $strings: "px", "cm", "mm", "%", "ch", "pica", "in", "em", "rem", "pt", "pc", "ex", "vw", "vh", "vmin", "vmax", "deg", "rad", "grad", "turn"; - $units: 1px, 1cm, 1mm, 1%, 1ch, 1pica, 1in, 1em, 1rem, 1pt, 1pc, 1ex, 1vw, 1vh, 1vmin, 1vmax, 1deg, 1rad, 1grad, 1turn; - $index: index($strings, $unit); - - @if not $index { - @warn "Unknown unit `#{$unit}`."; - @return false; - } - - @if type-of($number) != "number" { - @warn "`#{$number} is not a number`"; - @return false; - } - - @return $number * nth($units, $index); -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/helpers/_directional-values.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/helpers/_directional-values.scss deleted file mode 100644 index 6ee538db4..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/helpers/_directional-values.scss +++ /dev/null @@ -1,96 +0,0 @@ -@charset "UTF-8"; - -/// Directional-property mixins are shorthands for writing properties like the following -/// -/// @ignore You can also use `false` instead of `null`. -/// -/// @param {List} $vals -/// List of directional values -/// -/// @example scss - Usage -/// .element { -/// @include border-style(dotted null); -/// @include margin(null 0 10px); -/// } -/// -/// @example css - CSS Output -/// .element { -/// border-bottom-style: dotted; -/// border-top-style: dotted; -/// margin-bottom: 10px; -/// margin-left: 0; -/// margin-right: 0; -/// } -/// -/// @require {function} contains-falsy -/// -/// @return {List} - -@function collapse-directionals($vals) { - $output: null; - - $a: nth($vals, 1); - $b: if(length($vals) < 2, $a, nth($vals, 2)); - $c: if(length($vals) < 3, $a, nth($vals, 3)); - $d: if(length($vals) < 2, $a, nth($vals, if(length($vals) < 4, 2, 4))); - - @if $a == 0 { $a: 0; } - @if $b == 0 { $b: 0; } - @if $c == 0 { $c: 0; } - @if $d == 0 { $d: 0; } - - @if $a == $b and $a == $c and $a == $d { $output: $a; } - @else if $a == $c and $b == $d { $output: $a $b; } - @else if $b == $d { $output: $a $b $c; } - @else { $output: $a $b $c $d; } - - @return $output; -} - -/// Output directional properties, for instance `margin`. -/// -/// @access private -/// -/// @param {String} $pre -/// Prefix to use -/// @param {String} $suf -/// Suffix to use -/// @param {List} $vals -/// List of values -/// -/// @require {function} collapse-directionals -/// @require {function} contains-falsy - -@mixin directional-property($pre, $suf, $vals) { - // Property Names - $top: $pre + "-top" + if($suf, "-#{$suf}", ""); - $bottom: $pre + "-bottom" + if($suf, "-#{$suf}", ""); - $left: $pre + "-left" + if($suf, "-#{$suf}", ""); - $right: $pre + "-right" + if($suf, "-#{$suf}", ""); - $all: $pre + if($suf, "-#{$suf}", ""); - - $vals: collapse-directionals($vals); - - @if contains-falsy($vals) { - @if nth($vals, 1) { #{$top}: nth($vals, 1); } - - @if length($vals) == 1 { - @if nth($vals, 1) { #{$right}: nth($vals, 1); } - } @else { - @if nth($vals, 2) { #{$right}: nth($vals, 2); } - } - - @if length($vals) == 2 { - @if nth($vals, 1) { #{$bottom}: nth($vals, 1); } - @if nth($vals, 2) { #{$left}: nth($vals, 2); } - } @else if length($vals) == 3 { - @if nth($vals, 3) { #{$bottom}: nth($vals, 3); } - @if nth($vals, 2) { #{$left}: nth($vals, 2); } - } @else if length($vals) == 4 { - @if nth($vals, 3) { #{$bottom}: nth($vals, 3); } - @if nth($vals, 4) { #{$left}: nth($vals, 4); } - } - } @else { - #{$all}: $vals; - } -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/helpers/_font-source-declaration.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/helpers/_font-source-declaration.scss deleted file mode 100644 index 7f17586c9..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/helpers/_font-source-declaration.scss +++ /dev/null @@ -1,43 +0,0 @@ -// Used for creating the source string for fonts using @font-face -// Reference: http://goo.gl/Ru1bKP - -@function font-url-prefixer($asset-pipeline) { - @if $asset-pipeline == true { - @return font-url; - } @else { - @return url; - } -} - -@function font-source-declaration( - $font-family, - $file-path, - $asset-pipeline, - $file-formats, - $font-url) { - - $src: (); - - $formats-map: ( - eot: "#{$file-path}.eot?#iefix" format("embedded-opentype"), - woff2: "#{$file-path}.woff2" format("woff2"), - woff: "#{$file-path}.woff" format("woff"), - ttf: "#{$file-path}.ttf" format("truetype"), - svg: "#{$file-path}.svg##{$font-family}" format("svg") - ); - - @each $key, $values in $formats-map { - @if contains($file-formats, $key) { - $file-path: nth($values, 1); - $font-format: nth($values, 2); - - @if $asset-pipeline == true { - $src: append($src, font-url($file-path) $font-format, comma); - } @else { - $src: append($src, url($file-path) $font-format, comma); - } - } - } - - @return $src; -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/helpers/_gradient-positions-parser.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/helpers/_gradient-positions-parser.scss deleted file mode 100644 index 07d30b6cf..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/helpers/_gradient-positions-parser.scss +++ /dev/null @@ -1,13 +0,0 @@ -@function _gradient-positions-parser($gradient-type, $gradient-positions) { - @if $gradient-positions - and ($gradient-type == linear) - and (type-of($gradient-positions) != color) { - $gradient-positions: _linear-positions-parser($gradient-positions); - } - @else if $gradient-positions - and ($gradient-type == radial) - and (type-of($gradient-positions) != color) { - $gradient-positions: _radial-positions-parser($gradient-positions); - } - @return $gradient-positions; -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/helpers/_linear-angle-parser.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/helpers/_linear-angle-parser.scss deleted file mode 100644 index e0401ed8d..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/helpers/_linear-angle-parser.scss +++ /dev/null @@ -1,25 +0,0 @@ -// Private function for linear-gradient-parser -@function _linear-angle-parser($image, $first-val, $prefix, $suffix) { - $offset: null; - $unit-short: str-slice($first-val, str-length($first-val) - 2, str-length($first-val)); - $unit-long: str-slice($first-val, str-length($first-val) - 3, str-length($first-val)); - - @if ($unit-long == "grad") or - ($unit-long == "turn") { - $offset: if($unit-long == "grad", -100grad * 3, -0.75turn); - } - - @else if ($unit-short == "deg") or - ($unit-short == "rad") { - $offset: if($unit-short == "deg", -90 * 3, 1.6rad); - } - - @if $offset { - $num: _str-to-num($first-val); - - @return ( - webkit-image: -webkit- + $prefix + ($offset - $num) + $suffix, - spec-image: $image - ); - } -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/helpers/_linear-gradient-parser.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/helpers/_linear-gradient-parser.scss deleted file mode 100644 index 48a8f77f9..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/helpers/_linear-gradient-parser.scss +++ /dev/null @@ -1,41 +0,0 @@ -@function _linear-gradient-parser($image) { - $image: unquote($image); - $gradients: (); - $start: str-index($image, "("); - $end: str-index($image, ","); - $first-val: str-slice($image, $start + 1, $end - 1); - - $prefix: str-slice($image, 1, $start); - $suffix: str-slice($image, $end, str-length($image)); - - $has-multiple-vals: str-index($first-val, " "); - $has-single-position: unquote(_position-flipper($first-val) + ""); - $has-angle: is-number(str-slice($first-val, 1, 1)); - - @if $has-multiple-vals { - $gradients: _linear-side-corner-parser($image, $first-val, $prefix, $suffix, $has-multiple-vals); - } - - @else if $has-single-position != "" { - $pos: unquote($has-single-position + ""); - - $gradients: ( - webkit-image: -webkit- + $image, - spec-image: $prefix + "to " + $pos + $suffix - ); - } - - @else if $has-angle { - // Rotate degree for webkit - $gradients: _linear-angle-parser($image, $first-val, $prefix, $suffix); - } - - @else { - $gradients: ( - webkit-image: -webkit- + $image, - spec-image: $image - ); - } - - @return $gradients; -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/helpers/_linear-positions-parser.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/helpers/_linear-positions-parser.scss deleted file mode 100644 index 96d6a6d45..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/helpers/_linear-positions-parser.scss +++ /dev/null @@ -1,61 +0,0 @@ -@function _linear-positions-parser($pos) { - $type: type-of(nth($pos, 1)); - $spec: null; - $degree: null; - $side: null; - $corner: null; - $length: length($pos); - // Parse Side and corner positions - @if ($length > 1) { - @if nth($pos, 1) == "to" { // Newer syntax - $side: nth($pos, 2); - - @if $length == 2 { // eg. to top - // Swap for backwards compatibility - $degree: _position-flipper(nth($pos, 2)); - } - @else if $length == 3 { // eg. to top left - $corner: nth($pos, 3); - } - } - @else if $length == 2 { // Older syntax ("top left") - $side: _position-flipper(nth($pos, 1)); - $corner: _position-flipper(nth($pos, 2)); - } - - @if ("#{$side} #{$corner}" == "left top") or ("#{$side} #{$corner}" == "top left") { - $degree: _position-flipper(#{$side}) _position-flipper(#{$corner}); - } - @else if ("#{$side} #{$corner}" == "right top") or ("#{$side} #{$corner}" == "top right") { - $degree: _position-flipper(#{$side}) _position-flipper(#{$corner}); - } - @else if ("#{$side} #{$corner}" == "right bottom") or ("#{$side} #{$corner}" == "bottom right") { - $degree: _position-flipper(#{$side}) _position-flipper(#{$corner}); - } - @else if ("#{$side} #{$corner}" == "left bottom") or ("#{$side} #{$corner}" == "bottom left") { - $degree: _position-flipper(#{$side}) _position-flipper(#{$corner}); - } - $spec: to $side $corner; - } - @else if $length == 1 { - // Swap for backwards compatibility - @if $type == string { - $degree: $pos; - $spec: to _position-flipper($pos); - } - @else { - $degree: -270 - $pos; //rotate the gradient opposite from spec - $spec: $pos; - } - } - $degree: unquote($degree + ","); - $spec: unquote($spec + ","); - @return $degree $spec; -} - -@function _position-flipper($pos) { - @return if($pos == left, right, null) - if($pos == right, left, null) - if($pos == top, bottom, null) - if($pos == bottom, top, null); -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/helpers/_linear-side-corner-parser.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/helpers/_linear-side-corner-parser.scss deleted file mode 100644 index 7a691253d..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/helpers/_linear-side-corner-parser.scss +++ /dev/null @@ -1,31 +0,0 @@ -// Private function for linear-gradient-parser -@function _linear-side-corner-parser($image, $first-val, $prefix, $suffix, $has-multiple-vals) { - $val-1: str-slice($first-val, 1, $has-multiple-vals - 1); - $val-2: str-slice($first-val, $has-multiple-vals + 1, str-length($first-val)); - $val-3: null; - $has-val-3: str-index($val-2, " "); - - @if $has-val-3 { - $val-3: str-slice($val-2, $has-val-3 + 1, str-length($val-2)); - $val-2: str-slice($val-2, 1, $has-val-3 - 1); - } - - $pos: _position-flipper($val-1) _position-flipper($val-2) _position-flipper($val-3); - $pos: unquote($pos + ""); - - // Use old spec for webkit - @if $val-1 == "to" { - @return ( - webkit-image: -webkit- + $prefix + $pos + $suffix, - spec-image: $image - ); - } - - // Bring the code up to spec - @else { - @return ( - webkit-image: -webkit- + $image, - spec-image: $prefix + "to " + $pos + $suffix - ); - } -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/helpers/_radial-arg-parser.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/helpers/_radial-arg-parser.scss deleted file mode 100644 index 56c6030b7..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/helpers/_radial-arg-parser.scss +++ /dev/null @@ -1,69 +0,0 @@ -@function _radial-arg-parser($g1, $g2, $pos, $shape-size) { - @each $value in $g1, $g2 { - $first-val: nth($value, 1); - $pos-type: type-of($first-val); - $spec-at-index: null; - - // Determine if spec was passed to mixin - @if type-of($value) == list { - $spec-at-index: if(index($value, at), index($value, at), false); - } - @if $spec-at-index { - @if $spec-at-index > 1 { - @for $i from 1 through ($spec-at-index - 1) { - $shape-size: $shape-size nth($value, $i); - } - @for $i from ($spec-at-index + 1) through length($value) { - $pos: $pos nth($value, $i); - } - } - @else if $spec-at-index == 1 { - @for $i from ($spec-at-index + 1) through length($value) { - $pos: $pos nth($value, $i); - } - } - $g1: null; - } - - // If not spec calculate correct values - @else { - @if ($pos-type != color) or ($first-val != "transparent") { - @if ($pos-type == number) - or ($first-val == "center") - or ($first-val == "top") - or ($first-val == "right") - or ($first-val == "bottom") - or ($first-val == "left") { - - $pos: $value; - - @if $pos == $g1 { - $g1: null; - } - } - - @else if - ($first-val == "ellipse") - or ($first-val == "circle") - or ($first-val == "closest-side") - or ($first-val == "closest-corner") - or ($first-val == "farthest-side") - or ($first-val == "farthest-corner") - or ($first-val == "contain") - or ($first-val == "cover") { - - $shape-size: $value; - - @if $value == $g1 { - $g1: null; - } - - @else if $value == $g2 { - $g2: null; - } - } - } - } - } - @return $g1, $g2, $pos, $shape-size; -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/helpers/_radial-gradient-parser.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/helpers/_radial-gradient-parser.scss deleted file mode 100644 index 5444d8085..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/helpers/_radial-gradient-parser.scss +++ /dev/null @@ -1,50 +0,0 @@ -@function _radial-gradient-parser($image) { - $image: unquote($image); - $gradients: (); - $start: str-index($image, "("); - $end: str-index($image, ","); - $first-val: str-slice($image, $start + 1, $end - 1); - - $prefix: str-slice($image, 1, $start); - $suffix: str-slice($image, $end, str-length($image)); - - $is-spec-syntax: str-index($first-val, "at"); - - @if $is-spec-syntax and $is-spec-syntax > 1 { - $keyword: str-slice($first-val, 1, $is-spec-syntax - 2); - $pos: str-slice($first-val, $is-spec-syntax + 3, str-length($first-val)); - $pos: append($pos, $keyword, comma); - - $gradients: ( - webkit-image: -webkit- + $prefix + $pos + $suffix, - spec-image: $image - ); - } - - @else if $is-spec-syntax == 1 { - $pos: str-slice($first-val, $is-spec-syntax + 3, str-length($first-val)); - - $gradients: ( - webkit-image: -webkit- + $prefix + $pos + $suffix, - spec-image: $image - ); - } - - @else if str-index($image, "cover") or str-index($image, "contain") { - @warn "Radial-gradient needs to be updated to conform to latest spec."; - - $gradients: ( - webkit-image: null, - spec-image: $image - ); - } - - @else { - $gradients: ( - webkit-image: -webkit- + $image, - spec-image: $image - ); - } - - @return $gradients; -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/helpers/_radial-positions-parser.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/helpers/_radial-positions-parser.scss deleted file mode 100644 index 3c552ad79..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/helpers/_radial-positions-parser.scss +++ /dev/null @@ -1,18 +0,0 @@ -@function _radial-positions-parser($gradient-pos) { - $shape-size: nth($gradient-pos, 1); - $pos: nth($gradient-pos, 2); - $shape-size-spec: _shape-size-stripper($shape-size); - - $pre-spec: unquote(if($pos, "#{$pos}, ", null)) - unquote(if($shape-size, "#{$shape-size},", null)); - $pos-spec: if($pos, "at #{$pos}", null); - - $spec: "#{$shape-size-spec} #{$pos-spec}"; - - // Add comma - @if ($spec != " ") { - $spec: "#{$spec},"; - } - - @return $pre-spec $spec; -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/helpers/_render-gradients.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/helpers/_render-gradients.scss deleted file mode 100644 index 576567683..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/helpers/_render-gradients.scss +++ /dev/null @@ -1,26 +0,0 @@ -// User for linear and radial gradients within background-image or border-image properties - -@function _render-gradients($gradient-positions, $gradients, $gradient-type, $vendor: false) { - $pre-spec: null; - $spec: null; - $vendor-gradients: null; - @if $gradient-type == linear { - @if $gradient-positions { - $pre-spec: nth($gradient-positions, 1); - $spec: nth($gradient-positions, 2); - } - } - @else if $gradient-type == radial { - $pre-spec: nth($gradient-positions, 1); - $spec: nth($gradient-positions, 2); - } - - @if $vendor { - $vendor-gradients: -#{$vendor}-#{$gradient-type}-gradient(#{$pre-spec} $gradients); - } - @else if $vendor == false { - $vendor-gradients: "#{$gradient-type}-gradient(#{$spec} #{$gradients})"; - $vendor-gradients: unquote($vendor-gradients); - } - @return $vendor-gradients; -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/helpers/_shape-size-stripper.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/helpers/_shape-size-stripper.scss deleted file mode 100644 index ee5eda422..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/helpers/_shape-size-stripper.scss +++ /dev/null @@ -1,10 +0,0 @@ -@function _shape-size-stripper($shape-size) { - $shape-size-spec: null; - @each $value in $shape-size { - @if ($value == "cover") or ($value == "contain") { - $value: null; - } - $shape-size-spec: "#{$shape-size-spec} #{$value}"; - } - @return $shape-size-spec; -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/helpers/_str-to-num.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/helpers/_str-to-num.scss deleted file mode 100644 index 3ef1d873b..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/helpers/_str-to-num.scss +++ /dev/null @@ -1,50 +0,0 @@ -//************************************************************************// -// Helper function for linear/radial-gradient-parsers. -// Source: http://sassmeister.com/gist/9647408 -//************************************************************************// -@function _str-to-num($string) { - // Matrices - $strings: "0" "1" "2" "3" "4" "5" "6" "7" "8" "9"; - $numbers: 0 1 2 3 4 5 6 7 8 9; - - // Result - $result: 0; - $divider: 0; - $minus: false; - - // Looping through all characters - @for $i from 1 through str-length($string) { - $character: str-slice($string, $i, $i); - $index: index($strings, $character); - - @if $character == "-" { - $minus: true; - } - - @else if $character == "." { - $divider: 1; - } - - @else { - @if not $index { - $result: if($minus, $result * -1, $result); - @return _convert-units($result, str-slice($string, $i)); - } - - $number: nth($numbers, $index); - - @if $divider == 0 { - $result: $result * 10; - } - - @else { - // Move the decimal dot to the left - $divider: $divider * 10; - $number: $number / $divider; - } - - $result: $result + $number; - } - } - @return if($minus, $result * -1, $result); -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/settings/_asset-pipeline.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/settings/_asset-pipeline.scss deleted file mode 100644 index 4c6afc5bb..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/settings/_asset-pipeline.scss +++ /dev/null @@ -1,7 +0,0 @@ -@charset "UTF-8"; - -/// A global setting to enable or disable the `$asset-pipeline` variable for all functions that accept it. -/// -/// @type Bool - -$asset-pipeline: false !default; diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/settings/_prefixer.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/settings/_prefixer.scss deleted file mode 100644 index 8c390514d..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/settings/_prefixer.scss +++ /dev/null @@ -1,9 +0,0 @@ -@charset "UTF-8"; - -/// Global variables to enable or disable vendor prefixes - -$prefix-for-webkit: true !default; -$prefix-for-mozilla: true !default; -$prefix-for-microsoft: true !default; -$prefix-for-opera: true !default; -$prefix-for-spec: true !default; diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/settings/_px-to-em.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/settings/_px-to-em.scss deleted file mode 100644 index f2f9a3e8d..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/bourbon/settings/_px-to-em.scss +++ /dev/null @@ -1 +0,0 @@ -$em-base: 16px !default; diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/gridle/_grid-settings.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/gridle/_grid-settings.scss deleted file mode 100644 index 794343496..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/gridle/_grid-settings.scss +++ /dev/null @@ -1,23 +0,0 @@ -// Import gridle : -@import 'gridle/gridle'; - -// setup the grid (required) : -@include gridle_setup(( - context : 12, - gutter-width : 20px, - debug : true -)); - -// register special columns : -@include gridle_register_column("1on5", 1, 5); - -// clear each classes : -@include gridle_register_clear_each(2, left); -@include gridle_register_clear_each(12, both); - -// register states : -@include gridle_register_default_states(); -@include gridle_register_state(ipad-landscape, ( - query : "only screen and (min-device-width : 768px) and (max-device-width : 1024px) and (orientation : landscape)", - gutter-width : 0 -)); \ No newline at end of file diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/gridle/grid-bootstrap.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/gridle/grid-bootstrap.scss deleted file mode 100644 index 101ee15cd..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/gridle/grid-bootstrap.scss +++ /dev/null @@ -1,48 +0,0 @@ -// Import gridle : -@import 'gridle/gridle'; - - -// basic configuration : -@include gridle_setup( ( - context : 12, - gutter-width : 30px, - html-states-classes : true -) ); - -// register states : -@include gridle_register_default_mobile_first_states(); - -/* - * Optional : - * Change generation class names pattern (for example to match bootstrap naming conventions or generate with your own names) : - * Check documentation (http://gridle.org/documentation#name-pattern) for full list - * - * %- = separator sign (configurable by $gridle-class-separator) (no need to add separators if you doesn't want them) - * %state = the state name (mobile, ipad, etc...) - * %count = the column count (1, 2, 3, 4, etc...) - */ -$gridle-grid-name-pattern : ('col','%-','%state','%-','%count'); -$gridle-parent-name-pattern : ('row','%-','%state'); -$gridle-prefix-name-pattern : ('col','%-','%state','%-','offset','%-','%count'); -$gridle-push-name-pattern : ('col','%-','%state','%-','push','%-','%count'); -$gridle-pull-name-pattern : ('col','%-','%state','%-','pull','%-','%count'); -$gridle-show-name-pattern : ('visible','%-','%state'); -$gridle-hide-name-pattern : ('hidden','%-','%state'); - - -/** - * Mobile first approach : - */ -[class*="col-"] { - width:100%; // 100% by default -} - - -// Generate classes : -@include gridle_generate_classes(); - -// Max size : -.container { - margin:0 auto; - max-width:1200px; -} \ No newline at end of file diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/gridle/grid.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/gridle/grid.scss deleted file mode 100644 index d638093c6..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/gridle/grid.scss +++ /dev/null @@ -1,24 +0,0 @@ -// Import grid settings : -@import 'grid-settings'; - -// Generate classes : -@include gridle_generate_classes(); - -// you can generate classes separately if you need : -// @include gridle_generate_classes(default); // default is the base state always registered -// @include gridle_generate_classes(mobile, (grid, push, pull)) // generate only the grid, push and pull classes for mobile -// etc... - -// generate a center custom class for all the states : -@include gridle_generate_custom_class( ('center','%-','%state') ) { - text-align:center; -} -// this will produces classes : center, center-mobile, center-tablet, center-ipad-landscape -// for separators, you can use the %- (replaced by the $gridle-class-separator option), or -, --, _, __ - -// Max size : -.container { - margin:0 auto; - max-width:960px; - @include gridle_grid_background(); -} \ No newline at end of file diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/gridle/gridle/_common-mixins.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/gridle/gridle/_common-mixins.scss deleted file mode 100644 index cd8d5ada0..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/gridle/gridle/_common-mixins.scss +++ /dev/null @@ -1,124 +0,0 @@ -// |------------------------------------------------------ -// |------------------------------------------------------ -// | Common mixins -// |------------------------------------------------------ -// |------------------------------------------------------ - -@mixin _gridle_container_common( - $state : default -) { - @extend %gridle-simple-clearfix; - @extend %gridle-container-common; - // debug part - $debug : _gridle_get_var_value(debug, $state); - @if ($debug == true) { - #{$gridle-debug-selector} { - @extend %gridle-container-debug-common; - } - } -} -$_gridle-already-generated : (); -@mixin _gridle_grid_common() { - @extend %gridle-grid-common; - - // default values - $default-gutter-width : _gridle_get_var_value(gutter-width, default); - $default-direction : _gridle_get_var_value(direction, default); - - // loop on each states : - @each $stateName, $state in $_gridle-states - { - // selector key to be used in map - $key : "#{$stateName} #{&}"; - - // check if already generated classes - $already-generated : map-has-key($_gridle-already-generated, $key); - - // vars - $direction : _gridle_get_var_value(direction, $state); - $classes : _gridle_get_var_value(classes, $state); - $gutter-width : _gridle_get_var_value(gutter-width, $state); - $debug : _gridle_get_var_value(debug, $state); - - @if $already-generated != true and $classes and ( ($default-direction != $direction or $default-gutter-width != $gutter-width) or $stateName == default) - { - // set that we have already generated css for this selector - $_gridle-already-generated : map-set($_gridle-already-generated, $key, true) !global; - - // generate the css for this element - @include gridle_state($state) { - @if $direction != $default-direction or $stateName == default { - // content : "#{$key}"; - @if $direction == rtl { - float:right; - direction:rtl; - } @else { - float:left; - direction:ltr; - } - } - @if $gutter-width != $default-gutter-width or $stateName == default { - padding-left:$gutter-width/2; - padding-right:$gutter-width/2; - } - } - - @if $debug == true { - #{$gridle-debug-selector} { - @extend %gridle-grid-debug-common; - } - } - } - } -} -@mixin _gridle_parent_common() { - @extend %gridle-clearfix; - @extend %gridle-parent-common; -} -@mixin _gridle_push_common( - $state : default -) { - $debug : _gridle_get_var_value(debug, $state); - - // extend common : - @extend %gridle-push-pull-common; - @if $debug == true { - #{$gridle-debug-selector} { - @extend %gridle-push-pull-debug-background-common; - background-color:#f4efdf !important; - } - } -} -@mixin _gridle_pull_common( - $state : default -) { - $debug : _gridle_get_var_value(debug, $state); - - @extend %gridle-push-pull-common; - @if $debug == true { - #{$gridle-debug-selector} { - @extend %gridle-push-pull-debug-background-common; - background-color:#cfe4d5 !important; - } - } -} -@mixin _gridle_prefix_common( - $state : default -) { - $debug : _gridle_get_var_value(debug, $state); - @if $debug == true { - #{$gridle-debug-selector} { - @extend %gridle-prefix-debug-common; - } - } -} -@mixin _gridle_suffix_common( - $state : default -) { - $debug : _gridle_get_var_value(debug, $state); - @if $debug == true { - #{$gridle-debug-selector} { - @extend %gridle-suffix-debug-common; - } - } -} \ No newline at end of file diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/gridle/gridle/_default-states.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/gridle/gridle/_default-states.scss deleted file mode 100644 index aeb007cdf..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/gridle/gridle/_default-states.scss +++ /dev/null @@ -1,35 +0,0 @@ -// |------------------------------------------------------ -// |------------------------------------------------------ -// | Default states -// |------------------------------------------------------ -// |------------------------------------------------------ - -// retina -@include gridle_register_state("retina", ( - query : "(-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi), (min-resolution: 2dppx)", - classes : false -) ); - -// tv -@include gridle_register_state("tv", ( - query : "only tv", - classes : false -) ); - -// print -@include gridle_register_state("print", ( - query : "only print", - classes : false -) ); - -// portrait -@include gridle_register_state("portrait", ( - query : "only screen and (orientation: portrait)", - classes : false -) ); - -// landscape -@include gridle_register_state("landscape", ( - query : "only screen and (orientation: landscape)", - classes : false -) ); \ No newline at end of file diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/gridle/gridle/_functions.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/gridle/gridle/_functions.scss deleted file mode 100644 index 9e6fc0e7e..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/gridle/gridle/_functions.scss +++ /dev/null @@ -1,387 +0,0 @@ -// |------------------------------------------------------ -// |------------------------------------------------------ -// | Functions -// |------------------------------------------------------ -// |------------------------------------------------------ - -/** - * Str replace - * - * @param {string} $string String that you want to replace - * @param {string} $substr String that is to be replaced by `$newsubstr` - * @param {string} $newsubstr String that replaces `$substr` - * @param {number*} $all Flag for replaceing all (1+) or not (0) - * @return {string} - */ -@function str-replace($string, $substr, $newsubstr, $all: 0) { - $position-found: str-index($string, $substr); - $processed: (); - - @while ($position-found and $position-found > 0) { - $length-substr: str-length($substr); - $processed: append($processed, str-slice($string, 0, $position-found - 1)); - $processed: append($processed, $newsubstr); - $string: str-slice($string, $position-found + $length-substr); - - $position-found: 0; - - @if ($all > 0) { - $position-found: str-index($string, $substr); - } - } - - $processed: append($processed, $string); - $string: ""; - - @each $s in $processed { - $string: #{$string}#{$s}; - } - - @return $string; -} - -/** - * Map set - * - * @param Map $map The map to use - * @param String $key The key to update - * @param Mixed $value The new value - * @return Map The new map - */ -@function map-set($map, $key, $value) { - $new: ($key: $value); - @return map-merge($map, $new); -} - - -/** - * Get the column width in percent for the global or a specific context - * - * @param int $columns The number of columns to calculate - * @param int $context : $gridle-columns-count The context to use - * @return percentage The width in percent - */ -@function gridle_get_column_width( - $columns : 1, - $stateMap-or-stateName : null -) { - @return percentage(1 / $context * $columns); -} - - -/** - * Get a state map - * - * @param string $name The name of the state to get - * @return map A state map object - */ -@function _gridle_get_state( - $stateMap-or-stateName -) { - // check if has a state named like this - @if (type-of($stateMap-or-stateName) == string - and map-has-key($_gridle_states, unquote("#{$stateMap-or-stateName}"))) - { - @return map-get($_gridle_states, unquote("#{$stateMap-or-stateName}")); - } - - // a map is passed, so it's a state himself - @if $stateMap-or-stateName - and type-of($stateMap-or-stateName) == map - { - @return map-merge($_gridle-settings, $stateMap-or-stateName); - } - - // return the default one if exist - @if map-has-key($_gridle_states, default) - { - @return map-get($_gridle_states, default); - } - - // nothing finded, return the default state - @return $_gridle-settings; -} - - -/** - * Check if a state exist : - * - * @param string $name The name of the state to check - * @return Boolean true is exist - */ -@function _gridle_has_state( - $stateName -) { - @if map-has-key($_gridle_states, unquote("#{$stateName}")) { - @return true; - } @else { - @return false; - } -} - - -/** - * Get the media queries variables : - * - * @param int $index The media query indes - * @param String $var The media query variable name - * @return String|int The variable value - */ -@function _gridle_get_state_var( - $stateName, - $var : "name" -) { - - // get the state : - $state : _gridle_get_state($stateName); - - // check ig state and if has the variable : - @if $state - and map-has-key($state,unquote("#{$var}")) - { - @return map-get($state,unquote("#{$var}")); - } - - // nothing getted : - @return null; -} - - -/** - * Get a variable - * - * @param String $varName The variable name - * @param String $stateMap-or-stateName The state name or a map state value - * @return Mixed The finded value - */ -@function _gridle_get_var_value( - $varName, - $stateMap-or-stateName : null -) { - // if is a state : - $state : null; - - // get the state (if no state find, return the default one) : - $state : _gridle_get_state($stateMap-or-stateName); - - // extend default state with given state : - $props : map-merge($_gridle-settings, $state); - - @if map-has-key($props, unquote("#{$varName}")) { - @return map-get($state, unquote("#{$varName}")); - } - - // nothing finded : - @return null; -} - - -/** - * Set a variable in a state - * @param Mixed $stateName-or-stateIndex The state name of state index - * @param String $var Variable name to assign - * @param Mixed $newValue The new value to assign - * @return List The states list (full) - */ -@function _gridle_set_state_var( - $stateName, - $var, - $newValue -) { - // get the state : - $state : _gridle_get_state($stateName); - - // check ig state and if has the variable : - @if $state - and map-has-key($state,unquote("#{$var}")) - { - // set new value in state : - $state : map-set($state, unquote("#{$var}"), $newValue); - - // set states : - $_gridle_states : map-set($_gridle_states, unquote("#{$stateName}"), $state); - - // return new state : - @return $state; - } - - // nothing getted : - @return null; -} - - -/** - * Generate a column - * - * @param String $name The column name (often count) - * @param int $columns The column count that the column will take - * @param int $context The context on witch the with will be calculed - * @param Boolean $generateClasses Set if the column has to be generated in css - */ -@function _gridle_create_column( - $name, - $columns, - $context, - $name-multiplicator : 1 // used to extend the state on custom registered columns -) { - @return ( - name : $name, - columns : $columns, - context : $context, - name-multiplicator : $name-multiplicator - ); -} - - -/** - * Generate classname - * - * @param List $parrern The pattern to use to generate classname - * @param String $state The state - * @param int $count The column count - */ -@function _gridle_classname( - $pattern, - $state : null, - $count : null -) { - - // init selector : - $sel : "."; - - // delete default : - @if unquote("#{$state}") == default { - $state : null; - } - - // add class prefix : - @if $gridle-class-prefix and $gridle-class-prefix != '' { - $sel : "#{$sel}#{$gridle-class-prefix}"; - @if $gridle-class-separator { - $sel : "#{$sel}#{$gridle-class-separator}"; - } - } - - // construct class name : - $i : 1; - @each $var in $pattern { - - // replace tokens : - @if $var == '%state' and $state { - $sel : "#{$sel}#{$state}"; - } - @if $var == '%count' and $count { - $sel : "#{$sel}#{$count}"; - } - @if $var != '%state' and $var != '%count' and $var != '%-' and $var != '-' and $var != '--' and $var != '_' and $var != '__' and $var != '%prefix' { - $sel : "#{$sel}#{$var}"; - } - - // handle separators : - @if ($var == '%-' or $var == '-' or $var == '--' or $var == '_' or $var == '__') and $i < length($pattern) { - $index : $i + 1; - $value : nth($pattern, $index); - @if $value != '%state' and $value != '%count' and $value != '%-' and $value != '-' and $value != '--' and $value != '_' and $value != '__' and $value != '%prefix' { - @if $var == '%-' { - $sel : "#{$sel}#{$gridle-class-separator}"; - } @else { - $sel : "#{$sel}#{$var}"; - } - } - @if $value == '%state' and $state { - @if $var == '%-' { - $sel : "#{$sel}#{$gridle-class-separator}"; - } @else { - $sel : "#{$sel}#{$var}"; - } - } - @if $value == '%count' and $count { - @if $var == '%-' { - $sel : "#{$sel}#{$gridle-class-separator}"; - } @else { - $sel : "#{$sel}#{$var}"; - } - } - } - - // update i : - $i : $i + 1; - } - - // return generated class : - @return $sel; -} - - -/** - * Get the media query for a particular state, or with, etc... - * - * @param Mixed $state-or-min-width The state name of the min with - * @param Mixed $max-width The max width if first param is a min width - * @return String The media query string without the @media - */ -@function _gridle_get_media_query( - $state-or-settings -) { - // check if is a string : - $state : null; - @if type-of($state-or-settings) == string - { - $state : _gridle_get_state($state-or-settings); - } - @else if $state-or-settings == null - { - $state : $_gridle-settings; - } - @else - { - $state : map-merge($_gridle-settings, $state-or-settings); - } - - // if it's some settings or a state : - @if $state { - - // get vars : - $name : map-get($state, name); - $min-width : map-get($state, min-width); - $max-width : map-get($state, max-width); - $query : map-get($state, query); - - // direct query : - @if $query - { - @return $query; - } - @else if $min-width and $max-width - { - @return "screen and (min-width: #{$min-width}) and (max-width: #{$max-width})"; - } - @else if $min-width - { - @return "screen and (min-width: #{$min-width})"; - } - @else if $max-width - { - @return "screen and (max-width: #{$max-width})"; - } - @else - { - @return null; - } - - } - @else - { - @return null; - } -} - - -/** - * Get states count - * - * @return int The number of states defined - */ -@function _gridle_get_states_count() { - @return length($_gridle_states) / length($_gridle_states_vars_pattern); -} \ No newline at end of file diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/gridle/gridle/_generate-mixins.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/gridle/gridle/_generate-mixins.scss deleted file mode 100644 index d09a0e37c..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/gridle/gridle/_generate-mixins.scss +++ /dev/null @@ -1,624 +0,0 @@ -// |------------------------------------------------------ -// |------------------------------------------------------ -// | Generate mixins -// |------------------------------------------------------ -// |------------------------------------------------------ - - -/** - * Generate a custom class for all the states - * - * @param list $pattern The name pattern of the class - * @param list $statesNames The states names to generate - */ -@mixin gridle_generate_custom_class( - $pattern, - $statesNames : null -) { - // manage states to generate : - $states : (); - @if $statesNames == null { - // loop on each states to generate names list : - @each $stateName, $state in $_gridle_states { - $states : append($states, $stateName); - } - } @else { - $states : $statesNames; - } - - // loop on each states : - @each $stateName in $states - { - // manage statename : - @if type-of($stateName) != string { - $stateName : map-get($stateName, name); - } - - // classes : - $classes : _gridle_get_var_value(classes, $stateName); - - // genrate the classname : - @if $classes - { - @include gridle_state($stateName, false) { - #{_gridle_classname($pattern, $stateName)} { - @content; - } - } - } - } -} - -// Generate all helpers classes -// All the classes generated are not wrapper in gridle_state -// in this mixin... Just the names are generated accordingly to the -// requested state -@mixin _gridle_generate_helper_classes ( - $state : null, - $what : null -) { - // helpers : - @if $what == null or index($what, float) or index($what, helpers) { - #{_gridle_classname($gridle-float-left-name-pattern, $state)} { - @include gridle_float(left); - } - #{_gridle_classname($gridle-float-right-name-pattern, $state)} { - @include gridle_float(right); - } - } - - @if $what == null or index($what, clear) or index($what, helpers) { - #{_gridle_classname($gridle-clear-name-pattern, $state)} { - @include gridle_clear(both); - } - #{_gridle_classname($gridle-clear-left-name-pattern, $state)} { - @include gridle_clear(left); - } - #{_gridle_classname($gridle-clear-right-name-pattern, $state)} { - @include gridle_clear(right); - } - } - - @if $what == null or index($what, no_gutter) or index($what, no_margin) or index($what, helpers) { - #{_gridle_classname($gridle-no-gutter-name-pattern, $state)}, - #{_gridle_classname($gridle-no-margin-name-pattern, $state)} { - @include gridle_no_margin(); - } - #{_gridle_classname($gridle-no-gutter-left-name-pattern, $state)}, - #{_gridle_classname($gridle-no-margin-left-name-pattern, $state)} { - @include gridle_no_margin(left); - } - #{_gridle_classname($gridle-no-gutter-right-name-pattern, $state)}, - #{_gridle_classname($gridle-no-margin-right-name-pattern, $state)} { - @include gridle_no_margin(right); - } - #{_gridle_classname($gridle-no-gutter-top-name-pattern, $state)}, - #{_gridle_classname($gridle-no-margin-top-name-pattern, $state)} { - @include gridle_no_margin(top); - } - #{_gridle_classname($gridle-no-gutter-bottom-name-pattern, $state)}, - #{_gridle_classname($gridle-no-margin-bottom-name-pattern, $state)} { - @include gridle_no_margin(bottom); - } - } - - @if $what == null or index($what, gutter) or index($what, margin) or index($what, helpers) { - #{_gridle_classname($gridle-gutter-name-pattern, $state)}, - #{_gridle_classname($gridle-margin-name-pattern, $state)} { - @include gridle_margin(left right); - } - #{_gridle_classname($gridle-gutter-left-name-pattern, $state)}, - #{_gridle_classname($gridle-margin-left-name-pattern, $state)} { - @include gridle_margin(left); - } - #{_gridle_classname($gridle-gutter-right-name-pattern, $state)}, - #{_gridle_classname($gridle-margin-right-name-pattern, $state)} { - @include gridle_margin(right); - } - #{_gridle_classname($gridle-gutter-top-name-pattern, $state)}, - #{_gridle_classname($gridle-margin-top-name-pattern, $state)} { - @include gridle_margin(top); - } - #{_gridle_classname($gridle-gutter-bottom-name-pattern, $state)}, - #{_gridle_classname($gridle-margin-bottom-name-pattern, $state)} { - @include gridle_margin(bottom); - } - } - - @if $what == null or index($what, auto_height) or index($what, helpers) { - #{_gridle_classname($gridle-auto-height-name-pattern, $state)} { - height:inherit; - } - } - - @if $what == null or index($what, centered) or index($what, helpers) { - #{_gridle_classname($gridle-centered-name-pattern, $state)} { - @include gridle_centered(null); - } - } - - @if $what == null or index($what, parent) or index($what, helpers) { - #{_gridle_classname($gridle-parent-name-pattern, $state)} { - @include _gridle_parent(); - } - } - - @if $what == null or index($what, vertical_align) or index($what, helpers) { - #{_gridle_classname($gridle-vertical-align-middle-name-pattern, $state)} { - @include _gridle_vertical_align(); - } - #{_gridle_classname($gridle-vertical-align-top-name-pattern, $state)} { - @include _gridle_vertical_align(top); - } - #{_gridle_classname($gridle-vertical-align-bottom-name-pattern, $state)} { - @include _gridle_vertical_align(bottom); - } - } - - /** - * Visible, hide, etc... - */ - @if $what == null or index($what, hide) or index($what, helpers) { - #{_gridle_classname($gridle-hide-name-pattern, $state)} { - @include gridle_hide(null); - } - } - - @if $what == null or index($what, not_visible) or index($what, helpers) { - #{_gridle_classname($gridle-not-visible-name-pattern, $state)} { - @include gridle_not_visible(null); - } - } - - @if $what == null or index($what, show) or index($what, helpers) { - #{_gridle_classname($gridle-show-name-pattern, $state)} { - @include gridle_show(null); - } - #{_gridle_classname($gridle-show-inline-name-pattern, $state)} { - @include gridle_show_inline(null); - } - } - - @if $what == null or index($what, visible) or index($what, helpers) { - #{_gridle_classname($gridle-visible-name-pattern, $state)} { - @include gridle_visible(null); - } - } - - /** - * Clear each class : - */ - @if $what == null or index($what, clear_each) or index($what, helpers) { - @each $clearName, $clearMap in $_gridle_clear_classes { - // get count : - $clearCount : map-get($clearMap, clearEach); - // what to clear : - $clearWhat : map-get($clearMap, clearWhat); - // generate the class : - #{_gridle_classname($gridle-clear-each-pattern, $state, $clearCount)} { - @include _gridle_clear_each($clearCount, $clearWhat); - } - } - } - - // debug colors : - $debug : _gridle_get_var_value(debug, $state); - @if $debug and ( $what == null or index($what, debug_colors) or index($what, helpers) ) { - // debug color classes : - #{_gridle_classname($gridle-debug-color-name-pattern, $state, 1)} { - #{$gridle-debug-selector} { - background-color : #edeeb2; - } - } - #{_gridle_classname($gridle-debug-color-name-pattern, $state, 2)} { - #{$gridle-debug-selector} { - background-color : #fae4a7; - } - } - #{_gridle_classname($gridle-debug-color-name-pattern, $state, 3)} { - #{$gridle-debug-selector} { - background-color : #f5eacc; - } - } - #{_gridle_classname($gridle-debug-color-name-pattern, $state, 4)} { - #{$gridle-debug-selector} { - background-color : #eebdb2; - } - } - #{_gridle_classname($gridle-debug-color-name-pattern, $state, 5)} { - #{$gridle-debug-selector} { - background-color : #d4b2ee; - } - } - #{_gridle_classname($gridle-debug-color-name-pattern, $state, 6)} { - #{$gridle-debug-selector} { - background-color : #b2d8ee; - } - } - } -} - - -// generate settings json : -@mixin gridle_generate_json_settings() { - - // settings content : - $gridle-settings-states : "{"; - - // generate all classes for differents media queries : - $statesCount : length($_gridle_states); - $i : 0; - @each $stateName, $state in $_gridle_states { - - $name : $stateName; - - $gridle-settings-states : "#{$gridle-settings-states} \"#{$name}\":{"; - - @each $varName, $var in $state { - - $value : null; - @if $varName == "query" { - $value : _gridle_get_media_query($stateName); - } @else { - $value : map-get($state,$varName); - } - - @if $value == null { - $gridle-settings-states : "#{$gridle-settings-states} \"#{$varName}\" : null,"; - } @elseif type-of($value) == bool { - $gridle-settings-states : "#{$gridle-settings-states} \"#{$varName}\" : #{$value},"; - } @else { - $gridle-settings-states : "#{$gridle-settings-states} \"#{$varName}\" : \"#{$value}\","; - } - } - - $gridle-settings-states : "#{$gridle-settings-states} \"_gridle\" : true"; - - @if $i >= $statesCount - 1 { - $gridle-settings-states : "#{$gridle-settings-states} }"; - } @else { - $gridle-settings-states : "#{$gridle-settings-states} },"; - } - - // update i : - $i : $i + 1; - - } - - // generate settings json : - $gridle-settings-states : "#{$gridle-settings-states}}"; - $gridle-settings : "{"; - $gridle-settings : "#{$gridle-settings} \"version\" : \"#{$_gridle-version}\""; - - // states : - $gridle-settings : "#{$gridle-settings}, \"states\" : #{$gridle-settings-states}"; - - // debug devices : - $debug_devices : $_gridle_states_debug_devices; - @if length($_gridle_states_debug_devices) <= 0 { - $debug_devices : null; - } - // $gridle-settings : "#{$gridle-settings}, \"debugDevices\" : { #{$debug_devices} }"; - - // settings : - // $gridle-settings : "#{$gridle-settings}, \"classPrefix\" : \"#{$gridle-class-prefix}\""; - $gridle-settings : "#{$gridle-settings} }"; - #gridle-settings { - content : $gridle-settings; - } -} - - -// gridle mixin : -// Generate all the classes needed for a grid -@mixin gridle_generate_classes( - $stateName : null, - $what : null, - $scope : null -) { - // if the what parameter is not null, mean that we need to generate only certain classes in a certain order : - @if $what - { - // loop on each what item to generate the corresponding classes : - @each $w in $what - { - // check if a scope exist : - @if $scope { - // wrapp grid into scope : - .#{$scope} { - @include _gridle_generate_classes($stateName, ( $w ), true); - } - } @else { - // generate classes : - @include _gridle_generate_classes($stateName, ( $w ), false); - } - } - } - @else - { - // don't have any "what" parameter so generate all the classes - // check if a scope exist : - @if $scope { - // wrapp grid into scope : - .#{$scope} { - @include _gridle_generate_classes($stateName, null, true); - } - } @else { - // generate classes : - @include _gridle_generate_classes($stateName, null, false); - } - } -} -$_gridle_generateOnlyOnce : true; // keep track of generate once classes -@mixin _gridle_generate_classes( - $stateName : null, - $what : null, - $has-parent : false -) { - - // generate these classes only once : - @if $_gridle_generateOnlyOnce - { - - // update status : - $_gridle_generateOnlyOnce : false; - - // | ------------------------ - // | Windows 8 fix - // | ------------------------ - - // Windows 8 fix for snap mode : - @media screen and (max-width: 400px) { - @-ms-viewport { width: device-width; } - } - - - // | ------------------------ - // | Container - // | ------------------------ - - // generate container class : - @if $what == null or index($what, container) or index($what, default) - { - $container-selector : (); - $container-selector : append( $container-selector, unquote("#{_gridle_classname($gridle-container-name-pattern)}"), comma); - #{$container-selector} { - @include gridle_container(); - } - } - - - // | ------------------------ - // | Parent selector - // | ------------------------ - - // parent common css : - @if $what == null or index($what, parent) or index($what, default) - { - $parentSelector : _gridle_classname($gridle-parent-name-pattern,null,null); - #{$parentSelector} { - @extend %gridle-clearfix; - @extend %gridle-parent-common; - } - } - - - // // | ------------------------ - // // | JSON Settings - // // | ------------------------ - - // // generate json settings : - @if $gridle-generate-json-settings - { - @include gridle_generate_json_settings(); - } - - } - - - // | ------------------------ - // | Set the list of states to generate - // | ------------------------ - $states : $_gridle_states; - @if $stateName and _gridle_has_state($stateName) { - $states : map-set((), $stateName, _gridle_get_state($stateName)); - } - - - // | ------------------------ - // | Store all the generated common selectors - // | ------------------------ - - // generate all selector for extends : - $grid-common-selector : (); - $push-common-selector : (); - $pull-common-selector : (); - $prefix-common-selector : (); - $suffix-common-selector : (); - - - // | ------------------------ - // | Media queries classes common selectors - // | ------------------------ - - // generate all classes for media queries : - @each $stateName, $state in $states { - - // setup vars : - $media : $stateName; - $classes : map-get($state, classes); - $context : map-get($state, context); - $name-multiplicator : map-get($state, name-multiplicator); - $generate-push-classes : _gridle_get_var_value(generate-push-classes, $state); - $generate-pull-classes : _gridle_get_var_value(generate-pull-classes, $state); - $generate-prefix-classes : _gridle_get_var_value(generate-prefix-classes, $state); - $generate-suffix-classes : _gridle_get_var_value(generate-suffix-classes, $state); - - // generate classes : - @if $classes == true and $context { - - // get specials columns : - $columnsMap : map-merge((), $_gridle_columns); - - // register each default columns : - @for $j from 0 through $context { - - // name : - $columnName : "#{$j*$name-multiplicator}"; - $columnWidth : $j * $name-multiplicator; - - // // create a column : - $col : _gridle_create_column($columnName, $columnWidth, $context, $name-multiplicator); - - // // add column in columns map : - $columnsMap : map-set($columnsMap, $columnName, $col); - } - - // loop on each columns to generate common selector : - @each $columnName, $column in $columnsMap { - - // add selector : - @if $what == null or index($what, grid) or index($what, default) { - $grid-common-selector : append( $grid-common-selector, unquote("#{_gridle_classname($gridle-grid-name-pattern, $media, $columnName)}"), comma ); - } - @if $generate-push-classes and ($what == null or index($what, push) or index($what, default)) { - $push-common-selector : append( $push-common-selector, unquote("#{_gridle_classname($gridle-push-name-pattern, $media, $columnName)}"), comma ); - } - @if $generate-pull-classes and ($what == null or index($what, pull) or index($what, default)) { - $pull-common-selector : append( $pull-common-selector, unquote("#{_gridle_classname($gridle-pull-name-pattern, $media, $columnName)}"), comma ); - } - @if $generate-prefix-classes and ($what == null or index($what, prefix) or index($what, default)) { - $prefix-common-selector : append( $prefix-common-selector, unquote("#{_gridle_classname($gridle-prefix-name-pattern, $media, $columnName)}"), comma ); - } - @if $generate-suffix-classes and ($what == null or index($what, suffix) or index($what, default)) { - $suffix-common-selector : append( $suffix-common-selector, unquote("#{_gridle_classname($gridle-suffix-name-pattern, $media, $columnName)}"), comma ); - } - } - } - } - - // common css : - @if $what == null or index($what, grid) or index($what, default) { - #{$grid-common-selector} { - @include _gridle_grid_common(); - } - } - @if $what == null or index($what, push) or index($what, default) { - #{$push-common-selector} { - @include _gridle_push_common(); - } - } - @if $what == null or index($what, pull) or index($what, default) { - #{$pull-common-selector} { - @include _gridle_pull_common(); - } - } - @if $what == null or index($what, prefix) or index($what, default) { - #{$prefix-common-selector} { - @include _gridle_prefix_common(); - } - } - @if $what == null or index($what, suffix) or index($what, default) { - #{$suffix-common-selector} { - @include _gridle_suffix_common(); - } - } - - - // | ------------------------ - // | Media queries classes - // | ------------------------ - - // generate all classes for differents media queries : - @each $stateName, $state in $states { - - // setup vars : - $classes : _gridle_get_var_value(classes, $state); - $context : _gridle_get_var_value(context, $state); - $name-multiplicator : _gridle_get_var_value(name-multiplicator, $state); - $generate-push-classes : _gridle_get_var_value(generate-push-classes, $state); - $generate-pull-classes : _gridle_get_var_value(generate-pull-classes, $state); - $generate-prefix-classes : _gridle_get_var_value(generate-prefix-classes, $state); - $generate-suffix-classes : _gridle_get_var_value(generate-suffix-classes, $state); - $generate-helpers-classes : _gridle_get_var_value(generate-helpers-classes, $state); - - // generate all media queries grid classes : - @if $classes == true { - - // parent common css : - $parentSelector : _gridle_classname($gridle-parent-name-pattern,$stateName,null); - #{$parentSelector} { - @extend %gridle-clearfix; - @extend %gridle-parent-common; - } - - // generate all the classes : - @include gridle_state($stateName, $has-parent) { - - // get specials columns : - $columnsMap : map-merge((), $_gridle_columns); - - // register each default columns : - @for $j from 0 through $context { - - // name : - $columnName : "#{$j*$name-multiplicator}"; - $columnWidth : $j * $name-multiplicator; - - // // create a column : - $col : _gridle_create_column($columnName, $columnWidth, $context, $name-multiplicator); - - // // add column in columns map : - $columnsMap : map-set($columnsMap, $columnName, $col); - } - - // generate all classes for columns : - @each $columnName, $column in $columnsMap { - - // variables : - $columnsCount : map-get($column, columns); - $columnsContext : map-get($column, context); - $columnsNameMultiplicator : map-get($column, name-multiplicator); - - // extend context in state (for columns) : - $extendedState : map-merge($state, ( - context : $columnsContext, - name-multiplicator : $columnsNameMultiplicator // inject the name multiplicator here getted from column to handle custom registered columns - )); - - // classes : - @if $what == null or index($what, grid) or index($what, default) { - #{_gridle_classname($gridle-grid-name-pattern, $stateName, $columnName)} { - @include _gridle($columnsCount, $extendedState); - } - } - @if $generate-push-classes == true and ($what == null or index($what, push) or index($what, default)) { - #{_gridle_classname($gridle-push-name-pattern, $stateName, $columnName)} { - @include _gridle_push($columnsCount, $extendedState); - } - } - @if $generate-pull-classes == true and ($what == null or index($what, pull) or index($what, default)) { - #{_gridle_classname($gridle-pull-name-pattern, $stateName, $columnName)} { - @include _gridle_pull($columnsCount, $extendedState); - } - } - @if $generate-prefix-classes == true and ($what == null or index($what, prefix) or index($what, default)) { - #{_gridle_classname($gridle-prefix-name-pattern, $stateName, $columnName)} { - @include _gridle_prefix($columnsCount, $extendedState); - } - } - @if $generate-suffix-classes == true and ($what == null or index($what, suffix) or index($what, default)) { - #{_gridle_classname($gridle-suffix-name-pattern, $stateName, $columnName)} { - @include _gridle_suffix($columnsCount, $extendedState); - } - } - } - - // media queries helpers classes : - @if $generate-helpers-classes == true { - @include _gridle_generate_helper_classes($stateName, $what); - } - } - } - } -} \ No newline at end of file diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/gridle/gridle/_gridle.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/gridle/gridle/_gridle.scss deleted file mode 100644 index cddbe1376..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/gridle/gridle/_gridle.scss +++ /dev/null @@ -1,141 +0,0 @@ -// |------------------------------------------------------ -// |------------------------------------------------------ -// Gridle (.scss) -// Gridle is an one and unique grid system file that allows you to generate almost all -// grid you've ever dreamt about. -// |------------------------------------------------------ -// |------------------------------------------------------ - -// |------------------------------------------------------ -// |------------------------------------------------------ -// Copyright (c) 2014 Olivier Bossel - -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -// documentation files (the "Software"), to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -// and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -// The above copyright notice and this permission notice shall be included in all copies or substantial portions -// of the Software. - -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF -// CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -// |------------------------------------------------------ -// |------------------------------------------------------ - -// |------------------------------------------------------ -// |------------------------------------------------------ -// @created 25.03.13 -// @updated 09.06.15 -// @author Olivier Bossel -// @version 1.3.40 -// |------------------------------------------------------ -// |------------------------------------------------------ - -$_gridle-version : "1.3.40"; - - - - -// |------------------------------------------------------ -// |------------------------------------------------------ -// | Variables -// |------------------------------------------------------ -// |------------------------------------------------------ - -$_gridle_settings : (); // the default settings -$_gridle_states : (); // the variable map for each states -$_gridle_clear_classes :(); // store each automatic clear count -$_gridle_columns : (); // store the registered special columns -$_gridle_states_debug_devices : (); // save the debug states devices - - - - -// |------------------------------------------------------ -// |------------------------------------------------------ -// | Settings -// |------------------------------------------------------ -// |------------------------------------------------------ - -@import 'settings'; - - - - -// |------------------------------------------------------ -// |------------------------------------------------------ -// | Silent classes -// |------------------------------------------------------ -// |------------------------------------------------------ - -@import 'silent-classes'; - - - - -// |------------------------------------------------------ -// |------------------------------------------------------ -// | Common mixins -// |------------------------------------------------------ -// |------------------------------------------------------ - -@import 'common-mixins'; - - - - -// |------------------------------------------------------ -// |------------------------------------------------------ -// | Functions -// |------------------------------------------------------ -// |------------------------------------------------------ - -@import 'functions'; - - - - -// |------------------------------------------------------ -// |------------------------------------------------------ -// | Settings mixins -// |------------------------------------------------------ -// |------------------------------------------------------ - -@import 'settings-mixins'; - - - - -// |------------------------------------------------------ -// |------------------------------------------------------ -// | Mixins -// |------------------------------------------------------ -// |------------------------------------------------------ - -@import 'mixins'; - - - - -// |------------------------------------------------------ -// |------------------------------------------------------ -// | Generate mixins -// |------------------------------------------------------ -// |------------------------------------------------------ - -@import 'generate-mixins'; - - - - -// |------------------------------------------------------ -// |------------------------------------------------------ -// | Default states -// |------------------------------------------------------ -// |------------------------------------------------------ - -@import 'default-states'; diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/gridle/gridle/_mixins.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/gridle/gridle/_mixins.scss deleted file mode 100644 index b4e440443..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/gridle/gridle/_mixins.scss +++ /dev/null @@ -1,831 +0,0 @@ -// |------------------------------------------------------ -// |------------------------------------------------------ -// | Mixins -// |------------------------------------------------------ -// |------------------------------------------------------ - -// Helper to apply multiple config for a certain state with one mixin -@mixin gridle_set( - $settings, - $state : default -) { - - // loop on each settings - @each $settingName, $settingValue in $settings - { - $sn : unquote("#{$settingName}"); - $sv : $settingValue; - - // check if setting name is a state : - @if _gridle_has_state($sn) { - // process the state - @include gridle_set($sv, $sn); - } @else { - @if $sn == container { - @include gridle_container($state); - } @else if $sn == grid { - @include gridle($sv, $state); - } @else if $sn == push { - @include gridle_push($sv, $state); - } @else if $sn == pull { - @include gridle_pull($sv, $state); - } @else if $sn == prefix { - @include gridle_prefix($sv, $state); - } @else if $sn == suffix { - @include gridle_suffix($sv, $state); - } @else if $sn == pull { - @include gridle_pull($sv, $state); - } @else if $sn == clear_each { - @include gridle_clear_each(nth($sv,1), nth($sv,2), $state); - } @else if $sn == centered { - @include gridle_centered($state); - } @else if $sn == parent { - @include gridle_parent($state); - } @else if $sn == vertical_align { - @include gridle_vertical_align($sv, $state); - } @else if $sn == hide { - @if $sv == true { - @include gridle_hide($state); - } @else { - @include gridle_show($state); - } - } @else if $sn == show { - @if $sv == true { - @include gridle_show($state); - } @else { - @include gridle_hide($state); - } - } @else if $sn == visible { - @if $sv == true { - @include gridle_visible($state); - } @else { - @include gridle_not_visible($state); - } - } @else if $sn == not_visible { - @if $sv == true { - @include gridle_not_visible($state); - } @else { - @include gridle_visible($state); - } - } @else if $sn == show_inline { - @if $sv == true { - @include gridle_show_inline($state); - } @else { - @include gridle_hide($state); - } - } @else if $sn == float { - @include gridle_float($sv, $state); - } @else if $sn == clear { - @include gridle_clear($sv, $state); - } @else if $sn == no_gutter - or $sn == no_margin { - @include gridle_no_gutter($sv, $state); - } @else if $sn == gutter - or $sn == margin { - @include gridle_gutter($sv, $state); - } @else { - // we do nothing - } - } - } -} - -// Responsive helpers mixins : -@mixin gridle_state( - $states, - $has-parent : true -) { - - // check first param if is a state : - $firstState : nth($states,1); - @if _gridle_has_state($firstState) { - - // loop on each states : - @each $state in $states - { - // variables : - $html-states-classes : _gridle_get_var_value(html-states-classes, $state); - $debug : _gridle_get_var_value(debug, $state); - $stateName : _gridle_get_var_value(name, $state); - - // check if is a state : - @if ($html-states-classes or $debug) - and $stateName { - // html class : - @if $has-parent { - html#{_gridle_classname("#{$stateName}")} & { @content; } - } @else { - html#{_gridle_classname("#{$stateName}")} { @content; } - } - } - - // get the media query : - $q : _gridle_get_media_query($state); - - // make the media query if a query exist : - @if $q { - @media #{$q} { - @content; - } - } - @else - { - @content; - } - } - - } @else { - - // variables : - $html-states-classes : _gridle_get_var_value(html-states-classes, $states); - $debug : _gridle_get_var_value(debug, $states); - $stateName : _gridle_get_var_value(name, $states); - - // check if is a state : - @if ($html-states-classes or $debug) - and $stateName { - // html class : - @if $has-parent { - html#{_gridle_classname("#{$stateName}")} & { @content; } - } @else { - html#{_gridle_classname("#{$stateName}")} { @content; } - } - } - - // get the media query : - $q : _gridle_get_media_query($states); - - // make the media query if a query exist : - @if $q { - @media #{$q} { - @content; - } - } - @else - { - @content; - } - - } -} - - - -// Container mixin : -@mixin gridle_container( - $state : default -) { - @include _gridle_container_common($state); -} - - -// Grid mixin : -// Set the width of the specified grid column : -@mixin gridle( - $columns, - $state-or-context : default, - $state : default -) { - // manage state and context : - $context : null; - @if type-of($state-or-context) == number { - $context : $state-or-context; - } @else { - $state : $state-or-context; - } - - // common : - @include _gridle_grid_common(); - // check if need media query : - @if $state { - @include gridle_state($state) { - @include _gridle($columns, $state, $context); - } - } @else { - @include _gridle($columns, $state, $context); - } -} -@mixin _gridle( - $columns, - $state : default, - $context : null -) { - // vars : - $name : _gridle_get_var_value(name, $state); - @if type-of($context) != number { - $context : _gridle_get_var_value(context, $state); - } - $name-multiplicator : _gridle_get_var_value(name-multiplicator, $state); - $gutter-width : _gridle_get_var_value(gutter-width, $state); - $ie7-support : _gridle_get_var_value(ie7-support, $state); - $debug : _gridle_get_var_value(debug, $state); - $debug-show-class-names : _gridle_get_var_value(debug-show-class-names, $state); - - // manage columns - @if type-of($columns) != number and map-has-key($_gridle_columns, $columns) { - // the columns is a saved one, get the context and column value - $column : map-get($_gridle_columns, $columns); - $context : map-get($column, context); - $columns : map-get($column, columns); - } @else if type-of($columns) == number { - $columns : $columns / $name-multiplicator; - } @else { - @error "the column #{$columns} does not exist..."; - } - - // vars : - $width : percentage(1 / $context * $columns); - - // set value : - width:$width; - - // ie7 support : - @if $ie7-support == true { - *width: expression((this.parentNode.clientWidth/#{$context}*#{($columns / $name-multiplicator)} - parseInt(this.currentStyle['paddingLeft']) - parseInt(this.currentStyle['paddingRight'])) + 'px'); - } - - // debug : - @if $debug == true and $debug-show-class-names == true { - #{$gridle-debug-selector} { - &:before { - @if $name == default { - content:"grid-#{$name}-#{$columns}"; - } @else { - content:"grid-#{$name}-#{$columns}" !important; - } - } - &.parent:before { - @if $name == default { - content:"grid-parent-#{$name}-#{$columns}"; - } @else { - content:"grid-parent-#{$name}-#{$columns}" !important; - } - } - } - } -} - - -// push : -// Push the element of the count of column wanted -@mixin gridle_push( - $columns, - $state-or-context : default, - $state : default -) { - // manage state and context - $context : null; - @if type-of($state-or-context) == number { - $context : $state-or-context; - } @else { - $state : $state-or-context; - } - - // common : - @include _gridle_push_common($state); - - // check if need media query : - @if $state { - @include gridle_state($state) { - @include _gridle_push($columns, $state, $context); - } - } @else { - @include _gridle_push($columns, $state, $context); - } -} -@mixin _gridle_push( - $columns, - $state : default, - $context : null -) { - // variables : - $name : _gridle_get_var_value(name, $state); - @if type-of($context) != number { - $context : _gridle_get_var_value(context, $state); - } - $direction : _gridle_get_var_value(direction, $state); - $name-multiplicator : _gridle_get_var_value(name-multiplicator, $state); - $debug : _gridle_get_var_value(debug, $state); - $debug-show-class-names : _gridle_get_var_value(debug-show-class-names, $state); - - // vars : - $width : percentage(1 / $context) * ($columns / $name-multiplicator); - @if $direction == rtl { $width : $width*-1; } - left:$width; - - // debug css : - @if $debug == true and $debug-show-class-names == true { - #{$gridle-debug-selector} { - &:after { - @if $name == default { - content:"push-#{$name}-#{$columns}"; - } @else { - content:"push-#{$name}-#{$columns}" !important; - } - } - } - } - -} - - -// pull : -// Pull the element of the count of column wanted -@mixin gridle_pull( - $columns, - $state : default -) { - // common : - @include _gridle_pull_common($state); - - // check if need media query : - @if $state { - @include gridle_state($state) { - @include _gridle_pull($columns,$state); - } - } @else { - @include _gridle_pull($columns,$state); - } -} -@mixin _gridle_pull( - $columns, - $state : default -) { - // vars : - $name : _gridle_get_var_value(name, $state); - $context : _gridle_get_var_value(context, $state); - $direction : _gridle_get_var_value(direction, $state); - $name-multiplicator : _gridle_get_var_value(name-multiplicator, $state); - $debug : _gridle_get_var_value(debug, $state); - $debug-show-class-names : _gridle_get_var_value(debug-show-class-names, $state); - - // vars : - $width : percentage(1 / $context) * ($columns / $name-multiplicator); - @if $direction == rtl { $width : $width*-1; } - right:$width; - - // debug css : - @if $debug == true and $debug-show-class-names == true { - #{$gridle-debug-selector} { - &:after { - @if $name == default { - content:"pull-#{$name}-#{$columns}"; - } @else { - content:"pull-#{$name}-#{$columns}" !important; - } - } - } - } -} - - -// push : -// Push the element of the count of column wanted -@mixin gridle_prefix( - $columns, - $state : default -) { - // common : - @include _gridle_prefix_common($state); - - // check if need media query : - @if $state { - @include gridle_state($state) { - @include _gridle_prefix($columns,$state); - } - } @else { - @include _gridle_prefix($columns,$state); - } -} -@mixin _gridle_prefix( - $columns, - $state : default -) { - // vars : - $name : _gridle_get_var_value(name, $state); - $context : _gridle_get_var_value(context, $state); - $direction : _gridle_get_var_value(direction, $state); - $gutter-width : _gridle_get_var_value(gutter-width, $state); - $name-multiplicator : _gridle_get_var_value(name-multiplicator, $state); - $debug : _gridle_get_var_value(debug, $state); - $debug-show-class-names : _gridle_get_var_value(debug-show-class-names, $state); - - // vars : - $width : percentage(1 / $context) * ($columns / $name-multiplicator); - - // set value : - @if $direction == rtl { margin-right:$width; } - @else { margin-left:$width; } - - // debug css : - @if $debug == true and $debug-show-class-names == true { - #{$gridle-debug-selector} { - &:after { - @if $name == default { - content:"prefix-#{$name}-#{$columns}"; - } @else { - content:"prefix-#{$name}-#{$columns}" !important; - } - } - } - } -} - - -// pull : -// Pull the element of the count of column wanted -@mixin gridle_suffix( - $columns, - $state : default -) { - // common : - @include _gridle_suffix_common($state); - - // check if need media query : - @if $state { - @include gridle_state($state) { - @include _gridle_suffix($columns,$state); - } - } @else { - @include _gridle_suffix($columns,$state); - } -} -@mixin _gridle_suffix( - $columns, - $state : default -) { - // vars : - $name : _gridle_get_var_value(name, $state); - $context : _gridle_get_var_value(context, $state); - $direction : _gridle_get_var_value(direction, $state); - $gutter-width : _gridle_get_var_value(gutter-width, $state); - $name-multiplicator : _gridle_get_var_value(name-multiplicator, $state); - $debug : _gridle_get_var_value(debug, $state); - $debug-show-class-names : _gridle_get_var_value(debug-show-class-names, $state); - - // vars : - $width : percentage(1 / $context) * ($columns / $name-multiplicator); - - // set value : - @if $direction == rtl { margin-left:$width; } - @else { margin-right:$width; } - - // debug css : - @if $debug == true and $debug-show-class-names == true { - #{$gridle-debug-selector} { - &:after { - @if $name == default { - content:"suffix-#{$name}-#{$columns}"; - } @else { - content:"suffix-#{$name}-#{$columns}" !important; - } - } - } - } -} - - -// grid background : -// Display the grid background debug -@mixin gridle_grid_background( - $state : default -) { - // check if need media query : - @if $state { - @include gridle_state($state) { - @include _gridle_grid_background($state); - } - } @else { - @include _gridle_grid_background($state); - } -} -@mixin _gridle_grid_background( - $state : default -) { - - // variables : - $context : _gridle_get_var_value(context, $state); - - position:relative; - z-index:9999; - &:before { - content:''; - position:absolute; - top:0; left:0; - width:100%; height:100% !important; - // vars : - $width : percentage(1 / $context); - background: linear-gradient(to right, rgba(0,0,0,.01) 50% , rgba(0,0,0,.04) 50%); /* Standard syntax */ - background-size:($width*2) 100%; - // background-position:$gridle-gutter-width/2 0; - } -} - - -/** - * Parent clear each - */ -// Grid mixin : -// Set the width of the specified grid column : -@mixin gridle_clear_each( - $clearEach, - $clearWhat : both, - $state : default -) { - // check if need media query : - @if $state { - @include gridle_state($state) { - @include _gridle_clear_each($clearEach, $clearWhat); - } - } @else { - @include _gridle_clear_each($clearEach, $clearWhat); - } -} -@mixin _gridle_clear_each( - $clearEach, - $clearWhat -) { - > *:nth-child(#{$clearEach}n+1) { - clear : $clearWhat; - } -} - - -// Grid centered : -@mixin gridle_centered( - $state : default -) { - // check if need media query : - @if $state { - @include gridle_state($state) { - @include _gridle_centered(); - } - } @else { - @include _gridle_centered(); - } -} -@mixin _gridle_centered() { - display:block !important; - float:none !important; - margin-left:auto !important; - margin-right:auto !important; - clear:both !important; -} - - -// Grid parent : -@mixin gridle_parent( - $state : default -) { - // common : - @include _gridle_parent_common(); - // check if need media query : - @if $state { - @include gridle_state($state) { - @include _gridle_parent(); - } - } @else { - @include _gridle_parent(); - } -} -@mixin _gridle_parent() { - @include gridle_no_gutter(); -} - - -/** - * Vertical align : - */ -@mixin gridle_vertical_align( - $align : middle, - $state : default -) { - // check if need media query : - @if $state { - @include gridle_state($state) { - @include _gridle_vertical_align($align); - } - } @else { - @include _gridle_vertical_align($align); - } -} -@mixin _gridle_vertical_align( - $align : middle -) { - font-size:0; - clear:both; - - > * { - display:inline-block; - float:none !important; - vertical-align:$align; - font-size:1rem; - } -} - - -// Hide on : -// @param String $media On what state -@mixin gridle_hide( - $state : default -) { - // check if need media query : - @if $state { - @include gridle_state($state) { - @include _gridle_hide(); - } - } @else { - @include _gridle_hide(); - } -} -@mixin _gridle_hide() { - display:none; -} - - -// Not visible on : -// @param String $media What to hide (one of the 3 state classes name) -@mixin gridle_not_visible( - $state : default -) { - // check if need media query : - @if $state { - @include gridle_state($state) { - @include _gridle_not_visible(); - } - } @else { - @include _gridle_not_visible(); - } -} -@mixin _gridle_not_visible() { - visibility:hidden; -} - - -// Show on -// @param String $media What to hide (one of the 3 state classes name) -@mixin gridle_show( - $state : default -) { - // check if need media query : - @if $state { - @include gridle_state($state) { - @include _gridle_show(); - } - } @else { - @include _gridle_show(); - } -} -@mixin _gridle_show() { - display:block; -} - - -/** - * Show inline - * - * @param String $state The state name - */ -@mixin gridle_show_inline( - $state : default -) { - // check if need media query : - @if $state { - @include gridle_state($state) { - @include _gridle_show_inline(); - } - } @else { - @include _gridle_show_inline(); - } -} -@mixin _gridle_show_inline() { - display:inline-block; -} - - -// Visible on : -// @param String $media On what state -@mixin gridle_visible( - $state : default -) { - // check if need media query : - @if $state { - @include gridle_state($state) { - @include _gridle_visible(); - } - } @else { - @include _gridle_visible(); - } -} -@mixin _gridle_visible() { - visibility:visible; -} - - -// Gridle Right : -@mixin gridle_float( - $float-direction : left, - $state : default -) { - // check if need media query : - @if $state { - @include gridle_state($state) { - @include _gridle_float($float-direction); - } - } @else { - @include _gridle_float($float-direction); - } -} -@mixin _gridle_float( - $float-direction : left -) { - float:#{$float-direction}; -} - - -// Gridle clear : -// @param String $clear-direction The direction to clear -// @param String $state The state -@mixin gridle_clear( - $clear-direction : both, - $state : default -) { - // check if need media query : - @if $state { - @include gridle_state($state) { - @include _gridle_clear($clear-direction); - } - } @else { - @include _gridle_clear($clear-direction); - } -} -@mixin _gridle_clear( - $clear-direction : both -) { - clear:#{$clear-direction}; -} - - -// Gridle no gutter : -// @param String $side The side to clear -// @param String $state The state -@mixin gridle_no_gutter( - $side : left right, - $state : default -) { - // check if need media query : - @if $state { - @include gridle_state($state) { - @include _gridle_no_gutter($side); - } - } @else { - @include _gridle_no_gutter($side); - } -} -@mixin gridle_no_margin( - $side : left right, - $state : default -) { - @include gridle_no_gutter($side, $state); -} -@mixin _gridle_no_gutter( - $side : left right -) { - @each $s in $side { - padding-#{$s} : 0; - } -} - - -// Gridle gutter : -// @param String $side The side to clear -// @param String $state The state -@mixin gridle_gutter( - $side : left right, - $state : default -) { - // check if need media query : - @if $state { - @include gridle_state($state) { - @include _gridle_gutter($side); - } - } @else { - @include _gridle_gutter($side); - } -} -// shortcut : -@mixin gridle_margin( - $side : left right, - $state : default -) { - @include gridle_gutter($side, $state); -} -@mixin _gridle_gutter( - $side : left right, - $state : default -) { - $gutter-width : _gridle_get_var_value(gutter-width, $state); - @each $s in $side { - padding-#{$s} : $gutter-width / 2; - } -} \ No newline at end of file diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/gridle/gridle/_settings-mixins.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/gridle/gridle/_settings-mixins.scss deleted file mode 100644 index bfd2df859..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/gridle/gridle/_settings-mixins.scss +++ /dev/null @@ -1,139 +0,0 @@ -// |------------------------------------------------------ -// |------------------------------------------------------ -// | Settings mixins -// |------------------------------------------------------ -// |------------------------------------------------------ - -/** - * Setup - */ -@mixin gridle_setup( - $settings : () -) { - $_gridle-settings : map-merge(( - name : default, - min-width : null, - max-width : null, - query : null, - classes : true, - context : 12, - gutter-width : 20px, - direction : ltr, - name-multiplicator : 1, - debug : false, - debug-show-class-names : true, - ie7-support : false, - html-states-classes : false, - generate-push-classes : true, - generate-pull-classes : true, - generate-prefix-classes : true, - generate-suffix-classes : true, - generate-helpers-classes : true - ), $settings) !global; - - // register default state : - @include gridle_register_state(default, $_gridle-settings); - -} - -// Register an state : -@mixin gridle_register_state( - $name, - $settings -) { - // settings : - $settings : map-merge($_gridle-settings, $settings); - - // set name : - $settings : map-set($settings, name, $name); - - // add state in maps : - $_gridle_states : map-set($_gridle_states, $name, $settings) !global; -} - - -/** - * Register a clear each class - */ -@mixin gridle_register_clear_each( - $count, - $clearWhat -) { - // create the clear map : - $classMap : ( - clearEach : $count, - clearWhat : $clearWhat - ); - - // append to map : - $_gridle_clear_classes : map-set($_gridle_clear_classes, $count, $classMap) !global; -} - - -/** - * Register a special class - */ -@mixin gridle_register_column( - $name, - $columns, - $context -) { - // create a column : - $col : _gridle_create_column($name, $columns, $context); - - // add column in maps : - $_gridle_columns : map-set($_gridle_columns, $name, $col) !global; -} - - -/** - * Register default states - */ -@mixin gridle_register_default_states() { - @include gridle_register_state(mobile, ( - max-width : 480px - )); - @include gridle_register_state(tablet, ( - min-width : 481px, - max-width : 1024px - )); -} - - -/** - * Register default mobile first states : - */ -@mixin gridle_register_default_mobile_first_states() { - @include gridle_register_state(xs, ( - max-width : 768px - )); - @include gridle_register_state(sm, ( - min-width : 768px - )); - @include gridle_register_state(md, ( - min-width : 992px - )); - @include gridle_register_state(lg, ( - min-width : 1200px - )); -} - - -/** - * Set the debug device (not used for now) - * - * @param String $state The state to update - * @para m String $device The device to use (iphone, etc...) - */ -@mixin gridle_set_debug_device( - $state : default, - $device : null -) { - - // check params : - @if $state and $device { - // set the state device : - $_gridle_states_debug_devices : append($_gridle_states_debug_devices, unquote("\"#{$state}\" : \"#{$device}\""), comma); - } - -} \ No newline at end of file diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/gridle/gridle/_settings.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/gridle/gridle/_settings.scss deleted file mode 100644 index 991c35154..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/gridle/gridle/_settings.scss +++ /dev/null @@ -1,69 +0,0 @@ -// |------------------------------------------------------ -// |------------------------------------------------------ -// | Settings -// |------------------------------------------------------ -// |------------------------------------------------------ - -$gridle-generate-json-settings : true !default; - -$gridle-debug-selector : ".gridle-debug &, &.gridle-debug" !default; - -$gridle-class-prefix : '' !default; -$gridle-class-separator : '-' !default; - -$gridle-container-name-pattern : ('container') !default; - -$gridle-grid-name-pattern : ('grid','%-','%state','%-','%count') !default; -$gridle-push-name-pattern : ('push','%-','%state','%-','%count') !default; -$gridle-pull-name-pattern : ('pull','%-','%state','%-','%count') !default; -$gridle-prefix-name-pattern : ('prefix','%-','%state','%-','%count') !default; -$gridle-suffix-name-pattern : ('suffix','%-','%state','%-','%count') !default; - -$gridle-parent-name-pattern : ('parent','%-','%state') !default; -$gridle-centered-name-pattern : ('centered','%-','%state') !default; - -$gridle-vertical-align-middle-name-pattern : ('vertical','%-','align','%-','%state','%-','middle') !default; -$gridle-vertical-align-top-name-pattern : ('vertical','%-','align','%-','%state','%-','top') !default; -$gridle-vertical-align-bottom-name-pattern : ('vertical','%-','align','%-','%state','%-','bottom') !default; - -$gridle-hide-name-pattern : ('hide','%-','%state') !default; -$gridle-show-name-pattern : ('show','%-','%state') !default; -$gridle-show-inline-name-pattern : ('show','%-','inline','%-','%state') !default; -$gridle-not-visible-name-pattern : ('not','%-','visible','%-','%state') !default; -$gridle-visible-name-pattern : ('visible','%-','%state') !default; - -$gridle-float-left-name-pattern : ('float','%-','%state','%-','left') !default; -$gridle-float-right-name-pattern : ('float','%-','%state','%-','right') !default; - -$gridle-clear-name-pattern : ('clear','%-','%state') !default; -$gridle-clear-left-name-pattern : ('clear','%-','%state','%-','left') !default; -$gridle-clear-right-name-pattern : ('clear','%-','%state','%-','right') !default; -$gridle-clear-each-pattern : ('clear','%-','each','%-','%state','%-','%count') !default; - -$gridle-no-gutter-name-pattern : ('no','%-','gutter','%-','%state') !default; -$gridle-no-gutter-left-name-pattern : ('no','%-','gutter','%-','%state','%-','left') !default; -$gridle-no-gutter-right-name-pattern : ('no','%-','gutter','%-','%state','%-','right') !default; -$gridle-no-gutter-top-name-pattern : ('no','%-','gutter','%-','%state','%-','top') !default; -$gridle-no-gutter-bottom-name-pattern : ('no','%-','gutter','%-','%state','%-','bottom') !default; - -$gridle-no-margin-name-pattern : ('no','%-','margin','%-','%state') !default; -$gridle-no-margin-left-name-pattern : ('no','%-','margin','%-','%state','%-','left') !default; -$gridle-no-margin-right-name-pattern : ('no','%-','margin','%-','%state','%-','right') !default; -$gridle-no-margin-top-name-pattern : ('no','%-','margin','%-','%state','%-','top') !default; -$gridle-no-margin-bottom-name-pattern : ('no','%-','margin','%-','%state','%-','bottom') !default; - -$gridle-gutter-name-pattern : ('gutter','%-','%state') !default; -$gridle-gutter-left-name-pattern : ('gutter','%-','%state','%-','left') !default; -$gridle-gutter-right-name-pattern : ('gutter','%-','%state','%-','right') !default; -$gridle-gutter-top-name-pattern : ('gutter','%-','%state','%-','top') !default; -$gridle-gutter-bottom-name-pattern : ('gutter','%-','%state','%-','bottom') !default; - -$gridle-margin-name-pattern : ('margins','%-','%state') !default; -$gridle-margin-left-name-pattern : ('margin','%-','%state','%-','left') !default; -$gridle-margin-right-name-pattern : ('margin','%-','%state','%-','right') !default; -$gridle-margin-top-name-pattern : ('margin','%-','%state','%-','top') !default; -$gridle-margin-bottom-name-pattern : ('margin','%-','%state','%-','bottom') !default; - -$gridle-auto-height-name-pattern : ('auto','%-','height','%-','%state') !default; - -$gridle-debug-color-name-pattern : ('debug','%-','color','%-','%state','%-','%count') !default; \ No newline at end of file diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/gridle/gridle/_silent-classes.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/gridle/gridle/_silent-classes.scss deleted file mode 100644 index da72abc58..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/gridle/gridle/_silent-classes.scss +++ /dev/null @@ -1,106 +0,0 @@ -// |------------------------------------------------------ -// |------------------------------------------------------ -// | Silent classes -// |------------------------------------------------------ -// |------------------------------------------------------ - -%gridle-pie-clearfix { - &:after { - content: "."; - display: block; - clear: both; - visibility: hidden; - line-height: 0; - height: 0; - } - - & { - display: inline-block; - } - - html[xmlns] & { - display: block; - } - - * html & { - height: 1%; - } -} -%gridle-simple-clearfix { - &:after { - content: ""; - display: table; - clear: both; - border-spacing:0; - } -} -%gridle-clearfix { - // For modern browser - &:before, - &:after { - content:""; - display:table; - border-spacing:0; - } - &:after { - clear:both; - } - // For IE 6/7 (trigger hasLayout - & { - zoom:1; - } -} -%gridle-push-pull-debug-background-common { - background-size:50px 90%; - background-position:0 50%; - background-repeat:repeat-x; -} -%gridle-push-pull-common { - position:relative; -} -%gridle-container-common { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} -%gridle-parent-common { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} -%gridle-container-debug-common { - background-color:#f5f5f5; -} -%gridle-grid-debug-common { - &:before, - &:after { - content:""; - display:block; - background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDowRkEzNzVFNTg1NjgxMUUyOUI4RjhEMzg4QzM4QjZFOCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDowRkEzNzVFNjg1NjgxMUUyOUI4RjhEMzg4QzM4QjZFOCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjBGQTM3NUUzODU2ODExRTI5QjhGOEQzODhDMzhCNkU4IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjBGQTM3NUU0ODU2ODExRTI5QjhGOEQzODhDMzhCNkU4Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8++5+BhQAAAA9JREFUeNpiYGBgkAIIMAAAHwAbZIBtGgAAAABJRU5ErkJggg==); - margin:10px 0; - padding:5px 0; - text-align:center; - color:white; - font-size:11px; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - display:block !important; - } - background-color:#daeff5; -} -%gridle-grid-common { - display:inline-block; - min-height:1px; - - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - -} -%gridle-prefix-debug-common { - background-color:#dae7e9 !important; -} -%gridle-suffix-debug-common { - background-color:#dae7e9 !important; -} \ No newline at end of file diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/gridle/style-bootstrap.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/gridle/style-bootstrap.scss deleted file mode 100644 index f2536ae07..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/gridle/style-bootstrap.scss +++ /dev/null @@ -1,11 +0,0 @@ -/** - * Bootstrap style : - */ -.row { - margin:20px 0; // adding margin to rows -} -.thumb { - background:#eee; - width:100%; height:0; - padding-bottom:56%; -} \ No newline at end of file diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/gridle/style.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/gridle/style.scss deleted file mode 100644 index 6cc1f299b..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/gridle/style.scss +++ /dev/null @@ -1,76 +0,0 @@ -/** - * Imports : - */ -@import 'compass/reset'; - -/** - * Import grid settings : - * This to be able to use gridle mixins, etc... - */ -@import 'grid-settings'; - -/** - * Medias : - */ -ul#medias { - background:black; - text-align:center; - margin-bottom:30px; - - li { - display:inline-block; - height:50px; - line-height:50px; - padding:0 30px; - color:white; - font-size:16px; - cursor:pointer; - margin:0; - - &.active, - &:hover { - background:white; - color:black; - } - } - - @include gridle_state(( - max-width : 620px - )) { - display:none; - } -} - -/** - * Basic formatting : - */ -html { - font:11px/1.5 'Helvetica Neue', Verdana, sans-serif; -} -body { - @include gridle_state(mobile tablet) { - text-align:center; - } - - @include gridle_state(ipad-landscape) { - font-size:16px; - } -} - - -/** - * Gridle set sample : - */ -#myCoolItem { - @include gridle_set(( - grid : 8, - push : 2, - tablet : ( - grid : 12, - push : 0 - ), - mobile : ( - visible : false - ) - )); -} \ No newline at end of file diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/gridle/tests.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/gridle/tests.scss deleted file mode 100644 index 970fca71b..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/gridle/tests.scss +++ /dev/null @@ -1,280 +0,0 @@ -// Import grid settings : -@import 'grid-settings'; - -// standard grid mixins : -.grid-12 { - @include gridle(12); -} -.grid-1on5 { - @include gridle("1on5"); -} -.grid-12-on-100 { - @include gridle(12, 100); -} -.grid-15-on-100-on-mobile { - @include gridle(15, 100, mobile); -} -.grid-30-on-100-on-mobile-and-tablet { - @include gridle(30, 100); - @include gridle(30, 100, mobile tablet); -} - -// parent -.grid-parent { - @include gridle_parent(); -} -.grid-parent-set { - @include gridle_set(( - parent : true - )); -} - -// clear each -.clear-each-2 { - @include gridle_clear_each(2); -} -.clear-each-2-left { - @include gridle_clear_each(2, left); -} -.clear-each-2-left-mobile { - @include gridle_clear_each(2, left, mobile); -} -.clear-each-2-set { - @include gridle_set(( - clear_each : ( 2, left ) - )); -} - -// centered -.centered { - @include gridle_centered(); -} -.centered-mobile { - @include gridle_centered(mobile); -} - -// certical align -.vertical-align { - @include gridle_vertical_align(); -} -.vertical-align-bottom { - @include gridle_vertical_align(bottom); -} -.vertical-align-top-mobile { - @include gridle_vertical_align(top, mobile); -} -.vertical-align-set { - @include gridle_set(( - vertical_align : middle - )); -} - -// push -.push-6 { - @include gridle_push(6); -} -.push-12-mobile { - @include gridle_push(12, mobile); -} -.push-set { - @include gridle_set(( - push : 3 - )); -} - -// pull -.pull-6 { - @include gridle_pull(6); -} -.pull-12-mobile { - @include gridle_pull(12, mobile); -} -.pull-set { - @include gridle_set(( - pull : 3 - )); -} - -// prefix -.prefix-6 { - @include gridle_prefix(6); -} -.prefix-12-mobile { - @include gridle_prefix(12, mobile); -} -.prefix-set { - @include gridle_set(( - prefix : 3 - )); -} - -// suffix -.suffix-6 { - @include gridle_suffix(6); -} -.suffix-12-mobile { - @include gridle_suffix(12, mobile); -} -.suffix-set { - @include gridle_set(( - suffix : 3 - )); -} - -// hide -.hide { - @include gridle_hide(); -} -.hide-mobile { - @include gridle_hide(mobile); -} -.hide-set { - @include gridle_set(( - hide : true - )); -} - -// show -.show { - @include gridle_show(); -} -.show-mobile { - @include gridle_show(mobile); -} -.show-set { - @include gridle_set(( - show : false - )); -} - -// show_inline -.show_inline { - @include gridle_show_inline(); -} -.show_inline-mobile { - @include gridle_show_inline(mobile); -} -.show_inline-set { - @include gridle_set(( - show_inline : true - )); -} - -// not_visible -.not_visible { - @include gridle_not_visible(); -} -.not_visible-mobile { - @include gridle_not_visible(mobile); -} -.not_visible-set { - @include gridle_set(( - not_visible : true - )); -} - -// visible -.visible { - @include gridle_visible(); -} -.visible-mobile { - @include gridle_visible(mobile); -} -.visible-set { - @include gridle_set(( - visible : false - )); -} - -// gridle state -.gridle-state { - background: red; - - @include gridle_state(mobile tablet) { - background: pink; - } - @include gridle_state(( - query : "only print" - )) { - background: yellow; - } - @include gridle_state(( - max-width : 200px - )) { - background: green; - } -} - -// gridle set -.gridle-set { - @include gridle_set(( - grid : 6, - push : 2, - tablet : ( - grid : 8, - push : 0, - ), - clear : left, - mobile : ( - grid : 12, - push : 0, - pull : 0 - ) - )); -} -.gridle-set-multiple { - @include gridle_set(( - grid : 6 - )); - @include gridle_set(( - grid : 12 - ), mobile tablet); -} - -// float-right -.float-right { - @include gridle_float(right); -} -.float-left-mobile { - @include gridle_float(left, mobile); -} -.float-set { - @include gridle_set(( - float : right - )); -} - -// clear -.clear { - @include gridle_clear(); -} -.clear-left-mobile { - @include gridle_clear(left, mobile); -} -.clear-set { - @include gridle_set(( - clear : left - )); -} - -// gutters -.gutters { - @include gridle_gutter(); -} -.gutters-left { - @include gridle_gutter(left); -} -.gutters-side-mobile { - @include gridle_gutter(left right, mobile); -} - -// no-gutter -.no-gutter { - @include gridle_no_gutter(); -} -.no-gutter-left { - @include gridle_no_gutter(left); -} -.no-gutter-side-mobile { - @include gridle_no_gutter(left right, mobile); -} \ No newline at end of file diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/normalize/_normalize.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/normalize/_normalize.scss deleted file mode 100644 index 5e5e3c898..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/contrib/normalize/_normalize.scss +++ /dev/null @@ -1,424 +0,0 @@ -/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */ - -/** - * 1. Set default font family to sans-serif. - * 2. Prevent iOS and IE text size adjust after device orientation change, - * without disabling user zoom. - */ - -html { - font-family: sans-serif; /* 1 */ - -ms-text-size-adjust: 100%; /* 2 */ - -webkit-text-size-adjust: 100%; /* 2 */ -} - -/** - * Remove default margin. - */ - -body { - margin: 0; -} - -/* HTML5 display definitions - ========================================================================== */ - -/** - * Correct `block` display not defined for any HTML5 element in IE 8/9. - * Correct `block` display not defined for `details` or `summary` in IE 10/11 - * and Firefox. - * Correct `block` display not defined for `main` in IE 11. - */ - -article, -aside, -details, -figcaption, -figure, -footer, -header, -hgroup, -main, -menu, -nav, -section, -summary { - display: block; -} - -/** - * 1. Correct `inline-block` display not defined in IE 8/9. - * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. - */ - -audio, -canvas, -progress, -video { - display: inline-block; /* 1 */ - vertical-align: baseline; /* 2 */ -} - -/** - * Prevent modern browsers from displaying `audio` without controls. - * Remove excess height in iOS 5 devices. - */ - -audio:not([controls]) { - display: none; - height: 0; -} - -/** - * Address `[hidden]` styling not present in IE 8/9/10. - * Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22. - */ - -[hidden], -template { - display: none; -} - -/* Links - ========================================================================== */ - -/** - * Remove the gray background color from active links in IE 10. - */ - -a { - background-color: transparent; -} - -/** - * Improve readability of focused elements when they are also in an - * active/hover state. - */ - -a:active, -a:hover { - outline: 0; -} - -/* Text-level semantics - ========================================================================== */ - -/** - * Address styling not present in IE 8/9/10/11, Safari, and Chrome. - */ - -abbr[title] { - border-bottom: 1px dotted; -} - -/** - * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. - */ - -b, -strong { - font-weight: bold; -} - -/** - * Address styling not present in Safari and Chrome. - */ - -dfn { - font-style: italic; -} - -/** - * Address variable `h1` font-size and margin within `section` and `article` - * contexts in Firefox 4+, Safari, and Chrome. - */ - -h1 { - font-size: 2em; - margin: 0.67em 0; -} - -/** - * Address styling not present in IE 8/9. - */ - -mark { - background: #ff0; - color: #000; -} - -/** - * Address inconsistent and variable font size in all browsers. - */ - -small { - font-size: 80%; -} - -/** - * Prevent `sub` and `sup` affecting `line-height` in all browsers. - */ - -sub, -sup { - font-size: 75%; - line-height: 0; - position: relative; - vertical-align: baseline; -} - -sup { - top: -0.5em; -} - -sub { - bottom: -0.25em; -} - -/* Embedded content - ========================================================================== */ - -/** - * Remove border when inside `a` element in IE 8/9/10. - */ - -img { - border: 0; -} - -/** - * Correct overflow not hidden in IE 9/10/11. - */ - -svg:not(:root) { - overflow: hidden; -} - -/* Grouping content - ========================================================================== */ - -/** - * Address margin not present in IE 8/9 and Safari. - */ - -figure { - margin: 1em 40px; -} - -/** - * Address differences between Firefox and other browsers. - */ - -hr { - box-sizing: content-box; - height: 0; -} - -/** - * Contain overflow in all browsers. - */ - -pre { - overflow: auto; -} - -/** - * Address odd `em`-unit font size rendering in all browsers. - */ - -code, -kbd, -pre, -samp { - font-family: monospace, monospace; - font-size: 1em; -} - -/* Forms - ========================================================================== */ - -/** - * Known limitation: by default, Chrome and Safari on OS X allow very limited - * styling of `select`, unless a `border` property is set. - */ - -/** - * 1. Correct color not being inherited. - * Known issue: affects color of disabled elements. - * 2. Correct font properties not being inherited. - * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. - */ - -button, -input, -optgroup, -select, -textarea { - color: inherit; /* 1 */ - font: inherit; /* 2 */ - margin: 0; /* 3 */ -} - -/** - * Address `overflow` set to `hidden` in IE 8/9/10/11. - */ - -button { - overflow: visible; -} - -/** - * Address inconsistent `text-transform` inheritance for `button` and `select`. - * All other form control elements do not inherit `text-transform` values. - * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. - * Correct `select` style inheritance in Firefox. - */ - -button, -select { - text-transform: none; -} - -/** - * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` - * and `video` controls. - * 2. Correct inability to style clickable `input` types in iOS. - * 3. Improve usability and consistency of cursor style between image-type - * `input` and others. - */ - -button, -html input[type="button"], /* 1 */ -input[type="reset"], -input[type="submit"] { - -webkit-appearance: button; /* 2 */ - cursor: pointer; /* 3 */ -} - -/** - * Re-set default cursor for disabled elements. - */ - -button[disabled], -html input[disabled] { - cursor: default; -} - -/** - * Remove inner padding and border in Firefox 4+. - */ - -button::-moz-focus-inner, -input::-moz-focus-inner { - border: 0; - padding: 0; -} - -/** - * Address Firefox 4+ setting `line-height` on `input` using `!important` in - * the UA stylesheet. - */ - -input { - line-height: normal; -} - -/** - * It's recommended that you don't attempt to style these elements. - * Firefox's implementation doesn't respect box-sizing, padding, or width. - * - * 1. Address box sizing set to `content-box` in IE 8/9/10. - * 2. Remove excess padding in IE 8/9/10. - */ - -input[type="checkbox"], -input[type="radio"] { - box-sizing: border-box; /* 1 */ - padding: 0; /* 2 */ -} - -/** - * Fix the cursor style for Chrome's increment/decrement buttons. For certain - * `font-size` values of the `input`, it causes the cursor style of the - * decrement button to change from `default` to `text`. - */ - -input[type="number"]::-webkit-inner-spin-button, -input[type="number"]::-webkit-outer-spin-button { - height: auto; -} - -/** - * 1. Address `appearance` set to `searchfield` in Safari and Chrome. - * 2. Address `box-sizing` set to `border-box` in Safari and Chrome. - */ - -input[type="search"] { - -webkit-appearance: textfield; /* 1 */ - box-sizing: content-box; /* 2 */ -} - -/** - * Remove inner padding and search cancel button in Safari and Chrome on OS X. - * Safari (but not Chrome) clips the cancel button when the search input has - * padding (and `textfield` appearance). - */ - -input[type="search"]::-webkit-search-cancel-button, -input[type="search"]::-webkit-search-decoration { - -webkit-appearance: none; -} - -/** - * Define consistent border, margin, and padding. - */ - -fieldset { - border: 1px solid #c0c0c0; - margin: 0 2px; - padding: 0.35em 0.625em 0.75em; -} - -/** - * 1. Correct `color` not being inherited in IE 8/9/10/11. - * 2. Remove padding so people aren't caught out if they zero out fieldsets. - */ - -legend { - border: 0; /* 1 */ - padding: 0; /* 2 */ -} - -/** - * Remove default vertical scrollbar in IE 8/9/10/11. - */ - -textarea { - overflow: auto; -} - -/** - * Don't inherit the `font-weight` (applied by a rule above). - * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. - */ - -optgroup { - font-weight: bold; -} - -/* Tables - ========================================================================== */ - -/** - * Remove most spacing between table cells. - */ - -table { - border-collapse: collapse; - border-spacing: 0; -} - -td, -th { - padding: 0; -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/custom/_address.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/custom/_address.scss deleted file mode 100644 index afe4e573f..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/custom/_address.scss +++ /dev/null @@ -1,20 +0,0 @@ -div.chill_address { - div.chill_address_address { - margin: 0.7em 0; - font-size: 98%; - font-variant: small-caps; - - p { - display: inline-block; - margin: 0 0 0 1.5em; - text-indent: -1.5em; - } - - &.chill_address_address--multiline { - p { - display: block; - } - } - - } -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/custom/_box.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/custom/_box.scss deleted file mode 100644 index aa539f6a1..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/custom/_box.scss +++ /dev/null @@ -1,24 +0,0 @@ -.chill__box { - font-variant: small-caps; - display: inline; - padding: .2em .6em .3em; - font-size: 0.88rem; - font-weight: bold; - line-height: 1; - text-align: center; - white-space: nowrap; - vertical-align: baseline; - border-radius: .25em; - color: white; - - &.green { - background-color: var(--chill-green); - color: white; - } - - &.red { - background-color: var(--chill-red); - color: white; - } -} - diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/custom/_custom-fields.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/custom/_custom-fields.scss deleted file mode 100644 index e71c75ea6..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/custom/_custom-fields.scss +++ /dev/null @@ -1,14 +0,0 @@ -/* the cf title will look like a `form legend h2` */ -span.cf-title { - display: block; - font-weight: 700; - border-bottom: 3px solid $light-grey; - margin-bottom: 1em; -} - -span.cf-subtitle { - display: block; - font-weight: 600; - border-bottom: 1px solid $light-grey; - margin-bottom: 1em; -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/custom/_flash_messages.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/custom/_flash_messages.scss deleted file mode 100644 index 62f875dfa..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/custom/_flash_messages.scss +++ /dev/null @@ -1,14 +0,0 @@ -.flash_message { - margin-top: 2.5em; -} - -// note that other level are defined in modules/_alerts.scss - -// .alert { -// // override in modules/_alerts.scss -// @include alert($red); -// } -// -// .warning { -// @include alert($orange); -// } diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/custom/_fonts.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/custom/_fonts.scss deleted file mode 100644 index 973e7c27b..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/custom/_fonts.scss +++ /dev/null @@ -1,2 +0,0 @@ - -@import '../../../fonts/OpenSans/OpenSans'; \ No newline at end of file diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/custom/_pagination.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/custom/_pagination.scss deleted file mode 100644 index 0c125df04..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/custom/_pagination.scss +++ /dev/null @@ -1,32 +0,0 @@ -.pagination { - display: flex; - justify-content: center; - text-align: center; - margin-top: 1em; - - .link { - background-color: white; - display: inline-block; - } - - .link:nth-of-type(1n+2) { - border-left: 1px solid $chill-light-gray; - } - - .link.current { - font-weight: bold; - color: white; - background-color: #334d5c; - padding: 0.4em 0.8em; - } - - .link a { - display: block; - padding: 0.4em 0.8em; - } - .link a:hover { - color: white; - font-weight: bold; - background-color: $chill-green; - } -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/custom/_person.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/custom/_person.scss deleted file mode 100644 index 6d92923a9..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/custom/_person.scss +++ /dev/null @@ -1,3 +0,0 @@ -.person { - color: $chill-blue; -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/custom/_record_actions.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/custom/_record_actions.scss deleted file mode 100644 index ccbc5912a..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/custom/_record_actions.scss +++ /dev/null @@ -1,58 +0,0 @@ -// Symfony records actions -/*ul.record_actions { - padding-left: 0; -} -ul.record_actions li { - display: inline-block; -}*/ - -ul.record_actions, ul.record_actions_column { - display: flex; - justify-content: flex-end; - - &.record_actions--left { - justify-content: flex-start; - } - - padding: 0.5em 0; - flex-wrap: wrap-reverse; - - li { - display: inline-block; - list-style-type: none; - margin-right: 1em; - order: 99; - - &:last-child { - margin-right: 0; - } - } - - - li.cancel { - order: 1; - margin-right: auto; - } - -} - -ul.record_actions { - flex-direction: row; -} - -ul.record_actions_column { - flex-direction: column; -} - -ul.record_actions.sticky-form-buttons { - padding-left: 1em; - padding-right: 1em; -} - -// inside table, little space between elements -td ul.record_actions, - ul.record_actions_small { - li { - margin-right: 0.2em; - } -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/custom/_report.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/custom/_report.scss deleted file mode 100644 index dc7e48623..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/custom/_report.scss +++ /dev/null @@ -1,3 +0,0 @@ -.report { - color: $chill-red; -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/custom/_timeline.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/custom/_timeline.scss deleted file mode 100644 index b347cde75..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/custom/_timeline.scss +++ /dev/null @@ -1,53 +0,0 @@ -/* -Chill is a software for social workers -Copyright (C) 2015 Champs Libres - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License -along with this program. If not, see . -*/ - -div.timeline-item { - padding: 0.5em; - overflow: auto; - margin-bottom: 1.5em; - - &.odd { - background-color: $chill-llight-gray; - - .summary { - background-color: $white; - } - } - - &.even { - background-color: $white; - - .summary { - background-color: $chill-llight-gray; - } - } - - .summary { - margin: 1em; - overflow: auto; - } - - h3 { - margin-top: 0em; - font-size: 1em; - - &.single-line { - margin-bottom: 0em; - } - } -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/custom/config/_colors.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/custom/config/_colors.scss deleted file mode 100644 index 91e4ebae0..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/custom/config/_colors.scss +++ /dev/null @@ -1,65 +0,0 @@ - -$chill-blue: #334d5c; -$chill-green: #43b29d; -$chill-green-dark: #328474; -$chill-yellow: #eec84a; -$chill-orange: #e2793d; -$chill-red: #df4949; -$chill-gray: #ececec; -$chill-beige: #cabb9f; -$chill-pink: #dd506d; -$chill-dark-gray: #333333; -$chill-light-gray: #b2b2b2; -$chill-llight-gray: #e6e6e6; - -$dark-grey: $chill-dark-gray; - -$color-name: "blue" "green" "green-dark" "yellow" "orange" "red" "gray" "beige" "pink" "dark-gray" "light-gray"; -$color-code: #334d5c #43b29d #328474 #eec84a #e2793d #df4949 #ececec #cabb9f #dd506d #333333 #b2b2b2; - -@for $i from 1 through length($color-name) { - .chill-#{nth($color-name, $i)} { - color: nth($color-code, $i); - } -} - -$orange: $chill-orange; -$red: $chill-red; -$green: $chill-green; -$blue: $chill-blue; -$yellow: $chill-yellow; - -$black: #111111; -$white: #ffffff; -$light-grey: $chill-light-gray; - -/* - due to a bug in sass, we must re-declare the variable in css - (use of a sass variable after -- does not work) -*/ -:root { - --chill-blue: #334d5c; - --chill-green: #43b29d; - --chill-green-dark: #328474; - --chill-yellow: #eec84a; - --chill-orange: #e2793d; - --chill-red: #df4949; - --chill-gray: #ececec; - --chill-beige: #cabb9f; - --chill-pink: #dd506d; - --chill-dark-gray: #333333; - --chill-light-gray: #b2b2b2; - --chill-llight-gray: #e6e6e6; - - --dark-grey: #333333; - - --orange: #e2793d; - --red: #df4949; - --green: #43b29d; - --blue: #334d5c; - --yellow: #eec84a; - - --black: #111111; - --white: #ffffff; - --light-grey: #b2b2b2; -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/custom/config/_variables.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/custom/config/_variables.scss deleted file mode 100644 index 994a5a4e9..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/custom/config/_variables.scss +++ /dev/null @@ -1,45 +0,0 @@ -// General -$base-border-radius: 0; -$form-border-radius: 0; -$navigation-border-radius: 0; - -// Footer -$footer-vertical-padding: 10px; -$footer-background: $chill-dark-gray; -$footer-disclaimer-color: white; - -// Navigation -$navigation-background: $dark-grey; -$navigation-first-padding-top: 0; -$navigation-last-padding-bottom: 0; -$navigation-color: white; -$navigation-color-hover: white; -$navigation-search-padding: 17px 0 0; -$navigation-border-bottom: none; - -// Form -$form-border-color: $black; -$form-border-color-hover: $black; -$form-border-color-focus: $black; -$form-border-size: 1px; - -// Table -$table-head-bg-color: unset; -$table-head-text-color: $chill-blue; - -$table-body-tr-bg-color-even: $chill-llight-gray; -$table-body-tr-bg-color-odd: $white; -$table-body-td-border: 1px solid $black; -$table-body-td-text-align: left; - -// Tabs -$tabs-nav-margin-bottom: 0.2em; -$tabs-nav-bg-color: $yellow; -$tabs-nav-bg-color-light: lighten($yellow, 10%); -$tabs-nav-text-color: $blue; -$tabs-new-border: none; -$tabs-nav-hover-border: none; -$tabs-nav-hover-text-color: $blue; -$tabs-nav-font-family: 'Open Sans'; -$tabs-nav-font-weight: bold; -$tabs-nav-padding: 0.3em 0.3em 0.3em 0.6em; diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/custom/mixins/entity.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/custom/mixins/entity.scss deleted file mode 100644 index 28cc74d06..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/custom/mixins/entity.scss +++ /dev/null @@ -1,16 +0,0 @@ -@mixin entity($background-color, $color: white) { - font-variant: small-caps; - display: inline-block; - padding: .2em .6em .3em; - font-size: 88%; - font-weight: bold; - line-height: 1; - text-align: center; - white-space: nowrap; - vertical-align: baseline; - border-radius: .25em; - color: $color; - background-color: $background-color; - margin: 0.5em; -} - diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/custom/modules/_buttons.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/custom/modules/_buttons.scss deleted file mode 100644 index 7c332818d..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/custom/modules/_buttons.scss +++ /dev/null @@ -1,175 +0,0 @@ -.sc-button { - margin-bottom: 0.5rem; - - &.bt-submit, &.bt-save, &.bt-create, &.bt-new, &.bt-duplicate, &.bt-not-duplicate { - @include button($green, $white); - } - - &.bt-reset, &.bt-delete, &.bt-remove { - @include button($red, $white); - } - - &.bt-action, &.bt-edit, &.bt-update { - @include button($orange, $white); - } - - &.bt-show, &.bt-view { - @include button($blue, $white); - } - - &:not(.change-icon) { - - // icons using font-awesome "old way" - &.bt-create::before, - &.bt-save::before, - &.bt-new::before, - &.bt-delete::before, - &.bt-remove::before, - &.bt-update::before, - &.bt-edit::before, - &.bt-cancel::before, - &.bt-view::before, - &.bt-show::before { - font: normal normal normal 14px/1 ForkAwesome; - margin-right: 0.5em; - } - - // icons using font-awesome "new svg way" - &.bt-not-duplicate::before, - &.bt-duplicate::before { - display: inline-block; - width: 1em; - margin-right: 0.5em; - vertical-align: middle; - } - - &.bt-save::before { - // add a floppy - content: ""; - } - - &.bt-create::before, &.bt-new::before { - // add a plus - content: ""; - } - - &.bt-delete::before { - // add a trash - content: ""; - } - - &.bt-remove::before { - // add a times - content: ""; - } - - &.bt-edit::before, &.bt-update::before { - // add a pencil - content: ""; - } - - &.bt-cancel::before { - // add an arrow left - content: ""; - } - - &.bt-show::before, &.bt-view::before { - content: ""; - } - - &.bt-duplicate::before { - content: url("./copy-solid.svg"); - } - - &.bt-not-duplicate::before { /* - content: url("./users-slash-solid.svg"); */ - } - } - - > i.fa { - margin-right: 0.5em; - } - - &.has-hidden, - &:empty { - > i.fa { - margin-right: 0; - } - &:not(.change-icon) { - &.bt-create::before, - &.bt-save::before, - &.bt-new::before, - &.bt-delete::before, - &.bt-remove::before, - &.bt-update::before, - &.bt-edit::before, - &.bt-cancel::before, - &.bt-view::before, - &.bt-show::before { - margin-right: 0; - } - } - } - - &.has-hidden > span.show-on-hover { - display: none; - } - - &.has-hidden:hover { - - > span.show-on-hover { - display: inline-block; - } - - > i.fa { - margin-right: 0.5em; - } - - &:not(.change-icon) { - &.bt-create::before, - &.bt-save::before, - &.bt-new::before, - &.bt-delete::before, - &.bt-remove::before, - &.bt-update::before, - &.bt-edit::before, - &.bt-cancel::before, - &.bt-view::before, - &.bt-show::before { - margin-right: 0.5em; - } - } - } - - &.button-small { - font-size: 80%; - padding: 6px 8px; - } -} - - - -// Sticky form buttons -.sticky-form-buttons { - margin-top:1em; - background-color:$chill-beige; - position:sticky; - bottom:0.5em; - text-align:center; - padding:0.5em; - border-radius: $base-border-radius; - - -} - - - -.sticky-form-buttons .margin-5 { - margin-left: 5%; - margin-right: 5%; -} - -.sticky-form-buttons .margin-10 { - margin-left: 10%; - margin-right: 10%; -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/custom/modules/_forms.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/custom/modules/_forms.scss deleted file mode 100644 index 0e5099ef0..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/custom/modules/_forms.scss +++ /dev/null @@ -1,28 +0,0 @@ -// For cutomizing the form elements - -textarea { - height: 12em; -} - -span.force-inline-label label { - display: inline; -} - - -.chill-form-money { - display: flex; - flex-direction: row; - justify-content: flex-end; - - span.chill-form-money__money { - align-self: center; - margin-left: 1em; - } -} - - -.chill-form__errors { - .chill-form_errors__entry.chill-form__errors__entry--warning { - color: var(--chill-green-dark); - } -} \ No newline at end of file diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/custom/modules/_navigation.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/custom/modules/_navigation.scss deleted file mode 100644 index be28d0348..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/custom/modules/_navigation.scss +++ /dev/null @@ -1,88 +0,0 @@ -.navigation { - background-color: $chill-blue; - - - a.more:after { - color: $chill-dark-gray; - } - - li.nav-link2 { - a { - margin-bottom: 2px; - } - - &.lang-selection { - color: $chill-light-gray; - font-size: 0.7em; - - a.more:after { - color: $chill-light-gray; - } - } - - ul { - top: 58px; - - a { - padding-left: 0; - } - } - } - - div.nav, div.navigation-search { - float: right; - - input[type=search] { - padding: 0.2em; - float: left; - - border: none; - } - - button { - color: $chill-light-gray; - background-color: $chill-blue; - padding: 0 0 0 7px; - top: inherit; - font-size: 1.2em; - position: unset; - float: left; - } - } - - li.user-menu { - min-width: 14rem; - } - - ul.user-menu-list { - - li.user-menu__entry { - display: block; - background-color: $chill-dark-gray; - border-bottom: 1px solid #FFF; - padding-top: 0; - padding-bottom: 0; - line-height: 2; - } - - li.user-menu__entry--warning-entry { - background-color: $chill-red; - font-weight: 700; - } - } - - span.notification-counter { - display: inline-block; - padding: .25em .6em .25rem .6em; - font-size: 100%; - line-height: 1; - text-align: center; - white-space: nowrap; - - border-radius: 10rem; - background-color: $chill-red; - color: $white; - font-weight: 700; - margin-left: .5rem; - } -} \ No newline at end of file diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/custom/modules/copy-solid.svg b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/custom/modules/copy-solid.svg deleted file mode 100644 index 6acdf87cd..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/custom/modules/copy-solid.svg +++ /dev/null @@ -1,4 +0,0 @@ - \ No newline at end of file diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/custom/modules/users-slash-solid.svg b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/custom/modules/users-slash-solid.svg deleted file mode 100644 index 5bd2883cc..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/custom/modules/users-slash-solid.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/index.js b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/index.js deleted file mode 100644 index 6396103fc..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/index.js +++ /dev/null @@ -1 +0,0 @@ -require('./scratch.scss'); diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/mixins/_alerts.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/mixins/_alerts.scss deleted file mode 100644 index d7d4d62ab..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/mixins/_alerts.scss +++ /dev/null @@ -1,7 +0,0 @@ -@mixin alert($color) { - background: transparentize($color,0.8); - color: $color; - font-weight: bold; - margin-bottom: 0.75em; - padding: 0.75em; -} \ No newline at end of file diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/mixins/_buttons.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/mixins/_buttons.scss deleted file mode 100644 index 603856e23..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/mixins/_buttons.scss +++ /dev/null @@ -1,34 +0,0 @@ -@mixin button($button-background-color, $button-text-color) { - color: $button-text-color; - background: $button-background-color; - border: medium none; - box-shadow: none; - padding: $button-padding; - text-decoration: none; - text-align: center; - display: inline-block; - vertical-align: middle; - white-space: nowrap; - line-height: normal; - @include border-top-radius($base-border-radius); - @include border-bottom-radius($base-border-radius); - - &:hover, - &:active { - background: darken($button-background-color, 5%); - color: $button-text-color; // force text color for anchor tags - text-decoration: none; // remove underline on anchor tags - } - - &:focus { - background: darken($button-background-color, 5%); - } - - &[disabled] { - background: transparentize($button-background-color,0.4); - color: darken($button-background-color, 10%); - &:hover { - box-shadow: none; - } - } -} \ No newline at end of file diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/modules/_alerts.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/modules/_alerts.scss deleted file mode 100644 index 03ed6b5cd..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/modules/_alerts.scss +++ /dev/null @@ -1,15 +0,0 @@ -.success { - @include alert($green); -} - -.error { - @include alert($red); -} - -.alert { - @include alert($orange); -} - -.notice { - @include alert($blue); -} \ No newline at end of file diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/modules/_buttons.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/modules/_buttons.scss deleted file mode 100644 index 9e0036de5..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/modules/_buttons.scss +++ /dev/null @@ -1,12 +0,0 @@ -.sc-button { - @include button($grey-15, $button-text-color); - - &.blue { @include button($blue, $white); } - &.green { @include button($green, $white); } - &.orange { @include button($orange, $white); } - &.red { @include button($red, $white); } - &.black { @include button($grey-90, $white); } - &.white { @include button($white, $text-color); } -} - -@import "../custom/modules/buttons"; \ No newline at end of file diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/modules/_content.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/modules/_content.scss deleted file mode 100644 index 49be33d38..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/modules/_content.scss +++ /dev/null @@ -1,7 +0,0 @@ -html, body { - height: 100%; -} - -.content { - min-height: 85%; -} \ No newline at end of file diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/modules/_footer.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/modules/_footer.scss deleted file mode 100644 index 1193f4c30..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/modules/_footer.scss +++ /dev/null @@ -1,65 +0,0 @@ -.footer { - background: $footer-background; - padding-top: $footer-vertical-padding; - padding-bottom: $footer-vertical-padding; - width: 100%; - - .footer-logo { - text-align: center; - margin-bottom: 2em; - - img { - height: 3em; - } - } - - .footer-links { - @include gridle_clear(both); - - margin-bottom: $base-spacing; - @include gridle(8); - @include gridle_centered(); - } - - ul { - margin-bottom: $base-spacing*2; - @include gridle (4); - list-style-type: none; - } - - li { - text-align: center; - } - - li a { - color: $footer-link-color; - - &:hover { - color: transparentize($footer-color, 0); - } - } - - li h3 { - color: $footer-color; - font-size: 1em; - font-weight: 800; - margin-bottom: .4em; - } - - hr { - @include gridle_clear(both); - border: 1px solid transparentize($footer-disclaimer-color, .3); - margin: 0 auto $base-spacing; - width: 12em; - } - - p { - @include gridle_clear(both); - color: $footer-disclaimer-color; - font-size: .9em; - line-height: 1.5em; - margin: auto; - max-width: 35em; - text-align: center; - } -} \ No newline at end of file diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/modules/_forms.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/modules/_forms.scss deleted file mode 100644 index dcffddec9..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/modules/_forms.scss +++ /dev/null @@ -1,156 +0,0 @@ -fieldset { - border: none; - margin: 0 0 ($base-spacing / 2) 0; - padding: $base-spacing; -} - -input,label, select { - display: block; - /* font-family: $form-font-family; - font-size: $form-font-size; */ -} - -label { - padding-top: $base-spacing / 3; - padding-bottom: $base-spacing / 3; - font-weight: bold; - margin-bottom: $base-spacing / 4; - - &.required:after { - content: "*"; - color: $red; - font-weight: 900; - } - - abbr { - display: none; - } - - // mark the label for empty placeholder - &[for$="_placeholder"] { - font-style: italic; - } -} - -.inline-choice { - white-space:nowrap; - display: inline-block; - - label { - white-space:normal; - display: inline; - line-height: 1 + ((2 * $base-spacing) / 3); - margin-right: 1em; - font-weight: normal; - } -} - -div.choice-widget-expanded { - margin-top: 0.5em; - margin-bottom: 0.5em; -} - -textarea, -//input, -#{$all-text-inputs}, -select[multiple=multiple] { - //@include box-sizing(border-box); - //@include transition(border-color); - background-color: white; - @include border-top-radius($form-border-radius); - @include border-bottom-radius($form-border-radius); - border: $form-border-size solid $form-border-color; - box-shadow: none; - //box-shadow: $form-box-shadow; - /*font-family: $form-font-family; - font-size: $form-font-size; */ - margin-bottom: $base-spacing / 4; - padding: ($base-spacing / 3) ($base-spacing / 3); - width: 100%; - - &:hover { - border-color: $form-border-color-hover; - } - - &:focus { - border-color: $form-border-color-focus; - box-shadow: $form-box-shadow-focus; - outline: none; - } -} - -textarea { - resize: vertical; -} - -input[type="search"] { - //@include appearance(none); -} - -input[type="checkbox"], -input[type="radio"] { - display: inline; - margin-right: $base-spacing / 4; -} - -input[type="file"] { - padding-bottom: $base-spacing / 2; - width: 100%; -} - -select { - margin-bottom: $base-spacing / 4; - padding-top: ($base-spacing / 5); - padding-bottom: ($base-spacing / 5); - width: 100%; -} - -form { - p { - &.tip { - font-size: .875em; - position: relative; - text-align: center; - margin-top: -.3em; - } - - &.label { - padding: 0; - //margin: 0; - //color: $text-color; - white-space: normal; - } - } - - fieldset { - border: none; - margin-bottom: 1.5em; - padding: 0; - margin: 0; - - legend { - font-size: 1.438em; - font-weight: 700; - width: 100%; - border-bottom: 3px solid #ddd; - margin-bottom: 1em; - - & + * { - -webkit-margin-top-collapse: separate; // webkit hack that makes the legend margins work like they should - } - - h2 { - margin-bottom: 0; - } - } - } - - li { - label { - display: inline-block; - } - } -} - -@import "../custom/modules/forms"; - diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/modules/_navigation.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/modules/_navigation.scss deleted file mode 100644 index 789707f6a..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/modules/_navigation.scss +++ /dev/null @@ -1,299 +0,0 @@ -.navigation { - //$navigation-nav-button-background: $base-accent-color; - //$navigation-nav-button-background-hover: lighten($navigation-background, 10); - //$navigation-nav-button-border: 1px solid lighten($navigation-nav-button-background, 20); - //$navigation-search-background: lighten($navigation-background, 5); - //$navigation-search-border: 1px solid darken($navigation-background, 5); - //$horizontal-bar-mode: $large-screen; - - background-color: $navigation-background; - border-bottom: $navigation-border-bottom; - height: $navigation-height; - //width: 100%; - z-index: 999; - - .logo-container { - height: $navigation-height; - - &:before { /* create a full-height inline block pseudo=element */ - content: ' '; - display: inline-block; - vertical-align: middle; /* vertical alignment of the inline element */ - height: 100%; - } - - img.logo { - max-width: 89%; - max-height: $navigation-height - 10px; - display: inline-block; - vertical-align: middle; - } - } - - // Nav menu - .nav { - z-index: 9999999; - height: $navigation-height; - //float: none; - } - - ul.navigation-menu { - -webkit-transform-style: preserve-3d; // stop webkit flicker - clear: both; - display: none; - margin: 0 auto; - overflow: visible; - padding: 0; - width: 100%; - z-index: 9999; - - //@include media ($horizontal-bar-mode) { - display: inline-block; - margin: 0; - padding: 0; - //} - } - - // The nav items - ul li.nav-link { - background: $navigation-background; - display: block; - line-height: $navigation-height; - overflow: hidden; - padding-right: .8em; - text-align: right; - width: 100%; - z-index: 9999; - - // @include media ($horizontal-bar-mode) { - background: transparent; - display: inline; - line-height: $navigation-height; - text-decoration: none; - width: auto; - // } - - a { - color: $navigation-color; - display: inline-block; - font-weight: 400; - - //@include media ($horizontal-bar-mode) { - padding-right: 1em; - //} - - &:hover { - color: $navigation-color-hover; - } - } - } - - .active-nav-item a { - border-bottom: 1px solid $navigation-active-link-color; - padding-bottom: 3px; - } - - // Sub menus - li.more.nav-link { - padding-right: 0; - - // @include media($large-screen) { - padding-right: $navigation-submenu-padding; - // } - - > ul > li:first-child a { - padding-top: $navigation-first-padding-top; - } - - a { - margin-right: $navigation-submenu-padding; - } - - > a { - padding-right: 0.6em; - } - - > a:after { - @include position(absolute, auto -.4em auto auto); - content: $navigation-more-pin; - color: $navigation-more-pin-color; - } - } - - li.more { - overflow: visible; - padding-right: 0; - - a { - padding-right: .8em; - } - - > a { - padding-right: 1.6em; - position: relative; - - //@include media($large-screen) { - margin-right: $navigation-submenu-padding; - //} - - &:after { - content: '›'; - font-size: 1.2em; - position: absolute; - right: $navigation-submenu-padding / 2; - } - } - - &:hover > .submenu { - display: block; - } - - //@include media($horizontal-bar-mode) { - padding-right: .8em; - position: relative; - //} - } - - ul.submenu { - display: none; - padding-left: $navigation-ul-submenu-padding-left; - //@include media($horizontal-bar-mode) { - left: -$navigation-submenu-padding; - position: absolute; - top: $navigation-ul-submenu-top; - //} - - .submenu { - //@include media($horizontal-bar-mode) { - left: $navigation-submenu-width - .2em; - top: 0; - //} - } - - li { - display: block; - padding-right: 0; - - //@include media($horizontal-bar-mode) { - line-height: $navigation-height / 1.3; - - &:first-child > a { - border-top-left-radius: $navigation-border-radius; - border-top-right-radius: $navigation-border-radius; - } - - &:last-child > a { - border-bottom-left-radius: $navigation-border-radius; - border-bottom-right-radius: $navigation-border-radius; - padding-bottom: $navigation-last-padding-bottom; - } - //} - - a { - background-color: darken($navigation-background, 3); - display: inline-block; - text-align: right; - width: 100%; - - //@include media($horizontal-bar-mode) { - background-color: $navigation-background; - padding-left: $navigation-submenu-padding; - text-align: left; - //width: $navigation-submenu-width; - //} - } - } - } - - // Elements on the far right - .navigation-search { - padding: $navigation-search-padding; - position: relative; - - input[type=search] { - //background: $navigation-search-background; - //border: $navigation-search-border; - padding: .6em .55em; - padding-right: 3.5em; - width: calc(100% - 4.5em); - //padding: .6em .8em; - //padding: 0 30px 0 10px; - font-size: .9em; - //color: $navigation-color; - //border-radius: $navigation-border-radius * 2; - margin: 0; - } - - button { - padding: 7px 12px; - position: absolute; - top: .99em; - right: 1em; - } - } - - ul li.nav-link2 { - position: relative; - padding-right: 2em; - text-align: left; - line-height: $navigation-height; - z-index: 9999; - float: left; - list-style: none; - - & div.li-content { - display: inline-block; - line-height: normal; - vertical-align: middle; - - a.more:after { - content: $navigation-more-pin; - color: $navigation-more-pin-color; - padding-left: 0.4em; - font-size: 1.2em; - } - } - - ul { - display: none; - position: absolute; - line-height: normal; - list-style: none; - background-color: #333; - padding-right: 1.5em; - padding-left: 1.5em; - width: 100%; - } - - ul { - li { - padding-top:0.7em; - padding-bottom:0.7em; - - &:first-child { - padding-top: $navigation-first-padding-top; - } - - &:last-child { - padding-bottom: $navigation-first-padding-top; - } - } - } - - &:hover ul { - display: block; - } - - a { - width: 100%; - color: $navigation-color; - font-weight: 400; - - &:hover { - color: $navigation-color-hover; - } - } - } -} - -@import "../custom/modules/navigation"; \ No newline at end of file diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/modules/_table.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/modules/_table.scss deleted file mode 100644 index 14389580a..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/modules/_table.scss +++ /dev/null @@ -1,33 +0,0 @@ -table { - width: $table-width; - - thead { - background-color: $table-head-bg-color; - - tr th { - border: $table-head-td-border; - text-align: $table-head-td-text-align; - padding: $table-head-td-padding; - color: $table-head-text-color; - } - } - - tbody { - tr { - td { - border: $table-body-td-border; - text-align: $table-body-td-text-align; - padding: $table-body-td-padding; - color: $table-body-text-color; - } - - &:nth-of-type(even) { - background-color: $table-body-tr-bg-color-even; - } - - &:nth-of-type(odd) { - background-color: $table-body-tr-bg-color-odd; - } - } - } -} \ No newline at end of file diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/modules/_tabs.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/modules/_tabs.scss deleted file mode 100644 index 2f57e0ae7..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/modules/_tabs.scss +++ /dev/null @@ -1,54 +0,0 @@ -/* Tabs */ -$navigation-color: $red; -$gutter : 4; -$norm: 5; -$body-font-color: $white; -$default-color: $green; -$global-bg-color: $yellow; - -.tab-nav { - margin: 0.5em 0; - padding: 0; - - > li { - font-family: $tabs-nav-font-family; - display: inline-block; - width: 100%; - cursor: default; - @include border-top-radius($base-border-radius); - @include border-bottom-radius($base-border-radius); - margin-bottom: $tabs-nav-margin-bottom; - - &.title { - padding: $tabs-nav-title-padding; - font-weight: 900; - background-color: $tabs-nav-title-bg-color; - color: $tabs-nav-title-text-color; - } - - &.sub-menu { - padding-left: 20px; - > a { - background-color: $tabs-nav-bg-color-light; - } - } - > a { - display: block; - width: auto; - padding: $tabs-nav-padding; - margin: 0; - color: $tabs-nav-text-color; - cursor: pointer; - border: $tabs-new-border; - background-color: $tabs-nav-bg-color; - @include border-top-radius($base-border-radius); - @include border-bottom-radius($base-border-radius); - - &:hover, &:active { - border: $tabs-nav-hover-border; - color: $tabs-nav-hover-text-color; - text-decoration: none; - } - } - } -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/modules/_typography.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/modules/_typography.scss deleted file mode 100644 index dce6d75a6..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/modules/_typography.scss +++ /dev/null @@ -1,23 +0,0 @@ -ul { - &.unstyled { - list-style: none; - padding-left: 0em; - - ul { - list-style:disc outside; - } - } -} - -a { - color: $base-link-color; - text-decoration: none; - - &:hover { - color: $hover-link-color; - } -} - -.text-right { - text-align: right; -} \ No newline at end of file diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/scratch.scss b/src/Bundle/ChillMainBundle/Resources/public/module/scratch/scratch.scss deleted file mode 100644 index 78f9b393c..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/scratch/scratch.scss +++ /dev/null @@ -1,46 +0,0 @@ -@charset "UTF-8"; - -@import "contrib/normalize/normalize"; -@import "contrib/bourbon/bourbon"; - -@import "config/colors"; -@import "config/variables"; -@import "config/buttons"; - -/* etrange pour les forms */ -* { box-sizing: inherit; } -html { box-sizing: border-box; } - -@import "mixins/buttons"; -@import "mixins/alerts"; - -@import "contrib/gridle/gridle/gridle"; - -@include gridle_setup(( - context : 12, - gutter-width : 20px, - direction : ltr, -)); - -@include gridle_register_state(mobile , ( - max-width : 400px -)); - -@include gridle_register_state(tablet, ( - min-width : 401px, - max-width : 767px, -)); - -@include gridle_generate_classes(); - -@import "modules/typography"; -@import "modules/navigation"; -@import "modules/content"; -@import "modules/footer"; -@import "modules/alerts"; -@import "modules/forms"; -@import "modules/buttons"; -@import "modules/tabs"; -@import "modules/table"; - -@import "custom"; \ No newline at end of file diff --git a/src/Bundle/ChillMainBundle/Resources/public/page/export/index.js b/src/Bundle/ChillMainBundle/Resources/public/page/export/index.js new file mode 100644 index 000000000..d540e5ae5 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/page/export/index.js @@ -0,0 +1,6 @@ +// old method to show/hide filters when checking checkbox +import { chill } from 'ChillMainAssets/chill/js/chill'; +window.addEventListener("DOMContentLoaded", chill.listenerDisplayCheckbox); + +// TODO should be replaced by more recent showHide library +//import { ShowHide } from 'ShowHide'; diff --git a/src/Bundle/ChillMainBundle/Resources/public/types.ts b/src/Bundle/ChillMainBundle/Resources/public/types.ts new file mode 100644 index 000000000..b31b70897 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/types.ts @@ -0,0 +1,162 @@ +export interface DateTime { + datetime: string; + datetime8601: string +} + +export interface Civility { + id: number; + // TODO +} + +export interface Job { + id: number; + type: "user_job"; + label: { + "fr": string; // could have other key. How to do that in ts ? + } +} + +export interface Center { + id: number; + type: "center"; + name: string; +} + +export interface Scope { + id: number; + type: "scope"; + name: { + "fr": string + } +} + +export interface User { + type: "user"; + id: number; + username: string; + text: string; + text_without_absence: string; + email: string; + user_job: Job; + label: string; + // todo: mainCenter; mainJob; etc.. +} + +export interface UserAssociatedInterface { + type: "user"; + id: number; +}; + +export type TranslatableString = { + fr?: string; + nl?: string; +} + +export interface Postcode { + id: number; + name: string; + code: string; + center: Point; +} + +export type Point = { + type: "Point"; + coordinates: [lat: number, lon: number]; +} + +export interface Country { + id: number; + name: TranslatableString; + code: string; +} + +export type AddressRefStatus = 'match'|'to_review'|'reviewed'; + +export interface Address { + type: "address"; + address_id: number; + text: string; + street: string; + streetNumber: string; + postcode: Postcode; + country: Country; + floor: string | null; + corridor: string | null; + steps: string | null; + flat: string | null; + buildingName: string | null; + distribution: string | null; + extra: string | null; + confidential: boolean; + lines: string[]; + addressReference: AddressReference | null; + validFrom: DateTime; + validTo: DateTime | null; + point: Point | null; + refStatus: AddressRefStatus; + isNoAddress: boolean; +} + +export interface AddressWithPoint extends Address { + point: Point +} + +export interface AddressReference { + id: number; + createdAt: DateTime | null; + deletedAt: DateTime | null; + municipalityCode: string; + point: Point; + postcode: Postcode; + refId: string; + source: string; + street: string; + streetNumber: string; + updatedAt: DateTime | null; +} + +export interface SimpleGeographicalUnit { + id: number; + layerId: number; + unitName: string; + unitRefId: string; +} + +export interface GeographicalUnitLayer { + id: number; + name: TranslatableString; + refId: string; +} + +export interface Location { + type: "location"; + id: number; + active: boolean; + address: Address | null; + availableForUsers: boolean; + createdAt: DateTime | null; + createdBy: User | null; + updatedAt: DateTime | null; + updatedBy: User | null; + email: string | null + name: string; + phonenumber1: string | null; + phonenumber2: string | null; + locationType: LocationType; +} + +export interface LocationAssociated { + type: "location"; + id: number; +} + +export interface LocationType { + type: "location-type"; + id: number; + active: boolean; + addressRequired: "optional" | "required"; + availableForUsers: boolean; + editableByUsers: boolean; + contactData: "optional" | "required"; + title: TranslatableString; +} diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/api.js b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/api.js index b1489bfb6..294ea9480 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/api.js +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/api.js @@ -1,3 +1,5 @@ +import {getAddressById} from 'ChillMainAssets/lib/api/address'; + /** * Endpoint chill_api_single_country__index * method GET, get Country Object @@ -188,13 +190,7 @@ const postPostalCode = (postalCode) => { //<-- * @returns {Promise} a promise containing a Address object */ const getAddress = (id) => { - //console.log('<< get address'); - const url = `/api/1.0/main/address/${id}.json`; - return fetch(url) - .then(response => { - if (response.ok) { return response.json(); } - throw Error('Error with request resource response'); - }); + return getAddressById(id); }; export { diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress.vue index fcd64ed70..41279771f 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress.vue @@ -48,33 +48,35 @@ -
    - + @@ -118,40 +120,42 @@ -
    - + @@ -192,32 +196,34 @@ -
    - + @@ -309,8 +315,11 @@ export default { addressMap: { // Note: LeafletJs demands [lat, lon] // cfr https://macwright.com/lonlat/ - center : [48.8589, 2.3469], - zoom: 12 + center : [ + this.context.defaults.map_center.x, + this.context.defaults.map_center.y, + ], + zoom: this.context.defaults.map_center.z }, }, errorMsg: [] @@ -571,15 +580,15 @@ export default { this.entity.selected.city = this.context.edit ? this.entity.address.postcode : {}; this.entity.selected.address = {}; - this.entity.selected.address.street = this.context.edit ? this.entity.address.street: null; - this.entity.selected.address.streetNumber = this.context.edit ? this.entity.address.streetNumber: null; - this.entity.selected.address.floor = this.context.edit ? this.entity.address.floor: null; - this.entity.selected.address.corridor = this.context.edit ? this.entity.address.corridor: null; - this.entity.selected.address.steps = this.context.edit ? this.entity.address.steps: null; - this.entity.selected.address.flat = this.context.edit ? this.entity.address.flat: null; - this.entity.selected.address.buildingName = this.context.edit ? this.entity.address.buildingName: null; - this.entity.selected.address.distribution = this.context.edit ? this.entity.address.distribution: null; - this.entity.selected.address.extra = this.context.edit ? this.entity.address.extra: null; + this.entity.selected.address.street = this.context.edit ? this.entity.address.street: ''; + this.entity.selected.address.streetNumber = this.context.edit ? this.entity.address.streetNumber: ''; + this.entity.selected.address.floor = this.context.edit ? this.entity.address.floor: ''; + this.entity.selected.address.corridor = this.context.edit ? this.entity.address.corridor: ''; + this.entity.selected.address.steps = this.context.edit ? this.entity.address.steps: ''; + this.entity.selected.address.flat = this.context.edit ? this.entity.address.flat: ''; + this.entity.selected.address.buildingName = this.context.edit ? this.entity.address.buildingName: ''; + this.entity.selected.address.distribution = this.context.edit ? this.entity.address.distribution: ''; + this.entity.selected.address.extra = this.context.edit ? this.entity.address.extra: ''; this.entity.selected.writeNew.address = this.context.edit && this.entity.address.addressReference === null && this.entity.address.street.length > 0 this.entity.selected.writeNew.postcode = false // NB: this used to be this.context.edit, but think it was erroneous; diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressMap.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressMap.vue index 80d2ca01a..d3c04260f 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressMap.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressMap.vue @@ -8,8 +8,12 @@ import L from 'leaflet'; import markerIconPng from 'leaflet/dist/images/marker-icon.png' import 'leaflet/dist/leaflet.css'; +const lonLatForLeaflet = (coordinates) => { + return [coordinates[1], coordinates[0]]; +} + export default { - name: 'AddressMap', + name: 'AddressMap', props: ['entity'], data() { return { @@ -19,12 +23,49 @@ export default { }, computed: { center() { - return this.entity.selected.addressMap.center; + return this.entity.addressMap.center; + }, + hasAddressPoint() { + if (Object.keys(this.entity.address).length === 0) { + return false; + } + if (null !== this.entity.address.addressReference) { + return true; + } + if (null !== this.entity.address.postcode && null !== this.entity.address.postcode.center) { + return true; + } + return false; + }, + /** + * + * @returns {coordinates: [float, float], type: "Point"} + */ + addressPoint() { + if (Object.keys(this.entity.address).length === 0) { + return null; + } + + if (null !== this.entity.address.addressReference) { + return this.entity.address.addressReference.point; + } + + if (null !== this.entity.address.postcode && null !== this.entity.address.postcode.center) { + return this.entity.address.postcode.center; + } + + return null; }, }, methods:{ init() { - this.map = L.map('address_map').setView([46.67059, -1.42683], 12); + this.map = L.map('address_map'); + + if (!this.hasAddressPoint) { + this.map.setView(lonLatForLeaflet(this.entity.addressMap.center), this.entity.addressMap.zoom); + } else { + this.map.setView(lonLatForLeaflet(this.addressPoint.coordinates), 15); + } this.map.scrollWheelZoom.disable(); @@ -37,20 +78,22 @@ export default { iconAnchor: [12, 41], }); - this.marker = L.marker([48.8589, 2.3469], {icon: markerIcon}); + if (!this.hasAddressPoint) { + this.marker = L.marker(lonLatForLeaflet(this.entity.addressMap.center), {icon: markerIcon}); + } else { + this.marker = L.marker(lonLatForLeaflet(this.addressPoint.coordinates), {icon: markerIcon}); + } this.marker.addTo(this.map); }, update() { - //console.log('update map with : ', this.entity.addressMap.center) if (this.marker && this.entity.addressMap.center) { - this.marker.setLatLng(this.entity.addressMap.center); - this.map.setView(this.entity.addressMap.center, 15); + this.marker.setLatLng(lonLatForLeaflet(this.entity.addressMap.center)); + this.map.panTo(lonLatForLeaflet(this.entity.addressMap.center)); } } }, - mounted(){ + mounted() { this.init(); - this.update(); - } + }, } diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue index 292b0de16..ca10285d1 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue @@ -104,7 +104,7 @@ export default { this.entity.selected.postcode.name = this.value.name; this.entity.selected.postcode.code = this.value.code; this.$emit('getReferenceAddresses', this.value); - if (this.value.center) { + if (typeof this.value.center !== 'undefined') { this.updateMapCenter(this.value.center); if (this.value.center.coordinates) { this.entity.selected.postcode.coordinates = this.value.center.coordinates; diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CountrySelection.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CountrySelection.vue index 6e64b2985..1b220ff00 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CountrySelection.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CountrySelection.vue @@ -30,7 +30,7 @@ export default { data() { return { value: this.selectCountryByCode( - this.context.edit ? this.entity.selected.country.code : 'FR' + this.context.edit ? this.entity.selected.country.code : this.context.defaults.default_country ) } }, @@ -45,14 +45,12 @@ export default { }, }, mounted() { - this.init(); + console.log('country selection mounted', this.value); + if (this.value !== undefined) { + this.selectCountry(this.value); + } }, - methods: { - init() { - if (this.value !== undefined) { - this.selectCountry(this.value); - } - }, + methods: { selectCountryByCode(countryCode) { return this.entity.loaded.countries.filter(c => c.countryCode === countryCode)[0]; }, diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/DatePane.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/DatePane.vue index 6a83cbfe2..eb76604d4 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/DatePane.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/DatePane.vue @@ -54,7 +54,7 @@ - - + \ No newline at end of file diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/HomepageWidget/MyWorkflowsTable.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/HomepageWidget/MyWorkflowsTable.vue new file mode 100644 index 000000000..f98d7a5cb --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/HomepageWidget/MyWorkflowsTable.vue @@ -0,0 +1,83 @@ + + + + + diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/HomepageWidget/MyWorks.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/HomepageWidget/MyWorks.vue index 1a8e0bb76..78f78300f 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/HomepageWidget/MyWorks.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/HomepageWidget/MyWorks.vue @@ -1,6 +1,6 @@ // CURRENTLY NOT IN USE