From bbc91d381bb3d99f81bc1cc46b3db0804bace715 Mon Sep 17 00:00:00 2001 From: Michael Stilkerich Date: Fri, 5 Mar 2021 16:43:05 +0100 Subject: [PATCH] Doc for public API --- CHANGELOG.md | 2 + Makefile | 2 +- NOTES.md | 2 + README.md | 6 ++- doc/README.md | 2 + doc/SPNEGO.md | 30 +++++------ src/Account.php | 17 +++--- src/AddressbookCollection.php | 62 ++++++++++++++++----- src/CardDavClient.php | 23 ++++---- src/Config.php | 11 ++-- src/Exception/ClientException.php | 7 +-- src/Exception/NetworkException.php | 6 +-- src/Exception/XmlParseException.php | 9 ++-- src/HttpClientAdapter.php | 2 + src/HttpClientAdapterGuzzle.php | 2 + src/Services/Discovery.php | 49 ++++++++++------- src/Services/Sync.php | 81 +++++++++++++++++++++------- src/Services/SyncHandler.php | 38 +++++++++---- src/Services/SyncResult.php | 50 +++++++++++++---- src/WebDavCollection.php | 5 ++ src/WebDavResource.php | 12 ++++- src/XmlElements/Deserializers.php | 2 + src/XmlElements/ElementNames.php | 2 + src/XmlElements/Filter.php | 2 + src/XmlElements/Multistatus.php | 2 + src/XmlElements/ParamFilter.php | 2 + src/XmlElements/Prop.php | 3 +- src/XmlElements/PropFilter.php | 2 + src/XmlElements/Propstat.php | 2 + src/XmlElements/Response.php | 2 + src/XmlElements/ResponsePropstat.php | 2 + src/XmlElements/ResponseStatus.php | 3 +- src/XmlElements/TextMatch.php | 2 + 33 files changed, 320 insertions(+), 124 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f15e577..48de5a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,3 +4,5 @@ - New API AddressbookCollection::query() for server-side addressbook search - Generated API documentation for the latest release is now published to [github pages](https://mstilkerich.github.io/carddavclient/) + + diff --git a/Makefile b/Makefile index 40a23a5..faaf897 100644 --- a/Makefile +++ b/Makefile @@ -41,7 +41,7 @@ tests-interop: tests/interop/phpunit.xml doc: rm -rf $(DOCDIR) - phpDocumentor.phar -d src/ -t $(DOCDIR) --title="CardDAV Client Library" --setting=graphs.enabled=true + phpDocumentor.phar -d src/ -t $(DOCDIR) --title="CardDAV Client Library" --setting=graphs.enabled=true --validate [ -d ../carddavclient-pages ] && rsync -r --delete --exclude .git doc/api/ ../carddavclient-pages # For github CI system - if AccountData.php is not available, create from AccountData.php.dist diff --git a/NOTES.md b/NOTES.md index 81f061c..b3ea953 100644 --- a/NOTES.md +++ b/NOTES.md @@ -24,3 +24,5 @@ issues have so far been encountered: a bad request result should occur for any other value. [Issue](https://issuetracker.google.com/issues/160190530) - Google reports cards as deleted that have not been deleted between the last sync and the current one, but probably before that. [Issue](https://issuetracker.google.com/issues/160192237) + + diff --git a/README.md b/README.md index 04e1694..42e7bd6 100644 --- a/README.md +++ b/README.md @@ -101,5 +101,9 @@ also uses this library for the interaction with the CardDAV server. An overview of the API is available [here](doc/README.md). The API documentation for the latest released version can be found [here](https://mstilkerich.github.io/carddavclient/). +The public API of the library can be found via the `Public` package in the navigation sidebar. -Documentation for the API can be generated from the source code using [phpDocumentor](https://www.phpdoc.org/) by running `make doc`. +Documentation for the API can be generated from the source code using [phpDocumentor](https://www.phpdoc.org/) by +running `make doc`. + + diff --git a/doc/README.md b/doc/README.md index a2a229e..a604cec 100644 --- a/doc/README.md +++ b/doc/README.md @@ -5,3 +5,5 @@ The following diagram shows the classes of the library and shows the public (blu ![Class diagram](Classes.svg) This library uses [semantic versioning](https://semver.org), for which the above identifies the public API. + + diff --git a/doc/SPNEGO.md b/doc/SPNEGO.md index 55cf4f2..a448369 100644 --- a/doc/SPNEGO.md +++ b/doc/SPNEGO.md @@ -43,29 +43,29 @@ in Apache (it requires the Apache mod\_auth\_gssapi): AuthType GSSAPI AuthName "GSSAPI Logon" - # The server needs access to its kerberos key in the keytab - # It should contain a service principal like HTTP/baikal.domain.com@REALM + # The server needs access to its kerberos key in the keytab + # It should contain a service principal like HTTP/baikal.domain.com@REALM GssapiCredStore keytab:/etc/apache2/apache.keytab - + # The following enables server-side support for credential delegation (not that it needs to - # be enabled on the client-side as well, if desired. You need to specify a directory that the - # webserver can write to - # GSSAPI delegation enables the server to acquire tickets for additional backend services. For - # a CardDAV server, you will not normally need this. For a different service like roundcube - # webmail, this would enable the webmail client for example to authenticate on the user's behalf - # with backend IMAP, SMTP or CardDAV servers. + # be enabled on the client-side as well, if desired. You need to specify a directory that the + # webserver can write to + # GSSAPI delegation enables the server to acquire tickets for additional backend services. For + # a CardDAV server, you will not normally need this. For a different service like roundcube + # webmail, this would enable the webmail client for example to authenticate on the user's behalf + # with backend IMAP, SMTP or CardDAV servers. # GssapiDelegCcacheDir /var/run/apache2/krbclientcache - + # maps the kerberos principal to a local username based on the settings in /etc/krb5.conf # e. g. username@REALM -> username GssapiLocalName On - # Restrict the mechanisms offered by SPNEGO to Kerberos 5 + # Restrict the mechanisms offered by SPNEGO to Kerberos 5 GssapiAllowedMech krb5 - # Optional: The following allows to fallback to Basic authentication if no ticket is available. - # In this case, the username and kerberos password are required and the webserver would use them - # to acquire a ticket-granting ticket for the user from the KDC itself. + # Optional: The following allows to fallback to Basic authentication if no ticket is available. + # In this case, the username and kerberos password are required and the webserver would use them + # to acquire a ticket-granting ticket for the user from the KDC itself. #GssapiBasicAuth On #GssapiBasicAuthMech krb5 @@ -78,4 +78,4 @@ in Apache (it requires the Apache mod\_auth\_gssapi): ``` - + diff --git a/src/Account.php b/src/Account.php index ab3facb..7994030 100644 --- a/src/Account.php +++ b/src/Account.php @@ -29,10 +29,11 @@ /** * Represents an account on a CardDAV Server. + * + * @package Public\Entities */ class Account implements \JsonSerializable { - /********* PROPERTIES *********/ /** @var string */ private $username; @@ -53,6 +54,7 @@ class Account implements \JsonSerializable /** * Construct a new Account object. + * * @param string $discoveryUri * The URI to use for service discovery. This can be a partial URI, in the simplest case just a domain name. Note * that if no protocol is given, https will be used. Unencrypted HTTP will only be done if explicitly given (e.g. @@ -63,10 +65,11 @@ class Account implements \JsonSerializable * The password to use for authentication. If no password is needed (e.g. GSSAPI/Kerberos), this may be an empty * string. * @param string $baseUrl - * The full URL of the CardDAV service. This URL is used as base URL for the underlying {@see CardDavClient} that - * can be retrieved using {@see Account::getClient()}. When relative URIs are passed to the client, they will be - * relative to this base URL. If this account is used for discovery with the {@see Services\Discovery} service, - * this parameter can be omitted. + * The URL of the CardDAV server without the path part (e.g. https://carddav.example.com:443). This URL is used as + * base URL for the underlying {@see CardDavClient} that can be retrieved using {@see Account::getClient()}. When + * relative URIs are passed to the client, they will be relative to this base URL. If this account is used for + * discovery with the {@see Services\Discovery} service, this parameter can be omitted. + * @api */ public function __construct(string $discoveryUri, string $username, string $password, string $baseUrl = null) { @@ -84,6 +87,7 @@ public function __construct(string $discoveryUri, string $username, string $pass * @param array $props An associative array containing the Account attributes. * Keys: discoveryUri, username, password, baseUrl with the meaning from {@see Account::__construct()} * @see Account::jsonSerialize() + * @api */ public static function constructFromArray(array $props): Account { @@ -95,7 +99,6 @@ public static function constructFromArray(array $props): Account } /** @var array{discoveryUri: string, username: string, password: string} & array $props */ - return new Account($props["discoveryUri"], $props["username"], $props["password"], $props["baseUrl"] ?? null); } @@ -133,6 +136,7 @@ public function getClient(?string $baseUrl = null): CardDavClient /** * Returns the discovery URI for this Account. + * @api */ public function getDiscoveryUri(): string { @@ -149,6 +153,7 @@ public function setUrl(string $url): void /** * Returns the base URL of the CardDAV service. + * @api */ public function getUrl(): string { diff --git a/src/AddressbookCollection.php b/src/AddressbookCollection.php index d800e66..2677965 100644 --- a/src/AddressbookCollection.php +++ b/src/AddressbookCollection.php @@ -41,6 +41,8 @@ * message: string, * node: \Sabre\VObject\Component | \Sabre\VObject\Property * } + * + * @package Public\Entities */ class AddressbookCollection extends WebDavCollection { @@ -65,11 +67,12 @@ class AddressbookCollection extends WebDavCollection * component of the URL is returned. This is suggested by RFC6352 to compose the addressbook name. * * @return string Name of the addressbook + * @api */ public function getName(): string { $props = $this->getProperties(); - return $props[XmlEN::DISPNAME] ?? basename($this->uri); + return $props[XmlEN::DISPNAME] ?? $this->getBasename(); } /** @@ -89,6 +92,8 @@ public function __toString(): string * * Note that the result of this function is meant for display, not parsing. Thus the content and formatting of the * text may change without considering backwards compatibility. + * + * @api */ public function getDetails(): string { @@ -137,11 +142,23 @@ public function getDetails(): string return $desc; } + /** + * Queries whether the server supports the addressbook-multiget REPORT on this addressbook collection. + * + * @return bool True if addressbook-multiget is supported for this collection. + * @api + */ public function supportsMultiGet(): bool { return $this->supportsReport(XmlEN::REPORT_MULTIGET); } + /** + * Retrieves the getctag property for this addressbook collection (if supported by the server). + * + * @return string The getctag property, or null if not provided by the server. + * @api + */ public function getCTag(): ?string { $props = $this->getProperties(); @@ -158,6 +175,7 @@ public function getCTag(): ?string * - etag(string): Entity tag of the returned card * - vcf(string): VCard as string * - vcard(VCard): VCard as Sabre/VObject VCard + * @api */ public function getCard(string $uri): array { @@ -175,6 +193,7 @@ public function getCard(string $uri): array * Deletes a VCard from the addressbook. * * @param string $uri The URI of the VCard to be deleted. + * @api */ public function deleteCard(string $uri): void { @@ -186,14 +205,15 @@ public function deleteCard(string $uri): void * Creates a new VCard in the addressbook. * * If the given VCard lacks the mandatory UID property, one will be generated. If the server provides an add-member - * URI, the new card will be POSTed to that URI. Otherwise, the function attempts to store the card do a URI whose + * URI, the new card will be POSTed to that URI. Otherwise, the function attempts to store the card to a URI whose * last path component (filename) is derived from the UID of the VCard. * * @param VCard $vcard The VCard to be stored. - * @return array{uri: string, etag: string} - * Associative array with keys + * @psalm-return array{uri: string, etag: string} + * @return array Associative array with keys * - uri (string): URI of the new resource if the request was successful * - etag (string): Entity tag of the created resource if returned by server, otherwise empty string. + * @api */ public function createCard(VCard $vcard): array { @@ -247,6 +267,7 @@ public function createCard(VCard $vcard): array * @return ?string Returns the ETag of the updated card if provided by the server, null otherwise. If null is * returned, it must be assumed that the server stored the card with modifications and the card * should be read back from the server (this is a good idea anyway). + * @api */ public function updateCard(string $uri, VCard $vcard, string $etag): ?string { @@ -262,16 +283,24 @@ public function updateCard(string $uri, VCard $vcard, string $etag): ?string /** * Issues an addressbook-query report. * - * @param SimpleConditions|ComplexConditions $conditions The query filter conditions, see Filter class for format. - * @param list $requestedVCardProps A list of the requested VCard properties. If empty array, the full - * VCards are requested from the server. - * @param bool $matchAll Whether all or any of the conditions needs to match. - * @param int $limit Tell the server to return at most $limit results. 0 means no limit. - * - * @return array + * @psalm-param SimpleConditions|ComplexConditions $conditions + * @param array $conditions + * The query filter conditions, see {@see Filter::__construct()} for format. + * @psalm-param list $requestedVCardProps + * @param string[] $requestedVCardProps + * A list of the requested VCard properties. If empty array, the full VCards are requested from the server. + * @param bool $matchAll + * Whether all or any of the conditions needs to match. + * @param int $limit + * Tell the server to return at most $limit results. 0 means no limit. * + * @psalm-return array + * @return array Returns an array of matched VCards: + * - The keys of the array are the URIs of the vcards + * - The values are associative arrays with keys etag (type: string) and vcard (type: VCard) * @see Filter * @since v1.1.0 + * @api */ public function query( array $conditions, @@ -323,6 +352,9 @@ public function query( /** * This function replaces some well-known XML namespaces with a long name with shorter names for printing. + * + * @param string $s The fully-qualified XML element name (e.g. {urn:ietf:params:xml:ns:carddav}prop) + * @return string The short name (e.g. {CARDDAV}prop) */ protected function shortenXmlNamespacesForPrinting(string $s): string { @@ -337,6 +369,7 @@ protected function shortenXmlNamespacesForPrinting(string $s): string * Validates a VCard before sending it to a CardDAV server. * * @param VCard $vcard The VCard to be validated. + * @throws \InvalidArgumentException if the validation fails. */ protected function validateCard(VCard $vcard): void { @@ -368,10 +401,11 @@ protected function validateCard(VCard $vcard): void /** * Provides the list of property names that should be requested upon call of refreshProperties(). * - * @return list A list of property names including namespace prefix (e. g. '{DAV:}resourcetype'). + * @psalm-return list + * @return array A list of property names including namespace prefix (e. g. '{DAV:}resourcetype'). * - * @see parent::getProperties() - * @see parent::refreshProperties() + * @see WebDavResource::getProperties() + * @see WebDavResource::refreshProperties() */ protected function getNeededCollectionPropertyNames(): array { diff --git a/src/CardDavClient.php b/src/CardDavClient.php index d6f8995..7da162d 100644 --- a/src/CardDavClient.php +++ b/src/CardDavClient.php @@ -21,10 +21,6 @@ * along with PHP-CardDavClient. If not, see . */ -/** - * Class CardDavClient - */ - declare(strict_types=1); namespace MStilkerich\CardDavClient; @@ -37,15 +33,20 @@ use MStilkerich\CardDavClient\XmlElements\Deserializers; use MStilkerich\CardDavClient\Exception\XmlParseException; -/* -Other needed features: - - Setting extra headers (Depth, Content-Type, charset, If-Match, If-None-Match) - - Debug output HTTP traffic to logfile - */ - /** + * Implements the operations of the CardDAV protocol. + * + * This class implements the lower level interactions with the CardDAV server that are utilized by the higher-level + * operations offered by the public entities ({@see AddressbookCollection} etc.) and services ({@see Services\Sync}, + * {@see Services\Discovery}. + * + * An application interacting with the carddavclient library should not interact with this class directly, and it is + * considered an internal part of the library whose interfaces may change without being considered a change of the + * library's API. + * * @psalm-import-type RequestOptions from HttpClientAdapter * @psalm-import-type PropTypes from Prop + * @package Internal\Communication */ class CardDavClient { @@ -71,6 +72,8 @@ public function __construct(string $base_uri, string $username, string $password } /** + * Requests a sync-collection REPORT from the CardDAV server. + * * Note: Google's server does not accept an empty syncToken, though explicitly allowed for initial sync by RFC6578. * It will respond with 400 Bad Request and error message "Request contains an invalid argument." * diff --git a/src/Config.php b/src/Config.php index f28c3be..0545ea8 100644 --- a/src/Config.php +++ b/src/Config.php @@ -21,16 +21,17 @@ * along with PHP-CardDavClient. If not, see . */ -/** - * Class Config - */ - declare(strict_types=1); namespace MStilkerich\CardDavClient; use Psr\Log\{LoggerInterface, NullLogger}; +/** + * Central configuration of the carddavclient library. + * + * @package Public\Infrastructure + */ class Config { /** @var LoggerInterface */ @@ -44,8 +45,6 @@ public static function init(LoggerInterface $logger = null, LoggerInterface $htt self::$logger = $logger ?? new NullLogger(); self::$httplogger = $httplogger ?? new NullLogger(); } - - // TODO whether to allow repairing errors in VCards } // vim: ts=4:sw=4:expandtab:fenc=utf8:ff=unix:tw=120 diff --git a/src/Exception/ClientException.php b/src/Exception/ClientException.php index cc0dd61..f853472 100644 --- a/src/Exception/ClientException.php +++ b/src/Exception/ClientException.php @@ -21,10 +21,6 @@ * along with PHP-CardDavClient. If not, see . */ -/** - * Implementation of PSR-18 ClientExceptionInterface. - */ - declare(strict_types=1); namespace MStilkerich\CardDavClient\Exception; @@ -33,10 +29,11 @@ /** * Implementation of PSR-18 ClientExceptionInterface. + * + * @package Public\Exceptions */ class ClientException extends \Exception implements ClientExceptionInterface { - } // vim: ts=4:sw=4:expandtab:fenc=utf8:ff=unix:tw=120 diff --git a/src/Exception/NetworkException.php b/src/Exception/NetworkException.php index 766de8d..9f132aa 100644 --- a/src/Exception/NetworkException.php +++ b/src/Exception/NetworkException.php @@ -21,10 +21,6 @@ * along with PHP-CardDavClient. If not, see . */ -/** - * Implementation of PSR-18 NetworkExceptionInterface. - */ - declare(strict_types=1); namespace MStilkerich\CardDavClient\Exception; @@ -34,6 +30,8 @@ /** * Implementation of PSR-18 NetworkExceptionInterface. + * + * @package Public\Exceptions */ class NetworkException extends ClientException implements NetworkExceptionInterface { diff --git a/src/Exception/XmlParseException.php b/src/Exception/XmlParseException.php index 37eb99d..9741c73 100644 --- a/src/Exception/XmlParseException.php +++ b/src/Exception/XmlParseException.php @@ -21,14 +21,15 @@ * along with PHP-CardDavClient. If not, see . */ -/** - * Exception type to indicate that a parsed XML did not comply with the requirements described in its RFC definition. - */ - declare(strict_types=1); namespace MStilkerich\CardDavClient\Exception; +/** + * Exception type to indicate that a parsed XML did not comply with the requirements described in its RFC definition. + * + * @package Public\Exceptions + */ class XmlParseException extends \Exception { } diff --git a/src/HttpClientAdapter.php b/src/HttpClientAdapter.php index 588215f..a9c4542 100644 --- a/src/HttpClientAdapter.php +++ b/src/HttpClientAdapter.php @@ -46,6 +46,8 @@ * body?: string, * headers?: array> * } + * + * @package Internal\Communication */ abstract class HttpClientAdapter { diff --git a/src/HttpClientAdapterGuzzle.php b/src/HttpClientAdapterGuzzle.php index bcf0a48..8e5e603 100644 --- a/src/HttpClientAdapterGuzzle.php +++ b/src/HttpClientAdapterGuzzle.php @@ -34,6 +34,8 @@ * Adapter for the Guzzle HTTP client library. * * @psalm-import-type RequestOptions from HttpClientAdapter + * + * @package Internal\Communication */ class HttpClientAdapterGuzzle extends HttpClientAdapter { diff --git a/src/Services/Discovery.php b/src/Services/Discovery.php index 914c8c3..024a91a 100644 --- a/src/Services/Discovery.php +++ b/src/Services/Discovery.php @@ -28,7 +28,10 @@ use MStilkerich\CardDavClient\{Account, AddressbookCollection, CardDavClient, Config, WebDavCollection}; /** - * Class Discovery - Provides a service to discovery the addressbooks for a CardDAV account. + * Provides a service to discover the addressbooks for a CardDAV account. + * + * It implements the discovery using the mechanisms specified in RFC 6764, which is based on DNS SRV/TXT records and/or + * well-known URI redirection on the server. * * @psalm-type Server = array{ * host: string, @@ -40,28 +43,36 @@ * * @psalm-type SrvRecord = array{pri: int, weight: int, target: string, port: int} * @psalm-type TxtRecord = array{txt: string} + * + * @package Public\Services */ class Discovery { - /********* PROPERTIES *********/ - - /** @var array Some builtins for public providers that don't have discovery properly set up. */ + /** + * Some builtins for public providers that don't have discovery properly set up. + * + * It maps a domain name that is part of the typically used usernames to a working discovery URI. This allows + * discovery from data as typically provided by a user without the application having to care about it. + * + * @var array + */ private const KNOWN_SERVERS = [ "gmail.com" => "www.googleapis.com", "googlemail.com" => "www.googleapis.com", ]; - /********* PUBLIC FUNCTIONS *********/ - /** * Discover the addressbooks for a CardDAV account. * * @param Account $account The CardDAV account providing credentials and initial discovery URI. + * @psalm-return list + * @return AddressbookCollection[] The discovered addressbooks. * - * @return list An array of the discovered addressbooks. + * @throws \Exception + * In case of error, sub-classes of \Exception are thrown, with an error message contained within the \Exception + * object. * - * @throws \Exception In case of error, sub-classes of \Exception are thrown, with an error message contained within - * the \Exception object. + * @api */ public function discoverAddressbooks(Account $account): array { @@ -149,8 +160,6 @@ public function discoverAddressbooks(Account $account): array return $addressbooks; } - /********* PRIVATE FUNCTIONS *********/ - /** * Discovers the CardDAV service for the given domain using DNS SRV lookups. * @@ -158,8 +167,10 @@ public function discoverAddressbooks(Account $account): array * @param bool $force_ssl If true, only services with transport encryption (carddavs) will be discovered, * otherwise the function will try to discover unencrypted (carddav) services after failing * to discover encrypted ones. - * @return list Returns an array of associative arrays of services discovered via DNS. If nothing was found, - * the returned array is empty. + * @psalm-return list + * @return array + * Returns an array of associative arrays of services discovered via DNS. If nothing was found, the returned array + * is empty. */ private function discoverServers(string $host, bool $force_ssl): array { @@ -206,10 +217,10 @@ private function discoverServers(string $host, bool $force_ssl): array /** * Orders DNS records by their prio and weight. * - * TODO weight is not quite correctly handled atm, see RFC2782, but this is not crucial to functionality + * @psalm-param SrvRecord $a + * @psalm-param SrvRecord $b * - * @param SrvRecord $a - * @param SrvRecord $b + * @todo weight is not quite correctly handled atm, see RFC2782, but this is not crucial to functionality */ private static function orderDnsRecords(array $a, array $b): int { @@ -227,8 +238,10 @@ private static function orderDnsRecords(array $a, array $b): int * lookup is only performed for servers that have themselves been discovery using DNS SRV lookups, using the same * service resource record. * - * @param Server $server An server record (associative array) as returned by discoverServers() - * @return list Returns an array of context paths that should be tried for discovery in the provided order. + * @psalm-param Server $server + * @param array $server A server record (associative array) as returned by discoverServers() + * @psalm-return list + * @return string[] The context paths that should be tried for discovery in the provided order. * @see Discovery::discoverServers() */ private function discoverContextPath(array $server): array diff --git a/src/Services/Sync.php b/src/Services/Sync.php index 681e276..4ae19c2 100644 --- a/src/Services/Sync.php +++ b/src/Services/Sync.php @@ -21,10 +21,6 @@ * along with PHP-CardDavClient. If not, see . */ -/** - * Class Sync - */ - declare(strict_types=1); namespace MStilkerich\CardDavClient\Services; @@ -33,24 +29,33 @@ use MStilkerich\CardDavClient\XmlElements\ElementNames as XmlEN; use MStilkerich\CardDavClient\XmlElements\{ResponseStatus, ResponsePropstat}; +/** + * Performs a synchronization of a local cache of the addressbook to the current state on the server. + * + * If supported by the server, the synchronization uses the sync-collection (RFC 6578) report to efficiently request the + * changed cards and the addressbook-multiget (RFC 6352) report to fetch all changed cards in a single request. If the + * server does not support these operations, the service falls back to alternative methods transparently. + * + * @package Public\Services + */ class Sync { - /********* PROPERTIES *********/ - - - /********* PUBLIC FUNCTIONS *********/ - /** * Performs a synchronization of the given addressbook. * * @param AddressbookCollection $abook The addressbook to synchronize * @param SyncHandler $handler A SyncHandler object that will be informed about new/changed and deleted cards. - * @param list $requestedVCardProps List of VCard properties to request for retrieved VCards. If empty the - * full VCards are retrieved. - * @param string $prevSyncToken Sync-token of a previous sync when performing an incremental sync. Empty string to - * perform a full sync (all cards of the addressbook will be reported as changed). + * @psalm-param list $requestedVCardProps + * @param string[] $requestedVCardProps + * List of VCard properties to request for retrieved VCards. If empty the full VCards are retrieved. Note that many + * servers do not support this and will always provide the full cards regardless of this parameter. + * @param string $prevSyncToken + * Sync-token of a previous sync when performing an incremental sync. Empty string to perform a full sync (all + * cards of the addressbook will be reported as changed). * @return string * The sync token corresponding to the just synchronized (or slightly earlier) state of the collection. + * + * @api */ public function synchronize( AddressbookCollection $abook, @@ -81,12 +86,11 @@ public function synchronize( return $syncResult->syncToken; } - /********* PRIVATE FUNCTIONS *********/ - /** - * Performs a synchronization of the given addressbook for one synchronization chunk as dicated by the server. + * Performs a synchronization of the given addressbook for one synchronization chunk as dictated by the server. * - * @param list $requestedVCardProps + * @psalm-param list $requestedVCardProps + * @param string[] $requestedVCardProps * @return SyncResult The synchronization result object. */ private function synchronizeOneBatch( @@ -177,6 +181,18 @@ private function synchronizeOneBatch( return $syncResult; } + /** + * Determines changes to the addressbook at the server side using the sync-collection REPORT. + * + * @param CardDavClient $client + * The client to use for communicating with the server. + * @param AddressbookCollection $abook + * The addressbook that should be synchronized. + * @param string $prevSyncToken + * The sync token of the last sync, or empty string if this is the initial sync. + * @return SyncResult + * Changes to the addressbook reported by the server with respect to $prevSyncToken, including a new sync token. + */ private function syncCollection( CardDavClient $client, AddressbookCollection $abook, @@ -233,6 +249,21 @@ private function syncCollection( return $syncResult; } + /** + * Determines changes to the addressbook at the server side using PROPFIND. + * + * This performs a card-by-card ETag comparison of the current ETags reported by the server and the locally stored + * ETags corresponding to the state of the last retrieved cards. + * + * @param CardDavClient $client + * The client to use for communicating with the server. + * @param AddressbookCollection $abook + * The addressbook that should be synchronized. + * @param SyncHandler $handler + * The application-side sync handler, that will have to provide the list of local cards and their ETags. + * @return SyncResult + * Changes to the addressbook reported by the server with respect to $prevSyncToken, including a new sync token. + */ private function determineChangesViaETags( CardDavClient $client, AddressbookCollection $abook, @@ -289,7 +320,21 @@ private function determineChangesViaETags( } /** - * @param list $requestedVCardProps + * Downloads a set of cards from the server using addressbook-multiget. + * + * The downloaded cards are stored to {@see SyncResult::$changedObjects} along with the corresponding ETag. In case + * the data for a card cannot be retrieved, a warning is logged and the corresponding card will have no data + * associated. + * + * @param CardDavClient $client + * The client to use for communicating with the server. + * @param AddressbookCollection $abook + * The addressbook to fetch the cards from. + * @param SyncResult $syncResult + * The SyncResult object to store the retrieved cards to, which already contains the URIs of the changed cards to + * fetch. + * @psalm-param list $requestedVCardProps + * @param string[] $requestedVCardProps */ private function multiGetChanges( CardDavClient $client, diff --git a/src/Services/SyncHandler.php b/src/Services/SyncHandler.php index 54588dc..55d635a 100644 --- a/src/Services/SyncHandler.php +++ b/src/Services/SyncHandler.php @@ -21,14 +21,6 @@ * along with PHP-CardDavClient. If not, see . */ -/** - * Interface for application-level synchronization handler. - * - * During an addressbook synchronization, the corresponding methods of this interface - * are invoked for events such as changed or deleted address objects, to be handled - * in an application-specific manner. - */ - declare(strict_types=1); namespace MStilkerich\CardDavClient\Services; @@ -37,6 +29,11 @@ /** * Interface for application-level synchronization handler. + * + * During an addressbook synchronization, the corresponding methods of this interface are invoked for events such as + * changed or deleted address objects, to be handled in an application-specific manner. + * + * @package Public\Services */ interface SyncHandler { @@ -54,6 +51,9 @@ interface SyncHandler * @param ?VCard $card * A (partial) VCard containing (at least, if available)the requested VCard properties. Null in case an error * occurred retrieving or parsing the VCard retrieved from the server. + * + * @see Sync + * @api */ public function addressObjectChanged(string $uri, string $etag, ?VCard $card): void; @@ -62,6 +62,9 @@ public function addressObjectChanged(string $uri, string $etag, ?VCard $card): v * * @param string $uri * URI of the deleted address object. + * + * @see Sync + * @api */ public function addressObjectDeleted(string $uri): void; @@ -73,14 +76,29 @@ public function addressObjectDeleted(string $uri): void; * not support the sync-collection report, or if the sync-token has expired on the server and thus the server is not * able to report the changes against the local state. * + * For the first sync, returns an empty array. The {@see Sync} service will consider cards as: + * - new: URI not contained in the returned aray + * - changed: URI contained, assigned local ETag differs from server-side ETag + * - unchanged: URI contained, assigned local ETag equals server-side ETag + * - deleted: URI contained in array, but not reported by server as content of the addressbook + * + * Note: This array is only requested by the {@see Sync} service if needed, which is only the case if the + * sync-collection REPORT cannot be used. Therefore, if it is expensive to construct this array, make sure + * construction is done on demand in this method, which will not be called if the data is not needed. + * * @return array * Associative array with URIs (URL path component without server) as keys, ETags as values. + * + * @see Sync + * @api */ public function getExistingVCardETags(): array; /** - * Called upon completion of the synchronization process to enable the handler to - * perform final actions if needed. + * Called upon completion of the synchronization process to enable the handler to perform final actions if needed. + * + * @see Sync + * @api */ public function finalizeSync(): void; } diff --git a/src/Services/SyncResult.php b/src/Services/SyncResult.php index 6ebe9f5..646951c 100644 --- a/src/Services/SyncResult.php +++ b/src/Services/SyncResult.php @@ -21,10 +21,6 @@ * along with PHP-CardDavClient. If not, see . */ -/** - * Class SyncResult - */ - declare(strict_types=1); namespace MStilkerich\CardDavClient\Services; @@ -32,27 +28,63 @@ use Sabre\VObject\Component\VCard; use MStilkerich\CardDavClient\{CardDavClient, Config}; +/** + * Stores the changes reported by the server to be processed during a sync operation. + * + * This class is used internally only by the {@see Sync} service. + * + * @package Internal\Services + */ class SyncResult { - /** @var string */ + /** + * The new sync token returned by the server. + * @var string + */ public $syncToken; - /** @var bool */ + /** + * True if the server limited the returned differences and another followup sync is needed. + * @var bool + */ public $syncAgain = false; - /** @var list URIs of deleted objects */ + /** + * URIs of deleted objects. + * + * @psalm-var list + * @var string[] + */ public $deletedObjects = []; - /** @var list - * URIs and ETags of new or changed address objects. + /** + * URIs and ETags of new or changed address objects. + * + * @psalm-var list + * @var array */ public $changedObjects = []; + /** + * Construct a new sync result. + * + * @param string $syncToken The new sync token returned by the server. + */ public function __construct(string $syncToken) { $this->syncToken = $syncToken; } + /** + * Creates VCard objects for all changed cards. + * + * The objects are inserted into the {@see SyncResult::$changedObjects} array. In case the VCard object cannot be + * created for some of the cards (for example parse error), an error is logged. If no vcard string data is available + * in {@see SyncResult::$changedObjects} for a VCard, a warning is logged. + * + * @return bool + * True if a VCard could be created for all cards in {@see SyncResult::$changedObjects}, false otherwise. + */ public function createVCards(): bool { $ret = true; diff --git a/src/WebDavCollection.php b/src/WebDavCollection.php index 1498ff8..050882e 100644 --- a/src/WebDavCollection.php +++ b/src/WebDavCollection.php @@ -29,6 +29,8 @@ /** * Represents a collection on a WebDAV server. + * + * @package Public\Entities */ class WebDavCollection extends WebDavResource { @@ -52,6 +54,7 @@ class WebDavCollection extends WebDavResource * sync token is provided. * * @return ?string The sync token, or null if the server does not provide a sync-token for this collection. + * @api */ public function getSyncToken(): ?string { @@ -62,6 +65,7 @@ public function getSyncToken(): ?string /** * Queries whether the server supports the sync-collection REPORT on this collection. * @return bool True if sync-collection is supported for this collection. + * @api */ public function supportsSyncCollection(): bool { @@ -73,6 +77,7 @@ public function supportsSyncCollection(): bool * * @psalm-return list * @return array The children of this collection. + * @api */ public function getChildren(): array { diff --git a/src/WebDavResource.php b/src/WebDavResource.php index 2f15f7d..911b419 100644 --- a/src/WebDavResource.php +++ b/src/WebDavResource.php @@ -33,6 +33,8 @@ * Represents a resource on a WebDAV server. * * @psalm-import-type PropTypes from Prop + * + * @package Public\Entities */ class WebDavResource implements \JsonSerializable { @@ -88,6 +90,7 @@ class WebDavResource implements \JsonSerializable * @param null|array $restype * Array with the DAV:resourcetype properties of the URI (if already available saves the query) * @return WebDavResource An object that is an instance of the most suited subclass of WebDavResource. + * @api */ public static function createInstance(string $uri, Account $account, ?array $restype = null): WebDavResource { @@ -113,6 +116,7 @@ public static function createInstance(string $uri, Account $account, ?array $res * The target URI of the resource. * @param Account $account * The account by which the URI shall be accessed. + * @api */ public function __construct(string $uri, Account $account) { @@ -144,6 +148,7 @@ protected function getProperties(): array * Forces a refresh of the cached standard WebDAV properties for this resource. * * @see WebDavResource::getProperties() + * @api */ public function refreshProperties(): void { @@ -169,6 +174,7 @@ public function jsonSerialize(): array /** * Returns the Account this resource belongs to. + * @api */ public function getAccount(): Account { @@ -190,6 +196,7 @@ public function getClient(): CardDavClient /** * Returns the URI of this resource. + * @api */ public function getUri(): string { @@ -198,6 +205,7 @@ public function getUri(): string /** * Returns the path component of the URI of this resource. + * @api */ public function getUriPath(): string { @@ -207,6 +215,7 @@ public function getUriPath(): string /** * Returns the basename (last path component) of the URI of this resource. + * @api */ public function getBasename(): string { @@ -220,11 +229,12 @@ public function getBasename(): string * Downloads the content of a given resource. * * @param string $uri - * URI of the requested resource. May be relative to the URI of this resource. + * URI of the requested resource. * * @psalm-return array{body: string} * @return array * An associative array where the key 'body' maps to the content of the requested resource. + * @api */ public function downloadResource(string $uri): array { diff --git a/src/XmlElements/Deserializers.php b/src/XmlElements/Deserializers.php index aa07630..d34d6ce 100644 --- a/src/XmlElements/Deserializers.php +++ b/src/XmlElements/Deserializers.php @@ -38,6 +38,8 @@ * attributes: array, * value: mixed * } + * + * @package Internal\XmlElements */ class Deserializers { diff --git a/src/XmlElements/ElementNames.php b/src/XmlElements/ElementNames.php index b600e45..841e05d 100644 --- a/src/XmlElements/ElementNames.php +++ b/src/XmlElements/ElementNames.php @@ -31,6 +31,8 @@ * This class serves merely as a collection of constants containing fully qualified XML * element names used inside this library in clark notation, i.e. including the namespace * as a braced prefix. + * + * @package Internal\XmlElements */ class ElementNames { diff --git a/src/XmlElements/Filter.php b/src/XmlElements/Filter.php index 991820d..08b71ca 100644 --- a/src/XmlElements/Filter.php +++ b/src/XmlElements/Filter.php @@ -52,6 +52,8 @@ * @psalm-type SimpleConditions = array * @psalm-type ComplexCondition = array{matchAll?: bool} & array * @psalm-type ComplexConditions = list + * + * @package Internal\XmlElements */ class Filter implements \Sabre\Xml\XmlSerializable { diff --git a/src/XmlElements/Multistatus.php b/src/XmlElements/Multistatus.php index 017a337..4316310 100644 --- a/src/XmlElements/Multistatus.php +++ b/src/XmlElements/Multistatus.php @@ -43,6 +43,8 @@ * @template RT of Response * * @psalm-import-type DeserializedElem from Deserializers + * + * @package Internal\XmlElements */ class Multistatus implements \Sabre\Xml\XmlDeserializable { diff --git a/src/XmlElements/ParamFilter.php b/src/XmlElements/ParamFilter.php index 28cd43f..fffc106 100644 --- a/src/XmlElements/ParamFilter.php +++ b/src/XmlElements/ParamFilter.php @@ -44,6 +44,8 @@ * * * + * + * @package Internal\XmlElements */ class ParamFilter implements \Sabre\Xml\XmlSerializable { diff --git a/src/XmlElements/Prop.php b/src/XmlElements/Prop.php index 1a12cb5..0fa663b 100644 --- a/src/XmlElements/Prop.php +++ b/src/XmlElements/Prop.php @@ -21,7 +21,6 @@ * along with PHP-CardDavClient. If not, see . */ - declare(strict_types=1); namespace MStilkerich\CardDavClient\XmlElements; @@ -47,6 +46,8 @@ * '{urn:ietf:params:xml:ns:carddav}addressbook-description'?: string, * '{urn:ietf:params:xml:ns:carddav}max-resource-size'?: int, * } + * + * @package Internal\XmlElements */ class Prop implements \Sabre\Xml\XmlDeserializable { diff --git a/src/XmlElements/PropFilter.php b/src/XmlElements/PropFilter.php index f7cd879..33e2bcb 100644 --- a/src/XmlElements/PropFilter.php +++ b/src/XmlElements/PropFilter.php @@ -58,6 +58,8 @@ * allof logical AND for text-match/param-filter matches --> * * @psalm-import-type ComplexCondition from Filter + * + * @package Internal\XmlElements */ class PropFilter implements \Sabre\Xml\XmlSerializable { diff --git a/src/XmlElements/Propstat.php b/src/XmlElements/Propstat.php index 41c934e..5ec36c2 100644 --- a/src/XmlElements/Propstat.php +++ b/src/XmlElements/Propstat.php @@ -42,6 +42,8 @@ * @psalm-immutable * * @psalm-import-type DeserializedElem from Deserializers + * + * @package Internal\XmlElements */ class Propstat implements \Sabre\Xml\XmlDeserializable { diff --git a/src/XmlElements/Response.php b/src/XmlElements/Response.php index d3a9b74..5b5119d 100644 --- a/src/XmlElements/Response.php +++ b/src/XmlElements/Response.php @@ -59,6 +59,8 @@ * @psalm-immutable * * @psalm-import-type DeserializedElem from Deserializers + * + * @package Internal\XmlElements */ abstract class Response implements \Sabre\Xml\XmlDeserializable { diff --git a/src/XmlElements/ResponsePropstat.php b/src/XmlElements/ResponsePropstat.php index 2d637c2..aced71a 100644 --- a/src/XmlElements/ResponsePropstat.php +++ b/src/XmlElements/ResponsePropstat.php @@ -32,6 +32,8 @@ * Class to represent XML DAV:response elements with propstat children as PHP objects. * * @psalm-immutable + * + * @package Internal\XmlElements */ class ResponsePropstat extends Response { diff --git a/src/XmlElements/ResponseStatus.php b/src/XmlElements/ResponseStatus.php index 378310c..029be71 100644 --- a/src/XmlElements/ResponseStatus.php +++ b/src/XmlElements/ResponseStatus.php @@ -21,7 +21,6 @@ * along with PHP-CardDavClient. If not, see . */ - declare(strict_types=1); namespace MStilkerich\CardDavClient\XmlElements; @@ -33,6 +32,8 @@ * Class to represent XML DAV:response elements with status children as PHP objects. * * @psalm-immutable + * + * @package Internal\XmlElements */ class ResponseStatus extends Response { diff --git a/src/XmlElements/TextMatch.php b/src/XmlElements/TextMatch.php index c4ccb82..5ee5337 100644 --- a/src/XmlElements/TextMatch.php +++ b/src/XmlElements/TextMatch.php @@ -55,6 +55,8 @@ * collation CDATA "i;unicode-casemap" * negate-condition (yes | no) "no" * match-type (equals|contains|starts-with|ends-with) "contains"> + * + * @package Internal\XmlElements */ class TextMatch implements \Sabre\Xml\XmlSerializable {