migrate therinaldos.com data
Build & Deploy to DigitalOcean Space / build (push) Failing after 2m38s

This commit is contained in:
2024-05-05 15:50:45 -04:00
commit ef1ff240d4
23182 changed files with 3801898 additions and 0 deletions
@@ -0,0 +1,139 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020, Georg Ehrke
*
* @author Georg Ehrke <oc.list@georgehrke.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\DAV\Search;
use OCA\DAV\CalDAV\CalDavBackend;
use OCP\App\IAppManager;
use OCP\IL10N;
use OCP\IURLGenerator;
use OCP\Search\IProvider;
use Sabre\VObject\Component;
use Sabre\VObject\Reader;
/**
* Class ACalendarSearchProvider
*
* @package OCA\DAV\Search
*/
abstract class ACalendarSearchProvider implements IProvider {
/** @var IAppManager */
protected $appManager;
/** @var IL10N */
protected $l10n;
/** @var IURLGenerator */
protected $urlGenerator;
/** @var CalDavBackend */
protected $backend;
/**
* ACalendarSearchProvider constructor.
*
* @param IAppManager $appManager
* @param IL10N $l10n
* @param IURLGenerator $urlGenerator
* @param CalDavBackend $backend
*/
public function __construct(IAppManager $appManager,
IL10N $l10n,
IURLGenerator $urlGenerator,
CalDavBackend $backend) {
$this->appManager = $appManager;
$this->l10n = $l10n;
$this->urlGenerator = $urlGenerator;
$this->backend = $backend;
}
/**
* Get an associative array of calendars
* calendarId => calendar
*
* @param string $principalUri
* @return array
*/
protected function getSortedCalendars(string $principalUri): array {
$calendars = $this->backend->getCalendarsForUser($principalUri);
$calendarsById = [];
foreach ($calendars as $calendar) {
$calendarsById[(int) $calendar['id']] = $calendar;
}
return $calendarsById;
}
/**
* Get an associative array of subscriptions
* subscriptionId => subscription
*
* @param string $principalUri
* @return array
*/
protected function getSortedSubscriptions(string $principalUri): array {
$subscriptions = $this->backend->getSubscriptionsForUser($principalUri);
$subscriptionsById = [];
foreach ($subscriptions as $subscription) {
$subscriptionsById[(int) $subscription['id']] = $subscription;
}
return $subscriptionsById;
}
/**
* Returns the primary VEvent / VJournal / VTodo component
* If it's a component with recurrence-ids, it will return
* the primary component
*
* TODO: It would be a nice enhancement to show recurrence-exceptions
* as individual search-results.
* For now we will just display the primary element of a recurrence-set.
*
* @param string $calendarData
* @param string $componentName
* @return Component
*/
protected function getPrimaryComponent(string $calendarData, string $componentName): Component {
$vCalendar = Reader::read($calendarData, Reader::OPTION_FORGIVING);
$components = $vCalendar->select($componentName);
if (count($components) === 1) {
return $components[0];
}
// If it's a recurrence-set, take the primary element
foreach ($components as $component) {
/** @var Component $component */
if (!$component->{'RECURRENCE-ID'}) {
return $component;
}
}
// In case of error, just fallback to the first element in the set
return $components[0];
}
}
@@ -0,0 +1,205 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020, Georg Ehrke
*
* @author Georg Ehrke <oc.list@georgehrke.com>
* @author Joas Schilling <coding@schilljs.com>
* @author John Molakvoæ <skjnldsv@protonmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\DAV\Search;
use OCA\DAV\CardDAV\CardDavBackend;
use OCP\App\IAppManager;
use OCP\IL10N;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\Search\FilterDefinition;
use OCP\Search\IFilter;
use OCP\Search\IFilteringProvider;
use OCP\Search\ISearchQuery;
use OCP\Search\SearchResult;
use OCP\Search\SearchResultEntry;
use Sabre\VObject\Component\VCard;
use Sabre\VObject\Reader;
class ContactsSearchProvider implements IFilteringProvider {
private static array $searchPropertiesRestricted = [
'N',
'FN',
'NICKNAME',
'EMAIL',
];
private static array $searchProperties = [
'N',
'FN',
'NICKNAME',
'EMAIL',
'TEL',
'ADR',
'TITLE',
'ORG',
'NOTE',
];
public function __construct(
private IAppManager $appManager,
private IL10N $l10n,
private IURLGenerator $urlGenerator,
private CardDavBackend $backend,
) {
}
/**
* @inheritDoc
*/
public function getId(): string {
return 'contacts';
}
/**
* @inheritDoc
*/
public function getName(): string {
return $this->l10n->t('Contacts');
}
public function getOrder(string $route, array $routeParameters): ?int {
if ($this->appManager->isEnabledForUser('contacts')) {
return $route === 'contacts.Page.index' ? -1 : 25;
}
return null;
}
public function search(IUser $user, ISearchQuery $query): SearchResult {
if (!$this->appManager->isEnabledForUser('contacts', $user)) {
return SearchResult::complete($this->getName(), []);
}
$principalUri = 'principals/users/' . $user->getUID();
$addressBooks = $this->backend->getAddressBooksForUser($principalUri);
$addressBooksById = [];
foreach ($addressBooks as $addressBook) {
$addressBooksById[(int) $addressBook['id']] = $addressBook;
}
$searchResults = $this->backend->searchPrincipalUri(
$principalUri,
$query->getFilter('term')?->get() ?? '',
$query->getFilter('title-only')?->get() ? self::$searchPropertiesRestricted : self::$searchProperties,
[
'limit' => $query->getLimit(),
'offset' => $query->getCursor(),
'since' => $query->getFilter('since'),
'until' => $query->getFilter('until'),
'person' => $this->getPersonDisplayName($query->getFilter('person')),
'company' => $query->getFilter('company'),
],
);
$formattedResults = \array_map(function (array $contactRow) use ($addressBooksById):SearchResultEntry {
$addressBook = $addressBooksById[$contactRow['addressbookid']];
/** @var VCard $vCard */
$vCard = Reader::read($contactRow['carddata']);
$thumbnailUrl = '';
if ($vCard->PHOTO) {
$thumbnailUrl = $this->getDavUrlForContact($addressBook['principaluri'], $addressBook['uri'], $contactRow['uri']) . '?photo';
}
$title = (string)$vCard->FN;
$subline = $this->generateSubline($vCard);
$resourceUrl = $this->getDeepLinkToContactsApp($addressBook['uri'], (string) $vCard->UID);
return new SearchResultEntry($thumbnailUrl, $title, $subline, $resourceUrl, 'icon-contacts-dark', true);
}, $searchResults);
return SearchResult::paginated(
$this->getName(),
$formattedResults,
$query->getCursor() + count($formattedResults)
);
}
private function getPersonDisplayName(?IFilter $person): ?string {
$user = $person?->get();
if ($user instanceof IUser) {
return $user->getDisplayName();
}
return null;
}
protected function getDavUrlForContact(
string $principalUri,
string $addressBookUri,
string $contactsUri,
): string {
[, $principalType, $principalId] = explode('/', $principalUri, 3);
return $this->urlGenerator->getAbsoluteURL(
$this->urlGenerator->linkTo('', 'remote.php') . '/dav/addressbooks/'
. $principalType . '/'
. $principalId . '/'
. $addressBookUri . '/'
. $contactsUri
);
}
protected function getDeepLinkToContactsApp(
string $addressBookUri,
string $contactUid,
): string {
return $this->urlGenerator->getAbsoluteURL(
$this->urlGenerator->linkToRoute('contacts.contacts.direct', [
'contact' => $contactUid . '~' . $addressBookUri
])
);
}
protected function generateSubline(VCard $vCard): string {
$emailAddresses = $vCard->select('EMAIL');
if (!is_array($emailAddresses) || empty($emailAddresses)) {
return '';
}
return (string)$emailAddresses[0];
}
public function getSupportedFilters(): array {
return [
'term',
'since',
'until',
'person',
'title-only',
];
}
public function getAlternateIds(): array {
return [];
}
public function getCustomFilters(): array {
return [
new FilterDefinition('company'),
];
}
}
@@ -0,0 +1,292 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020, Georg Ehrke
*
* @author Georg Ehrke <oc.list@georgehrke.com>
* @author Joas Schilling <coding@schilljs.com>
* @author John Molakvoæ <skjnldsv@protonmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\DAV\Search;
use OCA\DAV\CalDAV\CalDavBackend;
use OCP\IUser;
use OCP\Search\IFilteringProvider;
use OCP\Search\ISearchQuery;
use OCP\Search\SearchResult;
use OCP\Search\SearchResultEntry;
use Sabre\VObject\Component;
use Sabre\VObject\DateTimeParser;
use Sabre\VObject\Property;
use function array_combine;
use function array_fill;
use function array_key_exists;
use function array_map;
/**
* Class EventsSearchProvider
*
* @package OCA\DAV\Search
*/
class EventsSearchProvider extends ACalendarSearchProvider implements IFilteringProvider {
/**
* @var string[]
*/
private static $searchProperties = [
'SUMMARY',
'LOCATION',
'DESCRIPTION',
'ATTENDEE',
'ORGANIZER',
'CATEGORIES',
];
/**
* @var string[]
*/
private static $searchParameters = [
'ATTENDEE' => ['CN'],
'ORGANIZER' => ['CN'],
];
/**
* @var string
*/
private static $componentType = 'VEVENT';
/**
* @inheritDoc
*/
public function getId(): string {
return 'calendar';
}
/**
* @inheritDoc
*/
public function getName(): string {
return $this->l10n->t('Events');
}
/**
* @inheritDoc
*/
public function getOrder(string $route, array $routeParameters): ?int {
if ($this->appManager->isEnabledForUser('calendar')) {
return $route === 'calendar.View.index' ? -1 : 30;
}
return null;
}
/**
* @inheritDoc
*/
public function search(
IUser $user,
ISearchQuery $query,
): SearchResult {
if (!$this->appManager->isEnabledForUser('calendar', $user)) {
return SearchResult::complete($this->getName(), []);
}
$principalUri = 'principals/users/' . $user->getUID();
$calendarsById = $this->getSortedCalendars($principalUri);
$subscriptionsById = $this->getSortedSubscriptions($principalUri);
/** @var string|null $term */
$term = $query->getFilter('term')?->get();
if ($term === null) {
$searchResults = [];
} else {
$searchResults = $this->backend->searchPrincipalUri(
$principalUri,
$term,
[self::$componentType],
self::$searchProperties,
self::$searchParameters,
[
'limit' => $query->getLimit(),
'offset' => $query->getCursor(),
'timerange' => [
'start' => $query->getFilter('since')?->get(),
'end' => $query->getFilter('until')?->get(),
],
]
);
}
/** @var IUser|null $person */
$person = $query->getFilter('person')?->get();
$personDisplayName = $person?->getDisplayName();
if ($personDisplayName !== null) {
$attendeeSearchResults = $this->backend->searchPrincipalUri(
$principalUri,
$personDisplayName,
[self::$componentType],
['ATTENDEE'],
self::$searchParameters,
[
'limit' => $query->getLimit(),
'offset' => $query->getCursor(),
'timerange' => [
'start' => $query->getFilter('since')?->get(),
'end' => $query->getFilter('until')?->get(),
],
],
);
$searchResultIndex = array_combine(
array_map(fn ($event) => $event['calendarid'] . '-' . $event['uri'], $searchResults),
array_fill(0, count($searchResults), null),
);
foreach ($attendeeSearchResults as $attendeeResult) {
if (array_key_exists($attendeeResult['calendarid'] . '-' . $attendeeResult['uri'], $searchResultIndex)) {
// Duplicate
continue;
}
$searchResults[] = $attendeeResult;
}
}
$formattedResults = \array_map(function (array $eventRow) use ($calendarsById, $subscriptionsById): SearchResultEntry {
$component = $this->getPrimaryComponent($eventRow['calendardata'], self::$componentType);
$title = (string)($component->SUMMARY ?? $this->l10n->t('Untitled event'));
$subline = $this->generateSubline($component);
if ($eventRow['calendartype'] === CalDavBackend::CALENDAR_TYPE_CALENDAR) {
$calendar = $calendarsById[$eventRow['calendarid']];
} else {
$calendar = $subscriptionsById[$eventRow['calendarid']];
}
$resourceUrl = $this->getDeepLinkToCalendarApp($calendar['principaluri'], $calendar['uri'], $eventRow['uri']);
return new SearchResultEntry('', $title, $subline, $resourceUrl, 'icon-calendar-dark', false);
}, $searchResults);
return SearchResult::paginated(
$this->getName(),
$formattedResults,
$query->getCursor() + count($formattedResults)
);
}
protected function getDeepLinkToCalendarApp(
string $principalUri,
string $calendarUri,
string $calendarObjectUri,
): string {
$davUrl = $this->getDavUrlForCalendarObject($principalUri, $calendarUri, $calendarObjectUri);
// This route will automatically figure out what recurrence-id to open
return $this->urlGenerator->getAbsoluteURL(
$this->urlGenerator->linkToRoute('calendar.view.index')
. 'edit/'
. base64_encode($davUrl)
);
}
protected function getDavUrlForCalendarObject(
string $principalUri,
string $calendarUri,
string $calendarObjectUri
): string {
[,, $principalId] = explode('/', $principalUri, 3);
return $this->urlGenerator->linkTo('', 'remote.php') . '/dav/calendars/'
. $principalId . '/'
. $calendarUri . '/'
. $calendarObjectUri;
}
protected function generateSubline(Component $eventComponent): string {
$dtStart = $eventComponent->DTSTART;
$dtEnd = $this->getDTEndForEvent($eventComponent);
$isAllDayEvent = $dtStart instanceof Property\ICalendar\Date;
$startDateTime = new \DateTime($dtStart->getDateTime()->format(\DateTimeInterface::ATOM));
$endDateTime = new \DateTime($dtEnd->getDateTime()->format(\DateTimeInterface::ATOM));
if ($isAllDayEvent) {
$endDateTime->modify('-1 day');
if ($this->isDayEqual($startDateTime, $endDateTime)) {
return $this->l10n->l('date', $startDateTime, ['width' => 'medium']);
}
$formattedStart = $this->l10n->l('date', $startDateTime, ['width' => 'medium']);
$formattedEnd = $this->l10n->l('date', $endDateTime, ['width' => 'medium']);
return "$formattedStart - $formattedEnd";
}
$formattedStartDate = $this->l10n->l('date', $startDateTime, ['width' => 'medium']);
$formattedEndDate = $this->l10n->l('date', $endDateTime, ['width' => 'medium']);
$formattedStartTime = $this->l10n->l('time', $startDateTime, ['width' => 'short']);
$formattedEndTime = $this->l10n->l('time', $endDateTime, ['width' => 'short']);
if ($this->isDayEqual($startDateTime, $endDateTime)) {
return "$formattedStartDate $formattedStartTime - $formattedEndTime";
}
return "$formattedStartDate $formattedStartTime - $formattedEndDate $formattedEndTime";
}
protected function getDTEndForEvent(Component $eventComponent):Property {
if (isset($eventComponent->DTEND)) {
$end = $eventComponent->DTEND;
} elseif (isset($eventComponent->DURATION)) {
$isFloating = $eventComponent->DTSTART->isFloating();
$end = clone $eventComponent->DTSTART;
$endDateTime = $end->getDateTime();
$endDateTime = $endDateTime->add(DateTimeParser::parse($eventComponent->DURATION->getValue()));
$end->setDateTime($endDateTime, $isFloating);
} elseif (!$eventComponent->DTSTART->hasTime()) {
$isFloating = $eventComponent->DTSTART->isFloating();
$end = clone $eventComponent->DTSTART;
$endDateTime = $end->getDateTime();
$endDateTime = $endDateTime->modify('+1 day');
$end->setDateTime($endDateTime, $isFloating);
} else {
$end = clone $eventComponent->DTSTART;
}
return $end;
}
protected function isDayEqual(
\DateTime $dtStart,
\DateTime $dtEnd,
): bool {
return $dtStart->format('Y-m-d') === $dtEnd->format('Y-m-d');
}
public function getSupportedFilters(): array {
return [
'term',
'person',
'since',
'until',
];
}
public function getAlternateIds(): array {
return [];
}
public function getCustomFilters(): array {
return [];
}
}
@@ -0,0 +1,172 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020, Georg Ehrke
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Georg Ehrke <oc.list@georgehrke.com>
* @author Joas Schilling <coding@schilljs.com>
* @author John Molakvoæ <skjnldsv@protonmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\DAV\Search;
use OCA\DAV\CalDAV\CalDavBackend;
use OCP\IUser;
use OCP\Search\ISearchQuery;
use OCP\Search\SearchResult;
use OCP\Search\SearchResultEntry;
use Sabre\VObject\Component;
/**
* Class TasksSearchProvider
*
* @package OCA\DAV\Search
*/
class TasksSearchProvider extends ACalendarSearchProvider {
/**
* @var string[]
*/
private static $searchProperties = [
'SUMMARY',
'DESCRIPTION',
'CATEGORIES',
];
/**
* @var string[]
*/
private static $searchParameters = [];
/**
* @var string
*/
private static $componentType = 'VTODO';
/**
* @inheritDoc
*/
public function getId(): string {
return 'tasks';
}
/**
* @inheritDoc
*/
public function getName(): string {
return $this->l10n->t('Tasks');
}
/**
* @inheritDoc
*/
public function getOrder(string $route, array $routeParameters): ?int {
if ($this->appManager->isEnabledForUser('tasks')) {
return $route === 'tasks.Page.index' ? -1 : 35;
}
return null;
}
/**
* @inheritDoc
*/
public function search(
IUser $user,
ISearchQuery $query,
): SearchResult {
if (!$this->appManager->isEnabledForUser('tasks', $user)) {
return SearchResult::complete($this->getName(), []);
}
$principalUri = 'principals/users/' . $user->getUID();
$calendarsById = $this->getSortedCalendars($principalUri);
$subscriptionsById = $this->getSortedSubscriptions($principalUri);
$searchResults = $this->backend->searchPrincipalUri(
$principalUri,
$query->getFilter('term')?->get() ?? '',
[self::$componentType],
self::$searchProperties,
self::$searchParameters,
[
'limit' => $query->getLimit(),
'offset' => $query->getCursor(),
'since' => $query->getFilter('since'),
'until' => $query->getFilter('until'),
]
);
$formattedResults = \array_map(function (array $taskRow) use ($calendarsById, $subscriptionsById):SearchResultEntry {
$component = $this->getPrimaryComponent($taskRow['calendardata'], self::$componentType);
$title = (string)($component->SUMMARY ?? $this->l10n->t('Untitled task'));
$subline = $this->generateSubline($component);
if ($taskRow['calendartype'] === CalDavBackend::CALENDAR_TYPE_CALENDAR) {
$calendar = $calendarsById[$taskRow['calendarid']];
} else {
$calendar = $subscriptionsById[$taskRow['calendarid']];
}
$resourceUrl = $this->getDeepLinkToTasksApp($calendar['uri'], $taskRow['uri']);
return new SearchResultEntry('', $title, $subline, $resourceUrl, 'icon-checkmark', false);
}, $searchResults);
return SearchResult::paginated(
$this->getName(),
$formattedResults,
$query->getCursor() + count($formattedResults)
);
}
protected function getDeepLinkToTasksApp(
string $calendarUri,
string $taskUri,
): string {
return $this->urlGenerator->getAbsoluteURL(
$this->urlGenerator->linkToRoute('tasks.page.index')
. '#/calendars/'
. $calendarUri
. '/tasks/'
. $taskUri
);
}
protected function generateSubline(Component $taskComponent): string {
if ($taskComponent->COMPLETED) {
$completedDateTime = new \DateTime($taskComponent->COMPLETED->getDateTime()->format(\DateTimeInterface::ATOM));
$formattedDate = $this->l10n->l('date', $completedDateTime, ['width' => 'medium']);
return $this->l10n->t('Completed on %s', [$formattedDate]);
}
if ($taskComponent->DUE) {
$dueDateTime = new \DateTime($taskComponent->DUE->getDateTime()->format(\DateTimeInterface::ATOM));
$formattedDate = $this->l10n->l('date', $dueDateTime, ['width' => 'medium']);
if ($taskComponent->DUE->hasTime()) {
$formattedTime = $this->l10n->l('time', $dueDateTime, ['width' => 'short']);
return $this->l10n->t('Due on %s by %s', [$formattedDate, $formattedTime]);
}
return $this->l10n->t('Due on %s', [$formattedDate]);
}
return '';
}
}