diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index bb5c8dd5d..e493e6556 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -122,7 +122,7 @@ unit_tests: - php tests/console chill:db:sync-views --env=test - php -d memory_limit=2G tests/console cache:clear --env=test - php -d memory_limit=3G tests/console doctrine:fixtures:load -n --env=test - - php -d memory_limit=4G bin/phpunit --colors=never --exclude-group dbIntensive + - php -d memory_limit=4G bin/phpunit --colors=never --exclude-group dbIntensive --exclude-group openstack-integration artifacts: expire_in: 1 day paths: diff --git a/docs/source/installation/enable-collabora-for-dev.rst b/docs/source/installation/enable-collabora-for-dev.rst new file mode 100644 index 000000000..17a9ae1cc --- /dev/null +++ b/docs/source/installation/enable-collabora-for-dev.rst @@ -0,0 +1,125 @@ + +Enable CODE for development +=========================== + +For editing a document, there must be a way to communicate between the collabora server and the symfony server, in +both direction. The domain name should also be the same for collabora server and for the browser which access to the +online editor. + +Using ngrok (or other http tunnel) +---------------------------------- + +One can configure a tunnel server to expose your local install to the web, and access to your local server using the +tunnel url. + +Start ngrok +^^^^^^^^^^^ + +This can be achieve using `ngrok `_. + +.. note:: + + The configuration of ngrok is outside of the scope of this document. Refers to the ngrok's documentation. + +.. code-block:: bash + + # ensuring that your server is running through http and port 8000 + ngrok http 8000 + # then open the link given by the ngrok utility and you should reach your app + +At this step, ensure that you can reach your local app using the ngrok url. + +Configure Collabora +^^^^^^^^^^^^^^^^^^^ + +The collabora server must be executed online and configure to access to your ngrok installation. Ensure that the aliasgroup +exists for your ngrok application (`See the CODE documentation: `_). + +Configure your app +^^^^^^^^^^^^^^^^^^ + +Set the :code:`EDITOR_SERVER` variable to point to your collabora server, this should be done in your :code:`.env.local` file. + +At this point, everything must be fine. In case of errors, watch the log from your collabora server, use the `profiler `_ +to debug the requests. + +.. note:: + + In case of error while validating proof (you'll see those message in the collabora's logs), you can temporarily disable + the proof validation adding this code snippet in `config/services.yaml`: + + .. code-block:: yaml + + when@dev: + # add only in dev environment, to avoid security problems + services: + ChampsLibres\WopiLib\Contract\Service\ProofValidatorInterface: + # this class will always validate proof + alias: Chill\WopiBundle\Service\Wopi\NullProofValidator + +With a local CODE image +----------------------- + +.. warning:: + + This configuration is not sure, and must be refined. The documentation does not seems to be entirely valid. + +Use a local domain name and https for your app +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Use the proxy feature from embedded symfony server to run your app. `See the dedicated doc ` + +Configure also the `https certificate `_ + +In this example, your local domain name will be :code:`my-domain` and the url will be :code:`https://my-domain.wip`. + +Ensure that the proxy is running. + +Create a certificate database for collabora +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Collabora must validate your certificate generated by symfony console. For that, you need `to create a NSS database ` +and configure collabora to use it. + +At first, export the certificate for symfony development. Use the graphical interface from your browser to get the +certificate as a PEM file. + +.. code-block:: bash + + # create your database in a custom directory + mkdir /path/to/your/directory + certutil -N -d /path/to/your/directory + cat /path/to/your/ca.crt | certutil -d . -A symfony -t -t C,P,C,u,w -a + +Launch CODE properly configured + +.. code-block:: yaml + + collabora: + image: collabora/code:latest + environment: + - SLEEPFORDEBUGGER=0 + - DONT_GEN_SSL_CERT="True" + # add path to the database + - extra_params=--o:ssl.enable=false --o:ssl.termination=false --o:logging.level=7 -o:certificates.database_path=/etc/custom-certificates/nss-database + - username=admin + - password=admin + - dictionaries=en_US + - aliasgroup1=https://my-domain.wip + ports: + - "127.0.0.1:9980:9980" + volumes: + - "/path/to/your/directory/nss-database:/etc/custom-certificates/nss-database" + extra_hosts: + - "my-domain.wip:host-gateway" + +Configure your app +^^^^^^^^^^^^^^^^^^ + +Into your :code:`.env.local` file: + +.. code-block:: env + + EDITOR_SERVER=http://${COLLABORA_HOST}:${COLLABORA_PORT} + +At this step, you should be able to edit a document through collabora. diff --git a/package.json b/package.json index e0417cd4f..f0d6ca062 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,6 @@ "masonry-layout": "^4.2.2", "mime": "^4.0.0", "pdfjs-dist": "^4.3.136", - "swagger-ui": "^4.15.5", "vis-network": "^9.1.0", "vue": "^3.2.37", "vue-i18n": "^9.1.6", diff --git a/src/Bundle/ChillActivityBundle/Controller/ActivityController.php b/src/Bundle/ChillActivityBundle/Controller/ActivityController.php index cbfb93400..f248c6559 100644 --- a/src/Bundle/ChillActivityBundle/Controller/ActivityController.php +++ b/src/Bundle/ChillActivityBundle/Controller/ActivityController.php @@ -68,7 +68,7 @@ final class ActivityController extends AbstractController private readonly FilterOrderHelperFactoryInterface $filterOrderHelperFactory, private readonly TranslatableStringHelperInterface $translatableStringHelper, private readonly PaginatorFactory $paginatorFactory, - private readonly ChillSecurity $security + private readonly ChillSecurity $security, ) {} /** diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityReasonAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityReasonAggregator.php index 33090f297..ad3937a6e 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityReasonAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityReasonAggregator.php @@ -28,7 +28,7 @@ class ActivityReasonAggregator implements AggregatorInterface, ExportElementVali public function __construct( protected ActivityReasonCategoryRepository $activityReasonCategoryRepository, protected ActivityReasonRepository $activityReasonRepository, - protected TranslatableStringHelper $translatableStringHelper + protected TranslatableStringHelper $translatableStringHelper, ) {} public function addRole(): ?string diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersJobAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersJobAggregator.php index 3628206ec..b90c2b0cc 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersJobAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersJobAggregator.php @@ -26,7 +26,7 @@ class ActivityUsersJobAggregator implements AggregatorInterface public function __construct( private readonly UserJobRepositoryInterface $userJobRepository, - private readonly TranslatableStringHelperInterface $translatableStringHelper + private readonly TranslatableStringHelperInterface $translatableStringHelper, ) {} public function addRole(): ?string diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersScopeAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersScopeAggregator.php index bffce629f..a93a2a875 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersScopeAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersScopeAggregator.php @@ -26,7 +26,7 @@ class ActivityUsersScopeAggregator implements AggregatorInterface public function __construct( private readonly ScopeRepositoryInterface $scopeRepository, - private readonly TranslatableStringHelperInterface $translatableStringHelper + private readonly TranslatableStringHelperInterface $translatableStringHelper, ) {} public function addRole(): ?string diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/CreatorJobAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/CreatorJobAggregator.php index d57670e7f..ef8dd8c50 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/CreatorJobAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/CreatorJobAggregator.php @@ -26,7 +26,7 @@ class CreatorJobAggregator implements AggregatorInterface public function __construct( private readonly UserJobRepositoryInterface $userJobRepository, - private readonly TranslatableStringHelper $translatableStringHelper + private readonly TranslatableStringHelper $translatableStringHelper, ) {} public function addRole(): ?string diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/CreatorScopeAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/CreatorScopeAggregator.php index 6641f0807..ddf90dfbc 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/CreatorScopeAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/CreatorScopeAggregator.php @@ -26,7 +26,7 @@ class CreatorScopeAggregator implements AggregatorInterface public function __construct( private readonly ScopeRepository $scopeRepository, - private readonly TranslatableStringHelper $translatableStringHelper + private readonly TranslatableStringHelper $translatableStringHelper, ) {} public function addRole(): ?string diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/StatActivityDuration.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/StatActivityDuration.php index 3cdadac67..547dbf6bc 100644 --- a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/StatActivityDuration.php +++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/StatActivityDuration.php @@ -42,7 +42,7 @@ class StatActivityDuration implements ExportInterface, GroupedExportInterface /** * The action for this report. */ - protected string $action = 'sum' + protected string $action = 'sum', ) { $this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center']; } diff --git a/src/Bundle/ChillActivityBundle/Export/Export/ListActivityHelper.php b/src/Bundle/ChillActivityBundle/Export/Export/ListActivityHelper.php index 15137b013..dd817e7ab 100644 --- a/src/Bundle/ChillActivityBundle/Export/Export/ListActivityHelper.php +++ b/src/Bundle/ChillActivityBundle/Export/Export/ListActivityHelper.php @@ -39,7 +39,7 @@ class ListActivityHelper private readonly TranslatorInterface $translator, private readonly TranslatableStringHelperInterface $translatableStringHelper, private readonly TranslatableStringExportLabelHelper $translatableStringLabelHelper, - private readonly UserHelper $userHelper + private readonly UserHelper $userHelper, ) {} public function addSelect(QueryBuilder $qb): void diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ActivityPresenceFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ActivityPresenceFilter.php index 201b9f810..c9eab9066 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/ActivityPresenceFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/ActivityPresenceFilter.php @@ -25,7 +25,7 @@ final readonly class ActivityPresenceFilter implements FilterInterface { public function __construct( private TranslatableStringHelperInterface $translatableStringHelper, - private TranslatorInterface $translator + private TranslatorInterface $translator, ) {} public function getTitle() diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ActivityTypeFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ActivityTypeFilter.php index ae47146df..96e42c3e6 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/ActivityTypeFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/ActivityTypeFilter.php @@ -26,7 +26,7 @@ class ActivityTypeFilter implements ExportElementValidatedInterface, FilterInter { public function __construct( protected TranslatableStringHelperInterface $translatableStringHelper, - protected ActivityTypeRepositoryInterface $activityTypeRepository + protected ActivityTypeRepositoryInterface $activityTypeRepository, ) {} public function addRole(): ?string diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/UsersJobFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/UsersJobFilter.php index f6d23ea78..cf2787359 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/UsersJobFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/UsersJobFilter.php @@ -29,7 +29,7 @@ class UsersJobFilter implements FilterInterface public function __construct( private readonly TranslatableStringHelperInterface $translatableStringHelper, - private readonly UserJobRepositoryInterface $userJobRepository + private readonly UserJobRepositoryInterface $userJobRepository, ) {} public function addRole(): ?string diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/UsersScopeFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/UsersScopeFilter.php index 4ce9c845a..bbf1630c4 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/UsersScopeFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/UsersScopeFilter.php @@ -29,7 +29,7 @@ class UsersScopeFilter implements FilterInterface public function __construct( private readonly ScopeRepositoryInterface $scopeRepository, - private readonly TranslatableStringHelperInterface $translatableStringHelper + private readonly TranslatableStringHelperInterface $translatableStringHelper, ) {} public function addRole(): ?string diff --git a/src/Bundle/ChillActivityBundle/Form/ActivityType.php b/src/Bundle/ChillActivityBundle/Form/ActivityType.php index 9e2358e8b..28c607acd 100644 --- a/src/Bundle/ChillActivityBundle/Form/ActivityType.php +++ b/src/Bundle/ChillActivityBundle/Form/ActivityType.php @@ -59,7 +59,7 @@ class ActivityType extends AbstractType protected TranslatableStringHelper $translatableStringHelper, protected array $timeChoices, protected SocialIssueRender $socialIssueRender, - protected SocialActionRender $socialActionRender + protected SocialActionRender $socialActionRender, ) { if (!$tokenStorage->getToken()->getUser() instanceof User) { throw new \RuntimeException('you should have a valid user'); diff --git a/src/Bundle/ChillActivityBundle/Form/Type/PickActivityReasonType.php b/src/Bundle/ChillActivityBundle/Form/Type/PickActivityReasonType.php index be1e372f1..ca411b6ac 100644 --- a/src/Bundle/ChillActivityBundle/Form/Type/PickActivityReasonType.php +++ b/src/Bundle/ChillActivityBundle/Form/Type/PickActivityReasonType.php @@ -27,7 +27,7 @@ class PickActivityReasonType extends AbstractType public function __construct( private readonly ActivityReasonRepository $activityReasonRepository, private readonly ActivityReasonRender $reasonRender, - private readonly TranslatableStringHelperInterface $translatableStringHelper + private readonly TranslatableStringHelperInterface $translatableStringHelper, ) {} public function configureOptions(OptionsResolver $resolver) diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepository.php b/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepository.php index c0fd359f1..f57da1a36 100644 --- a/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepository.php +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepository.php @@ -32,7 +32,7 @@ final readonly class ActivityDocumentACLAwareRepository implements ActivityDocum private EntityManagerInterface $em, private CenterResolverManagerInterface $centerResolverManager, private AuthorizationHelperForCurrentUserInterface $authorizationHelperForCurrentUser, - private Security $security + private Security $security, ) {} public function buildFetchQueryActivityDocumentLinkedToPersonFromPersonContext(Person $person, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null): FetchQueryInterface diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityReasonRepository.php b/src/Bundle/ChillActivityBundle/Repository/ActivityReasonRepository.php index 47a7b35e8..7943c0fb3 100644 --- a/src/Bundle/ChillActivityBundle/Repository/ActivityReasonRepository.php +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityReasonRepository.php @@ -25,7 +25,7 @@ class ActivityReasonRepository extends ServiceEntityRepository { public function __construct( ManagerRegistry $registry, - private readonly RequestStack $requestStack + private readonly RequestStack $requestStack, ) { parent::__construct($registry, ActivityReason::class); } diff --git a/src/Bundle/ChillActivityBundle/Security/Authorization/ActivityStoredObjectVoter.php b/src/Bundle/ChillActivityBundle/Security/Authorization/ActivityStoredObjectVoter.php index 4be101c45..78def7318 100644 --- a/src/Bundle/ChillActivityBundle/Security/Authorization/ActivityStoredObjectVoter.php +++ b/src/Bundle/ChillActivityBundle/Security/Authorization/ActivityStoredObjectVoter.php @@ -24,7 +24,7 @@ class ActivityStoredObjectVoter extends AbstractStoredObjectVoter public function __construct( private readonly ActivityRepository $repository, Security $security, - WorkflowStoredObjectPermissionHelper $workflowDocumentService + WorkflowStoredObjectPermissionHelper $workflowDocumentService, ) { parent::__construct($security, $workflowDocumentService); } diff --git a/src/Bundle/ChillActivityBundle/Security/Authorization/ActivityVoter.php b/src/Bundle/ChillActivityBundle/Security/Authorization/ActivityVoter.php index dd51a1f55..7a73a709c 100644 --- a/src/Bundle/ChillActivityBundle/Security/Authorization/ActivityVoter.php +++ b/src/Bundle/ChillActivityBundle/Security/Authorization/ActivityVoter.php @@ -75,7 +75,7 @@ class ActivityVoter extends AbstractChillVoter implements ProvideRoleHierarchyIn public function __construct( protected Security $security, - VoterHelperFactoryInterface $voterHelperFactory + VoterHelperFactoryInterface $voterHelperFactory, ) { $this->voterHelper = $voterHelperFactory->generate(self::class) ->addCheckFor(Person::class, [self::SEE, self::CREATE]) diff --git a/src/Bundle/ChillActivityBundle/Service/DocGenerator/ActivityContext.php b/src/Bundle/ChillActivityBundle/Service/DocGenerator/ActivityContext.php index 46e466ae1..cd0ca2a4f 100644 --- a/src/Bundle/ChillActivityBundle/Service/DocGenerator/ActivityContext.php +++ b/src/Bundle/ChillActivityBundle/Service/DocGenerator/ActivityContext.php @@ -50,7 +50,7 @@ class ActivityContext implements private readonly TranslatorInterface $translator, private readonly BaseContextData $baseContextData, private readonly ThirdPartyRender $thirdPartyRender, - private readonly ThirdPartyRepository $thirdPartyRepository + private readonly ThirdPartyRepository $thirdPartyRepository, ) {} public function adminFormReverseTransform(array $data): array diff --git a/src/Bundle/ChillActivityBundle/Service/DocGenerator/ListActivitiesByAccompanyingPeriodContext.php b/src/Bundle/ChillActivityBundle/Service/DocGenerator/ListActivitiesByAccompanyingPeriodContext.php index a2fa996a6..56b9de408 100644 --- a/src/Bundle/ChillActivityBundle/Service/DocGenerator/ListActivitiesByAccompanyingPeriodContext.php +++ b/src/Bundle/ChillActivityBundle/Service/DocGenerator/ListActivitiesByAccompanyingPeriodContext.php @@ -56,7 +56,7 @@ class ListActivitiesByAccompanyingPeriodContext implements private readonly SocialIssueRepository $socialIssueRepository, private readonly ThirdPartyRepository $thirdPartyRepository, private readonly TranslatableStringHelperInterface $translatableStringHelper, - private readonly UserRepository $userRepository + private readonly UserRepository $userRepository, ) {} public function adminFormReverseTransform(array $data): array diff --git a/src/Bundle/ChillActivityBundle/Tests/Form/Type/TranslatableActivityReasonTest.php b/src/Bundle/ChillActivityBundle/Tests/Form/Type/TranslatableActivityReasonTest.php index 014c3ae9f..750758f72 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Form/Type/TranslatableActivityReasonTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Form/Type/TranslatableActivityReasonTest.php @@ -76,7 +76,7 @@ final class TranslatableActivityReasonTest extends TypeTestCase */ protected function getTranslatableStringHelper( $locale = 'en', - $fallbackLocale = 'en' + $fallbackLocale = 'en', ) { $prophet = new \Prophecy\Prophet(); $requestStack = $prophet->prophesize(); diff --git a/src/Bundle/ChillActivityBundle/Tests/Security/Authorization/ActivityVoterTest.php b/src/Bundle/ChillActivityBundle/Tests/Security/Authorization/ActivityVoterTest.php index e3ab7b60a..0a9e7d8ba 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Security/Authorization/ActivityVoterTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Security/Authorization/ActivityVoterTest.php @@ -138,7 +138,7 @@ final class ActivityVoterTest extends KernelTestCase Scope $scope, Center $center, $attribute, - $message + $message, ) { $token = $this->prepareToken($user); $activity = $this->prepareActivity($scope, $this->preparePerson($center)); diff --git a/src/Bundle/ChillActivityBundle/Timeline/TimelineActivityProvider.php b/src/Bundle/ChillActivityBundle/Timeline/TimelineActivityProvider.php index 23a75e9fe..5fe56bbce 100644 --- a/src/Bundle/ChillActivityBundle/Timeline/TimelineActivityProvider.php +++ b/src/Bundle/ChillActivityBundle/Timeline/TimelineActivityProvider.php @@ -32,7 +32,7 @@ class TimelineActivityProvider implements TimelineProviderInterface protected EntityManagerInterface $em, protected AuthorizationHelperInterface $helper, TokenStorageInterface $storage, - protected ActivityACLAwareRepository $aclAwareRepository + protected ActivityACLAwareRepository $aclAwareRepository, ) { if (!$storage->getToken()->getUser() instanceof User) { throw new \RuntimeException('A user should be authenticated !'); diff --git a/src/Bundle/ChillAsideActivityBundle/src/Controller/AsideActivityController.php b/src/Bundle/ChillAsideActivityBundle/src/Controller/AsideActivityController.php index ffeab7630..fbc9338d6 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Controller/AsideActivityController.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Controller/AsideActivityController.php @@ -25,7 +25,7 @@ final class AsideActivityController extends CRUDController { public function __construct( private readonly AsideActivityCategoryRepository $categoryRepository, - private readonly Security $security + private readonly Security $security, ) {} public function createEntity(string $action, Request $request): object @@ -76,7 +76,7 @@ final class AsideActivityController extends CRUDController string $action, $query, Request $request, - PaginatorInterface $paginator + PaginatorInterface $paginator, ) { if ('index' === $action) { return $query->orderBy('e.date', 'DESC'); diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByActivityTypeAggregator.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByActivityTypeAggregator.php index 4271ae118..cc311d470 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByActivityTypeAggregator.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByActivityTypeAggregator.php @@ -22,7 +22,7 @@ class ByActivityTypeAggregator implements AggregatorInterface { public function __construct( private readonly AsideActivityCategoryRepository $asideActivityCategoryRepository, - private readonly TranslatableStringHelper $translatableStringHelper + private readonly TranslatableStringHelper $translatableStringHelper, ) {} public function addRole(): ?string diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserJobAggregator.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserJobAggregator.php index c3883b18a..58d5584b4 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserJobAggregator.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserJobAggregator.php @@ -26,7 +26,7 @@ class ByUserJobAggregator implements AggregatorInterface public function __construct( private readonly UserJobRepositoryInterface $userJobRepository, - private readonly TranslatableStringHelperInterface $translatableStringHelper + private readonly TranslatableStringHelperInterface $translatableStringHelper, ) {} public function addRole(): ?string diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserScopeAggregator.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserScopeAggregator.php index a99d2b75f..a0277a37b 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserScopeAggregator.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserScopeAggregator.php @@ -26,7 +26,7 @@ class ByUserScopeAggregator implements AggregatorInterface public function __construct( private readonly ScopeRepositoryInterface $scopeRepository, - private readonly TranslatableStringHelperInterface $translatableStringHelper + private readonly TranslatableStringHelperInterface $translatableStringHelper, ) {} public function addRole(): ?string diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Export/ListAsideActivity.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/ListAsideActivity.php index 33155c62f..816af5771 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Export/ListAsideActivity.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/ListAsideActivity.php @@ -41,7 +41,7 @@ final readonly class ListAsideActivity implements ListInterface, GroupedExportIn private AsideActivityCategoryRepository $asideActivityCategoryRepository, private CategoryRender $categoryRender, private LocationRepository $locationRepository, - private TranslatableStringHelperInterface $translatableStringHelper + private TranslatableStringHelperInterface $translatableStringHelper, ) {} public function buildForm(FormBuilderInterface $builder) {} diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByActivityTypeFilter.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByActivityTypeFilter.php index 708b12ef1..bfb8fe0b1 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByActivityTypeFilter.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByActivityTypeFilter.php @@ -27,7 +27,7 @@ class ByActivityTypeFilter implements FilterInterface public function __construct( private readonly CategoryRender $categoryRender, private readonly TranslatableStringHelperInterface $translatableStringHelper, - private readonly AsideActivityCategoryRepository $asideActivityTypeRepository + private readonly AsideActivityCategoryRepository $asideActivityTypeRepository, ) {} public function addRole(): ?string diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByLocationFilter.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByLocationFilter.php index 6de002606..23c141a06 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByLocationFilter.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByLocationFilter.php @@ -24,7 +24,7 @@ use Symfony\Component\Security\Core\Security; final readonly class ByLocationFilter implements FilterInterface { public function __construct( - private Security $security + private Security $security, ) {} public function getTitle(): string diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserJobFilter.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserJobFilter.php index ec2e3d220..551e91cd8 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserJobFilter.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserJobFilter.php @@ -29,7 +29,7 @@ class ByUserJobFilter implements FilterInterface public function __construct( private readonly TranslatableStringHelperInterface $translatableStringHelper, - private readonly UserJobRepositoryInterface $userJobRepository + private readonly UserJobRepositoryInterface $userJobRepository, ) {} public function addRole(): ?string diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserScopeFilter.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserScopeFilter.php index 8f8d50462..5257495aa 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserScopeFilter.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserScopeFilter.php @@ -29,7 +29,7 @@ class ByUserScopeFilter implements FilterInterface public function __construct( private readonly ScopeRepositoryInterface $scopeRepository, - private readonly TranslatableStringHelperInterface $translatableStringHelper + private readonly TranslatableStringHelperInterface $translatableStringHelper, ) {} public function addRole(): ?string diff --git a/src/Bundle/ChillAsideActivityBundle/src/Menu/UserMenuBuilder.php b/src/Bundle/ChillAsideActivityBundle/src/Menu/UserMenuBuilder.php index 154556bca..540b1264a 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Menu/UserMenuBuilder.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Menu/UserMenuBuilder.php @@ -44,7 +44,7 @@ class UserMenuBuilder implements LocalMenuBuilderInterface CountNotificationTask $counter, TokenStorageInterface $tokenStorage, TranslatorInterface $translator, - AuthorizationCheckerInterface $authorizationChecker + AuthorizationCheckerInterface $authorizationChecker, ) { $this->counter = $counter; $this->tokenStorage = $tokenStorage; diff --git a/src/Bundle/ChillAsideActivityBundle/src/Security/AsideActivityVoter.php b/src/Bundle/ChillAsideActivityBundle/src/Security/AsideActivityVoter.php index e15abd439..7fe56e5e4 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Security/AsideActivityVoter.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Security/AsideActivityVoter.php @@ -26,7 +26,7 @@ class AsideActivityVoter extends AbstractChillVoter implements ProvideRoleHierar private readonly VoterHelperInterface $voterHelper; public function __construct( - VoterHelperFactoryInterface $voterHelperFactory + VoterHelperFactoryInterface $voterHelperFactory, ) { $this->voterHelper = $voterHelperFactory ->generate(self::class) diff --git a/src/Bundle/ChillCalendarBundle/Command/SendTestShortMessageOnCalendarCommand.php b/src/Bundle/ChillCalendarBundle/Command/SendTestShortMessageOnCalendarCommand.php index 1b76f25fa..80ee424b8 100644 --- a/src/Bundle/ChillCalendarBundle/Command/SendTestShortMessageOnCalendarCommand.php +++ b/src/Bundle/ChillCalendarBundle/Command/SendTestShortMessageOnCalendarCommand.php @@ -47,7 +47,7 @@ class SendTestShortMessageOnCalendarCommand extends Command private readonly PhoneNumberHelperInterface $phoneNumberHelper, private readonly ShortMessageForCalendarBuilderInterface $messageForCalendarBuilder, private readonly ShortMessageTransporterInterface $transporter, - private readonly UserRepositoryInterface $userRepository + private readonly UserRepositoryInterface $userRepository, ) { parent::__construct('chill:calendar:test-send-short-message'); } diff --git a/src/Bundle/ChillCalendarBundle/Controller/CalendarController.php b/src/Bundle/ChillCalendarBundle/Controller/CalendarController.php index e57d2743d..94db03b1f 100644 --- a/src/Bundle/ChillCalendarBundle/Controller/CalendarController.php +++ b/src/Bundle/ChillCalendarBundle/Controller/CalendarController.php @@ -59,7 +59,7 @@ class CalendarController extends AbstractController private readonly AccompanyingPeriodRepository $accompanyingPeriodRepository, private readonly UserRepositoryInterface $userRepository, private readonly TranslatorInterface $translator, - private readonly \Doctrine\Persistence\ManagerRegistry $managerRegistry + private readonly \Doctrine\Persistence\ManagerRegistry $managerRegistry, ) {} /** diff --git a/src/Bundle/ChillCalendarBundle/Entity/CalendarDoc.php b/src/Bundle/ChillCalendarBundle/Entity/CalendarDoc.php index 90143231e..aff996095 100644 --- a/src/Bundle/ChillCalendarBundle/Entity/CalendarDoc.php +++ b/src/Bundle/ChillCalendarBundle/Entity/CalendarDoc.php @@ -47,7 +47,7 @@ class CalendarDoc implements TrackCreationInterface, TrackUpdateInterface Calendar $calendar, #[ORM\ManyToOne(targetEntity: StoredObject::class, cascade: ['persist'])] #[ORM\JoinColumn(nullable: false)] - private ?StoredObject $storedObject + private ?StoredObject $storedObject, ) { $this->setCalendar($calendar); $this->datetimeVersion = $calendar->getDateTimeVersion(); diff --git a/src/Bundle/ChillCalendarBundle/Export/Aggregator/JobAggregator.php b/src/Bundle/ChillCalendarBundle/Export/Aggregator/JobAggregator.php index 76cbe5cd8..1182ea374 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Aggregator/JobAggregator.php +++ b/src/Bundle/ChillCalendarBundle/Export/Aggregator/JobAggregator.php @@ -26,7 +26,7 @@ final readonly class JobAggregator implements AggregatorInterface public function __construct( private UserJobRepository $jobRepository, - private TranslatableStringHelper $translatableStringHelper + private TranslatableStringHelper $translatableStringHelper, ) {} public function addRole(): ?string diff --git a/src/Bundle/ChillCalendarBundle/Export/Aggregator/ScopeAggregator.php b/src/Bundle/ChillCalendarBundle/Export/Aggregator/ScopeAggregator.php index 4998f6d1f..ff24baa8c 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Aggregator/ScopeAggregator.php +++ b/src/Bundle/ChillCalendarBundle/Export/Aggregator/ScopeAggregator.php @@ -26,7 +26,7 @@ final readonly class ScopeAggregator implements AggregatorInterface public function __construct( private ScopeRepository $scopeRepository, - private TranslatableStringHelper $translatableStringHelper + private TranslatableStringHelper $translatableStringHelper, ) {} public function addRole(): ?string diff --git a/src/Bundle/ChillCalendarBundle/Export/Filter/JobFilter.php b/src/Bundle/ChillCalendarBundle/Export/Filter/JobFilter.php index 5543a453b..3c87397e4 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Filter/JobFilter.php +++ b/src/Bundle/ChillCalendarBundle/Export/Filter/JobFilter.php @@ -28,7 +28,7 @@ final readonly class JobFilter implements FilterInterface public function __construct( private TranslatableStringHelper $translatableStringHelper, - private UserJobRepositoryInterface $userJobRepository + private UserJobRepositoryInterface $userJobRepository, ) {} public function addRole(): ?string diff --git a/src/Bundle/ChillCalendarBundle/Export/Filter/ScopeFilter.php b/src/Bundle/ChillCalendarBundle/Export/Filter/ScopeFilter.php index 2f46db892..3a1c5a35a 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Filter/ScopeFilter.php +++ b/src/Bundle/ChillCalendarBundle/Export/Filter/ScopeFilter.php @@ -30,7 +30,7 @@ class ScopeFilter implements FilterInterface public function __construct( protected TranslatorInterface $translator, private readonly TranslatableStringHelper $translatableStringHelper, - private readonly ScopeRepositoryInterface $scopeRepository + private readonly ScopeRepositoryInterface $scopeRepository, ) {} public function addRole(): ?string diff --git a/src/Bundle/ChillCalendarBundle/Form/CalendarType.php b/src/Bundle/ChillCalendarBundle/Form/CalendarType.php index eec0b3f9f..eb1e1b5f2 100644 --- a/src/Bundle/ChillCalendarBundle/Form/CalendarType.php +++ b/src/Bundle/ChillCalendarBundle/Form/CalendarType.php @@ -37,7 +37,7 @@ class CalendarType extends AbstractType private readonly IdToUsersDataTransformer $idToUsersDataTransformer, private readonly IdToLocationDataTransformer $idToLocationDataTransformer, private readonly ThirdPartiesToIdDataTransformer $partiesToIdDataTransformer, - private readonly IdToCalendarRangeDataTransformer $calendarRangeDataTransformer + private readonly IdToCalendarRangeDataTransformer $calendarRangeDataTransformer, ) {} public function buildForm(FormBuilderInterface $builder, array $options) diff --git a/src/Bundle/ChillCalendarBundle/Messenger/Message/CalendarMessage.php b/src/Bundle/ChillCalendarBundle/Messenger/Message/CalendarMessage.php index 9bcd81c67..1476ed346 100644 --- a/src/Bundle/ChillCalendarBundle/Messenger/Message/CalendarMessage.php +++ b/src/Bundle/ChillCalendarBundle/Messenger/Message/CalendarMessage.php @@ -46,7 +46,7 @@ class CalendarMessage public function __construct( Calendar $calendar, private readonly string $action, - User $byUser + User $byUser, ) { $this->calendarId = $calendar->getId(); $this->byUserId = $byUser->getId(); diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MSUserAbsenceReader.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MSUserAbsenceReader.php index 4acc9de46..2c0e913f3 100644 --- a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MSUserAbsenceReader.php +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MSUserAbsenceReader.php @@ -59,7 +59,7 @@ final readonly class MSUserAbsenceReader implements MSUserAbsenceReaderInterface 'alwaysEnabled' => true, 'scheduled' => RemoteEventConverter::convertStringDateWithoutTimezone($automaticRepliesSettings['scheduledStartDateTime']['dateTime']) < $this->clock->now() && RemoteEventConverter::convertStringDateWithoutTimezone($automaticRepliesSettings['scheduledEndDateTime']['dateTime']) > $this->clock->now(), - default => throw new UserAbsenceSyncException('this status is not documented by Microsoft') + default => throw new UserAbsenceSyncException('this status is not documented by Microsoft'), }; } } diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MapCalendarToUser.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MapCalendarToUser.php index 5577adc2e..7b56fda34 100644 --- a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MapCalendarToUser.php +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MapCalendarToUser.php @@ -177,7 +177,7 @@ class MapCalendarToUser User $user, int $expiration, ?string $id = null, - ?string $secret = null + ?string $secret = null, ): void { $user->setAttributeByDomain(self::METADATA_KEY, self::EXPIRATION_SUBSCRIPTION_EVENT, $expiration); diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/RemoteEventConverter.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/RemoteEventConverter.php index 84797f6f1..2e2a8acaf 100644 --- a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/RemoteEventConverter.php +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/RemoteEventConverter.php @@ -57,7 +57,7 @@ class RemoteEventConverter private readonly LocationConverter $locationConverter, private readonly LoggerInterface $logger, private readonly PersonRenderInterface $personRender, - private readonly TranslatorInterface $translator + private readonly TranslatorInterface $translator, ) { $this->defaultDateTimeZone = (new \DateTimeImmutable())->getTimezone(); $this->remoteDateTimeZone = self::getRemoteTimeZone(); diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraphRemoteCalendarConnector.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraphRemoteCalendarConnector.php index a85de21d3..8b95e1c74 100644 --- a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraphRemoteCalendarConnector.php +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraphRemoteCalendarConnector.php @@ -351,7 +351,7 @@ class MSGraphRemoteCalendarConnector implements RemoteCalendarConnectorInterface [ 'id' => $id, 'lastModifiedDateTime' => $lastModified, - 'changeKey' => $changeKey + 'changeKey' => $changeKey, ] = $this->createOnRemote($eventData, $calendar->getMainUser(), 'calendar_'.$calendar->getId()); if (null === $id) { @@ -427,7 +427,7 @@ class MSGraphRemoteCalendarConnector implements RemoteCalendarConnectorInterface [ 'id' => $id, 'lastModifiedDateTime' => $lastModified, - 'changeKey' => $changeKey + 'changeKey' => $changeKey, ] = $this->createOnRemote( $eventData, $calendarRange->getUser(), @@ -564,7 +564,7 @@ class MSGraphRemoteCalendarConnector implements RemoteCalendarConnectorInterface [ 'id' => $id, 'lastModifiedDateTime' => $lastModified, - 'changeKey' => $changeKey + 'changeKey' => $changeKey, ] = $this->patchOnRemote( $calendar->getRemoteId(), $eventData, diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Model/RemoteEvent.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Model/RemoteEvent.php index 8da4e74d7..3d86da7f4 100644 --- a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Model/RemoteEvent.php +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Model/RemoteEvent.php @@ -33,6 +33,6 @@ class RemoteEvent #[Serializer\Groups(['read'])] public \DateTimeImmutable $endDate, #[Serializer\Groups(['read'])] - public bool $isAllDay = false + public bool $isAllDay = false, ) {} } diff --git a/src/Bundle/ChillCalendarBundle/Repository/CalendarRangeRepository.php b/src/Bundle/ChillCalendarBundle/Repository/CalendarRangeRepository.php index d6e1ac8a5..33f2f5818 100644 --- a/src/Bundle/ChillCalendarBundle/Repository/CalendarRangeRepository.php +++ b/src/Bundle/ChillCalendarBundle/Repository/CalendarRangeRepository.php @@ -65,7 +65,7 @@ class CalendarRangeRepository implements ObjectRepository \DateTimeImmutable $from, \DateTimeImmutable $to, ?int $limit = null, - ?int $offset = null + ?int $offset = null, ): array { $qb = $this->buildQueryAvailableRangesForUser($user, $from, $to); diff --git a/src/Bundle/ChillCalendarBundle/Service/DocGenerator/CalendarContext.php b/src/Bundle/ChillCalendarBundle/Service/DocGenerator/CalendarContext.php index 087f5ea86..c45acd41f 100644 --- a/src/Bundle/ChillCalendarBundle/Service/DocGenerator/CalendarContext.php +++ b/src/Bundle/ChillCalendarBundle/Service/DocGenerator/CalendarContext.php @@ -40,7 +40,7 @@ final readonly class CalendarContext implements CalendarContextInterface private PersonRepository $personRepository, private ThirdPartyRender $thirdPartyRender, private ThirdPartyRepository $thirdPartyRepository, - private TranslatableStringHelperInterface $translatableStringHelper + private TranslatableStringHelperInterface $translatableStringHelper, ) {} public function adminFormReverseTransform(array $data): array diff --git a/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/AccompanyingPeriodCalendarGenericDocProvider.php b/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/AccompanyingPeriodCalendarGenericDocProvider.php index 7d4544b37..ce851a31f 100644 --- a/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/AccompanyingPeriodCalendarGenericDocProvider.php +++ b/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/AccompanyingPeriodCalendarGenericDocProvider.php @@ -37,7 +37,7 @@ final readonly class AccompanyingPeriodCalendarGenericDocProvider implements Gen public function __construct( private Security $security, - private EntityManagerInterface $em + private EntityManagerInterface $em, ) {} /** diff --git a/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/PersonCalendarGenericDocProvider.php b/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/PersonCalendarGenericDocProvider.php index 5ca52744b..176f7978b 100644 --- a/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/PersonCalendarGenericDocProvider.php +++ b/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/PersonCalendarGenericDocProvider.php @@ -36,7 +36,7 @@ final readonly class PersonCalendarGenericDocProvider implements GenericDocForPe public function __construct( private Security $security, - private EntityManagerInterface $em + private EntityManagerInterface $em, ) {} private function addWhereClausesToQuery(FetchQuery $query, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery diff --git a/src/Bundle/ChillCalendarBundle/Tests/Form/CalendarTypeTest.php b/src/Bundle/ChillCalendarBundle/Tests/Form/CalendarTypeTest.php index 0d1202b67..83366c848 100644 --- a/src/Bundle/ChillCalendarBundle/Tests/Form/CalendarTypeTest.php +++ b/src/Bundle/ChillCalendarBundle/Tests/Form/CalendarTypeTest.php @@ -156,7 +156,7 @@ final class CalendarTypeTest extends TypeTestCase private function buildMultiToIdDataTransformer( string $classTransformer, - string $objClass + string $objClass, ) { $transformer = $this->prophesize($classTransformer); $transformer->transform(Argument::type('array')) @@ -195,7 +195,7 @@ final class CalendarTypeTest extends TypeTestCase private function buildSingleToIdDataTransformer( string $classTransformer, - string $class + string $class, ) { $transformer = $this->prophesize($classTransformer); $transformer->transform(Argument::type('object')) diff --git a/src/Bundle/ChillCalendarBundle/Tests/Service/DocGenerator/CalendarContextTest.php b/src/Bundle/ChillCalendarBundle/Tests/Service/DocGenerator/CalendarContextTest.php index c06872a66..8674a6b44 100644 --- a/src/Bundle/ChillCalendarBundle/Tests/Service/DocGenerator/CalendarContextTest.php +++ b/src/Bundle/ChillCalendarBundle/Tests/Service/DocGenerator/CalendarContextTest.php @@ -203,7 +203,7 @@ final class CalendarContextTest extends TestCase private function buildCalendarContext( ?EntityManagerInterface $entityManager = null, - ?NormalizerInterface $normalizer = null + ?NormalizerInterface $normalizer = null, ): CalendarContext { $baseContext = $this->prophesize(BaseContextData::class); $baseContext->getData(null)->willReturn(['base_context' => 'data']); diff --git a/src/Bundle/ChillCustomFieldsBundle/Command/CreateFieldsOnGroupCommand.php b/src/Bundle/ChillCustomFieldsBundle/Command/CreateFieldsOnGroupCommand.php index 24c531860..9fd474e24 100644 --- a/src/Bundle/ChillCustomFieldsBundle/Command/CreateFieldsOnGroupCommand.php +++ b/src/Bundle/ChillCustomFieldsBundle/Command/CreateFieldsOnGroupCommand.php @@ -44,7 +44,7 @@ class CreateFieldsOnGroupCommand extends Command private readonly EntityManager $entityManager, private readonly ValidatorInterface $validator, private $availableLanguages, - private $customizablesEntities + private $customizablesEntities, ) { parent::__construct(); } diff --git a/src/Bundle/ChillCustomFieldsBundle/Controller/CustomFieldsGroupController.php b/src/Bundle/ChillCustomFieldsBundle/Controller/CustomFieldsGroupController.php index 5d16269e6..7c1b0c5ea 100644 --- a/src/Bundle/ChillCustomFieldsBundle/Controller/CustomFieldsGroupController.php +++ b/src/Bundle/ChillCustomFieldsBundle/Controller/CustomFieldsGroupController.php @@ -39,7 +39,7 @@ class CustomFieldsGroupController extends AbstractController public function __construct( private readonly CustomFieldProvider $customFieldProvider, private readonly TranslatorInterface $translator, - private readonly \Doctrine\Persistence\ManagerRegistry $managerRegistry + private readonly \Doctrine\Persistence\ManagerRegistry $managerRegistry, ) {} /** diff --git a/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldChoice.php b/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldChoice.php index a54e4c21f..e803742fe 100644 --- a/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldChoice.php +++ b/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldChoice.php @@ -42,7 +42,7 @@ class CustomFieldChoice extends AbstractCustomField /** * @var TranslatableStringHelper Helper that find the string in current locale from an array of translation */ - private readonly TranslatableStringHelper $translatableStringHelper + private readonly TranslatableStringHelper $translatableStringHelper, ) {} public function allowOtherChoice(CustomField $cf) diff --git a/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldDate.php b/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldDate.php index dac447930..4eee4e829 100644 --- a/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldDate.php +++ b/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldDate.php @@ -44,7 +44,7 @@ class CustomFieldDate extends AbstractCustomField public function __construct( private readonly Environment $templating, - private readonly TranslatableStringHelper $translatableStringHelper + private readonly TranslatableStringHelper $translatableStringHelper, ) {} public function buildForm(FormBuilderInterface $builder, CustomField $customField) diff --git a/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldNumber.php b/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldNumber.php index e083b9b80..0f303fed6 100644 --- a/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldNumber.php +++ b/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldNumber.php @@ -41,7 +41,7 @@ class CustomFieldNumber extends AbstractCustomField public function __construct( private readonly Environment $templating, - private readonly TranslatableStringHelper $translatableStringHelper + private readonly TranslatableStringHelper $translatableStringHelper, ) {} public function buildForm(FormBuilderInterface $builder, CustomField $customField) diff --git a/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldText.php b/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldText.php index 17d8e95e7..00daee1c8 100644 --- a/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldText.php +++ b/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldText.php @@ -28,7 +28,7 @@ class CustomFieldText extends AbstractCustomField public function __construct( private readonly Environment $templating, - private readonly TranslatableStringHelper $translatableStringHelper + private readonly TranslatableStringHelper $translatableStringHelper, ) {} /** diff --git a/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldTitle.php b/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldTitle.php index 302e04350..75ecc0368 100644 --- a/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldTitle.php +++ b/src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldTitle.php @@ -31,7 +31,7 @@ class CustomFieldTitle extends AbstractCustomField /** * @var TranslatableStringHelper Helper that find the string in current locale from an array of translation */ - private readonly TranslatableStringHelper $translatableStringHelper + private readonly TranslatableStringHelper $translatableStringHelper, ) {} public function buildForm(FormBuilderInterface $builder, CustomField $customField) diff --git a/src/Bundle/ChillCustomFieldsBundle/Form/CustomFieldsGroupType.php b/src/Bundle/ChillCustomFieldsBundle/Form/CustomFieldsGroupType.php index 8cf3ea182..13e349012 100644 --- a/src/Bundle/ChillCustomFieldsBundle/Form/CustomFieldsGroupType.php +++ b/src/Bundle/ChillCustomFieldsBundle/Form/CustomFieldsGroupType.php @@ -26,7 +26,7 @@ class CustomFieldsGroupType extends AbstractType public function __construct( private readonly array $customizableEntities, // TODO : add comment about this variable - private readonly TranslatorInterface $translator + private readonly TranslatorInterface $translator, ) {} // TODO : details about the function diff --git a/src/Bundle/ChillDocGeneratorBundle/Controller/DocGeneratorTemplateController.php b/src/Bundle/ChillDocGeneratorBundle/Controller/DocGeneratorTemplateController.php index dfb70d1c8..7bcd6f6d5 100644 --- a/src/Bundle/ChillDocGeneratorBundle/Controller/DocGeneratorTemplateController.php +++ b/src/Bundle/ChillDocGeneratorBundle/Controller/DocGeneratorTemplateController.php @@ -48,7 +48,7 @@ final class DocGeneratorTemplateController extends AbstractController private readonly PaginatorFactory $paginatorFactory, private readonly EntityManagerInterface $entityManager, private readonly ClockInterface $clock, - private readonly ChillSecurity $security + private readonly ChillSecurity $security, ) {} #[Route(path: '{_locale}/admin/doc/gen/generate/test/from/{template}/for/{entityClassName}/{entityId}', name: 'chill_docgenerator_test_generate_from_template')] @@ -56,7 +56,7 @@ final class DocGeneratorTemplateController extends AbstractController DocGeneratorTemplate $template, string $entityClassName, int $entityId, - Request $request + Request $request, ): Response { return $this->generateDocFromTemplate( $template, @@ -71,7 +71,7 @@ final class DocGeneratorTemplateController extends AbstractController DocGeneratorTemplate $template, string $entityClassName, int $entityId, - Request $request + Request $request, ): Response { return $this->generateDocFromTemplate( $template, @@ -137,7 +137,7 @@ final class DocGeneratorTemplateController extends AbstractController DocGeneratorTemplate $template, int $entityId, Request $request, - bool $isTest + bool $isTest, ): Response { try { $context = $this->contextManager->getContextByDocGeneratorTemplate($template); diff --git a/src/Bundle/ChillDocGeneratorBundle/DataFixtures/ORM/LoadDocGeneratorTemplate.php b/src/Bundle/ChillDocGeneratorBundle/DataFixtures/ORM/LoadDocGeneratorTemplate.php index 97d436c5e..f38102a10 100644 --- a/src/Bundle/ChillDocGeneratorBundle/DataFixtures/ORM/LoadDocGeneratorTemplate.php +++ b/src/Bundle/ChillDocGeneratorBundle/DataFixtures/ORM/LoadDocGeneratorTemplate.php @@ -54,12 +54,15 @@ class LoadDocGeneratorTemplate extends AbstractFixture ]; foreach ($templates as $template) { - $newStoredObj = (new StoredObject()) - ->setFilename($template['file']['filename']) - ->setKeyInfos(json_decode($template['file']['key'], true)) - ->setIv(json_decode($template['file']['iv'], true)) + $newStoredObj = (new StoredObject()); + + $newStoredObj ->setCreatedAt(new \DateTime('today')) - ->setType($template['file']['type']); + ->registerVersion( + json_decode($template['file']['key'], true), + json_decode($template['file']['iv'], true), + $template['file']['type'], + ); $manager->persist($newStoredObj); diff --git a/src/Bundle/ChillDocGeneratorBundle/GeneratorDriver/RelatorioDriver.php b/src/Bundle/ChillDocGeneratorBundle/GeneratorDriver/RelatorioDriver.php index 1ee3f0ba1..4ce0ad139 100644 --- a/src/Bundle/ChillDocGeneratorBundle/GeneratorDriver/RelatorioDriver.php +++ b/src/Bundle/ChillDocGeneratorBundle/GeneratorDriver/RelatorioDriver.php @@ -28,7 +28,7 @@ final readonly class RelatorioDriver implements DriverInterface public function __construct( private HttpClientInterface $client, ParameterBagInterface $parameterBag, - private LoggerInterface $logger + private LoggerInterface $logger, ) { $this->url = $parameterBag->get('chill_doc_generator')['driver']['relatorio']['url']; } diff --git a/src/Bundle/ChillDocGeneratorBundle/Serializer/Normalizer/DocGenObjectNormalizer.php b/src/Bundle/ChillDocGeneratorBundle/Serializer/Normalizer/DocGenObjectNormalizer.php index a19e783d1..e6cec30bb 100644 --- a/src/Bundle/ChillDocGeneratorBundle/Serializer/Normalizer/DocGenObjectNormalizer.php +++ b/src/Bundle/ChillDocGeneratorBundle/Serializer/Normalizer/DocGenObjectNormalizer.php @@ -35,7 +35,7 @@ class DocGenObjectNormalizer implements NormalizerAwareInterface, NormalizerInte public function __construct( private readonly ClassMetadataFactoryInterface $classMetadataFactory, - private readonly TranslatableStringHelperInterface $translatableStringHelper + private readonly TranslatableStringHelperInterface $translatableStringHelper, ) { $this->propertyAccess = PropertyAccess::createPropertyAccessor(); } diff --git a/src/Bundle/ChillDocGeneratorBundle/Service/Generator/Generator.php b/src/Bundle/ChillDocGeneratorBundle/Service/Generator/Generator.php index 127d135bb..3e4d8fe3b 100644 --- a/src/Bundle/ChillDocGeneratorBundle/Service/Generator/Generator.php +++ b/src/Bundle/ChillDocGeneratorBundle/Service/Generator/Generator.php @@ -33,7 +33,7 @@ class Generator implements GeneratorInterface private readonly DriverInterface $driver, private readonly ManagerRegistry $objectManagerRegistry, private readonly LoggerInterface $logger, - private readonly StoredObjectManagerInterface $storedObjectManager + private readonly StoredObjectManagerInterface $storedObjectManager, ) {} public function generateDataDump( @@ -134,13 +134,11 @@ class Generator implements GeneratorInterface $content = Yaml::dump($data, 6); /* @var StoredObject $destinationStoredObject */ $destinationStoredObject - ->setType('application/yaml') - ->setFilename(sprintf('%s_yaml', uniqid('doc_', true))) ->setStatus(StoredObject::STATUS_READY) ; try { - $this->storedObjectManager->write($destinationStoredObject, $content); + $this->storedObjectManager->write($destinationStoredObject, $content, 'application/yaml'); } catch (StoredObjectManagerException $e) { $destinationStoredObject->addGenerationErrors($e->getMessage()); @@ -174,13 +172,11 @@ class Generator implements GeneratorInterface /* @var StoredObject $destinationStoredObject */ $destinationStoredObject - ->setType($template->getFile()->getType()) - ->setFilename(sprintf('%s_odt', uniqid('doc_', true))) ->setStatus(StoredObject::STATUS_READY) ; try { - $this->storedObjectManager->write($destinationStoredObject, $generatedResource); + $this->storedObjectManager->write($destinationStoredObject, $generatedResource, $template->getFile()->getType()); } catch (StoredObjectManagerException $e) { $destinationStoredObject->addGenerationErrors($e->getMessage()); diff --git a/src/Bundle/ChillDocGeneratorBundle/Service/Messenger/OnGenerationFails.php b/src/Bundle/ChillDocGeneratorBundle/Service/Messenger/OnGenerationFails.php index e3491f339..b9d538a63 100644 --- a/src/Bundle/ChillDocGeneratorBundle/Service/Messenger/OnGenerationFails.php +++ b/src/Bundle/ChillDocGeneratorBundle/Service/Messenger/OnGenerationFails.php @@ -39,7 +39,7 @@ final readonly class OnGenerationFails implements EventSubscriberInterface private MailerInterface $mailer, private StoredObjectRepositoryInterface $storedObjectRepository, private TranslatorInterface $translator, - private UserRepositoryInterface $userRepository + private UserRepositoryInterface $userRepository, ) {} public static function getSubscribedEvents() diff --git a/src/Bundle/ChillDocGeneratorBundle/tests/Service/Context/BaseContextDataTest.php b/src/Bundle/ChillDocGeneratorBundle/tests/Service/Context/BaseContextDataTest.php index ee5a6cc62..c7821e2dd 100644 --- a/src/Bundle/ChillDocGeneratorBundle/tests/Service/Context/BaseContextDataTest.php +++ b/src/Bundle/ChillDocGeneratorBundle/tests/Service/Context/BaseContextDataTest.php @@ -56,7 +56,7 @@ final class BaseContextDataTest extends KernelTestCase } private function buildBaseContext( - ?NormalizerInterface $normalizer = null + ?NormalizerInterface $normalizer = null, ): BaseContextData { return new BaseContextData( $normalizer ?? self::getContainer()->get(NormalizerInterface::class) diff --git a/src/Bundle/ChillDocStoreBundle/AsyncUpload/Driver/OpenstackObjectStore/TempUrlOpenstackGenerator.php b/src/Bundle/ChillDocStoreBundle/AsyncUpload/Driver/OpenstackObjectStore/TempUrlOpenstackGenerator.php index ae74f9d0e..19086b1c9 100644 --- a/src/Bundle/ChillDocStoreBundle/AsyncUpload/Driver/OpenstackObjectStore/TempUrlOpenstackGenerator.php +++ b/src/Bundle/ChillDocStoreBundle/AsyncUpload/Driver/OpenstackObjectStore/TempUrlOpenstackGenerator.php @@ -58,6 +58,7 @@ final readonly class TempUrlOpenstackGenerator implements TempUrlGeneratorInterf ?int $expire_delay = null, ?int $submit_delay = null, int $max_file_count = 1, + ?string $object_name = null, ): SignedUrlPost { $delay = $expire_delay ?? $this->max_expire_delay; $submit_delay ??= $this->max_submit_delay; @@ -84,7 +85,9 @@ final readonly class TempUrlOpenstackGenerator implements TempUrlGeneratorInterf $expires = $this->clock->now()->add(new \DateInterval('PT'.(string) $delay.'S')); - $object_name = $this->generateObjectName(); + if (null === $object_name) { + $object_name = $this->generateObjectName(); + } $g = new SignedUrlPost( $url = $this->generateUrl($object_name), @@ -141,7 +144,7 @@ final readonly class TempUrlOpenstackGenerator implements TempUrlGeneratorInterf { return match (str_ends_with($this->base_url, '/')) { true => $this->base_url.$relative_path, - false => $this->base_url.'/'.$relative_path + false => $this->base_url.'/'.$relative_path, }; } @@ -179,21 +182,19 @@ final readonly class TempUrlOpenstackGenerator implements TempUrlGeneratorInterf return \hash_hmac('sha512', $body, $this->key, false); } - private function generateSignature($method, $url, \DateTimeImmutable $expires) + private function generateSignature(string $method, $url, \DateTimeImmutable $expires) { if ('POST' === $method) { return $this->generateSignaturePost($url, $expires); } $path = \parse_url((string) $url, PHP_URL_PATH); - $body = sprintf( "%s\n%s\n%s", - $method, + strtoupper($method), $expires->format('U'), $path - ) - ; + ); $this->logger->debug( 'generate signature GET', diff --git a/src/Bundle/ChillDocStoreBundle/AsyncUpload/TempUrlGeneratorInterface.php b/src/Bundle/ChillDocStoreBundle/AsyncUpload/TempUrlGeneratorInterface.php index 08ac9e062..e6589b3c7 100644 --- a/src/Bundle/ChillDocStoreBundle/AsyncUpload/TempUrlGeneratorInterface.php +++ b/src/Bundle/ChillDocStoreBundle/AsyncUpload/TempUrlGeneratorInterface.php @@ -16,7 +16,8 @@ interface TempUrlGeneratorInterface public function generatePost( ?int $expire_delay = null, ?int $submit_delay = null, - int $max_file_count = 1 + int $max_file_count = 1, + ?string $object_name = null, ): SignedUrlPost; public function generate(string $method, string $object_name, ?int $expire_delay = null): SignedUrl; diff --git a/src/Bundle/ChillDocStoreBundle/AsyncUpload/Templating/AsyncUploadExtension.php b/src/Bundle/ChillDocStoreBundle/AsyncUpload/Templating/AsyncUploadExtension.php index e7c3e6cf0..8bece267b 100644 --- a/src/Bundle/ChillDocStoreBundle/AsyncUpload/Templating/AsyncUploadExtension.php +++ b/src/Bundle/ChillDocStoreBundle/AsyncUpload/Templating/AsyncUploadExtension.php @@ -25,7 +25,7 @@ class AsyncUploadExtension extends AbstractExtension { public function __construct( private readonly TempUrlGeneratorInterface $tempUrlGenerator, - private readonly UrlGeneratorInterface $routingUrlGenerator + private readonly UrlGeneratorInterface $routingUrlGenerator, ) {} public function getFilters() diff --git a/src/Bundle/ChillDocStoreBundle/Controller/AsyncUploadController.php b/src/Bundle/ChillDocStoreBundle/Controller/AsyncUploadController.php index 75be20d34..cdbb94d29 100644 --- a/src/Bundle/ChillDocStoreBundle/Controller/AsyncUploadController.php +++ b/src/Bundle/ChillDocStoreBundle/Controller/AsyncUploadController.php @@ -11,9 +11,11 @@ declare(strict_types=1); namespace Chill\DocStoreBundle\Controller; -use Chill\DocStoreBundle\AsyncUpload\Exception\TempUrlGeneratorException; use Chill\DocStoreBundle\AsyncUpload\TempUrlGeneratorInterface; -use Chill\DocStoreBundle\Security\Authorization\AsyncUploadVoter; +use Chill\DocStoreBundle\Entity\StoredObject; +use Chill\DocStoreBundle\Entity\StoredObjectVersion; +use Chill\DocStoreBundle\Security\Authorization\StoredObjectRoleEnum; +use Chill\MainBundle\Entity\User; use Psr\Log\LoggerInterface; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; @@ -30,62 +32,84 @@ final readonly class AsyncUploadController private TempUrlGeneratorInterface $tempUrlGenerator, private SerializerInterface $serializer, private Security $security, - private LoggerInterface $logger, + private LoggerInterface $chillLogger, ) {} - #[Route(path: '/asyncupload/temp_url/generate/{method}', name: 'async_upload.generate_url')] - public function getSignedUrl(string $method, Request $request): JsonResponse + #[Route(path: '/api/1.0/doc-store/async-upload/temp_url/{uuid}/generate/post', name: 'chill_docstore_asyncupload_getsignedurlpost')] + public function getSignedUrlPost(Request $request, StoredObject $storedObject): JsonResponse { - try { - switch (strtolower($method)) { - case 'post': - $p = $this->tempUrlGenerator - ->generatePost( - $request->query->has('expires_delay') ? $request->query->getInt('expires_delay') : null, - $request->query->has('submit_delay') ? $request->query->getInt('submit_delay') : null - ) - ; - break; - case 'get': - case 'head': - $object_name = $request->query->get('object_name', null); + if (!$this->security->isGranted(StoredObjectRoleEnum::EDIT->value, $storedObject)) { + throw new AccessDeniedHttpException('not able to edit the given stored object'); + } - if (null === $object_name) { - return (new JsonResponse((object) [ - 'message' => 'the object_name is null', - ])) - ->setStatusCode(JsonResponse::HTTP_BAD_REQUEST); - } - $p = $this->tempUrlGenerator->generate( - $method, - $object_name, - $request->query->has('expires_delay') ? $request->query->getInt('expires_delay') : null - ); - break; - default: - return (new JsonResponse((object) ['message' => 'the method ' - ."{$method} is not valid"])) - ->setStatusCode(JsonResponse::HTTP_BAD_REQUEST); + // we create a dummy version, to generate a filename + $version = $storedObject->registerVersion(); + + $p = $this->tempUrlGenerator + ->generatePost( + $request->query->has('expires_delay') ? $request->query->getInt('expires_delay') : null, + $request->query->has('submit_delay') ? $request->query->getInt('submit_delay') : null, + object_name: $version->getFilename() + ); + + $this->chillLogger->notice('[Privacy Event] a request to upload a document has been generated', [ + 'doc_uuid' => $storedObject->getUuid(), + ]); + + return new JsonResponse( + $this->serializer->serialize($p, 'json', [AbstractNormalizer::GROUPS => ['read']]), + Response::HTTP_OK, + [], + true + ); + } + + #[Route(path: '/api/1.0/doc-store/async-upload/temp_url/{uuid}/generate/{method}', name: 'chill_docstore_asyncupload_getsignedurlget', requirements: ['method' => 'get|head'])] + public function getSignedUrlGet(Request $request, StoredObject $storedObject, string $method): JsonResponse + { + if (!$this->security->isGranted(StoredObjectRoleEnum::SEE->value, $storedObject)) { + throw new AccessDeniedHttpException('not able to read the given stored object'); + } + + // we really want to be sure that there are no other method than get or head: + if (!in_array($method, ['get', 'head'], true)) { + throw new AccessDeniedHttpException('Only methods get and head are allowed'); + } + + if ($request->query->has('version')) { + $filename = $request->query->get('version'); + + $storedObjectVersion = $storedObject->getVersions()->findFirst(fn (int $index, StoredObjectVersion $version): bool => $version->getFilename() === $filename); + + if (null === $storedObjectVersion) { + // we are here in the case where the version is not stored into the database + // as the version is prefixed by the stored object prefix, we just have to check that this prefix + // is the same. It means that the user had previously the permission to "SEE_AND_EDIT" this stored + // object with same prefix that we checked before + if (!str_starts_with($filename, $storedObject->getPrefix())) { + throw new AccessDeniedHttpException('not able to match the version with the same filename'); + } } - } catch (TempUrlGeneratorException $e) { - $this->logger->warning('The client requested a temp url' - .' which sparkle an error.', [ - 'message' => $e->getMessage(), - 'expire_delay' => $request->query->getInt('expire_delay', 0), - 'file_count' => $request->query->getInt('file_count', 1), - 'method' => $method, - ]); - - $p = new \stdClass(); - $p->message = $e->getMessage(); - $p->status = JsonResponse::HTTP_BAD_REQUEST; - - return new JsonResponse($p, JsonResponse::HTTP_BAD_REQUEST); + } else { + $filename = $storedObject->getCurrentVersion()->getFilename(); } - if (!$this->security->isGranted(AsyncUploadVoter::GENERATE_SIGNATURE, $p)) { - throw new AccessDeniedHttpException('not allowed to generate this signature'); - } + $p = $this->tempUrlGenerator->generate( + $method, + $filename, + $request->query->has('expires_delay') ? $request->query->getInt('expires_delay') : null + ); + + $user = $this->security->getUser(); + $userId = match ($user instanceof User) { + true => $user->getId(), + false => $user->getUserIdentifier(), + }; + + $this->chillLogger->notice('[Privacy Event] a request to see a document has been granted', [ + 'doc_uuid' => $storedObject->getUuid()->toString(), + 'user_id' => $userId, + ]); return new JsonResponse( $this->serializer->serialize($p, 'json', [AbstractNormalizer::GROUPS => ['read']]), diff --git a/src/Bundle/ChillDocStoreBundle/Controller/DocumentAccompanyingCourseController.php b/src/Bundle/ChillDocStoreBundle/Controller/DocumentAccompanyingCourseController.php index a281fc663..018d6cb8f 100644 --- a/src/Bundle/ChillDocStoreBundle/Controller/DocumentAccompanyingCourseController.php +++ b/src/Bundle/ChillDocStoreBundle/Controller/DocumentAccompanyingCourseController.php @@ -35,7 +35,7 @@ class DocumentAccompanyingCourseController extends AbstractController protected TranslatorInterface $translator, protected EventDispatcherInterface $eventDispatcher, protected AuthorizationHelper $authorizationHelper, - private readonly \Doctrine\Persistence\ManagerRegistry $managerRegistry + private readonly \Doctrine\Persistence\ManagerRegistry $managerRegistry, ) {} #[Route(path: '/{id}/delete', name: 'chill_docstore_accompanying_course_document_delete')] diff --git a/src/Bundle/ChillDocStoreBundle/Controller/DocumentPersonController.php b/src/Bundle/ChillDocStoreBundle/Controller/DocumentPersonController.php index ba17c1c73..2d2cf66f3 100644 --- a/src/Bundle/ChillDocStoreBundle/Controller/DocumentPersonController.php +++ b/src/Bundle/ChillDocStoreBundle/Controller/DocumentPersonController.php @@ -44,7 +44,7 @@ class DocumentPersonController extends AbstractController protected AuthorizationHelper $authorizationHelper, protected PDFSignatureZoneParser $PDFSignatureZoneParser, protected StoredObjectManagerInterface $storedObjectManagerInterface, - private readonly \Doctrine\Persistence\ManagerRegistry $managerRegistry + private readonly \Doctrine\Persistence\ManagerRegistry $managerRegistry, ) {} #[Route(path: '/{id}/delete', name: 'chill_docstore_person_document_delete')] diff --git a/src/Bundle/ChillDocStoreBundle/Controller/StoredObjectApiController.php b/src/Bundle/ChillDocStoreBundle/Controller/StoredObjectApiController.php index 600b6e52d..b67298f4c 100644 --- a/src/Bundle/ChillDocStoreBundle/Controller/StoredObjectApiController.php +++ b/src/Bundle/ChillDocStoreBundle/Controller/StoredObjectApiController.php @@ -11,6 +11,46 @@ declare(strict_types=1); namespace Chill\DocStoreBundle\Controller; +use Chill\DocStoreBundle\Entity\StoredObject; use Chill\MainBundle\CRUD\Controller\ApiController; +use Doctrine\ORM\EntityManagerInterface; +use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; +use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Security\Core\Security; +use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; +use Symfony\Component\Serializer\SerializerInterface; -class StoredObjectApiController extends ApiController {} +class StoredObjectApiController extends ApiController +{ + public function __construct( + private readonly Security $security, + private readonly SerializerInterface $serializer, + private readonly EntityManagerInterface $entityManager, + ) {} + + /** + * Creates a new stored object. + * + * @return JsonResponse the response containing the serialized object in JSON format + * + * @throws AccessDeniedHttpException if the user does not have the necessary role to create a stored object + */ + #[Route('/api/1.0/doc-store/stored-object/create', methods: ['POST'])] + public function createStoredObject(): JsonResponse + { + if (!($this->security->isGranted('ROLE_ADMIN') || $this->security->isGranted('ROLE_USER'))) { + throw new AccessDeniedHttpException('Must be user or admin to create a stored object'); + } + + $object = new StoredObject(); + + $this->entityManager->persist($object); + $this->entityManager->flush(); + + return new JsonResponse( + $this->serializer->serialize($object, 'json', [AbstractNormalizer::GROUPS => ['read']]), + json: true + ); + } +} diff --git a/src/Bundle/ChillDocStoreBundle/Controller/WebdavController.php b/src/Bundle/ChillDocStoreBundle/Controller/WebdavController.php index 6e105f9ee..022d544cb 100644 --- a/src/Bundle/ChillDocStoreBundle/Controller/WebdavController.php +++ b/src/Bundle/ChillDocStoreBundle/Controller/WebdavController.php @@ -16,6 +16,7 @@ use Chill\DocStoreBundle\Dav\Response\DavResponse; use Chill\DocStoreBundle\Entity\StoredObject; use Chill\DocStoreBundle\Security\Authorization\StoredObjectRoleEnum; use Chill\DocStoreBundle\Service\StoredObjectManagerInterface; +use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; @@ -42,6 +43,7 @@ final readonly class WebdavController private \Twig\Environment $engine, private StoredObjectManagerInterface $storedObjectManager, private Security $security, + private EntityManagerInterface $entityManager, ) { $this->requestAnalyzer = new PropfindRequestAnalyzer(); } @@ -201,6 +203,8 @@ final readonly class WebdavController $this->storedObjectManager->write($storedObject, $request->getContent()); + $this->entityManager->flush(); + return new DavResponse('', Response::HTTP_NO_CONTENT); } diff --git a/src/Bundle/ChillDocStoreBundle/DependencyInjection/ChillDocStoreExtension.php b/src/Bundle/ChillDocStoreBundle/DependencyInjection/ChillDocStoreExtension.php index b156af1ea..efeb6362d 100644 --- a/src/Bundle/ChillDocStoreBundle/DependencyInjection/ChillDocStoreExtension.php +++ b/src/Bundle/ChillDocStoreBundle/DependencyInjection/ChillDocStoreExtension.php @@ -11,7 +11,6 @@ declare(strict_types=1); namespace Chill\DocStoreBundle\DependencyInjection; -use Chill\DocStoreBundle\Controller\StoredObjectApiController; use Chill\DocStoreBundle\Security\Authorization\AccompanyingCourseDocumentVoter; use Chill\DocStoreBundle\Security\Authorization\PersonDocumentVoter; use Chill\DocStoreBundle\Security\Authorization\StoredObjectVoterInterface; @@ -19,7 +18,6 @@ use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; use Symfony\Component\DependencyInjection\Loader; -use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\DependencyInjection\Extension; /** @@ -53,29 +51,6 @@ class ChillDocStoreExtension extends Extension implements PrependExtensionInterf $this->prependRoute($container); $this->prependAuthorization($container); $this->prependTwig($container); - $this->prependApis($container); - } - - protected function prependApis(ContainerBuilder $container) - { - $container->prependExtensionConfig('chill_main', [ - 'apis' => [ - [ - 'class' => \Chill\DocStoreBundle\Entity\StoredObject::class, - 'controller' => StoredObjectApiController::class, - 'name' => 'stored_object', - 'base_path' => '/api/1.0/docstore/stored-object', - 'base_role' => 'ROLE_USER', - 'actions' => [ - '_entity' => [ - 'methods' => [ - Request::METHOD_POST => true, - ], - ], - ], - ], - ], - ]); } protected function prependAuthorization(ContainerBuilder $container) diff --git a/src/Bundle/ChillDocStoreBundle/Entity/DocumentCategory.php b/src/Bundle/ChillDocStoreBundle/Entity/DocumentCategory.php index bac09414a..37882c930 100644 --- a/src/Bundle/ChillDocStoreBundle/Entity/DocumentCategory.php +++ b/src/Bundle/ChillDocStoreBundle/Entity/DocumentCategory.php @@ -42,7 +42,7 @@ class DocumentCategory */ #[ORM\Id] #[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER, name: 'id_inside_bundle')] - private $idInsideBundle + private $idInsideBundle, ) {} public function getBundleId() // ::class BundleClass (FQDN) diff --git a/src/Bundle/ChillDocStoreBundle/Entity/StoredObject.php b/src/Bundle/ChillDocStoreBundle/Entity/StoredObject.php index 4472d2177..271c7cd32 100644 --- a/src/Bundle/ChillDocStoreBundle/Entity/StoredObject.php +++ b/src/Bundle/ChillDocStoreBundle/Entity/StoredObject.php @@ -16,10 +16,14 @@ use ChampsLibres\WopiLib\Contract\Entity\Document; use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate; use Chill\MainBundle\Doctrine\Model\TrackCreationInterface; use Chill\MainBundle\Doctrine\Model\TrackCreationTrait; +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; use Ramsey\Uuid\Uuid; use Ramsey\Uuid\UuidInterface; +use Random\RandomException; use Symfony\Component\Serializer\Annotation as Serializer; +use Symfony\Component\Validator\Constraints as Assert; /** * Represent a document stored in an object store. @@ -28,13 +32,16 @@ use Symfony\Component\Serializer\Annotation as Serializer; * * The property `$deleteAt` allow a deletion of the document after the given date. But this property should * be set before the document is actually written by the StoredObjectManager. + * + * Each version is stored within a @see{StoredObjectVersion}, associated with this current's object. The creation + * of each new version should be done using the method @see{self::registerVersion}. */ #[ORM\Entity] -#[ORM\Table('chill_doc.stored_object')] -#[AsyncFileExists(message: 'The file is not stored properly')] +#[ORM\Table('stored_object', schema: 'chill_doc')] class StoredObject implements Document, TrackCreationInterface { use TrackCreationTrait; + final public const STATUS_EMPTY = 'empty'; final public const STATUS_READY = 'ready'; final public const STATUS_PENDING = 'pending'; final public const STATUS_FAILURE = 'failure'; @@ -43,9 +50,11 @@ class StoredObject implements Document, TrackCreationInterface #[ORM\Column(type: \Doctrine\DBAL\Types\Types::JSON, name: 'datas')] private array $datas = []; - #[Serializer\Groups(['write'])] - #[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT)] - private string $filename = ''; + /** + * the prefix of each version. + */ + #[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT, nullable: false, options: ['default' => ''])] + private string $prefix = ''; #[Serializer\Groups(['write'])] #[ORM\Id] @@ -53,25 +62,10 @@ class StoredObject implements Document, TrackCreationInterface #[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER)] private ?int $id = null; - /** - * @var int[] - */ #[Serializer\Groups(['write'])] - #[ORM\Column(type: \Doctrine\DBAL\Types\Types::JSON, name: 'iv')] - private array $iv = []; - - #[Serializer\Groups(['write'])] - #[ORM\Column(type: \Doctrine\DBAL\Types\Types::JSON, name: 'key')] - private array $keyInfos = []; - - #[Serializer\Groups(['write'])] - #[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT, name: 'title')] + #[ORM\Column(name: 'title', type: \Doctrine\DBAL\Types\Types::TEXT, options: ['default' => ''])] private string $title = ''; - #[Serializer\Groups(['write'])] - #[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT, name: 'type', options: ['default' => ''])] - private string $type = ''; - #[Serializer\Groups(['write'])] #[ORM\Column(type: 'uuid', unique: true)] private UuidInterface $uuid; @@ -94,14 +88,22 @@ class StoredObject implements Document, TrackCreationInterface #[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT, nullable: false, options: ['default' => ''])] private string $generationErrors = ''; + /** + * @var Collection + */ + #[ORM\OneToMany(mappedBy: 'storedObject', targetEntity: StoredObjectVersion::class, cascade: ['persist'], orphanRemoval: true)] + private Collection $versions; + /** * @param StoredObject::STATUS_* $status */ public function __construct( #[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT, options: ['default' => 'ready'])] - private string $status = 'ready' + private string $status = 'empty', ) { $this->uuid = Uuid::uuid4(); + $this->versions = new ArrayCollection(); + $this->prefix = self::generatePrefix(); } public function addGenerationTrial(): self @@ -125,14 +127,34 @@ class StoredObject implements Document, TrackCreationInterface return \DateTime::createFromImmutable($this->createdAt); } + #[AsyncFileExists(message: 'The file is not stored properly')] + #[Assert\NotNull(message: 'The store object version must be present')] + public function getCurrentVersion(): ?StoredObjectVersion + { + $maxVersion = null; + + foreach ($this->versions as $v) { + if ($v->getVersion() > ($maxVersion?->getVersion() ?? -1)) { + $maxVersion = $v; + } + } + + return $maxVersion; + } + public function getDatas(): array { return $this->datas; } + public function getPrefix(): string + { + return $this->prefix; + } + public function getFilename(): string { - return $this->filename; + return $this->getCurrentVersion()?->getFilename() ?? ''; } public function getGenerationTrialsCounter(): int @@ -145,14 +167,17 @@ class StoredObject implements Document, TrackCreationInterface return $this->id; } + /** + * @return list + */ public function getIv(): array { - return $this->iv; + return $this->getCurrentVersion()?->getIv() ?? []; } public function getKeyInfos(): array { - return $this->keyInfos; + return $this->getCurrentVersion()?->getKeyInfos() ?? []; } /** @@ -171,14 +196,14 @@ class StoredObject implements Document, TrackCreationInterface return $this->status; } - public function getTitle() + public function getTitle(): string { return $this->title; } - public function getType() + public function getType(): string { - return $this->type; + return $this->getCurrentVersion()?->getType() ?? ''; } public function getUuid(): UuidInterface @@ -209,27 +234,6 @@ class StoredObject implements Document, TrackCreationInterface return $this; } - public function setFilename(?string $filename): self - { - $this->filename = (string) $filename; - - return $this; - } - - public function setIv(?array $iv): self - { - $this->iv = (array) $iv; - - return $this; - } - - public function setKeyInfos(?array $keyInfos): self - { - $this->keyInfos = (array) $keyInfos; - - return $this; - } - /** * @param StoredObject::STATUS_* $status */ @@ -247,18 +251,16 @@ class StoredObject implements Document, TrackCreationInterface return $this; } - public function setType(?string $type): self - { - $this->type = (string) $type; - - return $this; - } - public function getTemplate(): ?DocGeneratorTemplate { return $this->template; } + public function getVersions(): Collection + { + return $this->versions; + } + public function hasTemplate(): bool { return null !== $this->template; @@ -314,18 +316,65 @@ class StoredObject implements Document, TrackCreationInterface return $this; } - public function saveHistory(): void - { - if ('' === $this->getFilename()) { - return; + public function registerVersion( + array $iv = [], + array $keyInfos = [], + string $type = '', + ?string $filename = null, + ): StoredObjectVersion { + $version = new StoredObjectVersion( + $this, + null === $this->getCurrentVersion() ? 0 : $this->getCurrentVersion()->getVersion() + 1, + $iv, + $keyInfos, + $type, + $filename + ); + + $this->versions->add($version); + + if ('empty' === $this->status) { + $this->status = self::STATUS_READY; } - $this->datas['history'][] = [ - 'filename' => $this->getFilename(), - 'iv' => $this->getIv(), - 'key_infos' => $this->getKeyInfos(), - 'type' => $this->getType(), - 'before' => (new \DateTimeImmutable('now'))->getTimestamp(), - ]; + return $version; + } + + public function removeVersion(StoredObjectVersion $storedObjectVersion): void + { + if (!$this->versions->contains($storedObjectVersion)) { + throw new \UnexpectedValueException('This stored object does not contains this version'); + } + $this->versions->removeElement($storedObjectVersion); + } + + /** + * @deprecated + */ + public function saveHistory(): void {} + + public static function generatePrefix(): string + { + try { + return base_convert(bin2hex(random_bytes(32)), 16, 36); + } catch (RandomException) { + return uniqid(more_entropy: true); + } + } + + /** + * Checks if a stored object can be deleted. + * + * Currently, return true if the deletedAt date is below the current date, and the object + * does not contains any version (which must be removed first). + * + * @param \DateTimeImmutable $now the current date and time + * @param StoredObject $storedObject the stored object to check + * + * @return bool returns true if the stored object can be deleted, false otherwise + */ + public static function canBeDeleted(\DateTimeImmutable $now, StoredObject $storedObject): bool + { + return $storedObject->getDeleteAt() < $now && $storedObject->getVersions()->isEmpty(); } } diff --git a/src/Bundle/ChillDocStoreBundle/Entity/StoredObjectVersion.php b/src/Bundle/ChillDocStoreBundle/Entity/StoredObjectVersion.php new file mode 100644 index 000000000..fed183057 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Entity/StoredObjectVersion.php @@ -0,0 +1,127 @@ + ''])] + private string $filename = ''; + + public function __construct( + /** + * The stored object associated with this version. + */ + #[ORM\ManyToOne(targetEntity: StoredObject::class, inversedBy: 'versions')] + #[ORM\JoinColumn(name: 'stored_object_id', nullable: false)] + private StoredObject $storedObject, + + /** + * The incremental version. + */ + #[ORM\Column(name: 'version', type: \Doctrine\DBAL\Types\Types::INTEGER, options: ['default' => 0])] + private int $version = 0, + + /** + * vector for encryption. + * + * @var int[] + */ + #[ORM\Column(type: \Doctrine\DBAL\Types\Types::JSON, name: 'iv')] + private array $iv = [], + + /** + * Key infos for document encryption. + * + * @var array + */ + #[ORM\Column(type: \Doctrine\DBAL\Types\Types::JSON, name: 'key')] + private array $keyInfos = [], + + /** + * type of the document. + */ + #[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT, name: 'type', options: ['default' => ''])] + private string $type = '', + ?string $filename = null, + ) { + $this->filename = $filename ?? self::generateFilename($this); + } + + public static function generateFilename(StoredObjectVersion $storedObjectVersion): string + { + try { + $suffix = base_convert(bin2hex(random_bytes(8)), 16, 36); + } catch (RandomException) { + $suffix = uniqid(more_entropy: true); + } + + return $storedObjectVersion->getStoredObject()->getPrefix().'/'.$suffix; + } + + public function getFilename(): string + { + return $this->filename; + } + + public function getId(): ?int + { + return $this->id; + } + + public function getIv(): array + { + return $this->iv; + } + + public function getKeyInfos(): array + { + return $this->keyInfos; + } + + public function getStoredObject(): StoredObject + { + return $this->storedObject; + } + + public function getType(): string + { + return $this->type; + } + + public function getVersion(): int + { + return $this->version; + } +} diff --git a/src/Bundle/ChillDocStoreBundle/Form/AccompanyingCourseDocumentType.php b/src/Bundle/ChillDocStoreBundle/Form/AccompanyingCourseDocumentType.php index 5d2096ca0..42711b01a 100644 --- a/src/Bundle/ChillDocStoreBundle/Form/AccompanyingCourseDocumentType.php +++ b/src/Bundle/ChillDocStoreBundle/Form/AccompanyingCourseDocumentType.php @@ -27,7 +27,7 @@ use Symfony\Component\OptionsResolver\OptionsResolver; final class AccompanyingCourseDocumentType extends AbstractType { public function __construct( - private readonly TranslatableStringHelperInterface $translatableStringHelper + private readonly TranslatableStringHelperInterface $translatableStringHelper, ) {} public function buildForm(FormBuilderInterface $builder, array $options) diff --git a/src/Bundle/ChillDocStoreBundle/Form/DataMapper/StoredObjectDataMapper.php b/src/Bundle/ChillDocStoreBundle/Form/DataMapper/StoredObjectDataMapper.php index 170b1ab0a..264f52365 100644 --- a/src/Bundle/ChillDocStoreBundle/Form/DataMapper/StoredObjectDataMapper.php +++ b/src/Bundle/ChillDocStoreBundle/Form/DataMapper/StoredObjectDataMapper.php @@ -55,16 +55,8 @@ class StoredObjectDataMapper implements DataMapperInterface return; } - /** @var StoredObject $viewData */ - if ($viewData->getFilename() !== $forms['stored_object']->getData()['filename']) { - // we want to keep the previous history - $viewData->saveHistory(); - } - - $viewData->setFilename($forms['stored_object']->getData()['filename']); - $viewData->setIv($forms['stored_object']->getData()['iv']); - $viewData->setKeyInfos($forms['stored_object']->getData()['keyInfos']); - $viewData->setType($forms['stored_object']->getData()['type']); + /* @var StoredObject $viewData */ + $viewData = $forms['stored_object']->getData(); if (array_key_exists('title', $forms)) { $viewData->setTitle($forms['title']->getData()); diff --git a/src/Bundle/ChillDocStoreBundle/Form/DataTransformer/StoredObjectDataTransformer.php b/src/Bundle/ChillDocStoreBundle/Form/DataTransformer/StoredObjectDataTransformer.php index e06d8d7cc..8df3e3eb9 100644 --- a/src/Bundle/ChillDocStoreBundle/Form/DataTransformer/StoredObjectDataTransformer.php +++ b/src/Bundle/ChillDocStoreBundle/Form/DataTransformer/StoredObjectDataTransformer.php @@ -12,7 +12,6 @@ declare(strict_types=1); namespace Chill\DocStoreBundle\Form\DataTransformer; use Chill\DocStoreBundle\Entity\StoredObject; -use Chill\DocStoreBundle\Serializer\Normalizer\StoredObjectNormalizer; use Symfony\Component\Form\DataTransformerInterface; use Symfony\Component\Form\Exception\UnexpectedTypeException; use Symfony\Component\Serializer\SerializerInterface; @@ -20,7 +19,7 @@ use Symfony\Component\Serializer\SerializerInterface; class StoredObjectDataTransformer implements DataTransformerInterface { public function __construct( - private readonly SerializerInterface $serializer + private readonly SerializerInterface $serializer, ) {} public function transform(mixed $value): mixed @@ -30,11 +29,7 @@ class StoredObjectDataTransformer implements DataTransformerInterface } if ($value instanceof StoredObject) { - return $this->serializer->serialize($value, 'json', [ - 'groups' => [ - StoredObjectNormalizer::ADD_DAV_EDIT_LINK_CONTEXT, - ], - ]); + return $this->serializer->serialize($value, 'json'); } throw new UnexpectedTypeException($value, StoredObject::class); @@ -46,6 +41,6 @@ class StoredObjectDataTransformer implements DataTransformerInterface return null; } - return json_decode((string) $value, true, 10, JSON_THROW_ON_ERROR); + return $this->serializer->deserialize($value, StoredObject::class, 'json'); } } diff --git a/src/Bundle/ChillDocStoreBundle/Form/Type/AsyncUploaderType.php b/src/Bundle/ChillDocStoreBundle/Form/Type/AsyncUploaderType.php index 58d4eebb1..cd87fa922 100644 --- a/src/Bundle/ChillDocStoreBundle/Form/Type/AsyncUploaderType.php +++ b/src/Bundle/ChillDocStoreBundle/Form/Type/AsyncUploaderType.php @@ -55,7 +55,7 @@ class AsyncUploaderType extends AbstractType public function buildView( FormView $view, FormInterface $form, - array $options + array $options, ) { $view->vars['attr']['data-async-file-upload'] = true; $view->vars['attr']['data-generate-temp-url-post'] = $this diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/GenericDocForAccompanyingPeriodProviderInterface.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/GenericDocForAccompanyingPeriodProviderInterface.php index bffa19b53..82a3ba0e3 100644 --- a/src/Bundle/ChillDocStoreBundle/GenericDoc/GenericDocForAccompanyingPeriodProviderInterface.php +++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/GenericDocForAccompanyingPeriodProviderInterface.php @@ -20,7 +20,7 @@ interface GenericDocForAccompanyingPeriodProviderInterface ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, - ?string $origin = null + ?string $origin = null, ): FetchQueryInterface; /** diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/GenericDocForPersonProviderInterface.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/GenericDocForPersonProviderInterface.php index 264a1d8f2..f85fc763a 100644 --- a/src/Bundle/ChillDocStoreBundle/GenericDoc/GenericDocForPersonProviderInterface.php +++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/GenericDocForPersonProviderInterface.php @@ -20,7 +20,7 @@ interface GenericDocForPersonProviderInterface ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, - ?string $origin = null + ?string $origin = null, ): FetchQueryInterface; /** diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/Manager.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/Manager.php index a185ce9b6..bec331523 100644 --- a/src/Bundle/ChillDocStoreBundle/GenericDoc/Manager.php +++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/Manager.php @@ -46,7 +46,7 @@ final readonly class Manager ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, - array $places = [] + array $places = [], ): int { ['sql' => $sql, 'params' => $params, 'types' => $types] = $this->buildUnionQuery($accompanyingPeriod, $startDate, $endDate, $content, $places); @@ -76,7 +76,7 @@ final readonly class Manager ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, - array $places = [] + array $places = [], ): int { ['sql' => $sql, 'params' => $params, 'types' => $types] = $this->buildUnionQuery($person, $startDate, $endDate, $content, $places); @@ -97,7 +97,7 @@ final readonly class Manager ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, - array $places = [] + array $places = [], ): iterable { ['sql' => $sql, 'params' => $params, 'types' => $types] = $this->buildUnionQuery($accompanyingPeriod, $startDate, $endDate, $content, $places); @@ -140,7 +140,7 @@ final readonly class Manager ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, - array $places = [] + array $places = [], ): iterable { ['sql' => $sql, 'params' => $params, 'types' => $types] = $this->buildUnionQuery($person, $startDate, $endDate, $content, $places); diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/PersonDocumentGenericDocProvider.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/PersonDocumentGenericDocProvider.php index 2b9239c74..e5c99b258 100644 --- a/src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/PersonDocumentGenericDocProvider.php +++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/PersonDocumentGenericDocProvider.php @@ -34,7 +34,7 @@ final readonly class PersonDocumentGenericDocProvider implements GenericDocForPe ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, - ?string $origin = null + ?string $origin = null, ): FetchQueryInterface { return $this->personDocumentACLAwareRepository->buildFetchQueryForPerson( $person, diff --git a/src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentACLAwareRepositoryInterface.php b/src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentACLAwareRepositoryInterface.php index f1bc70812..ef783a92e 100644 --- a/src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentACLAwareRepositoryInterface.php +++ b/src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentACLAwareRepositoryInterface.php @@ -31,13 +31,13 @@ interface PersonDocumentACLAwareRepositoryInterface Person $person, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, - ?string $content = null + ?string $content = null, ): FetchQueryInterface; public function buildFetchQueryForAccompanyingPeriod( AccompanyingPeriod $period, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, - ?string $content = null + ?string $content = null, ): FetchQueryInterface; } diff --git a/src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentRepository.php b/src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentRepository.php index d0ef4c9d9..26bc2d60a 100644 --- a/src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentRepository.php +++ b/src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentRepository.php @@ -25,7 +25,7 @@ readonly class PersonDocumentRepository implements ObjectRepository, AssociatedE private EntityRepository $repository; public function __construct( - private EntityManagerInterface $entityManager + private EntityManagerInterface $entityManager, ) { $this->repository = $this->entityManager->getRepository($this->getClassName()); } diff --git a/src/Bundle/ChillDocStoreBundle/Repository/StoredObjectRepository.php b/src/Bundle/ChillDocStoreBundle/Repository/StoredObjectRepository.php index 84bc7d4cb..d48792bc9 100644 --- a/src/Bundle/ChillDocStoreBundle/Repository/StoredObjectRepository.php +++ b/src/Bundle/ChillDocStoreBundle/Repository/StoredObjectRepository.php @@ -14,6 +14,7 @@ namespace Chill\DocStoreBundle\Repository; use Chill\DocStoreBundle\Entity\StoredObject; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; +use Doctrine\ORM\Query; final readonly class StoredObjectRepository implements StoredObjectRepositoryInterface { @@ -53,6 +54,21 @@ final readonly class StoredObjectRepository implements StoredObjectRepositoryInt return $this->repository->findOneBy($criteria); } + public function findByExpired(\DateTimeImmutable $expiredAtDate): iterable + { + $qb = $this->repository->createQueryBuilder('stored_object'); + $qb + ->where('stored_object.deleteAt <= :expiredAt') + ->setParameter('expiredAt', $expiredAtDate); + + return $qb->getQuery()->toIterable(hydrationMode: Query::HYDRATE_OBJECT); + } + + public function findOneByUUID(string $uuid): ?StoredObject + { + return $this->repository->findOneBy(['uuid' => $uuid]); + } + public function getClassName(): string { return StoredObject::class; diff --git a/src/Bundle/ChillDocStoreBundle/Repository/StoredObjectRepositoryInterface.php b/src/Bundle/ChillDocStoreBundle/Repository/StoredObjectRepositoryInterface.php index c694f1e09..877847677 100644 --- a/src/Bundle/ChillDocStoreBundle/Repository/StoredObjectRepositoryInterface.php +++ b/src/Bundle/ChillDocStoreBundle/Repository/StoredObjectRepositoryInterface.php @@ -17,4 +17,12 @@ use Doctrine\Persistence\ObjectRepository; /** * @extends ObjectRepository */ -interface StoredObjectRepositoryInterface extends ObjectRepository {} +interface StoredObjectRepositoryInterface extends ObjectRepository +{ + /** + * @return iterable + */ + public function findByExpired(\DateTimeImmutable $expiredAtDate): iterable; + + public function findOneByUUID(string $uuid): ?StoredObject; +} diff --git a/src/Bundle/ChillDocStoreBundle/Repository/StoredObjectVersionRepository.php b/src/Bundle/ChillDocStoreBundle/Repository/StoredObjectVersionRepository.php new file mode 100644 index 000000000..1ab9b9edd --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Repository/StoredObjectVersionRepository.php @@ -0,0 +1,92 @@ + + */ +class StoredObjectVersionRepository implements ObjectRepository +{ + private readonly EntityRepository $repository; + + private readonly Connection $connection; + + public function __construct(EntityManagerInterface $entityManager) + { + $this->repository = $entityManager->getRepository(StoredObjectVersion::class); + $this->connection = $entityManager->getConnection(); + } + + public function find($id): ?StoredObjectVersion + { + 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): ?StoredObjectVersion + { + return $this->repository->findOneBy($criteria); + } + + /** + * Finds the IDs of versions older than a given date and that are not the last version. + * + * Those version are good candidates for a deletion. + * + * @param \DateTimeImmutable $beforeDate the date to compare versions against + * + * @return iterable returns an iterable with the IDs of the versions + */ + public function findIdsByVersionsOlderThanDateAndNotLastVersion(\DateTimeImmutable $beforeDate): iterable + { + $results = $this->connection->executeQuery( + self::QUERY_FIND_IDS_BY_VERSIONS_OLDER_THAN_DATE_AND_NOT_LAST_VERSION, + [$beforeDate], + [Types::DATETIME_IMMUTABLE] + ); + + foreach ($results->iterateAssociative() as $row) { + yield $row['sov_id']; + } + } + + private const QUERY_FIND_IDS_BY_VERSIONS_OLDER_THAN_DATE_AND_NOT_LAST_VERSION = <<<'SQL' + SELECT + sov.id AS sov_id + FROM chill_doc.stored_object_version sov + WHERE + sov.createdat < ?::timestamp + AND + sov.version < (SELECT MAX(sub_sov.version) FROM chill_doc.stored_object_version sub_sov WHERE sub_sov.stored_object_id = sov.stored_object_id) + SQL; + + public function getClassName(): string + { + return StoredObjectVersion::class; + } +} diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/_components/helper.ts b/src/Bundle/ChillDocStoreBundle/Resources/public/js/async-upload/uploader.ts similarity index 75% rename from src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/_components/helper.ts rename to src/Bundle/ChillDocStoreBundle/Resources/public/js/async-upload/uploader.ts index 6d07e769d..592bd8111 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/_components/helper.ts +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/js/async-upload/uploader.ts @@ -1,5 +1,5 @@ import {makeFetch} from "../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods"; -import {PostStoreObjectSignature} from "../../types"; +import {PostStoreObjectSignature, StoredObject} from "../../types"; const algo = 'AES-CBC'; @@ -21,11 +21,22 @@ const createFilename = (): string => { return text; }; -export const uploadFile = async (uploadFile: ArrayBuffer): Promise => { +/** + * Fetches a new stored object from the server. + * + * @async + * @function fetchNewStoredObject + * @returns {Promise} A Promise that resolves to the newly created StoredObject. + */ +export const fetchNewStoredObject = async (): Promise => { + return makeFetch("POST", '/api/1.0/doc-store/stored-object/create', null); +} + +export const uploadVersion = async (uploadFile: ArrayBuffer, storedObject: StoredObject): Promise => { const params = new URLSearchParams(); params.append('expires_delay', "180"); params.append('submit_delay', "180"); - const asyncData: PostStoreObjectSignature = await makeFetch("GET", URL_POST + "?" + params.toString()); + const asyncData: PostStoreObjectSignature = await makeFetch("GET", `/api/1.0/doc-store/async-upload/temp_url/${storedObject.uuid}/generate/post` + "?" + params.toString()); const suffix = createFilename(); const filename = asyncData.prefix + suffix; const formData = new FormData(); @@ -50,7 +61,6 @@ export const uploadFile = async (uploadFile: ArrayBuffer): Promise => { } export const encryptFile = async (originalFile: ArrayBuffer): Promise<[ArrayBuffer, Uint8Array, JsonWebKey]> => { - console.log('encrypt', originalFile); const iv = crypto.getRandomValues(new Uint8Array(16)); const key = await window.crypto.subtle.generateKey(keyDefinition, true, [ "encrypt", "decrypt" ]); const exportedKey = await window.crypto.subtle.exportKey('jwk', key); diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/module/async_upload/index.ts b/src/Bundle/ChillDocStoreBundle/Resources/public/module/async_upload/index.ts index b7df11323..50849d635 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/public/module/async_upload/index.ts +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/module/async_upload/index.ts @@ -1,7 +1,7 @@ import {CollectionEventPayload} from "../../../../../ChillMainBundle/Resources/public/module/collection"; import {createApp} from "vue"; import DropFileWidget from "../../vuejs/DropFileWidget/DropFileWidget.vue" -import {StoredObject, StoredObjectCreated} from "../../types"; +import {StoredObject, StoredObjectVersion} from "../../types"; import {_createI18n} from "../../../../../ChillMainBundle/Resources/public/vuejs/_js/i18n"; const i18n = _createI18n({}); @@ -30,15 +30,17 @@ const startApp = (divElement: HTMLDivElement, collectionEntry: null|HTMLLIElemen DropFileWidget, }, methods: { - addDocument: function(object: StoredObjectCreated): void { - console.log('object added', object); - this.$data.existingDoc = object; - input_stored_object.value = JSON.stringify(object); + addDocument: function({stored_object, stored_object_version}: {stored_object: StoredObject, stored_object_version: StoredObjectVersion}): void { + console.log('object added', stored_object); + console.log('version added', stored_object_version); + this.$data.existingDoc = stored_object; + this.$data.existingDoc.currentVersion = stored_object_version; + input_stored_object.value = JSON.stringify(this.$data.existingDoc); }, removeDocument: function(object: StoredObject): void { console.log('catch remove document', object); input_stored_object.value = ""; - this.$data.existingDoc = null; + this.$data.existingDoc = undefined; console.log('collectionEntry', collectionEntry); if (null !== collectionEntry) { diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/types.ts b/src/Bundle/ChillDocStoreBundle/Resources/public/types.ts index 1fc8b1cdd..840421991 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/public/types.ts +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/types.ts @@ -1,36 +1,51 @@ -import {DateTime} from "../../../ChillMainBundle/Resources/public/types"; +import {DateTime, User} from "../../../ChillMainBundle/Resources/public/types"; -export type StoredObjectStatus = "ready"|"failure"|"pending"; +export type StoredObjectStatus = "empty"|"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, + id: number, + title: string|null, + uuid: string, + prefix: string, + status: StoredObjectStatus, + currentVersion: null|StoredObjectVersionCreated|StoredObjectVersionPersisted, + totalVersions: number, + datas: object, + /** @deprecated */ + creationDate: DateTime, + createdAt: DateTime|null, + createdBy: User|null, + _permissions: { + canEdit: boolean, + canSee: boolean, + }, _links?: { - dav_link?: { - href: string - expiration: number - }, - } + dav_link?: { + href: string + expiration: number + }, + }, } -export interface StoredObjectCreated { - status: "stored_object_created", - filename: string, - iv: Uint8Array, - keyInfos: object, - type: string, +export interface StoredObjectVersion { + /** + * filename of the object in the object storage + */ + filename: string, + iv: number[], + keyInfos: JsonWebKey, + type: string, +} + +export interface StoredObjectVersionCreated extends StoredObjectVersion { + persisted: false, +} + +export interface StoredObjectVersionPersisted extends StoredObjectVersionCreated { + version: number, + id: number, + createdAt: DateTime|null, + createdBy: User|null, } export interface StoredObjectStatusChange { @@ -82,4 +97,4 @@ export interface Signature { zones: SignatureZone[], } -export type SignedState = 'pending' | 'signed' | 'rejected' | 'canceled' | 'error'; \ No newline at end of file +export type SignedState = 'pending' | 'signed' | 'rejected' | 'canceled' | 'error'; diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/DocumentActionButtonsGroup.vue b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/DocumentActionButtonsGroup.vue index b4c53eacd..66546f2a5 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/DocumentActionButtonsGroup.vue +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/DocumentActionButtonsGroup.vue @@ -1,20 +1,20 @@