From ca7ba90717da8d9ca9eedc5d527f7528811d3715 Mon Sep 17 00:00:00 2001 From: nobohan Date: Tue, 28 Sep 2021 14:53:00 +0200 Subject: [PATCH 01/32] hotfix: repair creation activity (was an error linked to ListenToActivityCreate) --- .../Event/ListenToActivityCreate.php | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/Bundle/ChillCalendarBundle/Event/ListenToActivityCreate.php b/src/Bundle/ChillCalendarBundle/Event/ListenToActivityCreate.php index f3c4e7a59..d7073597a 100644 --- a/src/Bundle/ChillCalendarBundle/Event/ListenToActivityCreate.php +++ b/src/Bundle/ChillCalendarBundle/Event/ListenToActivityCreate.php @@ -29,17 +29,19 @@ class ListenToActivityCreate $activityData = $request->query->get('activityData'); if (array_key_exists('calendarId', $activityData)) { $calendarId = $activityData['calendarId']; + + // Attach the activity to the calendar + $em = $event->getObjectManager(); + + $calendar = $em->getRepository(\Chill\CalendarBundle\Entity\Calendar::class)->find($calendarId); + $calendar->setActivity($activity); + + $em->persist($calendar); + $em->flush(); } } - // Attach the activity to the calendar - $em = $event->getObjectManager(); - $calendar = $em->getRepository(\Chill\CalendarBundle\Entity\Calendar::class)->find($calendarId); - $calendar->setActivity($activity); - - $em->persist($calendar); - $em->flush(); } } From d5d12c4a17e1f52e797727833ba1e37dfc437601 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 28 Sep 2021 15:50:09 +0200 Subject: [PATCH 02/32] fix tests for user controller --- .../Resources/views/User/index.html.twig | 2 +- .../Tests/Controller/UserControllerTest.php | 87 ++++++++++--------- 2 files changed, 49 insertions(+), 40 deletions(-) diff --git a/src/Bundle/ChillMainBundle/Resources/views/User/index.html.twig b/src/Bundle/ChillMainBundle/Resources/views/User/index.html.twig index c57d9ed5f..53bd89482 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/User/index.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/User/index.html.twig @@ -10,7 +10,7 @@ {% endblock %} {% block table_entities_tbody %} {% for entity in entities %} - + {% if entity.isEnabled %} diff --git a/src/Bundle/ChillMainBundle/Tests/Controller/UserControllerTest.php b/src/Bundle/ChillMainBundle/Tests/Controller/UserControllerTest.php index 1953439dd..0017fbb4e 100644 --- a/src/Bundle/ChillMainBundle/Tests/Controller/UserControllerTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Controller/UserControllerTest.php @@ -2,12 +2,17 @@ namespace Chill\MainBundle\Tests\Controller; +use Chill\MainBundle\Entity\User; +use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; +use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface; class UserControllerTest extends WebTestCase { private $client; + private array $toDelete = []; + public function setUp() { self::bootKernel(); @@ -22,18 +27,14 @@ class UserControllerTest extends WebTestCase public function testList() { // get the list - $crawler = $this->client->request('GET', '/fr/admin/user/'); + $crawler = $this->client->request('GET', '/fr/admin/main/user'); $this->assertEquals(200, $this->client->getResponse()->getStatusCode(), - "Unexpected HTTP status code for GET /admin/user/"); - - $link = $crawler->selectLink('Ajouter un nouvel utilisateur')->link(); - $this->assertInstanceOf('Symfony\Component\DomCrawler\Link', $link); - $this->assertRegExp('|/fr/admin/user/new$|', $link->getUri()); + "Unexpected HTTP status code for GET /admin/main/user"); } public function testNew() { - $crawler = $this->client->request('GET', '/fr/admin/user/new'); + $crawler = $this->client->request('GET', '/fr/admin/main/user/new'); $username = 'Test_user'. uniqid(); $password = 'Password1234!'; @@ -54,22 +55,15 @@ class UserControllerTest extends WebTestCase $this->assertGreaterThan(0, $crawler->filter('td:contains("Test_user")')->count(), 'Missing element td:contains("Test user")'); - $update = $crawler->selectLink('Modifier')->link(); - - $this->assertInstanceOf('Symfony\Component\DomCrawler\Link', $update); - $this->assertRegExp('|/fr/admin/user/[0-9]{1,}/edit$|', $update->getUri()); - //test the auth of the new client $this->isPasswordValid($username, $password); - - return $update; } protected function isPasswordValid($username, $password) { /* @var $passwordEncoder \Symfony\Component\Security\Core\Encoder\UserPasswordEncoder */ - $passwordEncoder = self::$kernel->getContainer() - ->get('security.password_encoder'); + $passwordEncoder = self::$container + ->get(UserPasswordEncoderInterface::class); $user = self::$kernel->getContainer() ->get('doctrine.orm.entity_manager') @@ -81,46 +75,33 @@ class UserControllerTest extends WebTestCase /** * - * @param \Symfony\Component\DomCrawler\Link $update - * @depends testNew + * @dataProvider dataGenerateUserId */ - public function testUpdate(\Symfony\Component\DomCrawler\Link $update) + public function testUpdate(int $userId, string $username) { - $crawler = $this->client->click($update); + $crawler = $this->client->request('GET', "/fr/admin/main/user/$userId/edit"); $username = 'Foo bar '.uniqid(); - $form = $crawler->selectButton('Mettre à jour')->form(array( + $form = $crawler->selectButton('Enregistrer & fermer')->form(array( 'chill_mainbundle_user[username]' => $username, )); $this->client->submit($form); $crawler = $this->client->followRedirect(); // Check the element contains an attribute with value equals "Foo" - $this->assertGreaterThan(0, $crawler->filter('[value="'.$username.'"]')->count(), - 'Missing element [value="Foo bar"]'); - - $updatePassword = $crawler->selectLink('Modifier le mot de passe')->link(); - - $this->assertInstanceOf('Symfony\Component\DomCrawler\Link', $updatePassword); - $this->assertRegExp('|/fr/admin/user/[0-9]{1,}/edit_password$|', - $updatePassword->getUri()); - - return array('link' => $updatePassword, 'username' => $username); + $this->assertGreaterThan(0, $crawler->filter('[data-username="'.$username.'"]')->count(), + 'Missing element [data-username="Foo bar"]'); } /** * - * @param \Symfony\Component\DomCrawler\Link $updatePassword - * @depends testUpdate + * @dataProvider dataGenerateUserId */ - public function testUpdatePassword(array $params) + public function testUpdatePassword(int $userId, $username) { - $link = $params['link']; - $username = $params['username']; + $crawler = $this->client->request('GET', "/fr/admin/user/$userId/edit_password"); $newPassword = '1234Password!'; - $crawler = $this->client->click($link); - $form = $crawler->selectButton('Changer le mot de passe')->form(array( 'chill_mainbundle_user_password[new_password][first]' => $newPassword, 'chill_mainbundle_user_password[new_password][second]' => $newPassword, @@ -130,10 +111,38 @@ class UserControllerTest extends WebTestCase $this->assertTrue($this->client->getResponse()->isRedirect(), "the response is a redirection"); - $this->client->followRedirect(); $this->isPasswordValid($username, $newPassword); } + protected function tearDown() + { + self::bootKernel(); + $em = self::$container->get(EntityManagerInterface::class); + foreach ($this->toDelete as list($class, $id)) { + $obj = $em->getRepository($class)->find($id); + $em->remove($obj); + } + + $em->flush(); + } + + public function dataGenerateUserId() + { + self::bootKernel(); + $em = self::$container->get(EntityManagerInterface::class); + + $user = new User(); + $user->setUsername('Test_user '.uniqid()); + $user->setPassword(self::$container->get(UserPasswordEncoderInterface::class)->encodePassword($user, + 'password')); + + $em->persist($user); + $em->flush(); + + $this->toDelete[] = [User::class, $user->getId()]; + + yield [ $user->getId(), $user->getUsername() ]; + } } From 472866ce91cdb2dc5f5a72967e7ca58ffdfa4cb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 28 Sep 2021 15:53:41 +0200 Subject: [PATCH 03/32] fix search by birthdate --- .../ChillPersonBundle/Repository/PersonACLAwareRepository.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bundle/ChillPersonBundle/Repository/PersonACLAwareRepository.php b/src/Bundle/ChillPersonBundle/Repository/PersonACLAwareRepository.php index d15e8cd7a..cd4f8c9ce 100644 --- a/src/Bundle/ChillPersonBundle/Repository/PersonACLAwareRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/PersonACLAwareRepository.php @@ -199,7 +199,7 @@ final class PersonACLAwareRepository implements PersonACLAwareRepositoryInterfac } if (NULL !== $birthdate) { - $qb->andWhere($qb->expr()->eq('s.birthdate', ':birthdate')) + $qb->andWhere($qb->expr()->eq('p.birthdate', ':birthdate')) ->setParameter('birthdate', $birthdate); } From e2230409b807eb29b820cf3cc1ac15595e5b2ff3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 28 Sep 2021 15:59:31 +0200 Subject: [PATCH 04/32] remove dump --- src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadPeople.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadPeople.php b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadPeople.php index 848e8f874..adc37d5b5 100644 --- a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadPeople.php +++ b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadPeople.php @@ -247,7 +247,6 @@ class LoadPeople extends AbstractFixture implements OrderedFixtureInterface, Con if (\random_int(0, 10) > 3) { // always add social scope: $accompanyingPeriod->addScope($this->getReference('scope_social')); - var_dump(count($accompanyingPeriod->getScopes())); $accompanyingPeriod->setAddressLocation($this->createAddress()); $manager->persist($accompanyingPeriod->getAddressLocation()); From e43ae56e733c7a5e242b54b8327f9c488d24021e Mon Sep 17 00:00:00 2001 From: Pol Dellaiera Date: Tue, 28 Sep 2021 17:39:23 +0200 Subject: [PATCH 05/32] Update root composer.json. --- composer.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/composer.json b/composer.json index 747747290..0d134cbf8 100644 --- a/composer.json +++ b/composer.json @@ -22,6 +22,7 @@ "league/csv": "^9.7.1", "nyholm/psr7": "^1.4", "phpoffice/phpspreadsheet": "^1.16", + "ramsey/uuid-doctrine": "^1.7", "sensio/framework-extra-bundle": "^5.5", "symfony/asset": "4.*", "symfony/browser-kit": "^5.2", @@ -29,6 +30,7 @@ "symfony/expression-language": "4.*", "symfony/form": "4.*", "symfony/intl": "4.*", + "symfony/mime": "^4 || ^5", "symfony/monolog-bundle": "^3.5", "symfony/security-bundle": "4.*", "symfony/serializer": "^5.2", From 0aec2fc1d1a27d6c930b52e2dbb28b5d8cfe835a Mon Sep 17 00:00:00 2001 From: Pol Dellaiera Date: Tue, 28 Sep 2021 17:39:42 +0200 Subject: [PATCH 06/32] Update local `ChillWopiBundle` composer.json. --- src/Bundle/ChillWopiBundle/composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bundle/ChillWopiBundle/composer.json b/src/Bundle/ChillWopiBundle/composer.json index 7f7e6a4ed..41737c8b1 100644 --- a/src/Bundle/ChillWopiBundle/composer.json +++ b/src/Bundle/ChillWopiBundle/composer.json @@ -19,7 +19,7 @@ "php": ">= 7.4", "champs-libres/wopi-bundle": "dev-master", "nyholm/psr7": "^1.4", - "php-opencloud/openstack": "^3.2.1" + "symfony/mime": "^4 || ^5" }, "autoload": { "psr-4": { From 59fcd1e96b7ffbac0649f953e4f1f28932b5fd06 Mon Sep 17 00:00:00 2001 From: Pol Dellaiera Date: Tue, 28 Sep 2021 17:40:49 +0200 Subject: [PATCH 07/32] Update local `ChillDocStore` composer.json. --- src/Bundle/ChillDocStoreBundle/composer.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Bundle/ChillDocStoreBundle/composer.json b/src/Bundle/ChillDocStoreBundle/composer.json index 384f6c093..c011ce29b 100644 --- a/src/Bundle/ChillDocStoreBundle/composer.json +++ b/src/Bundle/ChillDocStoreBundle/composer.json @@ -3,9 +3,12 @@ "description": "A Chill bundle to store documents", "type": "symfony-bundle", "autoload": { - "psr-4": { "Chill\\DocStoreBundle\\" : "" } + "psr-4": { + "Chill\\DocStoreBundle\\": "" + } }, "require": { + "symfony/mime": "^4 || ^5" }, "license": "AGPL-3.0" } From dcb92b13787c351bc6cbf5050972c3da3b6ec807 Mon Sep 17 00:00:00 2001 From: Pol Dellaiera Date: Tue, 28 Sep 2021 17:42:28 +0200 Subject: [PATCH 08/32] Update `StoredObject` entity. --- .../Entity/StoredObject.php | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/Bundle/ChillDocStoreBundle/Entity/StoredObject.php b/src/Bundle/ChillDocStoreBundle/Entity/StoredObject.php index 9c88e502d..7ba21b5da 100644 --- a/src/Bundle/ChillDocStoreBundle/Entity/StoredObject.php +++ b/src/Bundle/ChillDocStoreBundle/Entity/StoredObject.php @@ -7,7 +7,9 @@ namespace Chill\DocStoreBundle\Entity; use Doctrine\ORM\Mapping as ORM; use ChampsLibres\AsyncUploaderBundle\Model\AsyncFileInterface; use ChampsLibres\AsyncUploaderBundle\Validator\Constraints\AsyncFileExists; +use ChampsLibres\WopiLib\Contract\Entity\Document; use DateTimeInterface; +use Ramsey\Uuid\Uuid; use Symfony\Component\Serializer\Annotation as Serializer; /** @@ -19,7 +21,7 @@ use Symfony\Component\Serializer\Annotation as Serializer; * message="The file is not stored properly" * ) */ -class StoredObject implements AsyncFileInterface +class StoredObject implements AsyncFileInterface, Document { /** * @ORM\Id() @@ -47,6 +49,11 @@ class StoredObject implements AsyncFileInterface */ private array $iv = []; + /** + * @ORM\Column(type="uuid", unique=true) + */ + private Uuid $uuid; + /** * @ORM\Column(type="datetime", name="creation_date") * @Serializer\Groups({"read"}) @@ -68,6 +75,7 @@ class StoredObject implements AsyncFileInterface public function __construct() { $this->creationDate = new \DateTime(); + $this->uuid = Uuid::uuid4(); } public function getId() @@ -155,5 +163,13 @@ class StoredObject implements AsyncFileInterface return $this; } + public function getUuid(): Uuid + { + return $this->uuid; + } + public function getWopiDocId(): string + { + return (string) $this->uuid; + } } From ab845d4569dd3537490920f9bb894b0a9551959e Mon Sep 17 00:00:00 2001 From: Pol Dellaiera Date: Tue, 28 Sep 2021 17:50:53 +0200 Subject: [PATCH 09/32] Synchronize `ChillWopiBundle` with latest upstream WOPI packages. --- .../ChillWopiBundle/src/Controller/Test.php | 29 ++- .../src/Resources/config/services.php | 12 +- .../src/Service/Wopi/ChillDocumentManager.php | 243 ++++++++++++++++++ .../src/Service/Wopi/ChillWopi.php | 225 +++------------- 4 files changed, 308 insertions(+), 201 deletions(-) create mode 100644 src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentManager.php diff --git a/src/Bundle/ChillWopiBundle/src/Controller/Test.php b/src/Bundle/ChillWopiBundle/src/Controller/Test.php index 202af20aa..283328f21 100644 --- a/src/Bundle/ChillWopiBundle/src/Controller/Test.php +++ b/src/Bundle/ChillWopiBundle/src/Controller/Test.php @@ -9,9 +9,10 @@ declare(strict_types=1); namespace Chill\WopiBundle\Controller; -use ChampsLibres\WopiLib\Configuration\WopiConfigurationInterface; -use ChampsLibres\WopiLib\Discovery\WopiDiscoveryInterface; -use Chill\DocStoreBundle\Repository\StoredObjectRepository; +use ChampsLibres\WopiLib\Contract\Service\Configuration\ConfigurationInterface; +use ChampsLibres\WopiLib\Contract\Service\Discovery\DiscoveryInterface; +use ChampsLibres\WopiLib\Contract\Service\DocumentManagerInterface; +use Chill\DocStoreBundle\Entity\StoredObject; use Chill\WopiBundle\Service\Controller\ResponderInterface; use Exception; use loophp\psr17\Psr17Interface; @@ -23,11 +24,11 @@ use Symfony\Component\Security\Core\Security; final class Test { - private StoredObjectRepository $storedObjectRepository; + private DiscoveryInterface $wopiDiscovery; - private WopiDiscoveryInterface $wopiDiscovery; + private DocumentManagerInterface $documentManager; - private WopiConfigurationInterface $wopiConfiguration; + private ConfigurationInterface $wopiConfiguration; private ResponderInterface $responder; @@ -38,15 +39,15 @@ final class Test private RouterInterface $router; public function __construct( - StoredObjectRepository $storedObjectRepository, - WopiConfigurationInterface $wopiConfiguration, - WopiDiscoveryInterface $wopiDiscovery, + ConfigurationInterface $wopiConfiguration, + DiscoveryInterface $wopiDiscovery, + DocumentManagerInterface $documentManager, ResponderInterface $responder, Security $security, Psr17Interface $psr17, RouterInterface $router ) { - $this->storedObjectRepository = $storedObjectRepository; + $this->documentManager = $documentManager; $this->wopiConfiguration = $wopiConfiguration; $this->wopiDiscovery = $wopiDiscovery; $this->responder = $responder; @@ -58,11 +59,11 @@ final class Test public function __invoke(string $fileId): Response { $configuration = $this->wopiConfiguration->jsonSerialize(); - - $storedObject = $this->storedObjectRepository->findOneBy(['filename' => $fileId]); + /** @var StoredObject $storedObject */ + $storedObject = $this->documentManager->findByDocumentId($fileId); if (null === $storedObject) { - throw new NotFoundHttpException(sprintf('Unable to find object named %s', $fileId)); + throw new NotFoundHttpException(sprintf('Unable to find object %s', $fileId)); } if ([] === $discoverExtension = $this->wopiDiscovery->discoverMimeType($storedObject->getType())) { @@ -83,7 +84,7 @@ final class Test ->generate( 'checkFileInfo', [ - 'fileId' => $storedObject->getFilename(), + 'fileId' => $this->documentManager->getDocumentId($storedObject), ], UrlGeneratorInterface::ABSOLUTE_URL ), diff --git a/src/Bundle/ChillWopiBundle/src/Resources/config/services.php b/src/Bundle/ChillWopiBundle/src/Resources/config/services.php index 455e658e2..4925404b3 100644 --- a/src/Bundle/ChillWopiBundle/src/Resources/config/services.php +++ b/src/Bundle/ChillWopiBundle/src/Resources/config/services.php @@ -10,8 +10,10 @@ declare(strict_types=1); namespace Symfony\Component\DependencyInjection\Loader\Configurator; use ChampsLibres\AsyncUploaderBundle\TempUrl\TempUrlGeneratorInterface; -use ChampsLibres\WopiLib\Service\Contract\WopiInterface; use Chill\WopiBundle\Service\Wopi\ChillWopi; +use ChampsLibres\WopiBundle\Service\Wopi as CLWopi; +use ChampsLibres\WopiLib\Contract\Service\DocumentManagerInterface; +use Chill\WopiBundle\Service\Wopi\ChillDocumentManager; return static function (ContainerConfigurator $container) { $services = $container @@ -30,8 +32,14 @@ return static function (ContainerConfigurator $container) { ->tag('controller.service_arguments'); $services - ->alias(WopiInterface::class, ChillWopi::class); + ->set(ChillWopi::class) + ->decorate(CLWopi::class) + ->arg('$wopi', service('.inner')); + $services + ->alias(DocumentManagerInterface::class, ChillDocumentManager::class); + + // TODO: Move this into the async bundle (low priority) $services ->alias(TempUrlGeneratorInterface::class, 'async_uploader.temp_url_generator'); }; diff --git a/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentManager.php b/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentManager.php new file mode 100644 index 000000000..bfa3ee8b0 --- /dev/null +++ b/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentManager.php @@ -0,0 +1,243 @@ +entityManager = $entityManager; + $this->psr17 = $psr17; + $this->storedObjectRepository = $storedObjectRepository; + $this->documentLockManager = $documentLockManager; + $this->tempUrlGenerator = $tempUrlGenerator; + $this->httpClient = $httpClient; + $this->request = $httpMessageFactory->createRequest($requestStack->getCurrentRequest()); + } + + public function create(array $data): Document + { + /** @var StoredObject $document */ + $document = (new ObjectNormalizer())->denormalize([], StoredObject::class); + + // Mime types / extension handling. + $mimeTypes = new MimeTypes(); + $mimeTypes->getMimeTypes($data['extension']); + $document->setType(reset($mimeTypes)); + + $document->setFilename($data['name']); + + $this->entityManager->persist($document); + $this->entityManager->flush($document); + + // TODO : Ask proper mapping. + // Available: basename, name, extension, content, size + $this->setContent($document, $data['content']); + + return $document; + } + + public function deleteLock(Document $document): void { + $this->documentLockManager->deleteLock($document, $this->request); + } + + /** + * @param string $documentFilename without extension ! + */ + public function findByDocumentFilename(string $documentFilename): ?Document { + return $this->storedObjectRepository->findOneBy( + [ + 'filename' => $documentFilename, + ] + ); + } + + public function findByDocumentId(string $documentId): ?Document { + return $this->storedObjectRepository->findOneBy( + [ + 'uuid' => $documentId, + ] + ); + } + + /** + * @param StoredObject $document + */ + public function getCreationDate(Document $document): DateTimeInterface + { + return $document->getCreationDate(); + } + + /** + * @param StoredObject $document + */ + public function getLastModifiedDate(Document $document): DateTimeInterface + { + // TODO: Add column 'LastModifiedDate' in StoredObject entity + return $document->getCreationDate(); + } + + public function getLock(Document $document): string { + return $this->documentLockManager->getLock($document, $this->request); + } + + public function getVersion(Document $document): string { + // TODO ? + return '0'; + } + + public function hasLock(Document $document): bool { + return $this->documentLockManager->hasLock($document, $this->request); + } + + public function lock(Document $document, string $lock): void { + $this->documentLockManager->setLock($document, $lock, $this->request); + } + + public function remove(Document $document): void { + $entityIsDeleted = false; + + try { + $this->entityManager->remove($document); + $entityIsDeleted = true; + } catch (Throwable $e) { + $entityIsDeleted = false; + } + + if ($entityIsDeleted === true) { + $this->deleteContent($document); + } + } + + public function write(Document $document, array $properties = []): void + { + $this->setContent($document, $properties['content']); + } + + /** + * @param StoredObject $document + * + * @return string The document filename with its extension. + */ + public function getBasename(Document $document): string { + $exts = (new MimeTypes())->getExtensions($document->getType()); + + if ([] === $exts) { + throw new Error('Unknown mimetype for stored document.'); + } + + return sprintf('%s.%s', $document->getFilename(), reset($exts)); + } + + /** + * @param StoredObject $document + */ + public function getDocumentId(Document $document): string { + return (string) $document->getUuid(); + } + + public function getSha256(Document $document): string { + return base64_encode(hash('sha256', $this->getContent($document))); + } + + public function getSize(Document $document): int { + return strlen(stream_get_contents($this->read($document))); + } + + public function read(Document $document): StreamInterface { + return $this + ->psr17 + ->createStream($this->getContent($document)); + } + + private function deleteContent(StoredObject $storedObject): string + { + /** @var StdClass $object */ + $object = $this->tempUrlGenerator->generate('DELETE', $storedObject->getFilename()); + + $response = $this->httpClient->request('DELETE', $object->url); + + if (200 !== $response->getStatusCode()) + { + throw new Error('Unable to delete stored object.'); + } + } + + private function getContent(StoredObject $storedObject): string + { + /** @var StdClass $object */ + $object = $this->tempUrlGenerator->generate('GET', $storedObject->getFilename()); + + $response = $this->httpClient->request('GET', $object->url); + + if (200 !== $response->getStatusCode()) + { + throw new Error('Unable to retrieve stored object.'); + } + + return $response->getContent(); + } + + private function setContent(StoredObject $storedObject, string $content): void + { + // TODO: Add strict typing in champs-libres/async-uploader-bundle + /** @var StdClass $object */ + $object = $this->tempUrlGenerator->generate('PUT', $storedObject->getFilename()); + + $response = $this->httpClient->request('PUT', $object->url, ['body' => $content]); + + if (200 !== $response->getStatusCode()) + { + throw new Error('Unable to save stored object.'); + } + } + +} diff --git a/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillWopi.php b/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillWopi.php index fc4e526a1..7061c5735 100644 --- a/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillWopi.php +++ b/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillWopi.php @@ -9,46 +9,28 @@ declare(strict_types=1); namespace Chill\WopiBundle\Service\Wopi; -use ChampsLibres\AsyncUploaderBundle\TempUrl\TempUrlGeneratorInterface; -use ChampsLibres\WopiLib\Discovery\WopiDiscoveryInterface; -use ChampsLibres\WopiLib\Service\Contract\WopiInterface; -use Chill\DocStoreBundle\Repository\StoredObjectRepository; -use Exception; +use ChampsLibres\WopiLib\Contract\Service\DocumentManagerInterface; +use ChampsLibres\WopiLib\Contract\Service\WopiInterface; use loophp\psr17\Psr17Interface; -use Psr\Http\Client\ClientInterface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; -use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; -use Symfony\Component\Security\Core\User\UserProviderInterface; final class ChillWopi implements WopiInterface { + private DocumentManagerInterface $documentManager; + private Psr17Interface $psr17; - private WopiDiscoveryInterface $wopiDiscovery; - - private StoredObjectRepository $storedObjectRepository; - - private ClientInterface $httpClient; - - private TempUrlGeneratorInterface $tempUrlGenerator; - - private UserProviderInterface $userProvider; + private WopiInterface $wopi; public function __construct( + DocumentManagerInterface $documentManager, Psr17Interface $psr17, - WopiDiscoveryInterface $wopiDiscovery, - StoredObjectRepository $storedObjectRepository, - ClientInterface $httpClient, - TempUrlGeneratorInterface $tempUrlGenerator, - UserProviderInterface $userProvider + WopiInterface $wopi ) { + $this->documentManager = $documentManager; $this->psr17 = $psr17; - $this->wopiDiscovery = $wopiDiscovery; - $this->storedObjectRepository = $storedObjectRepository; - $this->httpClient = $httpClient; - $this->tempUrlGenerator = $tempUrlGenerator; - $this->userProvider = $userProvider; + $this->wopi = $wopi; } public function checkFileInfo( @@ -56,59 +38,31 @@ final class ChillWopi implements WopiInterface ?string $accessToken, RequestInterface $request ): ResponseInterface { - try { - $user = $this->userProvider->loadUserByUsername($accessToken); - } catch (UsernameNotFoundException $e) { - return $this - ->psr17 - ->createResponse(401); - } + $response = $this->wopi->checkFileInfo($fileId, $accessToken, $request); + $document = $this->documentManager->findByDocumentId($fileId); - $storedObject = $this->storedObjectRepository->findOneBy(['filename' => $fileId]); + $body = json_decode((string) $response->getBody(), true); - if (null === $storedObject) { - throw new Exception(sprintf('Unable to find object named %s', $fileId)); - } - - $mimeType = $storedObject->getType(); - - if ([] === $this->wopiDiscovery->discoverMimeType($mimeType)) { - throw new Exception(sprintf('Unable to find mime type %s', $mimeType)); - } - - return $this - ->psr17 - ->createResponse() - ->withHeader('Content-Type', 'application/json') - ->withBody($this->psr17->createStream((string) json_encode( - [ - 'BaseFileName' => $storedObject->getFilename(), - 'OwnerId' => uniqid(), - 'Size' => 0, - 'UserId' => uniqid(), -// 'Version' => 'v' . uniqid(), - 'ReadOnly' => false, - 'UserCanWrite' => true, - 'UserCanNotWriteRelative' => true, - 'SupportsLocks' => false, - 'UserFriendlyName' => sprintf('User %s', $user->getUsername()), - 'UserExtraInfo' => [], - 'LastModifiedTime' => date('Y-m-d\TH:i:s.u\Z', $storedObject->getCreationDate()->getTimestamp()), - 'CloseButtonClosesWindow' => true, - 'EnableInsertRemoteImage' => true, - 'EnableShare' => false, - 'SupportsUpdate' => true, - 'SupportsRename' => false, - 'DisablePrint' => false, - 'DisableExport' => false, - 'DisableCopy' => false, - ] - ))); + return $response + ->withBody( + $this + ->psr17 + ->createStream( + (string) json_encode( + $body + + [ + 'Version' => sprintf('v%s', $this->documentManager->getVersion($document)), + // TODO: Add column 'LastModifiedDate' in StoredObject entity + 'LastModifiedTime' => $this->documentManager->getLastModifiedDate($document)->format('Y-m-d\TH:i:s.uP'), + ] + ) + ) + ); } public function deleteFile(string $fileId, ?string $accessToken, RequestInterface $request): ResponseInterface { - return $this->getDebugResponse(__FUNCTION__, $request); + return $this->wopi->deleteFile($fileId, $accessToken, $request); } public function enumerateAncestors( @@ -116,57 +70,17 @@ final class ChillWopi implements WopiInterface ?string $accessToken, RequestInterface $request ): ResponseInterface { - return $this->getDebugResponse(__FUNCTION__, $request); + return $this->wopi->enumerateAncestors($fileId, $accessToken, $request); } public function getFile(string $fileId, ?string $accessToken, RequestInterface $request): ResponseInterface { - try { - $user = $this->userProvider->loadUserByUsername($accessToken); - } catch (UsernameNotFoundException $e) { - return $this - ->psr17 - ->createResponse(401); - } - - $storedObject = $this->storedObjectRepository->findOneBy(['filename' => $fileId]); - - if (null === $storedObject) { - return $this - ->psr17 - ->createResponse(404); - } - - // TODO: Add strict typing in champs-libres/async-uploader-bundle - /** @var StdClass $object */ - $object = $this->tempUrlGenerator->generate('GET', $storedObject->getFilename()); - - $response = $this->httpClient->sendRequest($this->psr17->createRequest('GET', $object->url)); - - if (200 !== $response->getStatusCode()) - { - return $this - ->psr17 - ->createResponse(500); - } - - return $this - ->psr17 - ->createResponse() - ->withHeader( - 'Content-Type', - 'application/octet-stream', - ) - ->withHeader( - 'Content-Disposition', - sprintf('attachment; filename=%s', $storedObject->getFilename()) - ) - ->withBody($response->getBody()); + return $this->wopi->getFile($fileId, $accessToken, $request); } public function getLock(string $fileId, ?string $accessToken, RequestInterface $request): ResponseInterface { - return $this->getDebugResponse(__FUNCTION__, $request); + return $this->wopi->getLock($fileId, $accessToken, $request); } public function getShareUrl( @@ -174,7 +88,7 @@ final class ChillWopi implements WopiInterface ?string $accessToken, RequestInterface $request ): ResponseInterface { - return $this->getDebugResponse(__FUNCTION__, $request); + return $this->wopi->getShareUrl($fileId, $accessToken, $request); } public function lock( @@ -183,7 +97,7 @@ final class ChillWopi implements WopiInterface string $xWopiLock, RequestInterface $request ): ResponseInterface { - return $this->getDebugResponse(__FUNCTION__, $request); + return $this->wopi->lock($fileId, $accessToken, $xWopiLock, $request); } public function putFile( @@ -193,49 +107,17 @@ final class ChillWopi implements WopiInterface string $xWopiEditors, RequestInterface $request ): ResponseInterface { - try { - $user = $this->userProvider->loadUserByUsername($accessToken); - } catch (UsernameNotFoundException $e) { - return $this - ->psr17 - ->createResponse(401); - } - - $storedObject = $this->storedObjectRepository->findOneBy(['filename' => $fileId]); - - if (null === $storedObject) { - throw new Exception(sprintf('Unable to find object named %s', $fileId)); - } - - // TODO: Add strict typing in champs-libres/async-uploader-bundle - /** @var StdClass $object */ - $object = $this->tempUrlGenerator->generate('PUT', $storedObject->getFilename()); - - $response = $this->httpClient->sendRequest($this->psr17->createRequest('PUT', $object->url)->withBody($request->getBody())); - - if (201 !== $response->getStatusCode()) - { - return $this - ->psr17 - ->createResponse(500); - } - - return $this - ->psr17 - ->createResponse() - ->withHeader('Content-Type', 'application/json') - ->withAddedHeader('X-WOPI-Lock', $xWopiLock) - ->withBody($this->psr17->createStream((string) json_encode([]))); + return $this->wopi->putFile($fileId, $accessToken, $xWopiLock, $xWopiEditors, $request); } public function putRelativeFile(string $fileId, string $accessToken, ?string $suggestedTarget, ?string $relativeTarget, bool $overwriteRelativeTarget, int $size, RequestInterface $request): ResponseInterface { - return $this->getDebugResponse(__FUNCTION__, $request); + return $this->wopi->putRelativeFile($fileId, $accessToken, $suggestedTarget, $relativeTarget, $overwriteRelativeTarget, $size, $request); } public function putUserInfo(string $fileId, ?string $accessToken, RequestInterface $request): ResponseInterface { - return $this->getDebugResponse(__FUNCTION__, $request); + return $this->wopi->putUserInfo($fileId, $accessToken, $request); } public function refreshLock( @@ -244,7 +126,7 @@ final class ChillWopi implements WopiInterface string $xWopiLock, RequestInterface $request ): ResponseInterface { - return $this->getDebugResponse(__FUNCTION__, $request); + return $this->wopi->refreshLock($fileId, $accessToken, $xWopiLock, $request); } public function renameFile( @@ -254,7 +136,7 @@ final class ChillWopi implements WopiInterface string $xWopiRequestedName, RequestInterface $request ): ResponseInterface { - return $this->getDebugResponse(__FUNCTION__, $request); + return $this->wopi->renameFile($fileId, $accessToken, $xWopiLock, $xWopiRequestedName, $request); } public function unlock( @@ -263,7 +145,7 @@ final class ChillWopi implements WopiInterface string $xWopiLock, RequestInterface $request ): ResponseInterface { - return $this->getDebugResponse(__FUNCTION__, $request); + return $this->wopi->unlock($fileId, $accessToken, $xWopiLock, $request); } public function unlockAndRelock( @@ -273,33 +155,6 @@ final class ChillWopi implements WopiInterface string $xWopiOldLock, RequestInterface $request ): ResponseInterface { - return $this->getDebugResponse(__FUNCTION__, $request); - } - - private function getDebugResponse(string $method, RequestInterface $request): ResponseInterface - { - $params = []; - parse_str($request->getUri()->getQuery(), $params); - - $data = (string) json_encode(array_merge( - ['method' => $method], - $params, - $request->getHeaders() - )); - - return $this - ->psr17 - ->createResponse() - ->withHeader('content', 'application/json') - ->withBody($this->psr17->createStream($data)); - } - - private function getLockFilepath(string $fileId): string - { - return sprintf( - '%s/%s.lock', - $this->filesRepository, - $fileId - ); + return $this->wopi->unlockAndRelock($fileId, $accessToken, $xWopiLock, $xWopiOldLock, $request); } } From ba53ce40fa8c71464a221aa1b1abeecc6e24bacc Mon Sep 17 00:00:00 2001 From: Pol Dellaiera Date: Tue, 28 Sep 2021 20:32:43 +0200 Subject: [PATCH 10/32] Add migration file. --- .../migrations/Version20210928182542.php | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 src/Bundle/ChillDocStoreBundle/migrations/Version20210928182542.php diff --git a/src/Bundle/ChillDocStoreBundle/migrations/Version20210928182542.php b/src/Bundle/ChillDocStoreBundle/migrations/Version20210928182542.php new file mode 100644 index 000000000..756faf654 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/migrations/Version20210928182542.php @@ -0,0 +1,30 @@ +addSql('ALTER TABLE chill_doc.stored_object ADD uuid UUID NOT NULL'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_49604E36D17F50A6 ON chill_doc.stored_object (uuid)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('DROP INDEX UNIQ_49604E36D17F50A6'); + $this->addSql('ALTER TABLE chill_doc.stored_object DROP uuid'); + } +} From 6020ee2cb24479ac0dac4bbe0e9fb4a87514b728 Mon Sep 17 00:00:00 2001 From: Pol Dellaiera Date: Tue, 28 Sep 2021 20:35:19 +0200 Subject: [PATCH 11/32] chore: Update `.editorconfig` file. --- .editorconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/.editorconfig b/.editorconfig index d769b46a4..fe115d4c0 100644 --- a/.editorconfig +++ b/.editorconfig @@ -7,6 +7,7 @@ charset = utf-8 end_of_line = LF insert_final_newline = true trim_trailing_whitespace = true +indent_size = 4 [*.{php,html,twig}] indent_style = space From 704ec76ca43c5d1bd9326e6d0f03419c8022ace1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 28 Sep 2021 21:35:41 +0200 Subject: [PATCH 12/32] rewrite the destination for household / leave household --- .../components/Household.vue | 285 +++++++----------- .../vuejs/HouseholdMembersEditor/js/i18n.js | 9 +- .../HouseholdMembersEditor/store/index.js | 54 ++++ 3 files changed, 165 insertions(+), 183 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Household.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Household.vue index bdbacf2bd..51b86fefa 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Household.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Household.vue @@ -2,171 +2,110 @@

{{ $t('household_members_editor.household_part') }}

-
+
-
-
- -
-
+
{{ $t('household_members_editor.household.no_household_choose_one') }}
-
- -
-

- {{ $t('household_members_editor.household.where_live_the_household') }} -

-
-
-

- - -

-
-
-
-
-
-
-
-
    -
  • - -
  • -
-
-
    -
  • - - -
  • -
-
-
-
-
-
+
+
+
+
+
+
+
+ {{ $t('household_members_editor.household.new_household') }}
-
+
+
+ +
    +
  • + +
  • +
+
+
+ + +
+
+
+
+
+
+ + + + + {{ $t('household_members_editor.household.leave_without_household') }} +
+
+
+
+ {{ $t('household_members_editor.household.will_leave_any_household_explanation')}} +
+
+
    +
  • + +
  • +
+
+
+ +
+
+ +
    +
  • + +
  • +
- -
    -
  • - - -
  • -
- -
-
    -
  • - -
  • -
-
-
-
- {{ $t('household_members_editor.household.will_leave_any_household') }} -
-
{{ $t('household_members_editor.household.no_household_choose_one') }}
-
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
- -
-

- {{ $t('household_members_editor.household_for_participants_accompanying_period') }} : -

-
-
-

- - - -

-
-
-
-
- -
    -
  • - -
  • -
-
-
-
+
+
+
+ + +
+
    +
  • + +
  • +
+
+
diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Household.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Household.vue index a8c832d1d..a2ebc83da 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Household.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Household.vue @@ -94,7 +94,7 @@
@@ -134,37 +140,26 @@ export default { data() { return { addAddress: { - /* - context: { - target: { - name: 'household_create', - id: 0 - }, - edit: false, - addressId: null - }, - - */ key: 'household_new', options: { useDate: { - validFrom: false, - validTo: false, + validFrom: false, + validTo: false, }, hideAddress: true, button: { text: { create: 'household_members_editor.household.set_address', - edit: null, + edit: 'household_members_editor.household.update_address', } }, title: { create: 'household_members_editor.household.create_new_address', - edit: null, + edit: 'household_members_editor.household.update_address_title', }, } } - } + }; }, computed: { ...mapGetters([ @@ -213,17 +208,6 @@ export default { selectHousehold(h) { this.$store.dispatch('selectHousehold', h); }, - - - setHouseholdAddress(a) { - let payload = this.$refs.addAddress.submitNewAddress(); - console.log('setHouseholdAddress', a); - this.$store.commit('setHouseholdAddress', a); - }, - setHouseholdCreatedAddress(payload) { - console.log('setHouseholdAddress', payload); - this.$store.dispatch('setHouseholdNewAddress', payload); - }, removeHouseholdAddress() { this.$store.commit('removeHouseholdAddress'); }, @@ -248,28 +232,4 @@ div.householdSuggestionList { } } } - -/* -div.householdAddressSuggestionList { - display: flex; - list-style-type: none; - padding: 0; - & > li {} -} -.householdSuggestionList { - display: flex; - flex-direction: row; - flex-wrap: wrap; - justify-content: space-between; - & > .item { - margin-bottom: 0.8rem; - width: calc(50% - 1rem); - border: 1px solid var(--chill-light-gray); - padding: 0.5rem 0.5rem 0 0.5rem; - ul.record_actions { - margin-bottom: 0; - } - } -} -*/ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/js/i18n.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/js/i18n.js index 40201af77..c2d1d3dc3 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/js/i18n.js +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/js/i18n.js @@ -15,13 +15,18 @@ const appMessages = { leave_without_household: "Sans nouveau ménage", set_address: "Indiquer une adresse", reset_mode: "Modifier la destination", + remove_address: "Supprimer l'adresse", + update_address: "Mettre à jour l'adresse", // remove ? + /* where_live_the_household: "À quelle adresse habite ce ménage ?", household_live_to_this_address: "Sélectionner l'adresse", no_suggestions: "Aucune adresse à suggérer", delete_this_address: "Supprimer cette adresse", create_new_address: "Créer une nouvelle adresse", or_create_new_address: "Ou créer une nouvelle adresse", + + */ // end remove ? }, concerned: { diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/store/index.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/store/index.js index 9e1e047ea..5f45e31eb 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/store/index.js +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/store/index.js @@ -93,16 +93,27 @@ const store = createStore({ */ }, - getAddressContext(state) { - return { - edit: false, - addressId: null, - target: { - name: state.household.type, - id: state.household.id - }, - suggestions: state.addressesSuggestion - }; + getAddressContext(state, getters) { + if (!getters.hasHouseholdAddress) { + return { + edit: false, + addressId: null, + target: { + name: state.household.type, + id: state.household.id + }, + suggestions: state.addressesSuggestion + }; + } else { + return { + edit: true, + addressId: state.household.current_address.id, + target: { + name: state.household.type, + id: state.household.id + }, + }; + } }, hasHouseholdAddress(state) { if (null === state.household) { From 664fcd07efa08ac0d8ac68a4e02e9836861548af Mon Sep 17 00:00:00 2001 From: Pol Dellaiera Date: Wed, 29 Sep 2021 14:52:22 +0200 Subject: [PATCH 22/32] fix: Update `StoredObject` uuid property. --- src/Bundle/ChillDocStoreBundle/Entity/StoredObject.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Bundle/ChillDocStoreBundle/Entity/StoredObject.php b/src/Bundle/ChillDocStoreBundle/Entity/StoredObject.php index 7ba21b5da..8d350c2b2 100644 --- a/src/Bundle/ChillDocStoreBundle/Entity/StoredObject.php +++ b/src/Bundle/ChillDocStoreBundle/Entity/StoredObject.php @@ -9,6 +9,7 @@ use ChampsLibres\AsyncUploaderBundle\Model\AsyncFileInterface; use ChampsLibres\AsyncUploaderBundle\Validator\Constraints\AsyncFileExists; use ChampsLibres\WopiLib\Contract\Entity\Document; use DateTimeInterface; +use Ramsey\Uuid\Rfc4122\UuidInterface; use Ramsey\Uuid\Uuid; use Symfony\Component\Serializer\Annotation as Serializer; @@ -52,7 +53,7 @@ class StoredObject implements AsyncFileInterface, Document /** * @ORM\Column(type="uuid", unique=true) */ - private Uuid $uuid; + private UuidInterface $uuid; /** * @ORM\Column(type="datetime", name="creation_date") @@ -163,7 +164,7 @@ class StoredObject implements AsyncFileInterface, Document return $this; } - public function getUuid(): Uuid + public function getUuid(): UuidInterface { return $this->uuid; } From 86c2a5c3e80ad6070ce868179fdd742ec2649747 Mon Sep 17 00:00:00 2001 From: Pol Dellaiera Date: Wed, 29 Sep 2021 15:07:27 +0200 Subject: [PATCH 23/32] fix: Update `ChillDocumentManager`. Fix statuscode and return types. --- .../src/Service/Wopi/ChillDocumentManager.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentManager.php b/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentManager.php index bfa3ee8b0..1924edd01 100644 --- a/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentManager.php +++ b/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentManager.php @@ -21,6 +21,7 @@ use Error; use loophp\psr17\Psr17Interface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\StreamInterface; +use Ramsey\Uuid\Uuid; use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\Mime\MimeTypes; @@ -103,7 +104,7 @@ final class ChillDocumentManager implements DocumentManagerInterface public function findByDocumentId(string $documentId): ?Document { return $this->storedObjectRepository->findOneBy( [ - 'uuid' => $documentId, + 'uuid' => Uuid::fromString($documentId), ] ); } @@ -198,7 +199,7 @@ final class ChillDocumentManager implements DocumentManagerInterface ->createStream($this->getContent($document)); } - private function deleteContent(StoredObject $storedObject): string + private function deleteContent(StoredObject $storedObject): void { /** @var StdClass $object */ $object = $this->tempUrlGenerator->generate('DELETE', $storedObject->getFilename()); @@ -234,7 +235,7 @@ final class ChillDocumentManager implements DocumentManagerInterface $response = $this->httpClient->request('PUT', $object->url, ['body' => $content]); - if (200 !== $response->getStatusCode()) + if (201 !== $response->getStatusCode()) { throw new Error('Unable to save stored object.'); } From 2c1d3d08c31da6945ea73bb0f06c29362b19147f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 29 Sep 2021 15:15:10 +0200 Subject: [PATCH 24/32] temporary stored object fixes --- .../Controller/DocGeneratorTemplateController.php | 2 +- src/Bundle/ChillDocStoreBundle/Entity/StoredObject.php | 6 ++++-- .../migrations/Version20210928182542.php | 8 ++++---- .../components/AddEvaluation.vue | 2 +- .../src/Service/Wopi/ChillDocumentManager.php | 3 ++- 5 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/Bundle/ChillDocGeneratorBundle/Controller/DocGeneratorTemplateController.php b/src/Bundle/ChillDocGeneratorBundle/Controller/DocGeneratorTemplateController.php index f8fd69431..93068de3f 100644 --- a/src/Bundle/ChillDocGeneratorBundle/Controller/DocGeneratorTemplateController.php +++ b/src/Bundle/ChillDocGeneratorBundle/Controller/DocGeneratorTemplateController.php @@ -138,7 +138,7 @@ class DocGeneratorTemplateController extends AbstractController $em->flush(); return $this->redirectToRoute('chill_wopi_file_edit', [ - 'fileId' => $genDocName, + 'fileId' => $storedObject->getUuid(), 'returnPath' => $request->query->get('returnPath', "/") ]); } diff --git a/src/Bundle/ChillDocStoreBundle/Entity/StoredObject.php b/src/Bundle/ChillDocStoreBundle/Entity/StoredObject.php index 7ba21b5da..a0e614025 100644 --- a/src/Bundle/ChillDocStoreBundle/Entity/StoredObject.php +++ b/src/Bundle/ChillDocStoreBundle/Entity/StoredObject.php @@ -10,6 +10,7 @@ use ChampsLibres\AsyncUploaderBundle\Validator\Constraints\AsyncFileExists; use ChampsLibres\WopiLib\Contract\Entity\Document; use DateTimeInterface; use Ramsey\Uuid\Uuid; +use Ramsey\Uuid\UuidInterface; use Symfony\Component\Serializer\Annotation as Serializer; /** @@ -51,8 +52,9 @@ class StoredObject implements AsyncFileInterface, Document /** * @ORM\Column(type="uuid", unique=true) + * @Serializer\Groups({"read"}) */ - private Uuid $uuid; + private UuidInterface $uuid; /** * @ORM\Column(type="datetime", name="creation_date") @@ -163,7 +165,7 @@ class StoredObject implements AsyncFileInterface, Document return $this; } - public function getUuid(): Uuid + public function getUuid(): UuidInterface { return $this->uuid; } diff --git a/src/Bundle/ChillDocStoreBundle/migrations/Version20210928182542.php b/src/Bundle/ChillDocStoreBundle/migrations/Version20210928182542.php index 756faf654..20d45ca37 100644 --- a/src/Bundle/ChillDocStoreBundle/migrations/Version20210928182542.php +++ b/src/Bundle/ChillDocStoreBundle/migrations/Version20210928182542.php @@ -16,15 +16,15 @@ final class Version20210928182542 extends AbstractMigration public function up(Schema $schema): void { - // this up() migration is auto-generated, please modify it to your needs - $this->addSql('ALTER TABLE chill_doc.stored_object ADD uuid UUID NOT NULL'); + $this->addSql('CREATE EXTENSION IF NOT EXISTS "uuid-ossp"'); + $this->addSql('ALTER TABLE chill_doc.stored_object ADD uuid UUID DEFAULT NULL'); + $this->addSql('UPDATE chill_doc.stored_object SET uuid=uuid_generate_v4()'); + $this->addSql('ALTER TABLE chill_doc.stored_object ALTER uuid SET NOT NULL'); $this->addSql('CREATE UNIQUE INDEX UNIQ_49604E36D17F50A6 ON chill_doc.stored_object (uuid)'); } public function down(Schema $schema): void { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('DROP INDEX UNIQ_49604E36D17F50A6'); $this->addSql('ALTER TABLE chill_doc.stored_object DROP uuid'); } } diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/AddEvaluation.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/AddEvaluation.vue index 3321a8005..f05f9fca3 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/AddEvaluation.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/AddEvaluation.vue @@ -109,7 +109,7 @@ export default { this.toggleEditEvaluation(); }, buildEditLink(storedObject) { - return `/fr/chill_wopi/edit/${storedObject.filename}?returnPath=` + encodeURIComponent( + return `/fr/chill_wopi/edit/${storedObject.uuid}?returnPath=` + encodeURIComponent( window.location.pathname + window.location.search + window.location.hash); }, } diff --git a/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentManager.php b/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentManager.php index bfa3ee8b0..a6cf08063 100644 --- a/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentManager.php +++ b/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentManager.php @@ -21,6 +21,7 @@ use Error; use loophp\psr17\Psr17Interface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\StreamInterface; +use Ramsey\Uuid\Uuid; use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\Mime\MimeTypes; @@ -103,7 +104,7 @@ final class ChillDocumentManager implements DocumentManagerInterface public function findByDocumentId(string $documentId): ?Document { return $this->storedObjectRepository->findOneBy( [ - 'uuid' => $documentId, + 'uuid' => Uuid::fromString($documentId), ] ); } From c7931595938cc06f454efe27ce13907048b4dbd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 29 Sep 2021 15:27:11 +0200 Subject: [PATCH 25/32] quick fix stored boject --- src/Bundle/ChillDocStoreBundle/Entity/StoredObject.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Bundle/ChillDocStoreBundle/Entity/StoredObject.php b/src/Bundle/ChillDocStoreBundle/Entity/StoredObject.php index 4ad013fce..a0e614025 100644 --- a/src/Bundle/ChillDocStoreBundle/Entity/StoredObject.php +++ b/src/Bundle/ChillDocStoreBundle/Entity/StoredObject.php @@ -9,7 +9,6 @@ use ChampsLibres\AsyncUploaderBundle\Model\AsyncFileInterface; use ChampsLibres\AsyncUploaderBundle\Validator\Constraints\AsyncFileExists; use ChampsLibres\WopiLib\Contract\Entity\Document; use DateTimeInterface; -use Ramsey\Uuid\Rfc4122\UuidInterface; use Ramsey\Uuid\Uuid; use Ramsey\Uuid\UuidInterface; use Symfony\Component\Serializer\Annotation as Serializer; From a2df3c8144d71c0c5e2a59446f2a66cac4c7ef24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 29 Sep 2021 16:04:05 +0200 Subject: [PATCH 26/32] =?UTF-8?q?rdv=20avant=20=C3=A9changes=20dans=20le?= =?UTF-8?q?=20menu?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ChillCalendarBundle/Menu/AccompanyingCourseMenuBuilder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bundle/ChillCalendarBundle/Menu/AccompanyingCourseMenuBuilder.php b/src/Bundle/ChillCalendarBundle/Menu/AccompanyingCourseMenuBuilder.php index 244d7db78..4f0cf7b30 100644 --- a/src/Bundle/ChillCalendarBundle/Menu/AccompanyingCourseMenuBuilder.php +++ b/src/Bundle/ChillCalendarBundle/Menu/AccompanyingCourseMenuBuilder.php @@ -41,7 +41,7 @@ class AccompanyingCourseMenuBuilder implements LocalMenuBuilderInterface 'routeParameters' => [ 'accompanying_period_id' => $period->getId(), ]]) - ->setExtras(['order' => 41]); + ->setExtras(['order' => 35]); } } } From fb8b8b354f5c501a22c53f10202ef7c2e831fa32 Mon Sep 17 00:00:00 2001 From: Pol Dellaiera Date: Wed, 29 Sep 2021 16:11:46 +0200 Subject: [PATCH 27/32] refactor: Update `Wopi::checkFileInfo()` so it can authenticate users from access token. --- .../src/Service/Wopi/ChillWopi.php | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillWopi.php b/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillWopi.php index 7061c5735..e9e7e47cc 100644 --- a/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillWopi.php +++ b/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillWopi.php @@ -14,6 +14,8 @@ use ChampsLibres\WopiLib\Contract\Service\WopiInterface; use loophp\psr17\Psr17Interface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; +use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; +use Symfony\Component\Security\Core\User\UserProviderInterface; final class ChillWopi implements WopiInterface { @@ -21,15 +23,19 @@ final class ChillWopi implements WopiInterface private Psr17Interface $psr17; + private UserProviderInterface $userProvider; + private WopiInterface $wopi; public function __construct( DocumentManagerInterface $documentManager, Psr17Interface $psr17, + UserProviderInterface $userProvider, WopiInterface $wopi ) { $this->documentManager = $documentManager; $this->psr17 = $psr17; + $this->userProvider = $userProvider; $this->wopi = $wopi; } @@ -38,6 +44,52 @@ final class ChillWopi implements WopiInterface ?string $accessToken, RequestInterface $request ): ResponseInterface { + try { + $user = $this->userProvider->loadUserByUsername($accessToken); + } catch (UsernameNotFoundException $e) { + return $this + ->psr17 + ->createResponse(401); + } + + // @ TODO : Replace this with a call to decorated object once authentication is done. + $document = $this->documentManager->findByDocumentId($fileId); + $userIdentifier = $user->getUsername(); + $userCacheKey = sprintf('wopi_putUserInfo_%s', $userIdentifier); + + return $this + ->psr17 + ->createResponse() + ->withHeader('Content-Type', 'application/json') + ->withBody($this->psr17->createStream((string) json_encode( + [ + 'BaseFileName' => $this->documentManager->getBasename($document), + 'OwnerId' => 'Symfony', + 'Size' => $this->documentManager->getSize($document), + 'UserId' => $userIdentifier, + 'ReadOnly' => false, + 'UserCanAttend' => true, + 'UserCanPresent' => true, + 'UserCanRename' => true, + 'UserCanWrite' => true, + 'UserCanNotWriteRelative' => false, + 'SupportsUserInfo' => true, + 'SupportsDeleteFile' => true, + 'SupportsLocks' => true, + 'SupportsGetLock' => true, + 'SupportsExtendedLockLength' => true, + 'UserFriendlyName' => $userIdentifier, + 'SupportsUpdate' => true, + 'SupportsRename' => true, + 'DisablePrint' => false, + 'AllowExternalMarketplace' => true, + 'SupportedShareUrlTypes' => [ + 'ReadOnly', + ], + 'SHA256' => $this->documentManager->getSha256($document), + 'UserInfo' => (string) $this->cache->getItem($userCacheKey)->get(), + ] + ))); $response = $this->wopi->checkFileInfo($fileId, $accessToken, $request); $document = $this->documentManager->findByDocumentId($fileId); From 00259dae209d4eda914c9969e8930830cf0ec6db Mon Sep 17 00:00:00 2001 From: Pol Dellaiera Date: Wed, 29 Sep 2021 21:46:47 +0200 Subject: [PATCH 28/32] fix: Update `ChillDocumentManager::getSize()`. --- .../ChillWopiBundle/src/Service/Wopi/ChillDocumentManager.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentManager.php b/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentManager.php index 1924edd01..73a4dd285 100644 --- a/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentManager.php +++ b/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentManager.php @@ -190,7 +190,7 @@ final class ChillDocumentManager implements DocumentManagerInterface } public function getSize(Document $document): int { - return strlen(stream_get_contents($this->read($document))); + return strlen($this->read($document)->getContents()); } public function read(Document $document): StreamInterface { From 9a34aa59bb20cba83dda2f51f9309bf0693485ee Mon Sep 17 00:00:00 2001 From: Pol Dellaiera Date: Wed, 29 Sep 2021 21:50:44 +0200 Subject: [PATCH 29/32] fix: Add missing dependency. --- src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillWopi.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillWopi.php b/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillWopi.php index e9e7e47cc..12bc1b178 100644 --- a/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillWopi.php +++ b/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillWopi.php @@ -12,6 +12,7 @@ namespace Chill\WopiBundle\Service\Wopi; use ChampsLibres\WopiLib\Contract\Service\DocumentManagerInterface; use ChampsLibres\WopiLib\Contract\Service\WopiInterface; use loophp\psr17\Psr17Interface; +use Psr\Cache\CacheItemPoolInterface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; @@ -19,6 +20,8 @@ use Symfony\Component\Security\Core\User\UserProviderInterface; final class ChillWopi implements WopiInterface { + private CacheItemPoolInterface $cache; + private DocumentManagerInterface $documentManager; private Psr17Interface $psr17; @@ -28,11 +31,13 @@ final class ChillWopi implements WopiInterface private WopiInterface $wopi; public function __construct( + CacheItemPoolInterface $cache, DocumentManagerInterface $documentManager, Psr17Interface $psr17, UserProviderInterface $userProvider, WopiInterface $wopi ) { + $this->cache = $cache; $this->documentManager = $documentManager; $this->psr17 = $psr17; $this->userProvider = $userProvider; From 7f721da08a6eebc82351dad89f49a0277433a1e2 Mon Sep 17 00:00:00 2001 From: Pol Dellaiera Date: Wed, 29 Sep 2021 22:38:48 +0200 Subject: [PATCH 30/32] fix: Remove obsolete code. --- .../src/Service/Wopi/ChillWopi.php | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillWopi.php b/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillWopi.php index 12bc1b178..ba289630e 100644 --- a/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillWopi.php +++ b/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillWopi.php @@ -95,26 +95,6 @@ final class ChillWopi implements WopiInterface 'UserInfo' => (string) $this->cache->getItem($userCacheKey)->get(), ] ))); - $response = $this->wopi->checkFileInfo($fileId, $accessToken, $request); - $document = $this->documentManager->findByDocumentId($fileId); - - $body = json_decode((string) $response->getBody(), true); - - return $response - ->withBody( - $this - ->psr17 - ->createStream( - (string) json_encode( - $body + - [ - 'Version' => sprintf('v%s', $this->documentManager->getVersion($document)), - // TODO: Add column 'LastModifiedDate' in StoredObject entity - 'LastModifiedTime' => $this->documentManager->getLastModifiedDate($document)->format('Y-m-d\TH:i:s.uP'), - ] - ) - ) - ); } public function deleteFile(string $fileId, ?string $accessToken, RequestInterface $request): ResponseInterface From f8f019cfe629b2fff0ebd53a6e5d6709b186cd51 Mon Sep 17 00:00:00 2001 From: Pol Dellaiera Date: Thu, 30 Sep 2021 11:09:43 +0200 Subject: [PATCH 31/32] fix: Fix bug in `getSize()` method. --- .../ChillWopiBundle/src/Service/Wopi/ChillDocumentManager.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentManager.php b/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentManager.php index 73a4dd285..62362d48f 100644 --- a/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentManager.php +++ b/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentManager.php @@ -190,7 +190,7 @@ final class ChillDocumentManager implements DocumentManagerInterface } public function getSize(Document $document): int { - return strlen($this->read($document)->getContents()); + return strlen($this->getContent()); } public function read(Document $document): StreamInterface { From bd7f2d997edd47367cdafb4ea57311a8dc115779 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 30 Sep 2021 11:51:26 +0200 Subject: [PATCH 32/32] fix link to edit in wopi --- .../AccompanyingCourseWorkEdit/components/AddEvaluation.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/AddEvaluation.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/AddEvaluation.vue index f05f9fca3..9dee7693a 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/AddEvaluation.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/AddEvaluation.vue @@ -109,7 +109,7 @@ export default { this.toggleEditEvaluation(); }, buildEditLink(storedObject) { - return `/fr/chill_wopi/edit/${storedObject.uuid}?returnPath=` + encodeURIComponent( + return `/edit/${storedObject.uuid}?returnPath=` + encodeURIComponent( window.location.pathname + window.location.search + window.location.hash); }, }