mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2026-04-09 06:23:45 +00:00
Implements controllers for locking and unlocking with the webdav protocol (wip)
This commit is contained in:
@@ -75,7 +75,7 @@ final readonly class WebdavController
|
||||
;
|
||||
|
||||
// $response->headers->add(['Allow' => 'OPTIONS,GET,HEAD,DELETE,PROPFIND,PUT,PROPPATCH,COPY,MOVE,REPORT,PATCH,POST,TRACE']);
|
||||
$response->headers->add(['Allow' => 'OPTIONS,GET,HEAD,DELETE,PROPFIND,PUT']);
|
||||
$response->headers->add(['Allow' => 'OPTIONS,GET,HEAD,DELETE,PROPFIND,PUT,LOCK,UNLOCK']);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\DocStoreBundle\Controller;
|
||||
|
||||
use Chill\DocStoreBundle\Dav\Response\DavResponse;
|
||||
use Chill\DocStoreBundle\Dav\Utils\LockTimeoutAnalyzer;
|
||||
use Chill\DocStoreBundle\Dav\Utils\LockTokenParser;
|
||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||
use Chill\DocStoreBundle\Entity\StoredObjectLockMethodEnum;
|
||||
use Chill\DocStoreBundle\Service\Lock\LockTokenCheckResultEnum;
|
||||
use Chill\DocStoreBundle\Service\Lock\StoredObjectLockManager;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Symfony\Component\Clock\ClockInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\PreconditionFailedHttpException;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
use Twig\Environment;
|
||||
|
||||
class WebdavLockController
|
||||
{
|
||||
public function __construct(
|
||||
private StoredObjectLockManager $lockManager,
|
||||
private Security $security,
|
||||
private Environment $twig,
|
||||
private LockTimeoutAnalyzer $lockTimeoutAnalyzer,
|
||||
private ClockInterface $clock,
|
||||
private LockTokenParser $lockTokenParser,
|
||||
) {}
|
||||
|
||||
#[Route(path: '/dav/{access_token}/get/{uuid}/d', name: 'chill_docstore_dav_document_lock', methods: ['LOCK'])]
|
||||
public function lockDocument(StoredObject $storedObject, Request $request): Response
|
||||
{
|
||||
$timeout = $request->headers->get('Timeout', 'Second-3600');
|
||||
$timeoutInterval = $this->lockTimeoutAnalyzer->analyzeTimeout($timeout);
|
||||
|
||||
$user = $this->security->getUser();
|
||||
|
||||
$users = $user instanceof User ? [$user] : [];
|
||||
|
||||
$lock = $this->lockManager->setLock(
|
||||
$storedObject,
|
||||
StoredObjectLockMethodEnum::WEBDAV,
|
||||
expiresAt: $this->clock->now()->add($timeoutInterval),
|
||||
users: $users
|
||||
);
|
||||
|
||||
$content = $this->twig->render('@ChillDocStore/Webdav/doc_lock.xml.twig', [
|
||||
'lock' => $lock,
|
||||
'timeout' => $timeout,
|
||||
]);
|
||||
|
||||
return (new DavResponse($content))->setLockToken($lock->getToken());
|
||||
}
|
||||
|
||||
#[Route(path: '/dav/{access_token}/get/{uuid}/d', name: 'chill_docstore_dav_document_unlock', methods: ['UNLOCK'])]
|
||||
public function unlockDocument(StoredObject $storedObject, Request $request): Response
|
||||
{
|
||||
$lockToken = $this->lockTokenParser->parseLockToken($request);
|
||||
|
||||
if (null === $lockToken) {
|
||||
throw new BadRequestHttpException('LockToken not found');
|
||||
}
|
||||
|
||||
$check = $this->lockManager->checkLock($storedObject, $lockToken, $this->security->getUser());
|
||||
|
||||
if (true === $check) {
|
||||
$this->lockManager->deleteLock($storedObject, $this->clock->now()->add(new \DateInterval('PT3S')));
|
||||
|
||||
return new DavResponse(null, status: Response::HTTP_NO_CONTENT);
|
||||
}
|
||||
$e = match ($check) {
|
||||
LockTokenCheckResultEnum::LOCK_TOKEN_DO_NOT_MATCH, LockTokenCheckResultEnum::LOCK_TOKEN_DO_NOT_BELONG_TO_USER => new ConflictHttpException(),
|
||||
LockTokenCheckResultEnum::NO_LOCK_FOUND => new PreconditionFailedHttpException(),
|
||||
};
|
||||
|
||||
throw $e;
|
||||
|
||||
}
|
||||
}
|
||||
43
src/Bundle/ChillDocStoreBundle/Dav/Utils/LockTokenParser.php
Normal file
43
src/Bundle/ChillDocStoreBundle/Dav/Utils/LockTokenParser.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\DocStoreBundle\Dav\Utils;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
final readonly class LockTokenParser
|
||||
{
|
||||
public function parseLockToken(Request $request): ?string
|
||||
{
|
||||
$token = $request->headers->get('lock-token');
|
||||
|
||||
if (null === $token) {
|
||||
return null;
|
||||
}
|
||||
if (str_starts_with($token, '"')) {
|
||||
$token = substr($token, 1, -1);
|
||||
}
|
||||
|
||||
if (str_starts_with($token, '<')) {
|
||||
$token = substr($token, 1);
|
||||
}
|
||||
|
||||
if (str_ends_with($token, '>')) {
|
||||
$token = substr($token, 0, -1);
|
||||
}
|
||||
|
||||
if (str_ends_with($token, '"')) {
|
||||
$token = substr($token, 1, -1);
|
||||
}
|
||||
|
||||
return $token;
|
||||
}
|
||||
}
|
||||
@@ -129,4 +129,14 @@ class StoredObjectLock
|
||||
{
|
||||
return null === $this->getExpireAt() || $at < $this->getExpireAt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the lock token is exclusive.
|
||||
*
|
||||
* Currently, this is linked to the webdav method.
|
||||
*/
|
||||
public function isExclusive(): bool
|
||||
{
|
||||
return StoredObjectLockMethodEnum::WEBDAV === $this->getMethod();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<D:prop xmlns:D="DAV:">
|
||||
<D:lockdiscovery>
|
||||
<D:activelock>
|
||||
<D:lockscope><D:exclusive/></D:lockscope>
|
||||
<D:locktype><D:write/></D:locktype>
|
||||
<D:depth>infinity</D:depth>
|
||||
{% set user = lock.users.first %}
|
||||
{% if user is not same as null %}
|
||||
<D:owner><D:href>{{ lock.users.first }}</D:href></D:owner>
|
||||
{% endif %}
|
||||
<D:timeout>{{ timeout }}</D:timeout>
|
||||
<D:locktoken>
|
||||
<D:href>{{ lock.token }}</D:href>
|
||||
</D:locktoken>
|
||||
</D:activelock>
|
||||
</D:lockdiscovery>
|
||||
</D:prop>
|
||||
@@ -5,6 +5,12 @@
|
||||
{% if properties.lastModified or properties.contentLength or properties.resourceType or properties.etag or properties.contentType or properties.creationDate %}
|
||||
<d:propstat>
|
||||
<d:prop>
|
||||
<d:supportedlock>
|
||||
<d:lockentry>
|
||||
<d:lockscope><d:exclusive/></d:lockscope>
|
||||
<d:locktype><d:write/></d:locktype>
|
||||
</d:lockentry>
|
||||
</d:supportedlock>
|
||||
{% if properties.resourceType %}
|
||||
<d:resourcetype/>
|
||||
{% endif %}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\DocStoreBundle\Service\Lock\Exception;
|
||||
|
||||
class NoLockFoundException extends \RuntimeException
|
||||
{
|
||||
public function __construct(?string $message = null, int $code = 0, ?\Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($message ?? 'No lock found', $code, $previous);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\DocStoreBundle\Service\Lock;
|
||||
|
||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||
use Chill\DocStoreBundle\Entity\StoredObjectLock;
|
||||
use Chill\DocStoreBundle\Entity\StoredObjectLockMethodEnum;
|
||||
use Chill\DocStoreBundle\Service\Lock\Exception\NoLockFoundException;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use Symfony\Component\Clock\ClockInterface;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\HttpKernel\Event\TerminateEvent;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
|
||||
class StoredObjectLockManager implements EventSubscriberInterface
|
||||
{
|
||||
private bool $mustFlush = false;
|
||||
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly ClockInterface $clock,
|
||||
) {}
|
||||
|
||||
public function deleteLock(StoredObject $document, \DateTimeImmutable $expiresAt): bool
|
||||
{
|
||||
foreach ($document->getLocks() as $lock) {
|
||||
if ($lock->isActiveAt($this->clock->now())) {
|
||||
$lock->setExpireAt($expiresAt);
|
||||
$this->mustFlush = true;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getLock(StoredObject $document): StoredObjectLock
|
||||
{
|
||||
foreach ($document->getLocks() as $lock) {
|
||||
if ($lock->isActiveAt($this->clock->now())) {
|
||||
return $lock;
|
||||
}
|
||||
}
|
||||
|
||||
throw new NoLockFoundException();
|
||||
}
|
||||
|
||||
public function hasLock(StoredObject $document): bool
|
||||
{
|
||||
foreach ($document->getLocks() as $lock) {
|
||||
if ($lock->isActiveAt($this->clock->now())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function setLock(
|
||||
StoredObject $document,
|
||||
StoredObjectLockMethodEnum $method,
|
||||
?string $lockId = null,
|
||||
?\DateTimeImmutable $expiresAt = null,
|
||||
array $users = [],
|
||||
): StoredObjectLock {
|
||||
if (null === $lockId) {
|
||||
$lockId = 'opaquelocktoken:'.Uuid::uuid4();
|
||||
}
|
||||
|
||||
if (null === $expiresAt) {
|
||||
$expiresAt = $this->clock->now()->add(new \DateInterval('PT60M'));
|
||||
}
|
||||
|
||||
if ($document->isLockedAt($this->clock->now())) {
|
||||
foreach ($document->getLocks() as $lock) {
|
||||
if ($lock->isActiveAt($this->clock->now())) {
|
||||
$lock->setToken($lockId);
|
||||
$lock->setExpireAt($expiresAt);
|
||||
foreach ($users as $user) {
|
||||
$lock->addUser($user);
|
||||
}
|
||||
|
||||
$this->mustFlush = true;
|
||||
|
||||
return $lock;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// there is no lock yet, we must create one
|
||||
$lock = new StoredObjectLock(
|
||||
$document,
|
||||
method: $method,
|
||||
createdAt: $this->clock->now(),
|
||||
token: $lockId,
|
||||
expireAt: $expiresAt,
|
||||
);
|
||||
|
||||
foreach ($users as $user) {
|
||||
$lock->addUser($user);
|
||||
}
|
||||
|
||||
$this->entityManager->persist($lock);
|
||||
$this->mustFlush = true;
|
||||
|
||||
return $lock;
|
||||
}
|
||||
|
||||
public function checkLock(StoredObject $storedObject, string $lockId, ?UserInterface $byUser = null): true|LockTokenCheckResultEnum
|
||||
{
|
||||
if (!$this->hasLock($storedObject)) {
|
||||
return LockTokenCheckResultEnum::NO_LOCK_FOUND;
|
||||
}
|
||||
|
||||
$lock = $this->getLock($storedObject);
|
||||
|
||||
if ($lockId !== $lock->getToken()) {
|
||||
return LockTokenCheckResultEnum::LOCK_TOKEN_DO_NOT_MATCH;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [TerminateEvent::class => 'onKernelTerminate'];
|
||||
}
|
||||
|
||||
public function onKernelTerminate(TerminateEvent $event): void
|
||||
{
|
||||
if ($this->mustFlush) {
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\DocStoreBundle\Tests\Service\Lock;
|
||||
|
||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||
use Chill\DocStoreBundle\Entity\StoredObjectLock;
|
||||
use Chill\DocStoreBundle\Entity\StoredObjectLockMethodEnum;
|
||||
use Chill\DocStoreBundle\Service\Lock\Exception\NoLockFoundException;
|
||||
use Chill\DocStoreBundle\Service\Lock\StoredObjectLockManager;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\PhpUnit\ProphecyTrait;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Symfony\Component\Clock\MockClock;
|
||||
use Symfony\Component\HttpKernel\Event\TerminateEvent;
|
||||
use Symfony\Component\HttpKernel\HttpKernelInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @coversNothing
|
||||
*/
|
||||
class StoredObjectLockManagerTest extends TestCase
|
||||
{
|
||||
use ProphecyTrait;
|
||||
|
||||
private MockClock $clock;
|
||||
|
||||
/**
|
||||
* @var ObjectProphecy<EntityManagerInterface>
|
||||
*/
|
||||
private ObjectProphecy $entityManager;
|
||||
private StoredObjectLockManager $manager;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->clock = new MockClock();
|
||||
$this->entityManager = $this->prophesize(EntityManagerInterface::class);
|
||||
$this->manager = new StoredObjectLockManager(
|
||||
$this->entityManager->reveal(),
|
||||
$this->clock
|
||||
);
|
||||
}
|
||||
|
||||
public function testHasLockNoLock(): void
|
||||
{
|
||||
$document = new StoredObject();
|
||||
$this->assertFalse($this->manager->hasLock($document));
|
||||
}
|
||||
|
||||
public function testSetLockNew(): void
|
||||
{
|
||||
$document = new StoredObject();
|
||||
$method = StoredObjectLockMethodEnum::WEBDAV;
|
||||
$lockId = 'test-lock-id';
|
||||
$expiresAt = $this->clock->now()->add(new \DateInterval('PT30M'));
|
||||
$user = new User();
|
||||
|
||||
$this->entityManager->persist(Argument::type(StoredObjectLock::class))->shouldBeCalled();
|
||||
|
||||
$result = $this->manager->setLock($document, $method, $lockId, $expiresAt, [$user]);
|
||||
|
||||
$this->assertInstanceOf(StoredObjectLock::class, $result);
|
||||
$this->assertTrue($this->manager->hasLock($document));
|
||||
|
||||
$lock = $this->manager->getLock($document);
|
||||
$this->assertSame($document, $lock->getStoredObject());
|
||||
$this->assertSame($method, $lock->getMethod());
|
||||
$this->assertSame($lockId, $lock->getToken());
|
||||
$this->assertSame($expiresAt, $lock->getExpireAt());
|
||||
$this->assertContains($user, $lock->getUsers());
|
||||
}
|
||||
|
||||
public function testSetLockDefaultValues(): void
|
||||
{
|
||||
$document = new StoredObject();
|
||||
$method = StoredObjectLockMethodEnum::WOPI;
|
||||
|
||||
$this->entityManager->persist(Argument::type(StoredObjectLock::class))->shouldBeCalled();
|
||||
|
||||
$result = $this->manager->setLock($document, $method);
|
||||
|
||||
$this->assertInstanceOf(StoredObjectLock::class, $result);
|
||||
$lock = $this->manager->getLock($document);
|
||||
$this->assertNotEmpty($lock->getToken());
|
||||
$this->assertEquals($this->clock->now()->add(new \DateInterval('PT60M')), $lock->getExpireAt());
|
||||
}
|
||||
|
||||
public function testHasLockActive(): void
|
||||
{
|
||||
$document = new StoredObject();
|
||||
new StoredObjectLock($document, StoredObjectLockMethodEnum::WEBDAV, $this->clock->now(), 'token', $this->clock->now()->add(new \DateInterval('PT1M')));
|
||||
|
||||
$this->assertTrue($this->manager->hasLock($document));
|
||||
}
|
||||
|
||||
public function testHasLockExpired(): void
|
||||
{
|
||||
$document = new StoredObject();
|
||||
new StoredObjectLock($document, StoredObjectLockMethodEnum::WEBDAV, $this->clock->now()->sub(new \DateInterval('PT2M')), 'token', $this->clock->now()->sub(new \DateInterval('PT1M')));
|
||||
|
||||
$this->assertFalse($this->manager->hasLock($document));
|
||||
}
|
||||
|
||||
public function testGetLockThrowsExceptionWhenNoLock(): void
|
||||
{
|
||||
$document = new StoredObject();
|
||||
$this->expectException(NoLockFoundException::class);
|
||||
$this->manager->getLock($document);
|
||||
}
|
||||
|
||||
public function testSetLockExistingUpdatesLock(): void
|
||||
{
|
||||
$document = new StoredObject();
|
||||
$initialExpire = $this->clock->now()->add(new \DateInterval('PT10M'));
|
||||
$lock = new StoredObjectLock($document, StoredObjectLockMethodEnum::WEBDAV, $this->clock->now(), 'initial-token', $initialExpire);
|
||||
|
||||
$newLockId = 'new-token';
|
||||
$newExpire = $this->clock->now()->add(new \DateInterval('PT20M'));
|
||||
$user = new User();
|
||||
|
||||
// Should NOT call persist again
|
||||
$this->entityManager->persist(Argument::any())->shouldNotBeCalled();
|
||||
|
||||
$result = $this->manager->setLock($document, StoredObjectLockMethodEnum::WOPI, $newLockId, $newExpire, [$user]);
|
||||
|
||||
$this->assertInstanceOf(StoredObjectLock::class, $result);
|
||||
$this->assertCount(1, $document->getLocks());
|
||||
$this->assertSame($lock, $document->getLocks()->first());
|
||||
$this->assertSame($newLockId, $lock->getToken());
|
||||
$this->assertSame($newExpire, $lock->getExpireAt());
|
||||
$this->assertContains($user, $lock->getUsers());
|
||||
}
|
||||
|
||||
public function testDeleteLock(): void
|
||||
{
|
||||
$document = new StoredObject();
|
||||
$expiresAt = $this->clock->now()->add(new \DateInterval('PT10M'));
|
||||
$lock = new StoredObjectLock($document, StoredObjectLockMethodEnum::WEBDAV, $this->clock->now(), 'token', $expiresAt);
|
||||
|
||||
$this->assertTrue($this->manager->hasLock($document));
|
||||
|
||||
$newExpire = $this->clock->now();
|
||||
$result = $this->manager->deleteLock($document, $newExpire);
|
||||
|
||||
$this->assertTrue($result);
|
||||
$this->assertSame($newExpire, $lock->getExpireAt());
|
||||
// Since isActiveAt uses $at < $expireAt, and we passed $this->clock->now(), it should be inactive
|
||||
$this->assertFalse($this->manager->hasLock($document));
|
||||
}
|
||||
|
||||
public function testOnKernelTerminateFlushesWhenMustFlushIsTrue(): void
|
||||
{
|
||||
$document = new StoredObject();
|
||||
$this->entityManager->persist(Argument::any())->shouldBeCalled();
|
||||
$this->manager->setLock($document, StoredObjectLockMethodEnum::WEBDAV);
|
||||
|
||||
$this->entityManager->flush()->shouldBeCalledOnce();
|
||||
|
||||
$event = new TerminateEvent(
|
||||
$this->prophesize(HttpKernelInterface::class)->reveal(),
|
||||
new Request(),
|
||||
new \Symfony\Component\HttpFoundation\Response()
|
||||
);
|
||||
|
||||
$this->manager->onKernelTerminate($event);
|
||||
}
|
||||
|
||||
public function testOnKernelTerminateDoesNotFlushWhenMustFlushIsFalse(): void
|
||||
{
|
||||
$this->entityManager->flush()->shouldNotBeCalled();
|
||||
|
||||
$event = new TerminateEvent(
|
||||
$this->prophesize(HttpKernelInterface::class)->reveal(),
|
||||
new Request(),
|
||||
new \Symfony\Component\HttpFoundation\Response()
|
||||
);
|
||||
|
||||
$this->manager->onKernelTerminate($event);
|
||||
}
|
||||
|
||||
public function testGetSubscribedEvents(): void
|
||||
{
|
||||
$events = StoredObjectLockManager::getSubscribedEvents();
|
||||
$this->assertArrayHasKey(TerminateEvent::class, $events);
|
||||
$this->assertSame('onKernelTerminate', $events[TerminateEvent::class]);
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,8 @@ services:
|
||||
|
||||
Chill\DocStoreBundle\Service\:
|
||||
resource: '../Service/'
|
||||
exclude:
|
||||
'../Service/Lock/Exception/*'
|
||||
|
||||
Chill\DocStoreBundle\GenericDoc\Manager:
|
||||
arguments:
|
||||
@@ -63,3 +65,10 @@ services:
|
||||
|
||||
Chill\DocStoreBundle\AsyncUpload\Templating\:
|
||||
resource: '../AsyncUpload/Templating/'
|
||||
|
||||
Chill\DocStoreBundle\Dav\:
|
||||
resource: '../Dav/'
|
||||
exclude:
|
||||
- '../Dav/Exception/*'
|
||||
- '../Dav/Request/*'
|
||||
- '../Dav/Response/*'
|
||||
|
||||
Reference in New Issue
Block a user