📁
SKYSHELL MANAGER
PHP v8.2.31
Create
Create
Path:
root
/
home
/
thevaxnx
/
nativize.com
/
staging
/
wp-includes
/
js
/
tinymce
/
themes
/
Name
Size
Perm
Actions
📁
inlite
-
0755
🗑️
🏷️
🔒
📁
modern
-
0755
🗑️
🏷️
🔒
📄
wp-links-opml.php
6.83 KB
0444
🗑️
🏷️
⬇️
✏️
🔒
Edit: Sync.php
<?php /* * CardDAV client library for PHP ("PHP-CardDavClient"). * * Copyright (c) 2020-2021 Michael Stilkerich <ms@mike2k.de> * Licensed under the MIT license. See COPYING file in the project root for details. */ declare(strict_types=1); namespace MStilkerich\CardDavClient\Services; use MStilkerich\CardDavClient\{AddressbookCollection, CardDavClient, Config}; 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 { /** * 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. * @psalm-param list<string> $requestedVCardProps * @param array<int,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, SyncHandler $handler, array $requestedVCardProps = [], string $prevSyncToken = "" ): string { // just in case - never sync more than this number of batches in one call $batchLimit = 10; do { --$batchLimit; $syncResult = $this->synchronizeOneBatch($abook, $handler, $requestedVCardProps, $prevSyncToken); if ($syncResult->syncAgain) { // if the server replies with 507 insufficient storage, it needs to provide a sync-token, // otherwise we would never leave this loop. if (empty($syncResult->syncToken)) { Config::$logger->warning("Server reported partial changes only, but no sync-token - not repeating"); break; } else { Config::$logger->debug("Server reported partial changes only, repeating sync for next batch"); $prevSyncToken = $syncResult->syncToken; } } } while ($syncResult->syncAgain && ($batchLimit > 0)); return $syncResult->syncToken; } /** * Performs a synchronization of the given addressbook for one synchronization chunk as dictated by the server. * * @psalm-param list<string> $requestedVCardProps * @param array<int,string> $requestedVCardProps * @return SyncResult The synchronization result object. */ private function synchronizeOneBatch( AddressbookCollection $abook, SyncHandler $handler, array $requestedVCardProps, string $prevSyncToken ): SyncResult { $client = $abook->getClient(); $syncResult = null; // DETERMINE WHICH ADDRESS OBJECTS HAVE CHANGED // If sync-collection is supported by the server, attempt synchronization using the report if ($abook->supportsSyncCollection()) { Config::$logger->debug("Attempting sync using sync-collection report of " . $abook->getUri()); try { // even if the sync-collection failed, the server claims it supports the report. There are // implementations (Google Contacts), that do not accept a sync-collection report with empty sync token. // For these, we will subsequently perform the etag-based sync, but store the sync-token property so // that future syncs may use the sync-collection report $newSyncToken = $abook->getSyncToken(); $syncResult = $this->syncCollection($client, $abook, $prevSyncToken); } catch (\Exception $e) { Config::$logger->error("sync-collection REPORT produced exception", [ 'exception' => $e ]); } } // If sync-collection failed or is not supported, determine changes using getctag property, PROPFIND and address // objects' etags if (!isset($syncResult)) { // Fall back to using the deprecated CTag property to determine whether a collection has changed if // sync-token is not supported if (!isset($newSyncToken)) { $newSyncToken = $abook->getCTag(); } if ((strlen($prevSyncToken) == 0) || is_null($newSyncToken) || ($prevSyncToken !== $newSyncToken)) { Config::$logger->debug("Attempting sync by ETag comparison against local state of " . $abook->getUri()); $syncResult = $this->determineChangesViaETags($client, $abook, $handler); } else { Config::$logger->debug("Skipping sync of up-to-date addressbook (by ctag) " . $abook->getUri()); $syncResult = new SyncResult($prevSyncToken); } } // DELETE THE DELETED ADDRESS OBJECTS foreach ($syncResult->deletedObjects as $delUri) { $handler->addressObjectDeleted($delUri); } // FETCH THE CHANGED ADDRESS OBJECTS if (!empty($syncResult->changedObjects)) { if ($abook->supportsMultiGet()) { try { $this->multiGetChanges($client, $abook, $syncResult, $requestedVCardProps); } catch (\Exception $e) { // if the multiget failed, we can still try to get each card individually Config::$logger->error("addressbook-multiget REPORT produced exception", [ 'exception' => $e ]); } } // try to manually fill all VCards where multiget did not provide VCF data foreach ($syncResult->changedObjects as &$objref) { if (!isset($objref["vcf"])) { Config::$logger->debug("Fetching " . $objref['uri'] . " via GET"); [ 'etag' => $objref["etag"], 'vcf' => $objref["vcf"], 'vcard' => $objref["vcard"] ] = $abook->getCard($objref["uri"]); } } unset($objref); if ($syncResult->createVCards() === false) { Config::$logger->warning("Not for all changed objects, the VCard data was provided by the server"); } foreach ($syncResult->changedObjects as $obj) { $handler->addressObjectChanged($obj["uri"], $obj["etag"], $obj["vcard"] ?? null); } } $handler->finalizeSync(); 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, string $prevSyncToken ): SyncResult { $abookUrl = $abook->getUri(); $multistatus = $client->syncCollection($abookUrl, $prevSyncToken); if (!isset($multistatus->synctoken)) { throw new \Exception("No sync token contained in response to sync-collection REPORT."); } $syncResult = new SyncResult($multistatus->synctoken); foreach ($multistatus->responses as $response) { if ($response instanceof ResponseStatus) { $respStatus = $response->status; foreach ($response->hrefs as $respUri) { if (CardDavClient::compareUrlPaths($respUri, $abookUrl)) { // If the result set is truncated, the response MUST use status code 207 (Multi-Status), return // a DAV:multistatus response body, and indicate a status of 507 (Insufficient Storage) for the // request-URI. if (stripos($respStatus, " 507 ") !== false) { $syncResult->syncAgain = true; } else { Config::$logger->debug("Ignoring response on addressbook itself"); } } elseif (stripos($respStatus, " 404 ") !== false) { // For members that have been removed, the DAV:response MUST contain one DAV:status with a value // set to "404 Not Found" and MUST NOT contain any DAV:propstat element. $syncResult->deletedObjects[] = $respUri; } } } elseif ($response instanceof ResponsePropstat) { $respUri = $response->href; // For members that have changed (i.e., are new or have had their mapped resource modified), the // DAV:response MUST contain at least one DAV:propstat element and MUST NOT contain any DAV:status // element. foreach ($response->propstat as $propstat) { if (CardDavClient::compareUrlPaths($respUri, $abookUrl)) { Config::$logger->debug("Ignoring response on addressbook itself"); } elseif (stripos($propstat->status, " 200 ") !== false) { $syncResult->changedObjects[] = [ 'uri' => $respUri, 'etag' => $propstat->prop->props[XmlEN::GETETAG] ?? "" ]; } } } } 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, SyncHandler $handler ): SyncResult { $abookUrl = $abook->getUri(); $responses = $client->findProperties($abookUrl, [ XmlEN::GETCTAG, XmlEN::GETETAG, XmlEN::SYNCTOKEN ], "1"); // array of local VCards basename (i.e. only the filename) => etag $localCacheState = $handler->getExistingVCardETags(); $newSyncToken = ""; $changes = []; foreach ($responses as $response) { $url = $response["uri"]; $props = $response["props"]; if (CardDavClient::compareUrlPaths($url, $abookUrl)) { $newSyncToken = $props[XmlEN::SYNCTOKEN] ?? $props[XmlEN::GETCTAG] ?? ""; if (empty($newSyncToken)) { Config::$logger->notice("The server provides no token that identifies the addressbook version"); } } else { $etag = $props[XmlEN::GETETAG] ?? null; if (!isset($etag)) { Config::$logger->warning("Server did not provide an ETag for $url, skipping"); } else { ['path' => $uri] = \Sabre\Uri\parse($url); // add new or changed cards to the list of changes if ( (!isset($localCacheState[$uri])) || ($etag !== $localCacheState[$uri]) ) { $changes[] = [ 'uri' => $uri ?? '', 'etag' => $etag ]; } // remove seen so that only the unseen remain for removal if (isset($localCacheState[$uri])) { unset($localCacheState[$uri]); } } } } $syncResult = new SyncResult($newSyncToken); $syncResult->deletedObjects = array_keys($localCacheState); $syncResult->changedObjects = $changes; return $syncResult; } /** * 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<string> $requestedVCardProps * @param array<int,string> $requestedVCardProps */ private function multiGetChanges( CardDavClient $client, AddressbookCollection $abook, SyncResult $syncResult, array $requestedVCardProps ): void { $requestedUris = array_map( function (array $changeObj): string { return $changeObj["uri"]; }, $syncResult->changedObjects ); $multistatus = $client->multiGet($abook->getUri(), $requestedUris, $requestedVCardProps); $results = []; foreach ($multistatus->responses as $response) { $respUri = $response->href; if (!empty($response->propstat)) { foreach ($response->propstat as $propstat) { if (stripos($propstat->status, " 200 ") !== false) { Config::$logger->debug("VCF for $respUri received via multiget"); $results[$respUri] = [ "etag" => $propstat->prop->props[XmlEN::GETETAG] ?? "", "vcf" => $propstat->prop->props[XmlEN::ADDRDATA] ?? "" ]; } } } else { Config::$logger->warning("Unexpected response element in multiget result\n"); } } foreach ($syncResult->changedObjects as &$objref) { $couri = $objref["uri"]; if (isset($results[$couri])) { $objref["etag"] = $results[$couri]["etag"]; $objref["vcf"] = $results[$couri]["vcf"]; } else { Config::$logger->warning("Server did not return data for $couri"); } } unset($objref); } } // vim: ts=4:sw=4:expandtab:fenc=utf8:ff=unix:tw=120
Save