mirror of
https://github.com/tchapi/davis.git
synced 2025-04-21 13:01:57 +02:00
Add calendar sharing
This commit is contained in:
parent
b84a9590ea
commit
363e014e6c
@ -17,12 +17,52 @@ $(document).ready(function() {
|
||||
$('#delete-' + modalFlavour).modal('show');
|
||||
})
|
||||
|
||||
// "Sharing settings" modals
|
||||
$('a.share-modal').click(function() {
|
||||
// Grab calendar shares url and add url
|
||||
let shareesUrl = $(this).attr('data-sharees-href');
|
||||
let targetUrl = $(this).attr('data-href');
|
||||
|
||||
// Put it into the modal's OK button
|
||||
$('#share .add-sharee').attr('data-href', targetUrl);
|
||||
|
||||
// Get calendar shares
|
||||
$.get(shareesUrl, function(data) {
|
||||
// Catch error TODO
|
||||
$('#shares').empty()
|
||||
if (data.length === 0) {
|
||||
$('.none').removeClass('d-none')
|
||||
} else {
|
||||
$('.none').addClass('d-none')
|
||||
data.forEach(element => {
|
||||
const newShare = $($('#template-share').html())
|
||||
newShare.find('span.name').text(element.displayName)
|
||||
newShare.find('span.badge').text(element.accessText)
|
||||
console.log(element.isWriteAccess)
|
||||
if (element.isWriteAccess) {
|
||||
newShare.find('span.badge').addClass('badge-success').removeClass('badge-info')
|
||||
}
|
||||
newShare.appendTo($('#shares'));
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
// Show the modal
|
||||
$('#share').modal('show');
|
||||
})
|
||||
|
||||
// Color swatch : update it live (not working in IE ¯\_(ツ)_/¯ but it's just a nice to have)
|
||||
$('#calendar_instance_calendarColor').keyup(function() {
|
||||
document.body.style.setProperty('--calendar-color', $(this).val());
|
||||
})
|
||||
document.body.style.setProperty('--calendar-color', $('#calendar_instance_calendarColor').val());
|
||||
|
||||
// Modal to add a sharee on a calendar, catch the click to add the query parameter
|
||||
$('a.add-sharee').click(function(e) {
|
||||
e.preventDefault()
|
||||
window.location = $(this).attr('data-href') + "?principalId=" + $("#member").val() + "&write=" + ($("#write").is(':checked') ? 'true' : 'false')
|
||||
})
|
||||
|
||||
// Modal to add delegate, catch the click to add the query parameter
|
||||
$('a.add-delegate').click(function(e) {
|
||||
e.preventDefault()
|
||||
|
@ -16,6 +16,7 @@ use App\Form\CalendarInstanceType;
|
||||
use App\Form\UserType;
|
||||
use App\Services\Utils;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
@ -334,18 +335,23 @@ class AdminController extends AbstractController
|
||||
// Separate shared calendars
|
||||
$calendars = [];
|
||||
$shared = [];
|
||||
foreach($allCalendars as $calendar) {
|
||||
if ($calendar->getAccess() === CalendarInstance::ACCESS_OWNER){
|
||||
foreach ($allCalendars as $calendar) {
|
||||
if (CalendarInstance::ACCESS_OWNER === $calendar->getAccess()) {
|
||||
$calendars[] = $calendar;
|
||||
} else {
|
||||
$shared[] = $calendar;
|
||||
}
|
||||
}
|
||||
|
||||
// We need all the other users so we can propose to share calendars with them
|
||||
$allPrincipalsExcept = $this->get('doctrine')->getRepository(Principal::class)->findAllExceptPrincipal(Principal::PREFIX.$username);
|
||||
|
||||
return $this->render('calendars/index.html.twig', [
|
||||
'calendars' => $calendars,
|
||||
'shared' => $shared,
|
||||
'principal' => $principal,
|
||||
'username' => $username,
|
||||
'allPrincipals' => $allPrincipalsExcept,
|
||||
]);
|
||||
}
|
||||
|
||||
@ -374,7 +380,7 @@ class AdminController extends AbstractController
|
||||
|
||||
$form = $this->createForm(CalendarInstanceType::class, $calendarInstance, [
|
||||
'new' => !$id,
|
||||
'shared' => $calendarInstance->getAccess() !== CalendarInstance::ACCESS_OWNER
|
||||
'shared' => CalendarInstance::ACCESS_OWNER !== $calendarInstance->getAccess(),
|
||||
]);
|
||||
|
||||
$components = explode(',', $calendarInstance->getCalendar()->getComponents());
|
||||
@ -418,6 +424,71 @@ class AdminController extends AbstractController
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/calendars/{username}/shares/{calendarid}", name="calendar_shares", requirements={"calendarid":"\d+"})
|
||||
*/
|
||||
public function calendarShares(string $username, string $calendarid, TranslatorInterface $trans)
|
||||
{
|
||||
$instances = $this->get('doctrine')->getRepository(CalendarInstance::class)->findSharedInstancesOfInstance($calendarid);
|
||||
|
||||
$response = [];
|
||||
foreach ($instances as $instance) {
|
||||
$response[] = [
|
||||
'principalUri' => stream_get_contents($instance[0]['principalUri']),
|
||||
'displayName' => $instance['displayName'],
|
||||
'email' => stream_get_contents($instance['email']),
|
||||
'accessText' => $trans->trans('calendar.share_access.'.$instance[0]['access']),
|
||||
'isWriteAccess' => CalendarInstance::ACCESS_READWRITE === $instance[0]['access'],
|
||||
];
|
||||
}
|
||||
|
||||
return new JsonResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/calendars/{username}/share/{instanceid}", name="calendar_share_add", requirements={"instanceid":"\d+"})
|
||||
*/
|
||||
public function calendarShareAdd(Request $request, string $username, string $instanceid, TranslatorInterface $trans)
|
||||
{
|
||||
$instance = $this->get('doctrine')->getRepository(CalendarInstance::class)->findOneById($instanceid);
|
||||
if (!$instance) {
|
||||
throw $this->createNotFoundException('Calendar not found');
|
||||
}
|
||||
|
||||
$newShareeToAdd = $this->get('doctrine')->getRepository(Principal::class)->findOneById($request->get('principalId'));
|
||||
if (!$newShareeToAdd) {
|
||||
throw $this->createNotFoundException('Member not found');
|
||||
}
|
||||
|
||||
// Let's check that there wasn't another instance
|
||||
// already existing first, so we can update it:
|
||||
$existingSharedInstance = $this->get('doctrine')->getRepository(CalendarInstance::class)->findSharedInstanceOfInstanceFor($instance->getCalendar()->getId(), $newShareeToAdd->getUri());
|
||||
|
||||
$writeAccess = ('true' === $request->get('write') ? CalendarInstance::ACCESS_READWRITE : CalendarInstance::ACCESS_READ);
|
||||
|
||||
$entityManager = $this->get('doctrine')->getManager();
|
||||
|
||||
if ($existingSharedInstance) {
|
||||
$existingSharedInstance->setAccess($writeAccess);
|
||||
} else {
|
||||
$sharedInstance = new CalendarInstance();
|
||||
$sharedInstance->setTransparent(1)
|
||||
->setCalendar($instance->getCalendar())
|
||||
->setShareHref('mailto:'.$newShareeToAdd->getEmail())
|
||||
->setDescription($instance->getDescription())
|
||||
->setDisplayName($instance->getDisplayName())
|
||||
->setUri(\Sabre\DAV\UUIDUtil::getUUID())
|
||||
->setPrincipalUri($newShareeToAdd->getUri())
|
||||
->setAccess($writeAccess);
|
||||
$entityManager->persist($sharedInstance);
|
||||
}
|
||||
|
||||
$entityManager->flush();
|
||||
$this->addFlash('success', $trans->trans('calendar.shared'));
|
||||
|
||||
return $this->redirectToRoute('calendars', ['username' => $username]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/calendars/{username}/delete/{id}", name="calendar_delete", requirements={"id":"\d+"})
|
||||
*/
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\CalendarInstance;
|
||||
use App\Entity\Principal;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
@ -19,32 +20,35 @@ class CalendarInstanceRepository extends ServiceEntityRepository
|
||||
parent::__construct($registry, CalendarInstance::class);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @return CalendarInstance[] Returns an array of CalendarInstance objects
|
||||
// */
|
||||
/*
|
||||
public function findByExampleField($value)
|
||||
/**
|
||||
* @return CalendarInstance[] Returns an array of CalendarInstance objects
|
||||
*/
|
||||
public function findSharedInstancesOfInstance(int $calendarId)
|
||||
{
|
||||
return $this->createQueryBuilder('c')
|
||||
->andWhere('c.exampleField = :val')
|
||||
->setParameter('val', $value)
|
||||
->orderBy('c.id', 'ASC')
|
||||
->setMaxResults(10)
|
||||
->leftJoin(Principal::class, 'p', \Doctrine\ORM\Query\Expr\Join::WITH, 'c.principalUri = p.uri')
|
||||
->addSelect('p.displayName', 'p.email')
|
||||
->where('c.calendar = :id')
|
||||
->setParameter('id', $calendarId)
|
||||
->andWhere('c.access != :ownerAccess')
|
||||
->setParameter('ownerAccess', CalendarInstance::ACCESS_OWNER)
|
||||
->getQuery()
|
||||
->getResult()
|
||||
;
|
||||
->getArrayResult();
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
public function findOneBySomeField($value): ?CalendarInstance
|
||||
/**
|
||||
* @return CalendarInstance Returns a CalendarInstance object
|
||||
*/
|
||||
public function findSharedInstanceOfInstanceFor(int $calendarId, string $principalUri)
|
||||
{
|
||||
return $this->createQueryBuilder('c')
|
||||
->andWhere('c.exampleField = :val')
|
||||
->setParameter('val', $value)
|
||||
->where('c.calendar = :id')
|
||||
->setParameter('id', $calendarId)
|
||||
->andWhere('c.access != :ownerAccess')
|
||||
->setParameter('ownerAccess', CalendarInstance::ACCESS_OWNER)
|
||||
->andWhere('c.principalUri = :principalUri')
|
||||
->setParameter('principalUri', $principalUri)
|
||||
->getQuery()
|
||||
->getOneOrNullResult()
|
||||
;
|
||||
->getOneOrNullResult();
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
@ -16,6 +16,7 @@
|
||||
<option value="{{ principal.id}}">{{ principal.displayName }} ({{ principal.email }})</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<small class="form-text text-muted">{{ "delegates.member.help"|trans }}</small>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" value="" id="write">
|
||||
|
48
templates/_partials/share_modal.html.twig
Normal file
48
templates/_partials/share_modal.html.twig
Normal file
@ -0,0 +1,48 @@
|
||||
<div class="modal fade" id="share" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="exampleModalLabel">{{ "calendars.delegates.new"|trans }}</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<h4>{{ "calendars.delegates.existing"|trans }}</h4>
|
||||
<template id="template-share">
|
||||
<div class="list-group-item d-flex justify-content-between align-items-center">
|
||||
<div class="d-flex align-items-center">
|
||||
<span class="name"></span>
|
||||
<span class="badge badge-info badge-pill ml-2"></span>
|
||||
</div>
|
||||
<a href="#" class="btn btn-sm btn-danger">{{ "revoke"|trans }}</a>
|
||||
</div>
|
||||
</template>
|
||||
<span class="d-none none"><em>{{ "calendars.delegates.none"|trans }}</em></span>
|
||||
<ul class="list-group" id="shares">
|
||||
</ul>
|
||||
<form class="mt-2">
|
||||
<div class="form-group">
|
||||
<label for="member" class="col-form-label">{{ "calendars.delegates.member.add"|trans }}</label>
|
||||
<select class="form-control" id="member">
|
||||
{% for principal in principals %}
|
||||
<option value="{{ principal.id}}">{{ principal.displayName }} ({{ principal.email }})</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<small class="form-text text-muted">{{ "calendars.delegates.member.help"|trans }}</small>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" value="" id="write">
|
||||
<label class="form-check-label" for="write">
|
||||
{{ "calendars.delegates.write.give"|trans }}
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ "cancel"|trans }}</button>
|
||||
<a href="#" class="btn btn-primary add-sharee">{{ "add"|trans }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -13,6 +13,7 @@
|
||||
<div class="d-flex w-100 justify-content-between">
|
||||
<h5 class="mb-1 mr-auto">{{ calendar.displayName }} <span class="badge badge-pill" style="background-color: {{ calendar.calendarColor }}"> </span></h5>
|
||||
<div class="mr-0 text-right d-md-block d-none">
|
||||
<a href="#" data-sharees-href="{{ path('calendar_shares',{username: username, calendarid: calendar.calendar.id})}}" data-href="{{ path('calendar_share_add', {username: principal.username, instanceid: calendar.id}) }}" class="btn btn-sm btn-outline-info ml-1 share-modal">🔗 {{ "sharing"|trans }}</a>
|
||||
<a href="{{ path('calendar_edit',{username: username, id: calendar.id})}}" class="btn btn-sm btn-outline-primary ml-1">✎ {{ "edit"|trans }}</a>
|
||||
<a href="#" data-href="{{ path('calendar_delete',{username: username, id: calendar.id})}}" data-flavour="calendars" class="btn btn-sm btn-outline-danger ml-1 delete-modal">⚠ {{ "delete"|trans }}</a>
|
||||
</div>
|
||||
@ -27,6 +28,7 @@
|
||||
— {{ "calendars.entries"|trans({'%count%': calendar.calendar.objects|length}) }}
|
||||
</small>
|
||||
<div class="btn-group btn-group-sm mt-3 d-flex d-md-none" role="group">
|
||||
<a href="#" data-sharees-href="{{ path('calendar_shares',{username: username, calendarid: calendar.calendar.id})}}" data-href="{{ path('calendar_share_add', {username: principal.username, instanceid: calendar.id}) }}" class="btn btn-outline-info share-modal">🔗 {{ "sharing"|trans }}</a>
|
||||
<a href="{{ path('calendar_edit',{username: username, id: calendar.id})}}" class="btn btn-outline-primary">✎ {{ "edit"|trans }}</a>
|
||||
<a href="#" data-href="{{ path('calendar_delete',{username: username, id: calendar.id})}}" data-flavour="calendars" class="btn btn-outline-danger delete-modal">⚠ {{ "delete"|trans }}</a>
|
||||
</div>
|
||||
@ -41,7 +43,15 @@
|
||||
{% for calendar in shared %}
|
||||
<div class="list-group-item list-group-item-action p-3">
|
||||
<div class="d-flex w-100 justify-content-between">
|
||||
<h5 class="mb-1 mr-auto">{{ calendar.displayName }} <span class="badge badge-pill" style="background-color: {{ calendar.calendarColor }}"> </span></h5>
|
||||
<h5 class="mb-1 mr-auto">
|
||||
{{ calendar.displayName }}
|
||||
{% if calendar.access == constant('\\App\\Entity\\CalendarInstance::ACCESS_READWRITE') %}
|
||||
<span class="badge badge-success ml-1">{{ ('calendar.share_access.' ~ calendar.access)|trans }}</span>
|
||||
{% else %}
|
||||
<span class="badge badge-info ml-1">{{ ('calendar.share_access.' ~ calendar.access)|trans }}</span>
|
||||
{% endif %}
|
||||
<span class="badge badge-pill" style="background-color: {{ calendar.calendarColor }}"> </span>
|
||||
</h5>
|
||||
<div class="mr-0 text-right d-md-block d-none">
|
||||
<a href="{{ path('calendar_edit',{username: username, id: calendar.id})}}" class="btn btn-sm btn-outline-primary ml-1">✎ {{ "edit"|trans }}</a>
|
||||
<a href="#" data-href="{{ path('calendar_revoke',{username: username, id: calendar.id})}}" data-flavour="revoke" class="btn btn-sm btn-outline-danger ml-1 delete-modal">🚫 {{ "revoke"|trans }}</a>
|
||||
@ -67,6 +77,7 @@
|
||||
{% include '_partials/delete_modal.html.twig' with {flavour: 'revoke'} %}
|
||||
{% endif %}
|
||||
|
||||
{% include '_partials/share_modal.html.twig' with {principals: allPrincipals} %}
|
||||
{% include '_partials/delete_modal.html.twig' with {flavour: 'calendars'} %}
|
||||
|
||||
{% endblock %}
|
@ -245,10 +245,22 @@
|
||||
<source>calendar.deleted</source>
|
||||
<target>Calendar deleted successfully</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="qCIo2jM" resname="calendar.shared">
|
||||
<source>calendar.shared</source>
|
||||
<target>Calendar shares modified successfully</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="fxgFe6c" resname="calendar.revoked">
|
||||
<source>calendar.revoked</source>
|
||||
<target>Calendar revoked successfully</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="grS2E58" resname="calendars.delegates.member.add">
|
||||
<source>calendars.delegates.member.add</source>
|
||||
<target>Share this calendar with another user:</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="B95edtg" resname="calendars.delegates.member.help">
|
||||
<source>calendars.delegates.member.help</source>
|
||||
<target>Adding a user who already has a shared access to this calendar will only affect its access right</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Iq2tnfk" resname="addressbooks.saved">
|
||||
<source>addressbooks.saved</source>
|
||||
<target>Address Book saved successfully</target>
|
||||
@ -399,7 +411,11 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="QhzQj3K" resname="delegates.modal.text">
|
||||
<source>delegates.modal.text</source>
|
||||
<target>Are you sure you want to remove this delegate ? This user will no longer have access to the calendars.</target>
|
||||
<target>Are you sure you want to remove this delegate ? This user will no longer have access to the calendars, contacts, etc.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="bsFHDY4" resname="delegates.member.help">
|
||||
<source>delegates.member.help</source>
|
||||
<target>Adding a user who already is a delegate will only affect its access right</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="I3TZF5S" resname="cancel">
|
||||
<source>cancel</source>
|
||||
@ -445,6 +461,10 @@
|
||||
<source>revoke</source>
|
||||
<target>Revoke</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="efo4jbl" resname="sharing">
|
||||
<source>sharing</source>
|
||||
<target>Sharing</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="fWFSd1u" resname="calendars.delegates.add">
|
||||
<source>calendars.delegates.add</source>
|
||||
<target>Add a delegate</target>
|
||||
@ -457,6 +477,14 @@
|
||||
<source>calendars.delegates.new</source>
|
||||
<target>New delegate</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MLydEYK" resname="calendars.delegates.existing">
|
||||
<source>calendars.delegates.existing</source>
|
||||
<target>This calendar is shared with:</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="BROOTDT" resname="calendars.delegates.none">
|
||||
<source>calendars.delegates.none</source>
|
||||
<target>none</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="wPVq0sd" resname="calendars.delegates.member">
|
||||
<source>calendars.delegates.member</source>
|
||||
<target>Member:</target>
|
||||
@ -497,6 +525,14 @@
|
||||
<source>delegates.readonly</source>
|
||||
<target>has readonly access</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="pnQau7c" resname="calendar.share_access.2">
|
||||
<source>calendar.share_access.2</source>
|
||||
<target>readonly</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="BuDoAdU" resname="calendar.share_access.3">
|
||||
<source>calendar.share_access.3</source>
|
||||
<target>read / write</target>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
|
Loading…
x
Reference in New Issue
Block a user