Initial commit

This commit is contained in:
root
2026-01-04 22:05:37 -05:00
committed by Sebastian Krupinski
commit 4f979ced22
57 changed files with 11076 additions and 0 deletions

View File

@@ -0,0 +1,343 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2023 Sebastian Krupinski <krupinski01@gmail.com>
*
* @author Sebastian Krupinski <krupinski01@gmail.com>
*
* @license AGPL-3.0-or-later
*
* 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 KTXM\ProviderJmapc\Service\Remote\FM;
use DateTimeImmutable;
use JmapClient\Client;
use OCA\JMAPC\Jmap\FM\Request\Contacts\ContactParameters as ContactParametersRequest;
use OCA\JMAPC\Objects\Contact\ContactAnniversaryObject;
use OCA\JMAPC\Objects\Contact\ContactAnniversaryTypes;
use OCA\JMAPC\Objects\Contact\ContactEmailObject;
use OCA\JMAPC\Objects\Contact\ContactNoteObject;
use OCA\JMAPC\Objects\Contact\ContactObject as ContactObject;
use OCA\JMAPC\Objects\Contact\ContactOrganizationObject;
use OCA\JMAPC\Objects\Contact\ContactPhoneObject;
use OCA\JMAPC\Objects\Contact\ContactPhysicalLocationObject;
use OCA\JMAPC\Objects\Contact\ContactTitleObject;
use OCA\JMAPC\Objects\Contact\ContactTitleTypes;
use OCA\JMAPC\Objects\Contact\ContactVirtualLocationObject;
use OCA\JMAPC\Objects\OriginTypes;
use OCA\JMAPC\Service\Remote\RemoteContactsService;
class RemoteContactsServiceFM extends RemoteContactsService {
private const DATE_ANNIVERSARY = 'Y-m-d';
public function __construct() {
}
public function initialize(Client $dataStore, ?string $dataAccount = null) {
parent::initialize($dataStore, $dataAccount);
$this->resourceNamespace = 'https://www.fastmail.com/dev/contacts';
$this->resourceCollectionLabel = null;
$this->resourceEntityLabel = 'Contact';
$dataStore->configureRequestTypes('parameters', 'Contact.object', 'OCA\JMAPC\Jmap\FM\Request\Contacts\ContactParameters');
$dataStore->configureResponseTypes('command', 'Contact/get', 'JmapClient\Responses\Contacts\ContactGet');
$dataStore->configureResponseTypes('command', 'Contact/set', 'JmapClient\Responses\Contacts\ContactSet');
$dataStore->configureResponseTypes('command', 'Contact/changes', 'JmapClient\Responses\Contacts\ContactChanges');
$dataStore->configureResponseTypes('command', 'Contact/query', 'JmapClient\Responses\Contacts\ContactQuery');
$dataStore->configureResponseTypes('command', 'Contact/queryChanges', 'JmapClient\Responses\Contacts\ContactQueryChanges');
$dataStore->configureResponseTypes('parameters', 'Contact', 'OCA\JMAPC\Jmap\FM\Response\ContactParameters');
$dataStore->configureResponseTypes('parameters', 'Contact', 'OCA\JMAPC\Jmap\FM\Response\Contacts\ContactParameters');
}
/**
* convert jmap object to contact object
*
* @since Release 1.0.0
*
*/
public function toContactObject($so): ContactObject {
// create object
$do = new ContactObject();
// source origin
$do->Origin = OriginTypes::External;
// id
if ($so->id()) {
$do->ID = $so->id();
}
// universal id
if ($so->uid()) {
$do->UUID = $so->uid();
}
// name - last
if ($so->nameLast()) {
$do->Name->Last = $so->nameLast();
}
// name - first
if ($so->nameFirst()) {
$do->Name->First = $so->nameFirst();
}
// name - prefix
if ($so->namePrefix()) {
$do->Name->Prefix = $so->namePrefix();
}
// name - suffix
if ($so->nameSuffix()) {
$do->Name->Suffix = $so->nameSuffix();
}
// anniversary - birth day
if ($so->birthday() && $so->birthday() !== '0000-00-00') {
$when = new DateTimeImmutable($so->birthday());
if ($when) {
$anniversary = new ContactAnniversaryObject();
$anniversary->Type = ContactAnniversaryTypes::Birth;
$anniversary->When = $when;
$do->Anniversaries[] = $anniversary;
}
}
// anniversary - nuptial day
if ($so->nuptialDay() && $so->nuptialDay() !== '0000-00-00') {
$when = new DateTimeImmutable($so->nuptialDay());
if ($when) {
$anniversary = new ContactAnniversaryObject();
$anniversary->Type = ContactAnniversaryTypes::Nuptial;
$anniversary->When = $when;
$do->Anniversaries[] = $anniversary;
}
}
// physical location(s)
foreach ($so->location() as $id => $entry) {
$location = new ContactPhysicalLocationObject();
$location->Context = $entry->type();
$location->Label = $entry->label();
$location->Street = $entry->street();
$location->Locality = $entry->locality();
$location->Region = $entry->region();
$location->Code = $entry->code();
$location->Country = $entry->country();
$location->Id = (string)$id;
$location->Index = $id;
$do->PhysicalLocations[$id] = $location;
}
// phone(s)
foreach ($so->phone() as $id => $entry) {
$phone = new ContactPhoneObject();
$phone->Context = $entry->type();
$phone->Number = $entry->value();
$phone->Label = $entry->label();
$phone->Id = (string)$id;
$phone->Index = $id;
if ($entry->default()) {
$phone->Priority = 1;
}
$do->Phone[$id] = $phone;
}
// email(s)
foreach ($so->email() as $id => $entry) {
$email = new ContactEmailObject();
$email->Context = $entry->type();
$email->Address = $entry->value();
$email->Id = (string)$id;
$email->Index = $id;
$do->Email[$id] = $email;
}
// organization - name
if ($so->organizationName()) {
$organization = new ContactOrganizationObject();
$organization->Label = $so->organizationName();
$organization->Id = '0';
$organization->Index = 0;
$organization->Priority = 1;
$do->Organizations[] = $organization;
}
// title
if ($so->title()) {
$title = new ContactTitleObject();
$title->Kind = ContactTitleTypes::Title;
$title->Label = $so->title();
$title->Id = '0';
$title->Index = 0;
$title->Priority = 1;
$do->Titles[] = $title;
}
// notes
if ($so->notes()) {
$note = new ContactNoteObject();
$note->Content = $so->notes();
$note->Id = '0';
$note->Index = 0;
$note->Priority = 1;
$do->Notes[] = $note;
}
// virtual locations
if ($so->online()) {
foreach ($so->online() as $id => $entry) {
$entity = new ContactVirtualLocationObject();
$entity->Location = $entry->value();
$entity->Context = $entry->type();
$entity->Label = $entry->label();
$email->Id = (string)$id;
$email->Index = $id;
$do->VirtualLocations[$id] = $entity;
}
}
return $do;
}
/**
* convert contact object to jmap object
*
* @since Release 1.0.0
*
*/
public function fromContactObject(ContactObject $so): mixed {
// create object
$do = new ContactParametersRequest();
// universal id
if ($so->UUID) {
$do->uid($so->UUID);
}
// name - last
if ($so->Name->Last) {
$do->nameLast($so->Name->Last);
}
// name - first
if ($so->Name->First) {
$do->nameFirst($so->Name->First);
}
// name - prefix
if ($so->Name->Prefix) {
$do->namePrefix($so->Name->Prefix);
}
// name - suffix
if ($so->Name->Suffix) {
$do->nameSuffix($so->Name->Suffix);
}
// aliases
// only one aliases is supported
if ($so->Name->Aliases->count() > 0) {
$priority = $so->Name->Aliases->highestPriority();
$do->organizationName($so->Name->Aliases[$priority]->Label);
}
// anniversaries
$delta = [ContactAnniversaryTypes::Birth->name => true, ContactAnniversaryTypes::Nuptial->name => true];
foreach ($so->Anniversaries as $id => $entry) {
if ($entry->When === null) {
continue;
}
if ($entry->Type === ContactAnniversaryTypes::Birth) {
$do->birthday($entry->When->format(self::DATE_ANNIVERSARY));
unset($delta[ContactAnniversaryTypes::Birth->name]);
}
if ($entry->Type === ContactAnniversaryTypes::Nuptial) {
$do->nuptialDay($entry->When->format(self::DATE_ANNIVERSARY));
unset($delta[ContactAnniversaryTypes::Nuptial->name]);
}
}
foreach ($delta as $key => $value) {
if ($key === ContactAnniversaryTypes::Birth->name) {
$do->birthday('0000-00-00');
}
if ($key === ContactAnniversaryTypes::Nuptial->name) {
$do->nuptialDay('0000-00-00');
}
}
// phone(s)
foreach ($so->Phone as $id => $entry) {
$entity = $do->phone($id);
$entity->value((string)$entry->Number);
$context = strtolower($entry->Context);
if (in_array($context, ['home', 'work', 'mobile', 'fax', 'page', 'other'], true)) {
$entity->type($entry->Context);
} else {
$entity->type('other');
$entity->label($entry->Context);
}
if ($entry->Priority === 1) {
$entity->default(true);
}
}
// email(s)
foreach ($so->Email as $id => $entry) {
$entity = $do->email($id);
$entity->value((string)$entry->Address);
$context = strtolower($entry->Context);
if (in_array($context, ['personal', 'work', 'other'], true)) {
$entity->type($entry->Context);
} else {
$entity->type('other');
$entity->label($entry->Context);
}
if ($entry->Priority === 1) {
$entity->default(true);
}
}
// physical location(s)
foreach ($so->PhysicalLocations as $id => $entry) {
$entity = $do->location($id);
$entity->type((string)$entry->Context);
$entity->label((string)$entry->Label);
$entity->street((string)$entry->Street);
$entity->locality((string)$entry->Locality);
$entity->region((string)$entry->Region);
$entity->code((string)$entry->Code);
$entity->country((string)$entry->Country);
if ($entry->Priority === 1) {
$entity->default(true);
}
}
// organization - name
// only one organization is supported
if ($so->Organizations->count() > 0) {
$priority = $so->Organizations->highestPriority();
$do->organizationName($so->Organizations[$priority]->Label);
}
// titles
// only one title is supported
if ($so->Titles->count() > 0) {
$priority = $so->Titles->highestPriority(ContactTitleTypes::Title);
if ($priority !== null) {
$do->title($so->Titles[$priority]->Label);
}
}
// notes
// only one note is supported
if ($so->Notes->count() > 0) {
$do->notes($so->Notes[0]->Content);
}
// virtual locations
foreach ($so->VirtualLocations as $id => $entry) {
$entity = $do->online($id);
$entity->type((string)$entry->Context);
$entity->value((string)$entry->Location);
$entity->label((string)$entry->Label);
}
return $do;
}
}

View File

@@ -0,0 +1,109 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2023 Sebastian Krupinski <krupinski01@gmail.com>
*
* @author Sebastian Krupinski <krupinski01@gmail.com>
*
* @license AGPL-3.0-or-later
*
* 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 KTXM\ProviderJmapc\Service\Remote\FM;
use Exception;
use JmapClient\Client;
use JmapClient\Requests\Calendar\EventChanges;
use JmapClient\Requests\Calendar\EventGet;
use JmapClient\Responses\ResponseException;
use OCA\JMAPC\Exceptions\JmapUnknownMethod;
use OCA\JMAPC\Objects\BaseStringCollection;
use OCA\JMAPC\Objects\DeltaObject;
use OCA\JMAPC\Service\Remote\RemoteEventsService;
class RemoteEventsServiceFM extends RemoteEventsService {
public function initialize(Client $dataStore, ?string $dataAccount = null) {
parent::initialize($dataStore, $dataAccount);
$dataStore->configureRequestTypes('parameters', 'CalendarEvent.filter', 'OCA\JMAPC\Jmap\FM\Request\Events\EventFilter');
}
/**
* delta of changes for specific collection in remote storage
*
* @since Release 1.0.0
*
*/
public function entityDeltaSpecific(?string $location, string $state, string $granularity = 'D'): DeltaObject {
// construct set request
$r0 = new EventChanges($this->dataAccount, null, $this->resourceNamespace, $this->resourceEntityLabel);
// set state constraint
if (!empty($state)) {
$r0->state($state);
} else {
$r0->state('0');
}
// construct get for created
$r1 = new EventGet($this->dataAccount, null, $this->resourceNamespace, $this->resourceEntityLabel);
$r1->targetFromRequest($r0, '/created');
$r1->property('calendarIds', 'id', 'created', 'updated');
// construct get for updated
$r2 = new EventGet($this->dataAccount, null, $this->resourceNamespace, $this->resourceEntityLabel);
$r2->targetFromRequest($r0, '/updated');
$r2->property('calendarIds', 'id', 'created', 'updated');
// transceive
$bundle = $this->dataStore->perform([$r0, $r1, $r2]);
// extract response
$response0 = $bundle->response(0);
$response1 = $bundle->response(1);
$response2 = $bundle->response(2);
// determine if command errored
if ($response0 instanceof ResponseException) {
if ($response0->type() === 'unknownMethod') {
throw new JmapUnknownMethod($response0->description(), 1);
} else {
throw new Exception($response0->type() . ': ' . $response0->description(), 1);
}
}
// convert jmap object to delta object
$delta = new DeltaObject();
$delta->signature = $response0->stateNew();
$delta->additions = new BaseStringCollection();
foreach ($response1->objects() as $entry) {
if (in_array($location, $entry->in())) {
$delta->additions[] = $entry->id();
}
}
$delta->modifications = new BaseStringCollection();
foreach ($response2->objects() as $entry) {
if (in_array($location, $entry->in())) {
$delta->modifications[] = $entry->id();
}
}
$delta->deletions = new BaseStringCollection();
foreach ($response0->deleted() as $entry) {
$delta->deletions[] = $entry;
}
return $delta;
}
}