add events and command to retrieve them using caldav calendars
This commit is contained in:
parent
4d013a4550
commit
3d8cacd62c
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -3,7 +3,7 @@
|
||||||
/.env.local
|
/.env.local
|
||||||
/.env.local.php
|
/.env.local.php
|
||||||
/.env.*.local
|
/.env.*.local
|
||||||
/config/secrets/prod/prod.decrypt.private.php
|
/config/secrets/*
|
||||||
/public/bundles/
|
/public/bundles/
|
||||||
/public/js/
|
/public/js/
|
||||||
/src/Command/TestCommand.php
|
/src/Command/TestCommand.php
|
||||||
|
|
11
README.md
11
README.md
|
@ -14,3 +14,14 @@ Murph is an **open-source CMF** built on top of Symfony that helps you to **buil
|
||||||
**End users will be fond of the interface and the powerful tools 💜**
|
**End users will be fond of the interface and the powerful tools 💜**
|
||||||
|
|
||||||
📗 [Read the documentation](https://doc.murph-project.org/)
|
📗 [Read the documentation](https://doc.murph-project.org/)
|
||||||
|
|
||||||
|
## Suivi
|
||||||
|
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
```
|
||||||
|
$ mkdir config/secrets
|
||||||
|
$ openssl genrsa -out config/secrets/prod.private.key 4096
|
||||||
|
$ openssl rsa -in config/secrets/prod.private.key -out config/secrets/prod.public.key -pubout -outform PEM
|
||||||
|
```
|
||||||
|
|
|
@ -7,7 +7,8 @@
|
||||||
"prefer-stable": true,
|
"prefer-stable": true,
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=8.0.0",
|
"php": ">=8.0.0",
|
||||||
"murph/murph-core": "dev-master"
|
"murph/murph-core": "dev-master",
|
||||||
|
"sabre/dav": "^4.3"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"symfony/browser-kit": "^5.4",
|
"symfony/browser-kit": "^5.4",
|
||||||
|
|
|
@ -58,8 +58,12 @@ services:
|
||||||
calls:
|
calls:
|
||||||
- [ setAnnotationReader, [ "@annotation_reader" ] ]
|
- [ setAnnotationReader, [ "@annotation_reader" ] ]
|
||||||
|
|
||||||
App\UrlGenerator\FooUrlGenerator:
|
App\Security\OpenSSL:
|
||||||
public: true
|
public: true
|
||||||
|
arguments:
|
||||||
|
$publicKeyPath: '%kernel.project_dir%/config/secrets/prod.public.key'
|
||||||
|
$privateKeyPath: '%kernel.project_dir%/config/secrets/prod.private.key'
|
||||||
|
$filesystem: '@filesystem'
|
||||||
|
|
||||||
# add more service definitions when explicit configuration is needed
|
# add more service definitions when explicit configuration is needed
|
||||||
# please note that last definitions always *replace* previous ones
|
# please note that last definitions always *replace* previous ones
|
||||||
|
|
119
src/Command/CaldavSyncCommand.php
Normal file
119
src/Command/CaldavSyncCommand.php
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Command;
|
||||||
|
|
||||||
|
use App\Entity\Project;
|
||||||
|
use App\Repository\ProjectRepositoryQuery;
|
||||||
|
use App\Repository\SpeakerRepositoryQuery;
|
||||||
|
use App\Security\OpenSSL;
|
||||||
|
use App\Webdav\CaldavClient;
|
||||||
|
use App\Webdav\Event as CalEvent;
|
||||||
|
use Symfony\Component\Console\Attribute\AsCommand;
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
|
use App\Core\Manager\EntityManager;
|
||||||
|
use App\Repository\EventRepositoryQuery;
|
||||||
|
use App\Factory\EventFactory;
|
||||||
|
use App\Entity\Speaker;
|
||||||
|
|
||||||
|
#[AsCommand(
|
||||||
|
name: 'caldav:sync',
|
||||||
|
description: 'Sync caldav events',
|
||||||
|
)]
|
||||||
|
class CaldavSyncCommand extends Command
|
||||||
|
{
|
||||||
|
protected SpeakerRepositoryQuery $speakerQuery;
|
||||||
|
protected ProjectRepositoryQuery $projectQuery;
|
||||||
|
protected EventRepositoryQuery $eventQuery;
|
||||||
|
protected EventFactory $eventFactory;
|
||||||
|
protected OpenSSL $openSSL;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
SpeakerRepositoryQuery $speakerQuery,
|
||||||
|
ProjectRepositoryQuery $projectQuery,
|
||||||
|
EventRepositoryQuery $eventQuery,
|
||||||
|
EventFactory $eventFactory,
|
||||||
|
EntityManager $manager,
|
||||||
|
OpenSSL $openSSL
|
||||||
|
) {
|
||||||
|
parent::__construct();
|
||||||
|
|
||||||
|
$this->speakerQuery = $speakerQuery;
|
||||||
|
$this->projectQuery = $projectQuery;
|
||||||
|
$this->eventQuery = $eventQuery;
|
||||||
|
$this->eventFactory = $eventFactory;
|
||||||
|
$this->manager = $manager;
|
||||||
|
$this->openSSL = $openSSL;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getSpeakers(): array
|
||||||
|
{
|
||||||
|
$speakers = $this->speakerQuery->withCalendar()->find();
|
||||||
|
|
||||||
|
foreach ($speakers as $speaker) {
|
||||||
|
$this->openSSL->decryptEntity($speaker);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $speakers;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getProjects(): array
|
||||||
|
{
|
||||||
|
return $this->projectQuery->find();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||||
|
{
|
||||||
|
$io = new SymfonyStyle($input, $output);
|
||||||
|
$speakers = $this->getSpeakers();
|
||||||
|
$projects = $this->getProjects();
|
||||||
|
|
||||||
|
foreach ($speakers as $speaker) {
|
||||||
|
$client = new CaldavClient(
|
||||||
|
$speaker->getCaldavHost(),
|
||||||
|
$speaker->getCaldavUsername(),
|
||||||
|
$speaker->getCaldavPassword()
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->openSSL->encryptEntity($speaker);
|
||||||
|
|
||||||
|
$calEvents = $client->getEvents($speaker->getCaldavCalendarUri());
|
||||||
|
|
||||||
|
foreach ($projects as $project) {
|
||||||
|
foreach ($calEvents as $calEvent) {
|
||||||
|
$regex = sprintf('/%s/', preg_quote($project->getCaldavEventTag()));
|
||||||
|
|
||||||
|
if (!preg_match($regex, $calEvent->getDescription())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->addEvent($project, $calEvent, $speaker);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function addEvent(Project $project, CalEvent $calEvent, Speaker $speaker)
|
||||||
|
{
|
||||||
|
$event = $this->eventQuery->create()
|
||||||
|
->where('.uid = :uid')
|
||||||
|
->setParameter('uid', $calEvent->getUid())
|
||||||
|
->findOne();
|
||||||
|
|
||||||
|
if (!$event) {
|
||||||
|
$event = $this->eventFactory->createFromEvent($calEvent);
|
||||||
|
} else {
|
||||||
|
$this->eventFactory->updateFromEvent($event, $calEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
$project->addEvent($event);
|
||||||
|
$speaker->addEvent($event);
|
||||||
|
|
||||||
|
$this->manager->update($event);
|
||||||
|
$this->manager->update($speaker);
|
||||||
|
}
|
||||||
|
}
|
|
@ -109,17 +109,21 @@ class ProjectAdminController extends CrudController
|
||||||
// ->setSortableCollectionProperty('sortOrder')
|
// ->setSortableCollectionProperty('sortOrder')
|
||||||
|
|
||||||
// ->setAction('index', 'new', true)
|
// ->setAction('index', 'new', true)
|
||||||
->setAction('index', 'show', false)
|
// ->setAction('index', 'show', false)
|
||||||
// ->setAction('index', 'edit', true)
|
// ->setAction('index', 'edit', true)
|
||||||
// ->setAction('index', 'delete', true)
|
// ->setAction('index', 'delete', true)
|
||||||
|
|
||||||
// ->setAction('edit', 'back', true)
|
// ->setAction('edit', 'back', true)
|
||||||
->setAction('edit', 'show', false)
|
// ->setAction('edit', 'show', false)
|
||||||
// ->setAction('edit', 'delete', true)
|
// ->setAction('edit', 'delete', true)
|
||||||
|
|
||||||
// ->setAction('show', 'back', true)
|
// ->setAction('show', 'back', true)
|
||||||
// ->setAction('show', 'edit', true)
|
// ->setAction('show', 'edit', true)
|
||||||
|
|
||||||
|
->setView('show', 'admin/project/show.html.twig')
|
||||||
|
->setView('show_entity', 'admin/project/_show.html.twig')
|
||||||
|
->setView('edit', 'admin/project/edit.html.twig')
|
||||||
|
|
||||||
->setField('index', 'Label', Field\TextField::class, [
|
->setField('index', 'Label', Field\TextField::class, [
|
||||||
'property' => 'label',
|
'property' => 'label',
|
||||||
'sort' => ['label', '.label'],
|
'sort' => ['label', '.label'],
|
||||||
|
|
|
@ -11,6 +11,7 @@ use App\Entity\Speaker as Entity;
|
||||||
use App\Factory\SpeakerFactory as Factory;
|
use App\Factory\SpeakerFactory as Factory;
|
||||||
use App\Form\SpeakerType as Type;
|
use App\Form\SpeakerType as Type;
|
||||||
use App\Repository\SpeakerRepositoryQuery as RepositoryQuery;
|
use App\Repository\SpeakerRepositoryQuery as RepositoryQuery;
|
||||||
|
use App\Security\OpenSSL;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\HttpFoundation\Session\Session;
|
use Symfony\Component\HttpFoundation\Session\Session;
|
||||||
|
@ -29,9 +30,11 @@ class SpeakerAdminController extends CrudController
|
||||||
/**
|
/**
|
||||||
* @Route("/admin/speaker/new", name="admin_speaker_new", methods={"GET", "POST"})
|
* @Route("/admin/speaker/new", name="admin_speaker_new", methods={"GET", "POST"})
|
||||||
*/
|
*/
|
||||||
public function new(Factory $factory, EntityManager $entityManager, Request $request): Response
|
public function new(Factory $factory, EntityManager $entityManager, Request $request, OpenSSL $openSSL): Response
|
||||||
{
|
{
|
||||||
return $this->doNew($factory->create(), $entityManager, $request);
|
return $this->doNew($factory->create(), $entityManager, $request, function (Entity $entity) use ($openSSL) {
|
||||||
|
$openSSL->encryptEntity($entity);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -53,9 +56,18 @@ class SpeakerAdminController extends CrudController
|
||||||
/**
|
/**
|
||||||
* @Route("/admin/speaker/edit/{entity}", name="admin_speaker_edit", methods={"GET", "POST"})
|
* @Route("/admin/speaker/edit/{entity}", name="admin_speaker_edit", methods={"GET", "POST"})
|
||||||
*/
|
*/
|
||||||
public function edit(Entity $entity, EntityManager $entityManager, Request $request): Response
|
public function edit(Entity $entity, EntityManager $entityManager, Request $request, OpenSSL $openSSL): Response
|
||||||
{
|
{
|
||||||
return $this->doEdit($entity, $entityManager, $request);
|
$openSSL->decryptEntity($entity);
|
||||||
|
$caldavPassword = $entity->getCaldavPassword();
|
||||||
|
|
||||||
|
return $this->doEdit($entity, $entityManager, $request, function (Entity $entity) use ($openSSL, $caldavPassword) {
|
||||||
|
if (!$entity->getCaldavPassword()) {
|
||||||
|
$entity->setCaldavPassword($caldavPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
$openSSL->encryptEntity($entity);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -122,12 +134,19 @@ class SpeakerAdminController extends CrudController
|
||||||
|
|
||||||
->setDefaultSort('index', 'name')
|
->setDefaultSort('index', 'name')
|
||||||
|
|
||||||
|
->setFormOptions('new', [
|
||||||
|
'edit_caldav' => $this->isGranted('ROLE_ADMIN'),
|
||||||
|
])
|
||||||
|
->setFormOptions('edit', [
|
||||||
|
'edit_caldav' => $this->isGranted('ROLE_ADMIN'),
|
||||||
|
])
|
||||||
|
|
||||||
->setField('index', 'Name', Field\TextField::class, [
|
->setField('index', 'Name', Field\TextField::class, [
|
||||||
'property' => 'name',
|
'property' => 'name',
|
||||||
'sort' => ['name', '.name'],
|
'sort' => ['name', '.name'],
|
||||||
])
|
])
|
||||||
|
|
||||||
->setBatchAction('index', 'delete', 'Delete', function(EntityInterface $entity, EntityManager $manager) {
|
->setBatchAction('index', 'delete', 'Delete', function (EntityInterface $entity, EntityManager $manager) {
|
||||||
$manager->delete($entity);
|
$manager->delete($entity);
|
||||||
})
|
})
|
||||||
;
|
;
|
||||||
|
|
|
@ -29,7 +29,7 @@ class Conference implements EntityInterface
|
||||||
protected $persons;
|
protected $persons;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\ManyToOne(targetEntity=ThemeType::class, inversedBy="conferences")
|
* @ORM\ManyToOne(targetEntity=ThemeType::class, inversedBy="conferences", cascade={"persist", "remove"})
|
||||||
* @ORM\JoinColumn(nullable=false)
|
* @ORM\JoinColumn(nullable=false)
|
||||||
*/
|
*/
|
||||||
protected $themeType;
|
protected $themeType;
|
||||||
|
@ -52,7 +52,7 @@ class Conference implements EntityInterface
|
||||||
/**
|
/**
|
||||||
* @ORM\Column(type="date", nullable=true)
|
* @ORM\Column(type="date", nullable=true)
|
||||||
*/
|
*/
|
||||||
private $date;
|
protected $date;
|
||||||
|
|
||||||
public function getId(): ?int
|
public function getId(): ?int
|
||||||
{
|
{
|
||||||
|
|
169
src/Entity/Event.php
Normal file
169
src/Entity/Event.php
Normal file
|
@ -0,0 +1,169 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use App\Core\Entity\EntityInterface;
|
||||||
|
use App\Repository\EventRepository;
|
||||||
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
|
use Doctrine\Common\Collections\Collection;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Entity(repositoryClass=EventRepository::class)
|
||||||
|
*/
|
||||||
|
class Event implements EntityInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @ORM\Id
|
||||||
|
* @ORM\GeneratedValue
|
||||||
|
* @ORM\Column(type="integer")
|
||||||
|
*/
|
||||||
|
protected $id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="string", length=40)
|
||||||
|
*/
|
||||||
|
protected $uid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="string", length=255, nullable=true)
|
||||||
|
*/
|
||||||
|
protected $summary;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="text", nullable=true)
|
||||||
|
*/
|
||||||
|
protected $description;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="datetime", nullable=true)
|
||||||
|
*/
|
||||||
|
protected $startAt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="datetime", nullable=true)
|
||||||
|
*/
|
||||||
|
protected $finishAt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\ManyToOne(targetEntity=Project::class, inversedBy="events", cascade={"persist", "remove"})
|
||||||
|
* @ORM\JoinColumn(nullable=false)
|
||||||
|
*/
|
||||||
|
protected $projects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\ManyToMany(targetEntity=Speaker::class, inversedBy="events")
|
||||||
|
*/
|
||||||
|
private $speakers;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->speakers = new ArrayCollection();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getId(): ?int
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUid(): ?string
|
||||||
|
{
|
||||||
|
return $this->uid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setUid(string $uid): self
|
||||||
|
{
|
||||||
|
$this->uid = $uid;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSummary(): ?string
|
||||||
|
{
|
||||||
|
return $this->summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setSummary(?string $summary): self
|
||||||
|
{
|
||||||
|
$this->summary = $summary;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCleanedDescription(): string
|
||||||
|
{
|
||||||
|
return trim(preg_replace('/\s*#\{project:\d+\}\s*/', "\n", $this->getDescription()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDescription(): ?string
|
||||||
|
{
|
||||||
|
return $this->description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setDescription(?string $description): self
|
||||||
|
{
|
||||||
|
$this->description = $description;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getStartAt(): ?\DateTimeInterface
|
||||||
|
{
|
||||||
|
return $this->startAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setStartAt(?\DateTimeInterface $startAt): self
|
||||||
|
{
|
||||||
|
$this->startAt = $startAt;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFinishAt(): ?\DateTimeInterface
|
||||||
|
{
|
||||||
|
return $this->finishAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setFinishAt(?\DateTimeInterface $finishAt): self
|
||||||
|
{
|
||||||
|
$this->finishAt = $finishAt;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getProjects(): ?Project
|
||||||
|
{
|
||||||
|
return $this->projects;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setProjects(?Project $projects): self
|
||||||
|
{
|
||||||
|
$this->projects = $projects;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection<int, Speaker>
|
||||||
|
*/
|
||||||
|
public function getSpeakers(): Collection
|
||||||
|
{
|
||||||
|
return $this->speakers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addSpeaker(Speaker $speaker): self
|
||||||
|
{
|
||||||
|
if (!$this->speakers->contains($speaker)) {
|
||||||
|
$this->speakers[] = $speaker;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeSpeaker(Speaker $speaker): self
|
||||||
|
{
|
||||||
|
$this->speakers->removeElement($speaker);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
|
@ -50,10 +50,16 @@ class Project implements EntityInterface
|
||||||
*/
|
*/
|
||||||
protected $debriefings;
|
protected $debriefings;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\OneToMany(targetEntity=Event::class, mappedBy="projects", cascade={"persist", "remove"})
|
||||||
|
*/
|
||||||
|
protected $events;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->establishments = new ArrayCollection();
|
$this->establishments = new ArrayCollection();
|
||||||
$this->debriefings = new ArrayCollection();
|
$this->debriefings = new ArrayCollection();
|
||||||
|
$this->events = new ArrayCollection();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __toString()
|
public function __toString()
|
||||||
|
@ -128,7 +134,7 @@ class Project implements EntityInterface
|
||||||
|
|
||||||
public function getFiles(): ?array
|
public function getFiles(): ?array
|
||||||
{
|
{
|
||||||
usort($this->files, function($a, $b) {
|
usort($this->files, function ($a, $b) {
|
||||||
return $a['position'] <=> $b['position'];
|
return $a['position'] <=> $b['position'];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -168,4 +174,45 @@ class Project implements EntityInterface
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getCaldavEventTag(): string
|
||||||
|
{
|
||||||
|
return sprintf('#{project:%d}', $this->getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection<int, Event>
|
||||||
|
*/
|
||||||
|
public function getEvents(): Collection
|
||||||
|
{
|
||||||
|
$events = $this->events->toArray();
|
||||||
|
|
||||||
|
usort($events, function($a, $b) {
|
||||||
|
return $a->getStartAt() <=> $a->getFinishAt();
|
||||||
|
});
|
||||||
|
|
||||||
|
return new ArrayCollection($events);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addEvent(Event $event): self
|
||||||
|
{
|
||||||
|
if (!$this->events->contains($event)) {
|
||||||
|
$this->events[] = $event;
|
||||||
|
$event->setProjects($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeEvent(Event $event): self
|
||||||
|
{
|
||||||
|
if ($this->events->removeElement($event)) {
|
||||||
|
// set the owning side to null (unless already changed)
|
||||||
|
if ($event->getProjects() === $this) {
|
||||||
|
$event->setProjects(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,11 +7,12 @@ use App\Repository\SpeakerRepository;
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use App\Security\EncryptedEntityInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\Entity(repositoryClass=SpeakerRepository::class)
|
* @ORM\Entity(repositoryClass=SpeakerRepository::class)
|
||||||
*/
|
*/
|
||||||
class Speaker implements EntityInterface
|
class Speaker implements EntityInterface, EncryptedEntityInterface
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @ORM\Id
|
* @ORM\Id
|
||||||
|
@ -30,9 +31,35 @@ class Speaker implements EntityInterface
|
||||||
*/
|
*/
|
||||||
protected $interventions;
|
protected $interventions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="string", length=255, nullable=true)
|
||||||
|
*/
|
||||||
|
protected $caldavHost;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="string", length=255, nullable=true)
|
||||||
|
*/
|
||||||
|
protected $caldavUsername;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="blob", nullable=true)
|
||||||
|
*/
|
||||||
|
protected $caldavPassword;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="string", length=255, nullable=true)
|
||||||
|
*/
|
||||||
|
protected $caldavCalendarUri;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\ManyToMany(targetEntity=Event::class, mappedBy="speakers")
|
||||||
|
*/
|
||||||
|
private $events;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->interventions = new ArrayCollection();
|
$this->interventions = new ArrayCollection();
|
||||||
|
$this->events = new ArrayCollection();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __toString()
|
public function __toString()
|
||||||
|
@ -83,4 +110,86 @@ class Speaker implements EntityInterface
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getCaldavHost(): ?string
|
||||||
|
{
|
||||||
|
return $this->caldavHost;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCaldavHost(?string $caldavHost): self
|
||||||
|
{
|
||||||
|
$this->caldavHost = $caldavHost;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCaldavUsername(): ?string
|
||||||
|
{
|
||||||
|
return $this->caldavUsername;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCaldavUsername(?string $caldavUsername): self
|
||||||
|
{
|
||||||
|
$this->caldavUsername = $caldavUsername;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCaldavPassword()
|
||||||
|
{
|
||||||
|
return $this->caldavPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCaldavPassword(?string $caldavPassword): self
|
||||||
|
{
|
||||||
|
$this->caldavPassword = $caldavPassword;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCaldavCalendarUri(): ?string
|
||||||
|
{
|
||||||
|
return $this->caldavCalendarUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCaldavCalendarUri(?string $caldavCalendarUri): self
|
||||||
|
{
|
||||||
|
$this->caldavCalendarUri = $caldavCalendarUri;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEncryptedProperties(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'caldavPassword',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection<int, Event>
|
||||||
|
*/
|
||||||
|
public function getEvents(): Collection
|
||||||
|
{
|
||||||
|
return $this->events;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addEvent(Event $event): self
|
||||||
|
{
|
||||||
|
if (!$this->events->contains($event)) {
|
||||||
|
$this->events[] = $event;
|
||||||
|
$event->addSpeaker($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeEvent(Event $event): self
|
||||||
|
{
|
||||||
|
if ($this->events->removeElement($event)) {
|
||||||
|
$event->removeSpeaker($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
42
src/Factory/EventFactory.php
Normal file
42
src/Factory/EventFactory.php
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Factory;
|
||||||
|
|
||||||
|
use App\Core\Factory\FactoryInterface;
|
||||||
|
use App\Entity\Event as Entity;
|
||||||
|
use App\Webdav\Event as CalEvent;
|
||||||
|
|
||||||
|
class EventFactory implements FactoryInterface
|
||||||
|
{
|
||||||
|
public function create(): Entity
|
||||||
|
{
|
||||||
|
return new Entity();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createFromEvent(CalEvent $event): Entity
|
||||||
|
{
|
||||||
|
$entity = new Entity();
|
||||||
|
$entity
|
||||||
|
->setUid($event->getUid())
|
||||||
|
->setSummary($event->getSummary())
|
||||||
|
->setDescription($event->getDescription())
|
||||||
|
->setStartAt($event->getStartAt())
|
||||||
|
->setFinishAt($event->getFinishAt())
|
||||||
|
;
|
||||||
|
|
||||||
|
return $entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateFromEvent(Entity $entity, CalEvent $event): Entity
|
||||||
|
{
|
||||||
|
$entity
|
||||||
|
->setUid($event->getUid())
|
||||||
|
->setSummary($event->getSummary())
|
||||||
|
->setDescription($event->getDescription())
|
||||||
|
->setStartAt($event->getStartAt())
|
||||||
|
->setFinishAt($event->getFinishAt())
|
||||||
|
;
|
||||||
|
|
||||||
|
return $entity;
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ use App\Entity\Speaker;
|
||||||
use Symfony\Component\Form\AbstractType;
|
use Symfony\Component\Form\AbstractType;
|
||||||
use Symfony\Component\Form\FormBuilderInterface;
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
|
||||||
|
|
||||||
class SpeakerType extends AbstractType
|
class SpeakerType extends AbstractType
|
||||||
{
|
{
|
||||||
|
@ -14,12 +15,28 @@ class SpeakerType extends AbstractType
|
||||||
$builder
|
$builder
|
||||||
->add('name')
|
->add('name')
|
||||||
;
|
;
|
||||||
|
|
||||||
|
if ($options['edit_caldav']) {
|
||||||
|
$builder
|
||||||
|
->add('caldavHost', null, [
|
||||||
|
'help' => 'https://exemple.fr/remote.php/dav'
|
||||||
|
])
|
||||||
|
->add('caldavUsername')
|
||||||
|
->add('caldavPassword', PasswordType::class, [
|
||||||
|
'required' => false,
|
||||||
|
])
|
||||||
|
->add('caldavCalendarUri', null, [
|
||||||
|
'help' => '/remote.php/dav/calendars/johnDoe/myCalendar/'
|
||||||
|
])
|
||||||
|
;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function configureOptions(OptionsResolver $resolver): void
|
public function configureOptions(OptionsResolver $resolver): void
|
||||||
{
|
{
|
||||||
$resolver->setDefaults([
|
$resolver->setDefaults([
|
||||||
'data_class' => Speaker::class,
|
'data_class' => Speaker::class,
|
||||||
|
'edit_caldav' => false,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,27 @@
|
||||||
namespace App\Form;
|
namespace App\Form;
|
||||||
|
|
||||||
use App\Core\Form\UserType as BaseUserType;
|
use App\Core\Form\UserType as BaseUserType;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||||
|
|
||||||
class UserType extends BaseUserType
|
class UserType extends BaseUserType
|
||||||
{
|
{
|
||||||
|
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||||
|
{
|
||||||
|
parent::buildForm($builder, $options);
|
||||||
|
|
||||||
|
$builder->add(
|
||||||
|
'isManager',
|
||||||
|
CheckboxType::class,
|
||||||
|
[
|
||||||
|
'label' => 'Coordinateur⋅trice',
|
||||||
|
'required' => false,
|
||||||
|
'attr' => [
|
||||||
|
],
|
||||||
|
'constraints' => [
|
||||||
|
],
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
76
src/Repository/EventRepository.php
Normal file
76
src/Repository/EventRepository.php
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Repository;
|
||||||
|
|
||||||
|
use App\Entity\Event;
|
||||||
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
|
use Doctrine\ORM\OptimisticLockException;
|
||||||
|
use Doctrine\ORM\ORMException;
|
||||||
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @method Event|null find($id, $lockMode = null, $lockVersion = null)
|
||||||
|
* @method Event|null findOneBy(array $criteria, array $orderBy = null)
|
||||||
|
* @method Event[] findAll()
|
||||||
|
* @method Event[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
|
||||||
|
*/
|
||||||
|
class EventRepository extends ServiceEntityRepository
|
||||||
|
{
|
||||||
|
public function __construct(ManagerRegistry $registry)
|
||||||
|
{
|
||||||
|
parent::__construct($registry, Event::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws ORMException
|
||||||
|
* @throws OptimisticLockException
|
||||||
|
*/
|
||||||
|
public function add(Event $entity, bool $flush = true): void
|
||||||
|
{
|
||||||
|
$this->_em->persist($entity);
|
||||||
|
if ($flush) {
|
||||||
|
$this->_em->flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws ORMException
|
||||||
|
* @throws OptimisticLockException
|
||||||
|
*/
|
||||||
|
public function remove(Event $entity, bool $flush = true): void
|
||||||
|
{
|
||||||
|
$this->_em->remove($entity);
|
||||||
|
if ($flush) {
|
||||||
|
$this->_em->flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * @return Event[] Returns an array of Event objects
|
||||||
|
// */
|
||||||
|
/*
|
||||||
|
public function findByExampleField($value)
|
||||||
|
{
|
||||||
|
return $this->createQueryBuilder('e')
|
||||||
|
->andWhere('e.exampleField = :val')
|
||||||
|
->setParameter('val', $value)
|
||||||
|
->orderBy('e.id', 'ASC')
|
||||||
|
->setMaxResults(10)
|
||||||
|
->getQuery()
|
||||||
|
->getResult()
|
||||||
|
;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
public function findOneBySomeField($value): ?Event
|
||||||
|
{
|
||||||
|
return $this->createQueryBuilder('e')
|
||||||
|
->andWhere('e.exampleField = :val')
|
||||||
|
->setParameter('val', $value)
|
||||||
|
->getQuery()
|
||||||
|
->getOneOrNullResult()
|
||||||
|
;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
15
src/Repository/EventRepositoryQuery.php
Normal file
15
src/Repository/EventRepositoryQuery.php
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Repository;
|
||||||
|
|
||||||
|
use App\Core\Repository\RepositoryQuery;
|
||||||
|
use Knp\Component\Pager\PaginatorInterface;
|
||||||
|
use App\Repository\EventRepository as Repository;
|
||||||
|
|
||||||
|
class EventRepositoryQuery extends RepositoryQuery
|
||||||
|
{
|
||||||
|
public function __construct(Repository $repository, PaginatorInterface $paginator)
|
||||||
|
{
|
||||||
|
parent::__construct($repository, 'e', $paginator);
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,8 +3,8 @@
|
||||||
namespace App\Repository;
|
namespace App\Repository;
|
||||||
|
|
||||||
use App\Core\Repository\RepositoryQuery;
|
use App\Core\Repository\RepositoryQuery;
|
||||||
use Knp\Component\Pager\PaginatorInterface;
|
|
||||||
use App\Repository\SpeakerRepository as Repository;
|
use App\Repository\SpeakerRepository as Repository;
|
||||||
|
use Knp\Component\Pager\PaginatorInterface;
|
||||||
|
|
||||||
class SpeakerRepositoryQuery extends RepositoryQuery
|
class SpeakerRepositoryQuery extends RepositoryQuery
|
||||||
{
|
{
|
||||||
|
@ -12,4 +12,14 @@ class SpeakerRepositoryQuery extends RepositoryQuery
|
||||||
{
|
{
|
||||||
parent::__construct($repository, 's', $paginator);
|
parent::__construct($repository, 's', $paginator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function withCalendar()
|
||||||
|
{
|
||||||
|
return $this
|
||||||
|
->andWhere('.caldavHost IS NOT NULL')
|
||||||
|
->andWhere('.caldavUsername IS NOT NULL')
|
||||||
|
->andWhere('.caldavPassword IS NOT NULL')
|
||||||
|
->andWhere('.caldavCalendarUri IS NOT NULL')
|
||||||
|
;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
17
src/Resources/services.xml
Normal file
17
src/Resources/services.xml
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<?xml version="1.0" ?>
|
||||||
|
<container xmlns="http://symfony.com/schema/dic/services"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
|
||||||
|
|
||||||
|
<parameters>
|
||||||
|
<parameter key="app.security.openssl.class">Zen\Bundle\AppBundle\Security\OpenSSL</parameter>
|
||||||
|
</parameters>
|
||||||
|
|
||||||
|
<service id="app.security.openssl" class="%app.security.openssl.class%" public="true">
|
||||||
|
<argument>file://%kernel.project_dir%/app/ssl/public.key</argument>
|
||||||
|
<argument>file://%kernel.project_dir%/app/ssl/private.key</argument>
|
||||||
|
<argument type="service" id="filesystem" />
|
||||||
|
</service>
|
||||||
|
<service id="%app.security.openssl.class%" alias="app.security.openssl" public="true"></service>
|
||||||
|
</services>
|
||||||
|
</container>
|
13
src/Security/EncryptedEntityInterface.php
Normal file
13
src/Security/EncryptedEntityInterface.php
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Security;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface EncryptedEntityInterface.
|
||||||
|
*
|
||||||
|
* @author Simon Vieille <simon@deblan.fr>
|
||||||
|
*/
|
||||||
|
interface EncryptedEntityInterface
|
||||||
|
{
|
||||||
|
public function getEncryptedProperties(): array;
|
||||||
|
}
|
86
src/Security/OpenSSL.php
Normal file
86
src/Security/OpenSSL.php
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Security;
|
||||||
|
|
||||||
|
use Symfony\Component\Filesystem\Filesystem;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* class OpenSSL.
|
||||||
|
*
|
||||||
|
* @author Simon Vieille <simon@deblan.fr>
|
||||||
|
*/
|
||||||
|
class OpenSSL
|
||||||
|
{
|
||||||
|
protected ?\OpenSSLAsymmetricKey $publicKey;
|
||||||
|
protected ?\OpenSSLAsymmetricKey $privateKey;
|
||||||
|
|
||||||
|
public function __construct(string $publicKeyPath, string $privateKeyPath, Filesystem $filesystem)
|
||||||
|
{
|
||||||
|
if ($filesystem->exists($publicKeyPath)) {
|
||||||
|
$this->publicKey = openssl_pkey_get_public(file_get_contents($publicKeyPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($filesystem->exists($privateKeyPath)) {
|
||||||
|
$this->privateKey = openssl_pkey_get_private(file_get_contents($privateKeyPath));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypts data by using the public key.
|
||||||
|
*/
|
||||||
|
public function encrypt($data): ?string
|
||||||
|
{
|
||||||
|
if (empty($this->publicKey)) {
|
||||||
|
throw new \RuntimeException('Public key needed.');
|
||||||
|
}
|
||||||
|
|
||||||
|
openssl_public_encrypt($data, $result, $this->publicKey);
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypts data by using the private key.
|
||||||
|
*/
|
||||||
|
public function decrypt($data): ?string
|
||||||
|
{
|
||||||
|
if (empty($this->privateKey)) {
|
||||||
|
throw new \RuntimeException('Private key needed.');
|
||||||
|
}
|
||||||
|
|
||||||
|
openssl_private_decrypt($data, $result, $this->privateKey);
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function decryptEntity(EncryptedEntityInterface $entity): EncryptedEntityInterface
|
||||||
|
{
|
||||||
|
foreach ($entity->getEncryptedProperties() as $property) {
|
||||||
|
$getter = 'get'.$property;
|
||||||
|
$setter = 'set'.$property;
|
||||||
|
$encryptedValue = $entity->{$getter}();
|
||||||
|
|
||||||
|
if (!is_resource($encryptedValue)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$encryptedValue = stream_get_contents($encryptedValue, -1, 0);
|
||||||
|
|
||||||
|
$entity->{$setter}($this->decrypt($encryptedValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function encryptEntity(EncryptedEntityInterface $entity): EncryptedEntityInterface
|
||||||
|
{
|
||||||
|
foreach ($entity->getEncryptedProperties() as $property) {
|
||||||
|
$getter = 'get'.$property;
|
||||||
|
$setter = 'set'.$property;
|
||||||
|
$value = (string) $entity->{$getter}();
|
||||||
|
$entity->{$setter}($this->encrypt($value));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $entity;
|
||||||
|
}
|
||||||
|
}
|
123
src/Webdav/CaldavClient.php
Normal file
123
src/Webdav/CaldavClient.php
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Webdav;
|
||||||
|
|
||||||
|
use Sabre\DAV\Client;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* class CaldavClient.
|
||||||
|
*
|
||||||
|
* @author Simon Vieille <simon@deblan.fr>
|
||||||
|
*/
|
||||||
|
class CaldavClient
|
||||||
|
{
|
||||||
|
protected string $host;
|
||||||
|
protected string $username;
|
||||||
|
protected string $password;
|
||||||
|
protected ?Client $client = null;
|
||||||
|
|
||||||
|
public function __construct(string $host, string $username, string $password)
|
||||||
|
{
|
||||||
|
$this->host = $host;
|
||||||
|
$this->username = $username;
|
||||||
|
$this->password = $password;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getClient(): Client
|
||||||
|
{
|
||||||
|
if (null === $this->client) {
|
||||||
|
$this->client = new Client([
|
||||||
|
'baseUri' => $this->host,
|
||||||
|
'userName' => $this->username,
|
||||||
|
'password' => $this->password,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEvents(string $calendarUri)
|
||||||
|
{
|
||||||
|
$body = '<c:calendar-query xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">
|
||||||
|
<d:prop>
|
||||||
|
<d:getetag />
|
||||||
|
<c:calendar-data />
|
||||||
|
</d:prop>
|
||||||
|
<c:filter>
|
||||||
|
<c:comp-filter name="VCALENDAR">
|
||||||
|
</c:comp-filter>
|
||||||
|
</c:filter>
|
||||||
|
</c:calendar-query>';
|
||||||
|
|
||||||
|
$response = $this->getClient()->request(
|
||||||
|
'REPORT',
|
||||||
|
$calendarUri,
|
||||||
|
$body,
|
||||||
|
[
|
||||||
|
'Depth' => 1,
|
||||||
|
'Prefer' => 'return-minimal',
|
||||||
|
'Content-Type' => 'application/xml; charset=utf-8',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
$body = $response['body'];
|
||||||
|
$events = [];
|
||||||
|
preg_match_all('#<cal:calendar-data>(.+)</cal:calendar-data>#isU', $body, $rawEvents, PREG_SET_ORDER);
|
||||||
|
|
||||||
|
foreach ($rawEvents as $rawEvent) {
|
||||||
|
$events[] = $this->createEvent($rawEvent[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $events;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function createEvent(string $rawEvent): Event
|
||||||
|
{
|
||||||
|
preg_match_all('/
|
||||||
|
^([A-Z\-]+)[:;]{1}([^\n]+)\n
|
||||||
|
((\ [^\n]+\n)*\3*)
|
||||||
|
/xm', $rawEvent, $matches, PREG_SET_ORDER);
|
||||||
|
|
||||||
|
$datas = [];
|
||||||
|
|
||||||
|
foreach ($matches as $match) {
|
||||||
|
$key = $match[1];
|
||||||
|
|
||||||
|
if (isset($datas[$key])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$lines = array_map(fn ($line) => trim($line), explode("\n", $match[2].$match[3]));
|
||||||
|
$lines = str_replace('\\n', "\n", implode('', $lines));
|
||||||
|
|
||||||
|
if (in_array($key, ['DTSTART', 'DTEND'])) {
|
||||||
|
if (empty($lines)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@list($tz, $time) = explode(':', $lines);
|
||||||
|
|
||||||
|
if (empty($time)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$lines = new \DateTime($time);
|
||||||
|
}
|
||||||
|
|
||||||
|
$datas[$key] = $lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
$event = new Event();
|
||||||
|
$event
|
||||||
|
->setUid($datas['UID'])
|
||||||
|
->setSummary($datas['SUMMARY'] ?? null)
|
||||||
|
->setDescription($datas['DESCRIPTION'] ?? null)
|
||||||
|
->setLocation($datas['LOCATION'] ?? null)
|
||||||
|
->setSummary($datas['SUMMARY'] ?? null)
|
||||||
|
->setStartAt($datas['DTSTART'] ?? null)
|
||||||
|
->setFinishAt($datas['DTEND'] ?? null)
|
||||||
|
;
|
||||||
|
|
||||||
|
return $event;
|
||||||
|
}
|
||||||
|
}
|
90
src/Webdav/Event.php
Normal file
90
src/Webdav/Event.php
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Webdav;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* class Event.
|
||||||
|
*
|
||||||
|
* @author Simon Vieille <simon@deblan.fr>
|
||||||
|
*/
|
||||||
|
class Event
|
||||||
|
{
|
||||||
|
protected ?string $uid;
|
||||||
|
protected ?string $summary;
|
||||||
|
protected ?string $description;
|
||||||
|
protected ?string $location;
|
||||||
|
protected ?\DateTime $startAt;
|
||||||
|
protected ?\DateTime $finishAt;
|
||||||
|
|
||||||
|
public function setUid(?string $uid): self
|
||||||
|
{
|
||||||
|
$this->uid = $uid;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUid(): ?string
|
||||||
|
{
|
||||||
|
return $this->uid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setSummary(?string $summary): self
|
||||||
|
{
|
||||||
|
$this->summary = $summary;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSummary(): ?string
|
||||||
|
{
|
||||||
|
return $this->summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setDescription(?string $description): self
|
||||||
|
{
|
||||||
|
$this->description = $description;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDescription(): ?string
|
||||||
|
{
|
||||||
|
return $this->description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setLocation(?string $location): self
|
||||||
|
{
|
||||||
|
$this->location = $location;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLocation(): ?string
|
||||||
|
{
|
||||||
|
return $this->location;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setStartAt(?\DateTime $startAt): self
|
||||||
|
{
|
||||||
|
$this->startAt = $startAt;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getStartAt(): ?\DateTime
|
||||||
|
{
|
||||||
|
return $this->startAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setFinishAt(?\DateTime $finishAt): self
|
||||||
|
{
|
||||||
|
$this->finishAt = $finishAt;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFinishAt(): ?\DateTime
|
||||||
|
{
|
||||||
|
return $this->finishAt;
|
||||||
|
}
|
||||||
|
}
|
79
templates/admin/project/_show.html.twig
Normal file
79
templates/admin/project/_show.html.twig
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-3 p-3">
|
||||||
|
<ul class="list-group">
|
||||||
|
<li class="list-group-item">
|
||||||
|
<span class="font-weight-bold pb-2 d-block">Établissements</span>
|
||||||
|
|
||||||
|
{% for item in entity.establishments %}
|
||||||
|
{{ item.name }}<br>
|
||||||
|
{% else %}
|
||||||
|
-
|
||||||
|
{% endfor %}
|
||||||
|
</li>
|
||||||
|
<li class="list-group-item">
|
||||||
|
<span class="font-weight-bold pb-2 d-block">Client</span>
|
||||||
|
|
||||||
|
{{ entity.client|nl2br }}
|
||||||
|
</li>
|
||||||
|
<li class="list-group-item">
|
||||||
|
<span class="font-weight-bold pb-2 d-block">Fichiers</span>
|
||||||
|
|
||||||
|
{% for item in entity.files %}
|
||||||
|
<a class="btn btn-light border btn-sm mb-2 d-block" href="{{ asset(item.file) }}">
|
||||||
|
{{ item.name }}
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
-
|
||||||
|
{% endfor %}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-9 p-3">
|
||||||
|
<p class="font-weight-bold">{{ entity.label }}</p>
|
||||||
|
|
||||||
|
{{ entity.description|raw }}
|
||||||
|
|
||||||
|
<div class="pt-4">
|
||||||
|
<div class="row">
|
||||||
|
{% for event in entity.events %}
|
||||||
|
<div class="col-12 card mb-2">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="h5 card-title">{{ event.summary }}</div>
|
||||||
|
<div class="card-text">
|
||||||
|
{% set description = event.cleanedDescription %}
|
||||||
|
|
||||||
|
<div class="{{ description ? 'mb-2' : '' }}">
|
||||||
|
{% if event.startAt or event.finishAt %}
|
||||||
|
{% if event.startAt %}
|
||||||
|
<span class="btn btn-sm btn-light mr-1">
|
||||||
|
<span class="fa fa-calendar-alt text-black-50 mr-2"></span>
|
||||||
|
|
||||||
|
{{ event.startAt|date('d/m/Y H:i') }}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if event.finishAt %}
|
||||||
|
<span class="btn btn-sm btn-light mr-1">
|
||||||
|
<span class="fa fa-calendar-alt text-black-50 mr-2"></span>
|
||||||
|
|
||||||
|
{{ event.finishAt|date('d/m/Y H:i') }}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% for speaker in event.speakers %}
|
||||||
|
<span class="btn btn-primary btn-sm mr-1">
|
||||||
|
{{ speaker.name }}
|
||||||
|
</span>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ description|nl2br }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
10
templates/admin/project/edit.html.twig
Normal file
10
templates/admin/project/edit.html.twig
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{% extends '@Core/admin/crud/edit.html.twig' %}
|
||||||
|
|
||||||
|
{% block header %}
|
||||||
|
{{ parent() }}
|
||||||
|
|
||||||
|
<div class="alert alert-info">
|
||||||
|
Tag à ajouter dans les évènements du calendrier : <strong>{{ entity.caldavEventTag }}</strong>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
9
templates/admin/project/show.html.twig
Normal file
9
templates/admin/project/show.html.twig
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
{% extends '@Core/admin/crud/show.html.twig' %}
|
||||||
|
|
||||||
|
{% block header %}
|
||||||
|
{{ parent() }}
|
||||||
|
|
||||||
|
<div class="alert alert-info">
|
||||||
|
Tag à ajouter dans les évènements du calendrier : <strong>{{ entity.caldavEventTag }}</strong>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
11
templates/core/user/user_admin/_form.html.twig
Normal file
11
templates/core/user/user_admin/_form.html.twig
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 p-3">
|
||||||
|
<div class="row">
|
||||||
|
{% for item in ['displayName', 'email', 'isAdmin', 'isWriter', 'isManager'] %}
|
||||||
|
<div class="col-12">
|
||||||
|
{{ form_row(form[item]) }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -21,3 +21,7 @@
|
||||||
"Project": "Projet"
|
"Project": "Projet"
|
||||||
"Internal contributors": "Contributeurs internes"
|
"Internal contributors": "Contributeurs internes"
|
||||||
"External contributors": "Contributeurs externes"
|
"External contributors": "Contributeurs externes"
|
||||||
|
"Caldav host": "Domaine du serveur Caldav"
|
||||||
|
"Caldav username": "Nom d'utilisateur du serveur Caldav"
|
||||||
|
"Caldav password": "Mot de passe du serveur Caldav"
|
||||||
|
"Caldav calendar uri": "URI du calendrier Caldav"
|
||||||
|
|
Loading…
Reference in a new issue