Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/Controller/Admin/AddressBookController.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,13 @@ public function addressBooks(ManagerRegistry $doctrine, #[MapEntity(id: 'userId'
$principal = $doctrine->getRepository(Principal::class)->findOneByUri($principalUri);
$addressbooks = $doctrine->getRepository(AddressBook::class)->findByPrincipalUri($principalUri);

$cardCounts = $doctrine->getRepository(AddressBook::class)->countCardsByAddressBook(
array_map(fn (AddressBook $addressbook) => $addressbook->getId(), $addressbooks)
);

return $this->render('addressbooks/index.html.twig', [
'addressbooks' => $addressbooks,
'cardCounts' => $cardCounts,
'principal' => $principal,
'userId' => $userId,
]);
Expand Down
27 changes: 14 additions & 13 deletions src/Controller/Admin/CalendarController.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,30 +30,31 @@ public function calendars(ManagerRegistry $doctrine, UrlGeneratorInterface $rout
$principalUri = $user->getPrincipalUri();

$principal = $doctrine->getRepository(Principal::class)->findOneByUri($principalUri);
$allCalendars = $doctrine->getRepository(CalendarInstance::class)->findByPrincipalUri($principalUri);
$allCalendars = $doctrine->getRepository(CalendarInstance::class)->findByPrincipalUriWithCalendars($principalUri);

$subscriptions = $doctrine->getRepository(CalendarSubscription::class)->findByPrincipalUri($principalUri);

$objectCounts = $doctrine->getRepository(CalendarInstance::class)->countObjectsByCalendar(
array_map(fn (CalendarInstance $instance) => $instance->getCalendar()->getId(), $allCalendars)
);

// Separate shared calendars
$calendars = [];
$shared = [];
$auto = [];
foreach ($allCalendars as $calendar) {
$compoundObject = [
'entity' => $calendar,
'uri' => $router->generate('dav', ['path' => 'calendars/'.$username.'/'.$calendar->getUri()], UrlGeneratorInterface::ABSOLUTE_URL),
'objectCount' => $objectCounts[$calendar->getCalendar()->getId()],
];

if ($calendar->isAutomaticallyGenerated()) {
$auto[] = [
'entity' => $calendar,
'uri' => $router->generate('dav', ['path' => 'calendars/'.$username.'/'.$calendar->getUri()], UrlGeneratorInterface::ABSOLUTE_URL),
];
$auto[] = $compoundObject;
} elseif (!$calendar->isShared()) {
$calendars[] = [
'entity' => $calendar,
'uri' => $router->generate('dav', ['path' => 'calendars/'.$username.'/'.$calendar->getUri()], UrlGeneratorInterface::ABSOLUTE_URL),
];
$calendars[] = $compoundObject;
} else {
$shared[] = [
'entity' => $calendar,
'uri' => $router->generate('dav', ['path' => 'calendars/'.$username.'/'.$calendar->getUri()], UrlGeneratorInterface::ABSOLUTE_URL),
];
$shared[] = $compoundObject;
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/Controller/Admin/UserController.php
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ public function userDelete(ManagerRegistry $doctrine, Request $request, #[MapEnt
$principalUri = $user->getPrincipalUri();

// Remove calendars and addressbooks
$calendars = $doctrine->getRepository(CalendarInstance::class)->findByPrincipalUri($principalUri);
$calendars = $doctrine->getRepository(CalendarInstance::class)->findByPrincipalUriWithCalendars($principalUri);
foreach ($calendars ?? [] as $instance) {
// We're only removing the calendar objects / changes / and calendar if the deleted user is an owner,
// which means that the underlying calendar instance should not have another principal as owner.
Expand Down
4 changes: 2 additions & 2 deletions src/Controller/Api/ApiController.php
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ public function getUserCalendars(Request $request, int $userId, ManagerRegistry
return $this->json(['status' => 'error', 'message' => 'Principal Not Found', 'timestamp' => $this->getTimestamp()], 404);
}

$allCalendars = $doctrine->getRepository(CalendarInstance::class)->findByPrincipalUri($principalUri);
$allCalendars = $doctrine->getRepository(CalendarInstance::class)->findByPrincipalUriWithCalendars($principalUri);
$allSubscriptions = $doctrine->getRepository(CalendarSubscription::class)->findByPrincipalUri($principalUri);

$calendars = [];
Expand Down Expand Up @@ -243,7 +243,7 @@ public function getUserCalendarDetails(Request $request, int $userId, int $calen
return $this->json(['status' => 'error', 'message' => 'Principal Not Found', 'timestamp' => $this->getTimestamp()], 404);
}

$allCalendars = $doctrine->getRepository(CalendarInstance::class)->findByPrincipalUri($principalUri);
$allCalendars = $doctrine->getRepository(CalendarInstance::class)->findByPrincipalUriWithCalendars($principalUri);

$calendar_details = [];
foreach ($allCalendars as $calendar) {
Expand Down
3 changes: 2 additions & 1 deletion src/Entity/AddressBook.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@

namespace App\Entity;

use App\Repository\AddressBookRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Validator\Constraints as Assert;

#[ORM\Entity()]
#[ORM\Entity(repositoryClass: AddressBookRepository::class)]
#[ORM\Table(name: 'addressbooks')]
#[UniqueEntity(fields: ['principalUri', 'uri'], errorPath: 'uri', message: 'form.uri.unique')]
class AddressBook
Expand Down
54 changes: 54 additions & 0 deletions src/Repository/AddressBookRepository.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

namespace App\Repository;

use App\Entity\AddressBook;
use App\Entity\Card;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;

/**
* @method AddressBook|null find($id, $lockMode = null, $lockVersion = null)
* @method AddressBook|null findOneBy(array $criteria, array $orderBy = null)
* @method AddressBook[] findAll()
* @method AddressBook[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class AddressBookRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, AddressBook::class);
}

/**
* Counts the cards of several address books at once, so that listing a principal's address
* books costs a single query instead of one per address book.
*
* @param int[] $addressBookIds
*
* @return array<int, int> count per address book id, including the address books that hold nothing
*/
public function countCardsByAddressBook(array $addressBookIds): array
{
$counts = array_fill_keys($addressBookIds, 0);

if (!$addressBookIds) {
return $counts;
}

$results = $this->getEntityManager()->getRepository(Card::class)
->createQueryBuilder('c')
->select('IDENTITY(c.addressBook) AS addressBookId, COUNT(c.id) AS count')
->where('c.addressBook IN (:addressBookIds)')
->setParameter('addressBookIds', $addressBookIds)
->groupBy('c.addressBook')
->getQuery()
->getResult();

foreach ($results as $result) {
$counts[(int) $result['addressBookId']] = (int) $result['count'];
}

return $counts;
}
}
49 changes: 49 additions & 0 deletions src/Repository/CalendarInstanceRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,23 @@ public function __construct(ManagerRegistry $registry)
parent::__construct($registry, CalendarInstance::class);
}

/**
* Returns every instance the principal has, with its `calendars` row already loaded. Every
* caller reads it, so leaving it lazy costs one extra query per calendar.
*
* @return CalendarInstance[]
*/
public function findByPrincipalUriWithCalendars(string $principalUri): array
{
return $this->createQueryBuilder('c')
->addSelect('cal')
->join('c.calendar', 'cal')
->where('c.principalUri = :principalUri')
->setParameter('principalUri', $principalUri)
->getQuery()
->getResult();
}

/**
* @return CalendarInstance[] Returns an array of CalendarInstance objects
*/
Expand Down Expand Up @@ -118,6 +135,38 @@ public function findAllSchedulingObjectsForCalendar(int $calendarInstanceId, str
->getResult();
}

/**
* Counts the objects of several calendars at once, so that listing a principal's calendars
* costs a single query instead of one per calendar.
*
* @param int[] $calendarIds
*
* @return array<int, int> count per calendar id, including the calendars that hold nothing
*/
public function countObjectsByCalendar(array $calendarIds): array
{
$counts = array_fill_keys($calendarIds, 0);

if (!$calendarIds) {
return $counts;
}

$results = $this->getEntityManager()->getRepository(CalendarObject::class)
->createQueryBuilder('o')
->select('IDENTITY(o.calendar) AS calendarId, COUNT(o.id) AS count')
->where('o.calendar IN (:calendarIds)')
->setParameter('calendarIds', $calendarIds)
->groupBy('o.calendar')
->getQuery()
->getResult();

foreach ($results as $result) {
$counts[(int) $result['calendarId']] = (int) $result['count'];
}

return $counts;
}

/**
* Get counts of calendar objects by component type for a calendar instance.
*
Expand Down
2 changes: 1 addition & 1 deletion templates/addressbooks/index.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
</div>
</div>
<p class="mb-1">{{ addressbook.description }}</p>
<small>{{ "addressbooks.uri"|trans }} : <code>{{ addressbook.uri }}</code> — {{ "addressbooks.contacts"|trans({'%count%': addressbook.cards|length}) }}</small>
<small>{{ "addressbooks.uri"|trans }} : <code>{{ addressbook.uri }}</code> — {{ "addressbooks.contacts"|trans({'%count%': cardCounts[addressbook.id]}) }}</small>
<div class="btn-group btn-group-sm mt-3 d-flex d-md-none" role="group">
<a href="{{ path('addressbook_edit',{userId: userId, id: addressbook.id})}}" class="btn btn-sm btn-outline-primary">✎ {{ "edit"|trans }}</a>
<a href="#"
Expand Down
6 changes: 3 additions & 3 deletions templates/calendars/index.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
{% if constant('\\App\\Entity\\Calendar::COMPONENT_NOTES') in calendar.calendar.components %}<span class="badge bg-info">{{ "calendars.component.notes"|trans }}</span>{% endif %}
{% if constant('\\App\\Entity\\Calendar::COMPONENT_TODOS') in calendar.calendar.components %}<span class="badge bg-primary-subtle">{{ "calendars.component.todos"|trans }}</span>{% endif %}
{% endif %}
— {{ "calendars.entries"|trans({'%count%': calendar.calendar.objects|length}) }}
— {{ "calendars.entries"|trans({'%count%': compoundObject.objectCount}) }}
</small>
<div class="btn-group btn-group-sm mt-3 d-flex d-md-none" role="group">
{% if not calendar.isPublic() %}
Expand Down Expand Up @@ -103,7 +103,7 @@
{% if constant('\\App\\Entity\\Calendar::COMPONENT_NOTES') in calendar.calendar.components %}<span class="badge bg-info">{{ "calendars.component.notes"|trans }}</span>{% endif %}
{% if constant('\\App\\Entity\\Calendar::COMPONENT_TODOS') in calendar.calendar.components %}<span class="badge bg-primary-subtle">{{ "calendars.component.todos"|trans }}</span>{% endif %}
{% endif %}
— {{ "calendars.entries"|trans({'%count%': calendar.calendar.objects|length}) }}
— {{ "calendars.entries"|trans({'%count%': compoundObject.objectCount}) }}
</small>
<div class="btn-group btn-group-sm mt-3 d-flex d-md-none" role="group">
<a href="{{ path('calendar_edit',{userId: userId, id: calendar.id})}}" class="btn btn-outline-primary">✎ {{ "edit"|trans }}</a>
Expand Down Expand Up @@ -147,7 +147,7 @@
{% if constant('\\App\\Entity\\Calendar::COMPONENT_NOTES') in calendar.calendar.components %}<span class="badge bg-info">{{ "calendars.component.notes"|trans }}</span>{% endif %}
{% if constant('\\App\\Entity\\Calendar::COMPONENT_TODOS') in calendar.calendar.components %}<span class="badge bg-primary-subtle">{{ "calendars.component.todos"|trans }}</span>{% endif %}
{% endif %}
— {{ "calendars.entries"|trans({'%count%': calendar.calendar.objects|length}) }}
— {{ "calendars.entries"|trans({'%count%': compoundObject.objectCount}) }}
</small>
<div class="btn-group btn-group-sm mt-3 d-flex d-md-none" role="group">
<a href="{{ path('calendar_edit',{userId: userId, id: calendar.id})}}" class="btn btn-outline-primary">✎ {{ "edit"|trans }}</a>
Expand Down
Loading