{{ $t('no_data') }}
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
-
{% endblock %}
+
{% endembed %}
{% endblock %}
diff --git a/src/Bundle/ChillMainBundle/Resources/views/Workflow/_attachment.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Workflow/_attachment.html.twig
index b825e4d43..24cf3cab4 100644
--- a/src/Bundle/ChillMainBundle/Resources/views/Workflow/_attachment.html.twig
+++ b/src/Bundle/ChillMainBundle/Resources/views/Workflow/_attachment.html.twig
@@ -1,4 +1,7 @@
-{# TODO Adapt condition #}
+{# TODO
+ Check if this template is used
+ Adapt condition or Delete it
+#}
{% if random(1) == 0 %}
{# For a document #}
@@ -22,7 +25,7 @@
{# For an action #}
- {%- if w.socialAction.issue -%}
+ {%- if w.socialAction.issue -%}{# Problématique sociale #}
{{ 'Social issue'|trans }}
@@ -94,56 +109,94 @@
-
- {% include 'ChillPersonBundle:AccompanyingCourseWork:_objectifs_results_evaluations.html.twig' %}
-
-
-
-
- {% set notif_counter = chill_count_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork', w.id) %}
- {% if notif_counter.total > 0 %}
- {{ chill_counter_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork', w.id) }}
- {% endif %}
-
- {% import '@ChillPerson/Macro/updatedBy.html.twig' as macro %}
- {{ macro.updatedBy(w) }}
+ {% if displayContent is not defined or displayContent == 'short' %}
+
+ {% include 'ChillPersonBundle:AccompanyingCourseWork:_objectifs_results_evaluations.html.twig' with {
+ 'displayContent': displayContent
+ } %}
+ {% endif %}
- {% if displayAction is defined and displayAction == true %}
-
- {% set suppEvaluations = [] %}
- {% for e in w.accompanyingPeriodWorkEvaluations %}
- {% set suppEvaluations = suppEvaluations|merge([
- {'relatedEntityClass': 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluation', 'relatedEntityId': e.id }
- ]) %}
+ {% if displayContent is not defined or displayContent == 'short' %}
+
- {% for d in e.documents %}
- {% set suppEvaluations = suppEvaluations|merge([
- {'relatedEntityClass': 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument', 'relatedEntityId': d.id }
- ]) %}
- {% endfor %}
- {% endfor %}
-
{% endif %}
- {% if is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_DELETE', w) %}
-
-
-
- {% endif %}
-
- {% endif %}
-
+
+ {% endif %}
+
+{#
+ # This is for 'long' version of content
+ # Note: this include is wrapped in a flex-table container.
+ # We start by closing the flex-table so we can add more.
+ # At the end we leave the last flex-table open, as it will be closed in the container.
+#}
+{% if displayContent is defined and displayContent == 'long' %}
+
+
+ {% if w.results|length > 0 or w.goals|length > 0 or w.accompanyingPeriodWorkEvaluations|length > 0 %}
+
{{ 'Dispositifs' }}
+
+
{# new flex-table wrapper #}
+
+ {% include 'ChillPersonBundle:AccompanyingCourseWork:_objectifs_results_evaluations.html.twig' with {
+ 'displayContent': displayContent
+ } %}
+
+ {% endif %}
{% endif %}
+
{% if e.warningInterval and e.warningInterval.d > 0 %}
{% set days = (e.warningInterval.d + e.warningInterval.m * 30) %}
{{ 'accompanying_course_work.warning_interval'|trans ~ ' : ' }}
{{ 'accompanying_course_work.%days% days before max_date'|trans({'%days%': days }) }}
+ {% else %}
+ {% if displayContent is defined and displayContent == 'long' %}
+
{% for w in works %}
- {% include '@ChillPerson/AccompanyingCourseWork/_item.html.twig' with { 'displayAction': true } %}
+ {% include '@ChillPerson/AccompanyingCourseWork/_item.html.twig' with {
+ 'displayAction': true,
+ 'displayContent': 'short',
+ 'itemBlocClass': ''
+ } %}
{% endfor %}
diff --git a/src/Bundle/ChillPersonBundle/Search/PersonSearch.php b/src/Bundle/ChillPersonBundle/Search/PersonSearch.php
index 7a8c2dd14..c85f1dabd 100644
--- a/src/Bundle/ChillPersonBundle/Search/PersonSearch.php
+++ b/src/Bundle/ChillPersonBundle/Search/PersonSearch.php
@@ -12,6 +12,7 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Search;
use Chill\MainBundle\Form\Type\ChillDateType;
+use Chill\MainBundle\Form\Type\ChillPhoneNumberType;
use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Search\AbstractSearch;
use Chill\MainBundle\Search\HasAdvancedSearchFormInterface;
@@ -24,6 +25,7 @@ use Chill\PersonBundle\Form\Type\GenderType;
use Chill\PersonBundle\Repository\PersonACLAwareRepositoryInterface;
use DateTime;
use Exception;
+use libphonenumber\PhoneNumber;
use Symfony\Component\Form\Extension\Core\Type\TelType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
@@ -94,7 +96,7 @@ class PersonSearch extends AbstractSearch implements HasAdvancedSearchFormInterf
'label' => 'Birthdate before',
'required' => false,
])
- ->add('phonenumber', TelType::class, [
+ ->add('phonenumber', ChillPhoneNumberType::class, [
'required' => false,
'label' => 'Part of the phonenumber',
])
@@ -116,11 +118,12 @@ class PersonSearch extends AbstractSearch implements HasAdvancedSearchFormInterf
$string .= empty($data['_default']) ? '' : $data['_default'] . ' ';
- foreach (['firstname', 'lastname', 'gender', 'phonenumber', 'city'] as $key) {
+ foreach (['firstname', 'lastname', 'gender', 'city'] as $key) {
$string .= empty($data[$key]) ? '' : $key . ':' .
// add quote if contains spaces
(strpos($data[$key], ' ') !== false ? '"' . $data[$key] . '"' : $data[$key])
. ' ';
+
}
foreach (['birthdate', 'birthdate-before', 'birthdate-after'] as $key) {
@@ -130,6 +133,8 @@ class PersonSearch extends AbstractSearch implements HasAdvancedSearchFormInterf
$key . ':' . $data[$key]->format('Y-m-d') . ' ';
}
+ $string .= empty($data['phonenumber']) ? '' : 'phonenumber:' . $data['phonenumber']->getNationalNumber();
+
return $string;
}
@@ -154,6 +159,18 @@ class PersonSearch extends AbstractSearch implements HasAdvancedSearchFormInterf
$data[$key] = $date ?? null;
}
+ if (array_key_exists('phonenumber', $terms)) {
+ try {
+ $phonenumber = new PhoneNumber();
+ $phonenumber->setNationalNumber($terms['phonenumber']);
+ } catch (Exception $ex) {
+ throw new ParsingException("The date for {$key} is "
+ . 'not parsable', 0, $ex);
+ }
+
+ $data['phonenumber'] = $phonenumber ?? null;
+ }
+
return $data;
}
diff --git a/src/Bundle/ChillPersonBundle/Security/Authorization/AccompanyingPeriodWorkEvaluationVoter.php b/src/Bundle/ChillPersonBundle/Security/Authorization/AccompanyingPeriodWorkEvaluationVoter.php
index 6f711f8b3..3861d5dd6 100644
--- a/src/Bundle/ChillPersonBundle/Security/Authorization/AccompanyingPeriodWorkEvaluationVoter.php
+++ b/src/Bundle/ChillPersonBundle/Security/Authorization/AccompanyingPeriodWorkEvaluationVoter.php
@@ -11,16 +11,25 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Security\Authorization;
+use Chill\MainBundle\Security\Authorization\ChillVoterInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use Symfony\Component\Security\Core\Security;
use UnexpectedValueException;
+use function in_array;
-class AccompanyingPeriodWorkEvaluationVoter extends Voter
+class AccompanyingPeriodWorkEvaluationVoter extends Voter implements ChillVoterInterface
{
+ public const ALL = [
+ self::SEE,
+ self::STATS,
+ ];
+
public const SEE = 'CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_EVALUATION_SHOW';
+ public const STATS = 'CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_EVALUATION_STATS';
+
private Security $security;
public function __construct(Security $security)
@@ -31,7 +40,7 @@ class AccompanyingPeriodWorkEvaluationVoter extends Voter
protected function supports($attribute, $subject)
{
return $subject instanceof AccompanyingPeriodWorkEvaluation
- && self::SEE === $attribute;
+ && in_array($attribute, self::ALL, true);
}
/**
@@ -41,6 +50,9 @@ class AccompanyingPeriodWorkEvaluationVoter extends Voter
protected function voteOnAttribute($attribute, $subject, TokenInterface $token): bool
{
switch ($attribute) {
+ case self::STATS:
+ return $this->security->isGranted(AccompanyingPeriodWorkVoter::STATS, $subject);
+
case self::SEE:
return $this->security->isGranted(AccompanyingPeriodWorkVoter::SEE, $subject->getAccompanyingPeriodWork());
diff --git a/src/Bundle/ChillPersonBundle/Security/Authorization/AccompanyingPeriodWorkVoter.php b/src/Bundle/ChillPersonBundle/Security/Authorization/AccompanyingPeriodWorkVoter.php
index 54872f877..84f31c202 100644
--- a/src/Bundle/ChillPersonBundle/Security/Authorization/AccompanyingPeriodWorkVoter.php
+++ b/src/Bundle/ChillPersonBundle/Security/Authorization/AccompanyingPeriodWorkVoter.php
@@ -11,8 +11,13 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Security\Authorization;
+use Chill\MainBundle\Security\Authorization\ChillVoterInterface;
+use Chill\MainBundle\Security\Authorization\VoterHelperFactoryInterface;
+use Chill\MainBundle\Security\Authorization\VoterHelperInterface;
+use Chill\MainBundle\Security\ProvideRoleHierarchyInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
+use Chill\PersonBundle\Entity\Person;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use Symfony\Component\Security\Core\Security;
@@ -20,8 +25,15 @@ use UnexpectedValueException;
use function get_class;
use function in_array;
-class AccompanyingPeriodWorkVoter extends Voter
+class AccompanyingPeriodWorkVoter extends Voter implements ProvideRoleHierarchyInterface, ChillVoterInterface
{
+ public const ALL = [
+ self::SEE,
+ self::CREATE,
+ self::UPDATE,
+ self::DELETE,
+ ];
+
public const CREATE = 'CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_CREATE';
public const DELETE = 'CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_DELETE';
@@ -32,9 +44,39 @@ class AccompanyingPeriodWorkVoter extends Voter
private Security $security;
- public function __construct(Security $security)
- {
+ private VoterHelperInterface $voterHelper;
+
+ public function __construct(
+ Security $security,
+ VoterHelperFactoryInterface $voterHelperFactory
+ ) {
$this->security = $security;
+ $this->voterHelper = $voterHelperFactory
+ ->generate(self::class)
+ ->addCheckFor(null, [self::CREATE])
+ ->addCheckFor(AccompanyingPeriod::class, [self::ALL])
+ ->addCheckFor(Person::class, [self::SEE, self::CREATE])
+ ->build();
+ }
+
+ public function getRoles(): array
+ {
+ return [
+ self::SEE,
+ self::CREATE,
+ self::UPDATE,
+ self::DELETE,
+ ];
+ }
+
+ public function getRolesWithHierarchy(): array
+ {
+ return ['Social actions' => $this->getRoles()];
+ }
+
+ public function getRolesWithoutScope(): array
+ {
+ return [];
}
protected function supports($attribute, $subject): bool
@@ -86,9 +128,4 @@ class AccompanyingPeriodWorkVoter extends Voter
throw new UnexpectedValueException(sprintf("attribute {$attribute} on instance %s is not supported", get_class($subject)));
}
-
- private function getRoles(): array
- {
- return [self::SEE, self::CREATE, self::UPDATE, self::DELETE];
- }
}
diff --git a/src/Bundle/ChillPersonBundle/Security/Authorization/HouseholdVoter.php b/src/Bundle/ChillPersonBundle/Security/Authorization/HouseholdVoter.php
index 5d57cadcc..47a15db54 100644
--- a/src/Bundle/ChillPersonBundle/Security/Authorization/HouseholdVoter.php
+++ b/src/Bundle/ChillPersonBundle/Security/Authorization/HouseholdVoter.php
@@ -12,6 +12,7 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Security\Authorization;
use Chill\MainBundle\Entity\Center;
+use Chill\MainBundle\Security\Authorization\ChillVoterInterface;
use Chill\MainBundle\Security\Authorization\VoterHelperFactoryInterface;
use Chill\MainBundle\Security\Authorization\VoterHelperInterface;
use Chill\MainBundle\Security\ProvideRoleHierarchyInterface;
@@ -23,7 +24,7 @@ use Symfony\Component\Security\Core\Security;
use UnexpectedValueException;
use function in_array;
-class HouseholdVoter extends Voter implements ProvideRoleHierarchyInterface
+class HouseholdVoter extends Voter implements ProvideRoleHierarchyInterface, ChillVoterInterface
{
public const EDIT = 'CHILL_PERSON_HOUSEHOLD_EDIT';
@@ -37,7 +38,9 @@ class HouseholdVoter extends Voter implements ProvideRoleHierarchyInterface
public const STATS = 'CHILL_PERSON_HOUSEHOLD_STATS';
private const ALL = [
- self::EDIT, self::SEE,
+ self::SEE,
+ self::EDIT,
+ self::STATS,
];
private VoterHelperInterface $helper;
@@ -60,7 +63,7 @@ class HouseholdVoter extends Voter implements ProvideRoleHierarchyInterface
public function getRolesWithHierarchy(): array
{
- return ['Person' => $this->getRoles()];
+ return ['Household' => $this->getRoles()];
}
public function getRolesWithoutScope(): array
diff --git a/src/Bundle/ChillPersonBundle/Tests/Export/Export/CountSocialWorkActionsTest.php b/src/Bundle/ChillPersonBundle/Tests/Export/Export/CountAccompanyingPeriodWorkTest.php
similarity index 81%
rename from src/Bundle/ChillPersonBundle/Tests/Export/Export/CountSocialWorkActionsTest.php
rename to src/Bundle/ChillPersonBundle/Tests/Export/Export/CountAccompanyingPeriodWorkTest.php
index 3ad0f7c1b..a16e04359 100644
--- a/src/Bundle/ChillPersonBundle/Tests/Export/Export/CountSocialWorkActionsTest.php
+++ b/src/Bundle/ChillPersonBundle/Tests/Export/Export/CountAccompanyingPeriodWorkTest.php
@@ -13,15 +13,15 @@ namespace Export\Export;
use Chill\MainBundle\Test\Export\AbstractExportTest;
use Chill\PersonBundle\Export\Declarations;
-use Chill\PersonBundle\Export\Export\CountSocialWorkActions;
+use Chill\PersonBundle\Export\Export\CountAccompanyingPeriodWork;
/**
* @internal
* @coversNothing
*/
-final class CountSocialWorkActionsTest extends AbstractExportTest
+final class CountAccompanyingPeriodWorkTest extends AbstractExportTest
{
- private CountSocialWorkActions $export;
+ private CountAccompanyingPeriodWork $export;
protected function setUp(): void
{
diff --git a/src/Bundle/ChillPersonBundle/config/services/exports_evaluation.yaml b/src/Bundle/ChillPersonBundle/config/services/exports_evaluation.yaml
index f58b7112b..83b80d54e 100644
--- a/src/Bundle/ChillPersonBundle/config/services/exports_evaluation.yaml
+++ b/src/Bundle/ChillPersonBundle/config/services/exports_evaluation.yaml
@@ -8,6 +8,12 @@ services:
tags:
- { name: chill.export, alias: count_evaluation }
+ Chill\PersonBundle\Export\Export\ListEvaluation:
+ autowire: true
+ autoconfigure: true
+ tags:
+ - { name: chill.export, alias: list_evaluation }
+
## Filters
chill.person.export.filter_evaluationtype:
class: Chill\PersonBundle\Export\Filter\EvaluationFilters\EvaluationTypeFilter
diff --git a/src/Bundle/ChillPersonBundle/config/services/exports_household.yaml b/src/Bundle/ChillPersonBundle/config/services/exports_household.yaml
index 0b8c5f1ce..1a12c1bc8 100644
--- a/src/Bundle/ChillPersonBundle/config/services/exports_household.yaml
+++ b/src/Bundle/ChillPersonBundle/config/services/exports_household.yaml
@@ -8,6 +8,12 @@ services:
tags:
- { name: chill.export, alias: count_household }
+ Chill\PersonBundle\Export\Export\ListHouseholdInPeriod:
+ autowire: true
+ autoconfigure: true
+ tags:
+ - { name: chill.export, alias: list_household_in_period }
+
## Filters
chill.person.export.filter_household_composition:
class: Chill\PersonBundle\Export\Filter\HouseholdFilters\CompositionFilter
diff --git a/src/Bundle/ChillPersonBundle/config/services/exports_social_actions.yaml b/src/Bundle/ChillPersonBundle/config/services/exports_social_actions.yaml
index 13c51762f..ecf0ec399 100644
--- a/src/Bundle/ChillPersonBundle/config/services/exports_social_actions.yaml
+++ b/src/Bundle/ChillPersonBundle/config/services/exports_social_actions.yaml
@@ -1,14 +1,19 @@
services:
- chill.person.export.count_social_work_actions:
- class: Chill\PersonBundle\Export\Export\CountSocialWorkActions
+ ## Indicators
+ Chill\PersonBundle\Export\Export\CountAccompanyingPeriodWork:
autowire: true
autoconfigure: true
tags:
- { name: chill.export, alias: count_social_work_actions }
- ## FILTERS
+ Chill\PersonBundle\Export\Export\ListAccompanyingPeriodWork:
+ autowire: true
+ autoconfigure: true
+ tags:
+ - { name: chill.export, alias: list_social_work_actions }
+ ## FILTERS
chill.person.export.filter_social_work_type:
class: Chill\PersonBundle\Export\Filter\SocialWorkFilters\SocialWorkTypeFilter
autowire: true
diff --git a/src/Bundle/ChillPersonBundle/migrations/Version20230117152610.php b/src/Bundle/ChillPersonBundle/migrations/Version20230117152610.php
new file mode 100644
index 000000000..81b61d2b5
--- /dev/null
+++ b/src/Bundle/ChillPersonBundle/migrations/Version20230117152610.php
@@ -0,0 +1,52 @@
+addSql('ALTER TABLE country ALTER name TYPE JSON');
+ $this->addSql('ALTER TABLE country ALTER name SET DEFAULT \'[]\'::json');
+ $this->addSql('ALTER TABLE country ALTER name DROP NOT NULL');
+ $this->addSql('COMMENT ON COLUMN country.name IS \'(DC2Type:simple_array)\'');
+
+ $this->addSql('ALTER TABLE chill_person_household_composition_type ALTER label TYPE JSON');
+ $this->addSql('ALTER TABLE chill_person_household_composition_type ALTER label SET DEFAULT \'[]\'::json');
+ $this->addSql('ALTER TABLE chill_person_household_composition_type ALTER label DROP NOT NULL');
+ $this->addSql('COMMENT ON COLUMN chill_person_household_composition_type.label IS \'(DC2Type:simple_array)\'');
+ }
+
+ public function getDescription(): string
+ {
+ return 'convert json fields to jsonb';
+ }
+
+ public function up(Schema $schema): void
+ {
+ $this->addSql('ALTER TABLE chill_person_household_composition_type ALTER label TYPE JSONB');
+ $this->addSql('ALTER TABLE chill_person_household_composition_type ALTER label SET DEFAULT \'[]\'::jsonb');
+ $this->addSql('ALTER TABLE chill_person_household_composition_type ALTER label DROP NOT NULL');
+ $this->addSql('COMMENT ON COLUMN chill_person_household_composition_type.label IS \'(DC2Type:json)\'');
+
+ $this->addSql('ALTER TABLE country ALTER name TYPE JSONB');
+ $this->addSql('ALTER TABLE country ALTER name SET DEFAULT \'[]\'::jsonb');
+ $this->addSql('ALTER TABLE country ALTER name DROP NOT NULL');
+ $this->addSql('COMMENT ON COLUMN country.name IS \'(DC2Type:json)\'');
+ }
+}
diff --git a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml
index d76d7c2a9..55f1282cb 100644
--- a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml
+++ b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml
@@ -232,6 +232,8 @@ No resources: "Pas d'interlocuteurs privilégiés"
Persons associated: Personnes concernés
Referrer: Référent
Referrers: Agents traitants
+Referrer2: Agent traitant
+No referrer: Pas d'agent traitant
Some peoples does not belong to any household currently. Add them to an household soon: Certaines personnes n'appartiennent à aucun ménage actuellement. Renseignez leur ménage dès que possible.
Add to household now: Ajouter à un ménage
Any resource for this accompanying course: Aucun interlocuteur privilégié pour ce parcours
@@ -244,7 +246,6 @@ The accompanying course has been successfully removed.: La période d'accompagne
Concerned scopes: Services concernés
# person resource
-
person_resources_menu: "Personnes ressources"
Person resources: "Ressources de la personne"
Add a person resource: "Ajouter une ressource"
@@ -257,8 +258,6 @@ There are no available resources: "Aucun ressource"
no comment found: "Aucun commentaire"
Select a type: "Choisissez un type"
Select a person: "Choisissez une personne"
-Select a thirdparty: "Choisissez un tiers"
-Contact person: "Personne de contact"
Kind: "Type"
@@ -286,14 +285,11 @@ Residential addresses history: Historique des adresses de résidence
Add a residential address: Ajouter une adresse de résidence
Which kind of residential address would you create ?: Quel type d'adresse de résidence voulez-vous créer?
The address of another person: L'adresse d'une autre personne
-The address of a third party: L'adresse d'un tiers
A new address: Une nouvelle adresse
residential_address_person_explanation: L'adresse sera positionnée auprès d'une personne. Lorsque la personne déménage, l'adresse de résidence suivra également cette personne
-residential_address_third_party_explanation: L'adresse sera associée à celle d'un tiers.
residential_address_new_address_explanation: Créer une nouvelle adresse. L'adresse sera fixe.
New residential address: Nouvelle adresse de résidence
Host person: Choisir l'adresse d'une personne
-Host third party: Choisir l'adresse d'un tiers
The new residential address was created successfully: La nouvelle adresse de résidence a été créée
Edit a residential address: Modifier l'addresse de résidence
The residential address was updated successfully: L'adresse de résidence a été mise à jour
@@ -311,13 +307,14 @@ Closing the accompanying period: Fermeture de la période d'accompagnement
Opening the accompanying period: Ouverture d'une période d'accompagnement
'Timeline for %name%': 'Historique de %name%'
-#roles
+# ROLES
CHILL_PERSON_SEE: Voir les personnes
CHILL_PERSON_UPDATE: Modifier les personnes
CHILL_PERSON_CREATE: Ajouter des personnes
CHILL_PERSON_STATS: Statistiques sur les personnes
CHILL_PERSON_LISTS: Liste des personnes
CHILL_PERSON_DUPLICATE: Gérer les doublons de personnes
+
CHILL_PERSON_ACCOMPANYING_PERIOD_SEE: Vision simplifiée d'une période d'accompagnement
CHILL_PERSON_ACCOMPANYING_PERIOD_CONFIDENTIAL: Voir et modifier les périodes d'accompagnement confidentielles
CHILL_PERSON_ACCOMPANYING_PERIOD_DELETE: Supprimer une période d'accompagnement
@@ -330,6 +327,18 @@ CHILL_PERSON_ACCOMPANYING_PERIOD_FULL: Voir les détails, créer, supprimer et m
CHILL_PERSON_ACCOMPANYING_COURSE_REASSIGN_BULK: Réassigner les parcours en lot
CHILL_PERSON_ACCOMPANYING_PERIOD_SEE_DETAILS: Voir les détails d'une période d'accompagnement
CHILL_PERSON_ACCOMPANYING_PERIOD_STATS: Statistiques sur les parcours d'accompagnement
+
+CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_CREATE: Créer une action d'accompagnement
+CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_DELETE: Supprimer une action d'accompagnement
+CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_SEE: Voir les actions d'accompagnement
+CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_UPDATE: Modifier une action d'accompagnement
+CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_STATS: Statistiques sur les actions d'accompagnement
+
+CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_EVALUATION_SHOW: Voir les évaluations
+CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_EVALUATION_STATS: Statistiques sur les évaluations
+
+CHILL_PERSON_HOUSEHOLD_SEE: Voir les ménages
+CHILL_PERSON_HOUSEHOLD_EDIT: Modifier les ménages
CHILL_PERSON_HOUSEHOLD_STATS: Statistiques sur les ménages
#period
@@ -407,7 +416,6 @@ Maximum age: Âge maximum
The minimum age should be less than the maximum age.: L'âge minimum doit être plus bas que l'âge maximum.
Date during which residential address was valid: Date de validité
-Filtered by person\'s who have a residential address located at a thirdparty of type %thirparty_type%: Uniquement les personnes qui ont une addresse de résidence chez un tiers de catégorie %thirdparty_type%
Family composition: Composition familiale
Family composition at this time: Composition familiale à cette date.
@@ -516,7 +524,6 @@ Filter by requestor: Filtrer les parcours selon la présence du demandeur au sei
Accepted choices: ''
is person concerned: Le demandeur est une personne concernée
is other person: Le demandeur est une personne, mais n'est pas concernée
-is thirdparty: Le demandeur est un tiers
no requestor: Le parcours ne comporte pas de demandeur
"Filtered by requestor: only %choice%": "Filtré par présence du demandeur au sein des personnes concernées: uniquement si %choice%"
Group by requestor: Grouper les parcours selon la nature du demandeur
@@ -653,9 +660,6 @@ Aggregate by household position: Grouper les personnes par position dans le mén
Household position in relation to this date: Position dans le ménage par rapport à cette date
Household position: Position dans le ménage
-Filter by person's who have a residential address located at a thirdparty of type: Filtrer les personnes qui ont une addresse de résidence chez un tiers de catégorie "xxx"
-"Filtered by person's who have a residential address located at a thirdparty of type %thirdparty_type% and valid on %date_calc%": "Uniquement les personnes qui ont une addresse de résidence chez un tiers de catégorie %thirdparty_type% et valide sur la date %date_calc%"
-
Aggregate by age: Grouper les personnes par âge
Calculate age in relation to this date: Calculer l'âge par rapport à cette date
@@ -886,6 +890,7 @@ accompanying_course_work:
create: Créer une action
Create accompanying course work: Créer une action d'accompagnement
Edit accompanying course work: Modifier une action d'accompagnement
+ Show accompanying course work: Action d'accompagnement
List accompanying course work: Liste des actions d'accompagnement
action: Action
create_date: Date de création
@@ -927,8 +932,6 @@ docgen:
A context for accompanying period work evaluation: Contexte pour les évaluations dans les actions d'accompagnement
Person basic: Personne (basique)
A basic context for person: Contexte pour les personnes
- Person with third party: Personne avec choix d'un tiers
- A context for person with a third party (for sending mail): Un contexte d'une personne avec un tiers (pour envoyer un courrier à ce tiers, par exemple)
Label for third party: Label à afficher aux utilisateurs
Document title: Titre du document généré
@@ -1046,6 +1049,8 @@ export:
Max date: Date d'échéance
filter:
+ by_geog_unit:
+ Filtered by person's geographical unit (based on address) computed at %datecalc%, only %units%: Filtré par unité géographique (sur base de l'adresse), calculé le %datecalc%, seulement %units%
person:
by_composition:
Filter by household composition: Filtrer les personnes par composition du ménage
@@ -1101,16 +1106,80 @@ export:
locationPersonId: Identifiant de l'usager auprès duquel le parcours est localisé
acpaddress_fieldscountry: Pays de l'adresse
isRequestorPerson: Le demandeur est-il un usager ?
- isRequestorThirdParty: Le demandeur est-il un tiers ?
requestorPersonId: Identifiant du demandeur personne
- requestorThirdPartyId: Identifiant du tiers
acprequestorPerson: Nom du demandeur personne
- acprequestorThirdPaty: Nom du demandeur tiers
scopes: Services
socialIssues: Problématiques sociales
-
-
-
+ eval:
+ List of evaluations: Liste des évaluations
+ Generate a list of evaluations, filtered on different parameters: Génère une liste des évaluations, filtrée sur différents paramètres.
+ Date of calculation for associated elements: Date de calcul des éléments associés
+ help_description: Les éléments associés, tel que le référent, l'adresse des usagers, etc. seront évalués à cette date
+ id: Identifiant de l'évaluation
+ startDate: Date de début
+ endDate: Date de fin
+ maxDate: Date d'échéance
+ warningInterval: Rappel
+ acpw_id: Identifiant de l'action
+ acpw_startDate: Début de l'action
+ acpw_endDate: Fin de l'action
+ acpw_socialaction_id: Identifiant de l'action
+ acpw_socialaction: Intitulé de l'action
+ acpw_socialissue: Problématique sociale
+ acpw_note: Commentaire de l'action
+ acpw_acp_id: Identifiant du parcours
+ acpw_acp_user: Référent du parcours
+ acpw_referrers: Agent traitant à la date spécifiée
+ acpw_persons_id: Identifiant des usagers
+ acpw_persons: Usagers de l'action
+ comment: Commentaire de l'évaluation
+ eval_title: Intitulé de l'évaluation
+ createdAt: Date de création
+ updatedAt: Date de modification
+ createdBy: Créé par
+ updatedBy: Modifié par
+ acpw:
+ List of accompanying period works: Liste des actions
+ List description: Génère une liste des actions d'accompagnement, filtrée sur différents paramètres.
+ help_description: L'agent traitant de l'action sera valide à cette date
+ id: Identifiant de l'action
+ startDate: Date de début
+ endDate: Date de fin
+ note: Commentaire de l'action
+ createdAt: Date de création
+ updatedAt: Date de modification
+ socialActionId: Identifiant de l'action
+ socialAction: Intitulé de l'action
+ socialIssue: Problématique sociale
+ createdBy: Créé par
+ updatedBy: Modifié par
+ acp_id: Identifiant du parcours
+ acp_user: Référent du parcours
+ referrers: Agents traitants
+ personsId: Identifiants des usagers
+ personsName: Usagers de l'action
+ goalsId: Identifiants des objectifs
+ goalsTitle: Objectifs
+ goalResultsId: Identifiants des résultats d'objectifs
+ goalResultsTitle: Résultats des objectifs
+ resultsId: Identifiants des Résultats
+ resultsTitle: Résultats
+ evaluationsId: Identifiants des évaluations
+ evaluationsTitle: Évaluations
+ household:
+ List household associated with accompanying period title: Liste des ménages impliqués dans un parcours
+ List description: Génère la liste des ménages, filtrée selon divers paramètres.
+ Date of calculation for associated elements: Date de calcul des éléments associés
+ help_description: Les éléments associés, comme l'adresse, les membres et la composition du ménage seront valides à cette date
+ id: Identifiant du ménage
+ address: Adresse du ménage
+ membersId: Identifiants des membres du ménage
+ membersName: Membres du ménage
+ membersCount: Nombre de membres
+ compositionNumberOfChildren: Nombre d'enfants dans la composition
+ compositionComment: Commentaire sur la composition
+ compositionType: Type de composition
+ acpaddress_fieldscountry: Pays # addressHelper effect
social_action:
and children: et dérivés
diff --git a/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/List/index_my_tasks.html.twig b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/List/index_my_tasks.html.twig
index 5d7e88542..359d02ef0 100644
--- a/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/List/index_my_tasks.html.twig
+++ b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/List/index_my_tasks.html.twig
@@ -1,4 +1,4 @@
-{% extends 'ChillMainBundle::layout.html.twig' %}
+{% extends '@ChillMain/layout.html.twig' %}
{% block title 'My tasks'|trans %}
diff --git a/src/Bundle/ChillTaskBundle/translations/messages.fr.yml b/src/Bundle/ChillTaskBundle/translations/messages.fr.yml
index a2a57708f..8d1d2b39d 100644
--- a/src/Bundle/ChillTaskBundle/translations/messages.fr.yml
+++ b/src/Bundle/ChillTaskBundle/translations/messages.fr.yml
@@ -116,3 +116,6 @@ CHILL_TASK_TASK_CREATE: Ajouter une tâche
CHILL_TASK_TASK_DELETE: Supprimer une tâche
CHILL_TASK_TASK_SHOW: Voir une tâche
CHILL_TASK_TASK_UPDATE: Modifier une tâche
+CHILL_TASK_TASK_CREATE_FOR_COURSE: Créer une tâche pour un parcours
+CHILL_TASK_TASK_CREATE_FOR_PERSON: Créer une tâche pour une personne
+
diff --git a/src/Bundle/ChillThirdPartyBundle/Export/Helper/LabelThirdPartyHelper.php b/src/Bundle/ChillThirdPartyBundle/Export/Helper/LabelThirdPartyHelper.php
index dc5f8ddc6..f87fe41b4 100644
--- a/src/Bundle/ChillThirdPartyBundle/Export/Helper/LabelThirdPartyHelper.php
+++ b/src/Bundle/ChillThirdPartyBundle/Export/Helper/LabelThirdPartyHelper.php
@@ -28,6 +28,21 @@ class LabelThirdPartyHelper
$this->thirdPartyRepository = $thirdPartyRepository;
}
+ public function getLabel(string $key, array $values, string $header): callable
+ {
+ return function ($value) use ($header) {
+ if ('_header' === $value) {
+ return $header;
+ }
+
+ if (null === $value || null === $thirdParty = $this->thirdPartyRepository->find($value)) {
+ return '';
+ }
+
+ return $this->thirdPartyRender->renderString($thirdParty, []);
+ };
+ }
+
public function getLabelMulti(string $key, array $values, string $header): callable
{
return function ($value) use ($header) {
diff --git a/src/Bundle/ChillThirdPartyBundle/Menu/AdminMenuBuilder.php b/src/Bundle/ChillThirdPartyBundle/Menu/AdminMenuBuilder.php
index bc1e6ef08..75b6a0c64 100644
--- a/src/Bundle/ChillThirdPartyBundle/Menu/AdminMenuBuilder.php
+++ b/src/Bundle/ChillThirdPartyBundle/Menu/AdminMenuBuilder.php
@@ -39,7 +39,6 @@ class AdminMenuBuilder implements LocalMenuBuilderInterface
->setAttribute('class', 'list-group-item-header')
->setExtras([
'order' => 3000,
- 'icons' => ['male'],
]);
$menu->addChild('Third party category', [
diff --git a/src/Bundle/ChillThirdPartyBundle/Security/Voter/ThirdPartyVoter.php b/src/Bundle/ChillThirdPartyBundle/Security/Voter/ThirdPartyVoter.php
index 8c59ea799..e1dae28b9 100644
--- a/src/Bundle/ChillThirdPartyBundle/Security/Voter/ThirdPartyVoter.php
+++ b/src/Bundle/ChillThirdPartyBundle/Security/Voter/ThirdPartyVoter.php
@@ -54,7 +54,7 @@ class ThirdPartyVoter extends AbstractChillVoter implements ProvideRoleHierarchy
public function getRolesWithHierarchy(): array
{
return [
- 'Third Party' => $this->getRoles(),
+ 'Third party' => $this->getRoles(),
];
}
diff --git a/src/Bundle/ChillThirdPartyBundle/translations/messages.fr.yml b/src/Bundle/ChillThirdPartyBundle/translations/messages.fr.yml
index 0ea68c8e8..94a10177b 100644
--- a/src/Bundle/ChillThirdPartyBundle/translations/messages.fr.yml
+++ b/src/Bundle/ChillThirdPartyBundle/translations/messages.fr.yml
@@ -82,6 +82,15 @@ Third party category: Catégories de tiers
Third party configuration: Gestion des tiers
+# person resource
+Select a thirdparty: "Choisissez un tiers"
+Contact person: "Personne de contact"
+
+# Residential address
+The address of a third party: L'adresse d'un tiers
+residential_address_third_party_explanation: L'adresse sera associée à celle d'un tiers.
+Host third party: Choisir l'adresse d'un tiers
+
# ROLES
CHILL_3PARTY_3PARTY_CREATE: Ajouter un Tiers
CHILL_3PARTY_3PARTY_SHOW: Voir un Tiers
@@ -99,3 +108,25 @@ crud:
title_new: Nouvelle catégorie de tiers
title_edit: Modifier la catégorie de tiers
+# docgen
+docgen:
+ A context for person with a third party (for sending mail): Un contexte d'une personne avec un tiers (pour envoyer un courrier à ce tiers, par exemple)
+ Person with third party: Personne avec choix d'un tiers
+
+# exports
+export:
+ list:
+ acp:
+ isRequestorThirdParty: Le demandeur est-il un tiers ?
+ requestorThirdPartyId: Identifiant du tiers
+ acprequestorThirdPaty: Nom du demandeur tiers
+ acpw:
+ handlingThierParty: Tiers traitant
+ thirdParties: Tiers intervenant
+
+# exports filters/aggregators
+Filtered by person\'s who have a residential address located at a thirdparty of type %thirparty_type%: Uniquement les personnes qui ont une addresse de résidence chez un tiers de catégorie %thirdparty_type%
+is thirdparty: Le demandeur est un tiers
+
+Filter by person's who have a residential address located at a thirdparty of type: Filtrer les personnes qui ont une addresse de résidence chez un tiers de catégorie "xxx"
+"Filtered by person's who have a residential address located at a thirdparty of type %thirdparty_type% and valid on %date_calc%": "Uniquement les personnes qui ont une addresse de résidence chez un tiers de catégorie %thirdparty_type% et valide sur la date %date_calc%"
diff --git a/src/Bundle/ChillWopiBundle/src/Controller/Editor.php b/src/Bundle/ChillWopiBundle/src/Controller/Editor.php
index 486a63eb2..4be848489 100644
--- a/src/Bundle/ChillWopiBundle/src/Controller/Editor.php
+++ b/src/Bundle/ChillWopiBundle/src/Controller/Editor.php
@@ -15,11 +15,13 @@ use ChampsLibres\WopiLib\Contract\Service\Configuration\ConfigurationInterface;
use ChampsLibres\WopiLib\Contract\Service\Discovery\DiscoveryInterface;
use ChampsLibres\WopiLib\Contract\Service\DocumentManagerInterface;
use Chill\DocStoreBundle\Entity\StoredObject;
+use Chill\MainBundle\Entity\User;
use Chill\WopiBundle\Service\Controller\ResponderInterface;
use Exception;
+use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface;
use loophp\psr17\Psr17Interface;
-use Symfony\Component\Finder\Exception\AccessDeniedException;
use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\RouterInterface;
@@ -33,6 +35,8 @@ final class Editor
{
private DocumentManagerInterface $documentManager;
+ private JWTTokenManagerInterface $JWTTokenManager;
+
private Psr17Interface $psr17;
private ResponderInterface $responder;
@@ -49,12 +53,14 @@ final class Editor
ConfigurationInterface $wopiConfiguration,
DiscoveryInterface $wopiDiscovery,
DocumentManagerInterface $documentManager,
+ JWTTokenManagerInterface $JWTTokenManager,
ResponderInterface $responder,
Security $security,
Psr17Interface $psr17,
RouterInterface $router
) {
$this->documentManager = $documentManager;
+ $this->JWTTokenManager = $JWTTokenManager;
$this->wopiConfiguration = $wopiConfiguration;
$this->wopiDiscovery = $wopiDiscovery;
$this->responder = $responder;
@@ -65,8 +71,12 @@ final class Editor
public function __invoke(string $fileId): Response
{
- if (null === $user = $this->security->getUser()->getUsername()) {
- throw new AccessDeniedException('You must be logged in to access to this resource.');
+ if (null === $user = $this->security->getUser()) {
+ throw new AccessDeniedHttpException('Please authenticate to access this feature');
+ }
+
+ if (!$user instanceof User) {
+ throw new AccessDeniedHttpException('Please authenticate as a user to access this feature');
}
$configuration = $this->wopiConfiguration->jsonSerialize();
@@ -82,7 +92,19 @@ final class Editor
}
$configuration['favIconUrl'] = '';
- $configuration['access_token'] = $user;
+ $configuration['access_token'] = $this->JWTTokenManager->createFromPayload($user, [
+ 'UserCanWrite' => true,
+ 'UserCanAttend' => true,
+ 'UserCanPresent' => true,
+ 'fileId' => $fileId,
+ ]);
+
+ // we parse the token back to get the access_token_ttl
+ // reminder: access_token_ttl is a javascript epoch, not a number of seconds; it is the
+ // time when the token will expire, not the time to live:
+ // https://learn.microsoft.com/en-us/microsoft-365/cloud-storage-partner-program/rest/concepts#the-access_token_ttl-property
+ $jwt = $this->JWTTokenManager->parse($configuration['access_token']);
+ $configuration['access_token_ttl'] = $jwt['exp'] * 1000;
$configuration['server'] = $this
->psr17
diff --git a/src/Bundle/ChillWopiBundle/src/Resources/config/services.php b/src/Bundle/ChillWopiBundle/src/Resources/config/services.php
index ffd1a7947..fc19e9c0d 100644
--- a/src/Bundle/ChillWopiBundle/src/Resources/config/services.php
+++ b/src/Bundle/ChillWopiBundle/src/Resources/config/services.php
@@ -12,12 +12,16 @@ declare(strict_types=1);
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use ChampsLibres\AsyncUploaderBundle\TempUrl\TempUrlGeneratorInterface;
+use ChampsLibres\WopiBundle\Contracts\AuthorizationManagerInterface;
+use ChampsLibres\WopiBundle\Contracts\UserManagerInterface;
use ChampsLibres\WopiBundle\Service\Wopi as CLWopi;
use ChampsLibres\WopiLib\Contract\Service\DocumentLockManagerInterface;
use ChampsLibres\WopiLib\Contract\Service\DocumentManagerInterface;
+use Chill\WopiBundle\Service\Wopi\AuthorizationManager;
use Chill\WopiBundle\Service\Wopi\ChillDocumentLockManager;
use Chill\WopiBundle\Service\Wopi\ChillDocumentManager;
use Chill\WopiBundle\Service\Wopi\ChillWopi;
+use Chill\WopiBundle\Service\Wopi\UserManager;
return static function (ContainerConfigurator $container) {
$services = $container
@@ -47,6 +51,16 @@ return static function (ContainerConfigurator $container) {
->set(ChillDocumentLockManager::class)
->decorate(DocumentLockManagerInterface::class);
+ $services
+ ->set(AuthorizationManager::class);
+
+ $services->alias(AuthorizationManagerInterface::class, AuthorizationManager::class);
+
+ $services
+ ->set(UserManager::class);
+
+ $services->alias(UserManagerInterface::class, UserManager::class);
+
// TODO: Move this into the async bundle (low priority)
$services
->alias(TempUrlGeneratorInterface::class, 'async_uploader.temp_url_generator');
diff --git a/src/Bundle/ChillWopiBundle/src/Service/Wopi/AuthorizationManager.php b/src/Bundle/ChillWopiBundle/src/Service/Wopi/AuthorizationManager.php
new file mode 100644
index 000000000..24ea00982
--- /dev/null
+++ b/src/Bundle/ChillWopiBundle/src/Service/Wopi/AuthorizationManager.php
@@ -0,0 +1,88 @@
+tokenManager = $tokenManager;
+ $this->security = $security;
+ }
+
+ public function isRestrictedWebViewOnly(string $accessToken, Document $document, RequestInterface $request): bool
+ {
+ return false;
+ }
+
+ public function isTokenValid(string $accessToken, Document $document, RequestInterface $request): bool
+ {
+ $metadata = $this->tokenManager->parse($accessToken);
+
+ if (false === $metadata) {
+ return false;
+ }
+
+ $user = $this->security->getUser();
+
+ if (!$user instanceof User) {
+ return false;
+ }
+
+ return $document->getWopiDocId() === $metadata['fileId'];
+ }
+
+ public function userCanAttend(string $accessToken, Document $document, RequestInterface $request): bool
+ {
+ return $this->isTokenValid($accessToken, $document, $request);
+ }
+
+ public function userCanDelete(string $accessToken, Document $document, RequestInterface $request): bool
+ {
+ return false;
+ }
+
+ public function userCannotWriteRelative(string $accessToken, Document $document, RequestInterface $request): bool
+ {
+ return true;
+ }
+
+ public function userCanPresent(string $accessToken, Document $document, RequestInterface $request): bool
+ {
+ return $this->isTokenValid($accessToken, $document, $request);
+ }
+
+ public function userCanRead(string $accessToken, Document $document, RequestInterface $request): bool
+ {
+ return $this->isTokenValid($accessToken, $document, $request);
+ }
+
+ public function userCanRename(string $accessToken, Document $document, RequestInterface $request): bool
+ {
+ return false;
+ }
+
+ public function userCanWrite(string $accessToken, Document $document, RequestInterface $request): bool
+ {
+ return $this->isTokenValid($accessToken, $document, $request);
+ }
+}
diff --git a/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentLockManager.php b/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentLockManager.php
index 30388852b..fce25de58 100644
--- a/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentLockManager.php
+++ b/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentLockManager.php
@@ -21,7 +21,10 @@ class ChillDocumentLockManager implements DocumentLockManagerInterface
{
private const LOCK_DURATION = 60 * 30;
- private int $postDeleteLockDurationMs;
+ /**
+ * Number of seconds to keep the lock after the delete lock operation.
+ */
+ private const LOCK_GRACEFUL_DURATION_TIME = 3;
private ChillRedis $redis;
@@ -32,9 +35,13 @@ class ChillDocumentLockManager implements DocumentLockManagerInterface
public function deleteLock(Document $document, RequestInterface $request): bool
{
- $this->redis->del($this->getCacheId($document));
+ if (0 === $this->redis->exists($this->getCacheId($document))) {
+ return true;
+ }
- return true;
+ // some queries (ex.: putFile) may be executed on the same time than the unlock, so
+ // we add a delay before unlocking the file, instead of deleting it immediatly
+ return $this->redis->expire($this->getCacheId($document), self::LOCK_GRACEFUL_DURATION_TIME);
}
public function getLock(Document $document, RequestInterface $request): string
diff --git a/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentManager.php b/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentManager.php
index 1ae6b395a..19b4dc9b4 100644
--- a/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentManager.php
+++ b/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentManager.php
@@ -24,11 +24,12 @@ use loophp\psr17\Psr17Interface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\StreamInterface;
use Ramsey\Uuid\Uuid;
+use RuntimeException;
use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Mime\MimeTypes;
-use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
+use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use function strlen;
final class ChillDocumentManager implements DocumentManagerInterface
@@ -86,7 +87,9 @@ final class ChillDocumentManager implements DocumentManagerInterface
public function deleteLock(Document $document): void
{
- $this->documentLockManager->deleteLock($document, $this->request);
+ if (false === $this->documentLockManager->deleteLock($document, $this->request)) {
+ throw new RuntimeException('could not remove the lock');
+ }
}
/**
@@ -190,7 +193,12 @@ final class ChillDocumentManager implements DocumentManagerInterface
public function remove(Document $document): void
{
- // TODO: To implement when we have a clearer view and API.
+ throw new RuntimeException('this is not implemented and should not happens');
+ }
+
+ public function rename(Document $document, string $requestedName): void
+ {
+ throw new RuntimeException('this is not implemented and should not happens');
}
public function write(Document $document, array $properties = []): void
diff --git a/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillWopi.php b/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillWopi.php
index 3cafc4c5d..82dbadf7b 100644
--- a/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillWopi.php
+++ b/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillWopi.php
@@ -11,41 +11,17 @@ declare(strict_types=1);
namespace Chill\WopiBundle\Service\Wopi;
-use ChampsLibres\WopiLib\Contract\Service\DocumentManagerInterface;
use ChampsLibres\WopiLib\Contract\Service\WopiInterface;
-use DateTimeImmutable;
-use DateTimeInterface;
-use loophp\psr17\Psr17Interface;
-use Psr\Cache\CacheItemPoolInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
-use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
-use Symfony\Component\Security\Core\User\UserProviderInterface;
-use function strlen;
final class ChillWopi implements WopiInterface
{
- private CacheItemPoolInterface $cache;
-
- private DocumentManagerInterface $documentManager;
-
- private Psr17Interface $psr17;
-
- private UserProviderInterface $userProvider;
-
private WopiInterface $wopi;
public function __construct(
- CacheItemPoolInterface $cache,
- DocumentManagerInterface $documentManager,
- Psr17Interface $psr17,
- UserProviderInterface $userProvider,
WopiInterface $wopi
) {
- $this->cache = $cache;
- $this->documentManager = $documentManager;
- $this->psr17 = $psr17;
- $this->userProvider = $userProvider;
$this->wopi = $wopi;
}
@@ -54,55 +30,9 @@ final class ChillWopi implements WopiInterface
?string $accessToken,
RequestInterface $request
): ResponseInterface {
- try {
- $user = $this->userProvider->loadUserByUsername($accessToken);
- } catch (UsernameNotFoundException $e) {
- return $this
- ->psr17
- ->createResponse(401);
- }
-
- // @ TODO : Replace this with a call to decorated object once authentication is done.
- $document = $this->documentManager->findByDocumentId($fileId);
- $userIdentifier = $user->getUsername();
- $userCacheKey = sprintf('wopi_putUserInfo_%s', $userIdentifier);
-
- return $this
- ->psr17
- ->createResponse()
- ->withHeader('Content-Type', 'application/json')
- ->withBody($this->psr17->createStream((string) json_encode(
- [
- 'BaseFileName' => $this->documentManager->getBasename($document),
- 'OwnerId' => 'Symfony',
- 'Size' => $this->documentManager->getSize($document),
- 'UserId' => $userIdentifier,
- 'ReadOnly' => false,
- 'UserCanAttend' => true,
- 'UserCanPresent' => true,
- 'UserCanRename' => true,
- 'UserCanWrite' => true,
- 'UserCanNotWriteRelative' => false,
- 'SupportsUserInfo' => true,
- 'SupportsDeleteFile' => true,
- 'SupportsLocks' => true,
- 'SupportsGetLock' => true,
- 'SupportsExtendedLockLength' => true,
- 'UserFriendlyName' => $userIdentifier,
- 'SupportsUpdate' => true,
- 'SupportsRename' => true,
- 'SupportsFolder' => false,
- 'DisablePrint' => false,
- 'AllowExternalMarketplace' => true,
- 'SupportedShareUrlTypes' => [
- 'ReadOnly',
- ],
- 'LastModifiedTime' => $this->documentManager->getLastModifiedDate($document)
- ->format(DateTimeInterface::ATOM),
- 'SHA256' => $this->documentManager->getSha256($document),
- 'UserInfo' => (string) $this->cache->getItem($userCacheKey)->get(),
- ]
- )));
+ return $this->wopi->checkFileInfo($fileId, $accessToken, $request, [
+ 'SupportsRename' => false,
+ ]);
}
public function deleteFile(string $fileId, ?string $accessToken, RequestInterface $request): ResponseInterface
@@ -152,97 +82,7 @@ final class ChillWopi implements WopiInterface
string $xWopiEditors,
RequestInterface $request
): ResponseInterface {
- $document = $this->documentManager->findByDocumentId($fileId);
- $version = $this->documentManager->getVersion($document);
-
- // File is unlocked, we must reject the document, except if collabora is autosaving
- if (false === $this->documentManager->hasLock($document) && 'true' !== ($request->getHeader('x-lool-wopi-isexitsave') ?? ['false'])[0]) {
- if (0 !== $this->documentManager->getSize($document)) {
- return $this
- ->psr17
- ->createResponse(409)
- ->withHeader(
- WopiInterface::HEADER_ITEM_VERSION,
- sprintf('v%s', $version)
- );
- }
- }
-
- // File is locked, we check for the lock
- if ($this->documentManager->hasLock($document)) {
- if ($xWopiLock !== $currentLock = $this->documentManager->getLock($document)) {
- return $this
- ->psr17
- ->createResponse(409)
- ->withHeader(
- WopiInterface::HEADER_LOCK,
- $currentLock
- )
- ->withHeader(
- WopiInterface::HEADER_ITEM_VERSION,
- sprintf('v%s', $version)
- );
- }
- }
-
- // for collabora online editor, check timestamp if present
- /* delete because it seems that collabora send always the first wopi-timestamp, not the last known one
- // example:
- // load the doc: the last-wopi is 12:00 in FileInfo
- // save the doc: x-cool-wopi-timestamp is 12:00, but we replace the ts with the time of save (12:05)
- // save the doc again: x-cool-wopi-timestamp is still 12:00...
- if ($request->hasHeader('x-cool-wopi-timestamp')) {
- $date = DateTimeImmutable::createFromFormat(
- DateTimeImmutable::ATOM,
- $request->getHeader('x-cool-wopi-timestamp')[0]
- );
-
- if (false === $date) {
- throw new RuntimeException('Error parsing date: ' . implode('', DateTimeImmutable::getLastErrors()));
- }
-
- if ($this->documentManager->getLastModifiedDate($document)->getTimestamp() < $date->getTimestamp()) {
- return $this
- ->psr17
- ->createResponse(409)
- ->withHeader(
- WopiInterface::HEADER_LOCK,
- $currentLock
- )
- ->withHeader(
- WopiInterface::HEADER_ITEM_VERSION,
- sprintf('v%s', $version)
- )
- ->withBody(
- $this->psr17->createStream(
- json_encode(['COOLStatusCode' => 1010])
- )
- );
- }
- }
- */
-
- $body = (string) $request->getBody();
- $this->documentManager->write(
- $document,
- [
- 'content' => $body,
- 'size' => (string) strlen($body),
- ]
- );
- $version = $this->documentManager->getVersion($document);
-
- return $this
- ->psr17
- ->createResponse()
- ->withHeader(
- WopiInterface::HEADER_LOCK,
- $xWopiLock
- )
- ->withHeader(
- WopiInterface::HEADER_ITEM_VERSION,
- sprintf('v%s', $version)
- );
+ return $this->wopi->putFile($fileId, $accessToken, $xWopiLock, $xWopiEditors, $request);
}
public function putRelativeFile(string $fileId, string $accessToken, ?string $suggestedTarget, ?string $relativeTarget, bool $overwriteRelativeTarget, int $size, RequestInterface $request): ResponseInterface
diff --git a/src/Bundle/ChillWopiBundle/src/Service/Wopi/UserManager.php b/src/Bundle/ChillWopiBundle/src/Service/Wopi/UserManager.php
new file mode 100644
index 000000000..dc030192b
--- /dev/null
+++ b/src/Bundle/ChillWopiBundle/src/Service/Wopi/UserManager.php
@@ -0,0 +1,53 @@
+security = $security;
+ }
+
+ public function getUserFriendlyName(string $accessToken, string $fileId, RequestInterface $request): ?string
+ {
+ $user = $this->security->getUser();
+
+ if (!$user instanceof User) {
+ return null;
+ }
+
+ return (string) $user->getLabel();
+ }
+
+ public function getUserId(string $accessToken, string $fileId, RequestInterface $request): ?string
+ {
+ $user = $this->security->getUser();
+
+ if (!$user instanceof User) {
+ return null;
+ }
+
+ return (string) $user->getId();
+ }
+
+ public function isAnonymousUser(string $accessToken, string $fileId, RequestInterface $request): bool
+ {
+ return false;
+ }
+}