ContactsManager.php 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. <?php
  2. /**
  3. * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
  4. * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
  5. * SPDX-License-Identifier: AGPL-3.0-only
  6. */
  7. namespace OC;
  8. use OCP\Constants;
  9. use OCP\Contacts\IManager;
  10. use OCP\IAddressBook;
  11. class ContactsManager implements IManager {
  12. /**
  13. * This function is used to search and find contacts within the users address books.
  14. * In case $pattern is empty all contacts will be returned.
  15. *
  16. * @param string $pattern which should match within the $searchProperties
  17. * @param array $searchProperties defines the properties within the query pattern should match
  18. * @param array $options = array() to define the search behavior
  19. * - 'types' boolean (since 15.0.0) If set to true, fields that come with a TYPE property will be an array
  20. * example: ['id' => 5, 'FN' => 'Thomas Tanghus', 'EMAIL' => ['type => 'HOME', 'value' => 'g@h.i']]
  21. * - 'escape_like_param' - If set to false wildcards _ and % are not escaped
  22. * - 'limit' - Set a numeric limit for the search results
  23. * - 'offset' - Set the offset for the limited search results
  24. * - 'enumeration' - (since 23.0.0) Whether user enumeration on system address book is allowed
  25. * - 'fullmatch' - (since 23.0.0) Whether matching on full detail in system address book is allowed
  26. * - 'strict_search' - (since 23.0.0) Whether the search pattern is full string or partial search
  27. * @psalm-param array{types?: bool, escape_like_param?: bool, limit?: int, offset?: int, enumeration?: bool, fullmatch?: bool, strict_search?: bool} $options
  28. * @return array an array of contacts which are arrays of key-value-pairs
  29. */
  30. public function search($pattern, $searchProperties = [], $options = []) {
  31. $this->loadAddressBooks();
  32. $result = [];
  33. foreach ($this->addressBooks as $addressBook) {
  34. $searchOptions = $options;
  35. $strictSearch = array_key_exists('strict_search', $options) && $options['strict_search'] === true;
  36. if ($addressBook->isSystemAddressBook()) {
  37. $enumeration = !\array_key_exists('enumeration', $options) || $options['enumeration'] !== false;
  38. $fullMatch = !\array_key_exists('fullmatch', $options) || $options['fullmatch'] !== false;
  39. if (!$enumeration && !$fullMatch) {
  40. // No access to system address book AND no full match allowed
  41. continue;
  42. }
  43. if ($strictSearch) {
  44. $searchOptions['wildcard'] = false;
  45. } else {
  46. $searchOptions['wildcard'] = $enumeration;
  47. }
  48. } else {
  49. $searchOptions['wildcard'] = !$strictSearch;
  50. }
  51. $r = $addressBook->search($pattern, $searchProperties, $searchOptions);
  52. $contacts = [];
  53. foreach ($r as $c) {
  54. $c['addressbook-key'] = $addressBook->getKey();
  55. $contacts[] = $c;
  56. }
  57. $result = array_merge($result, $contacts);
  58. }
  59. return $result;
  60. }
  61. /**
  62. * This function can be used to delete the contact identified by the given id
  63. *
  64. * @param int $id the unique identifier to a contact
  65. * @param string $addressBookKey identifier of the address book in which the contact shall be deleted
  66. * @return bool successful or not
  67. */
  68. public function delete($id, $addressBookKey) {
  69. $addressBook = $this->getAddressBook($addressBookKey);
  70. if (!$addressBook) {
  71. return false;
  72. }
  73. if ($addressBook->getPermissions() & Constants::PERMISSION_DELETE) {
  74. return $addressBook->delete($id);
  75. }
  76. return false;
  77. }
  78. /**
  79. * This function is used to create a new contact if 'id' is not given or not present.
  80. * Otherwise the contact will be updated by replacing the entire data set.
  81. *
  82. * @param array $properties this array if key-value-pairs defines a contact
  83. * @param string $addressBookKey identifier of the address book in which the contact shall be created or updated
  84. * @return ?array representing the contact just created or updated
  85. */
  86. public function createOrUpdate($properties, $addressBookKey) {
  87. $addressBook = $this->getAddressBook($addressBookKey);
  88. if (!$addressBook) {
  89. return null;
  90. }
  91. if ($addressBook->getPermissions() & Constants::PERMISSION_CREATE) {
  92. return $addressBook->createOrUpdate($properties);
  93. }
  94. return null;
  95. }
  96. /**
  97. * Check if contacts are available (e.g. contacts app enabled)
  98. *
  99. * @return bool true if enabled, false if not
  100. */
  101. public function isEnabled(): bool {
  102. return !empty($this->addressBooks) || !empty($this->addressBookLoaders);
  103. }
  104. /**
  105. * @param IAddressBook $addressBook
  106. */
  107. public function registerAddressBook(IAddressBook $addressBook) {
  108. $this->addressBooks[$addressBook->getKey()] = $addressBook;
  109. }
  110. /**
  111. * @param IAddressBook $addressBook
  112. */
  113. public function unregisterAddressBook(IAddressBook $addressBook) {
  114. unset($this->addressBooks[$addressBook->getKey()]);
  115. }
  116. /**
  117. * Return a list of the user's addressbooks
  118. *
  119. * @return IAddressBook[]
  120. * @since 16.0.0
  121. */
  122. public function getUserAddressBooks(): array {
  123. $this->loadAddressBooks();
  124. return $this->addressBooks;
  125. }
  126. /**
  127. * removes all registered address book instances
  128. */
  129. public function clear() {
  130. $this->addressBooks = [];
  131. $this->addressBookLoaders = [];
  132. }
  133. /**
  134. * @var IAddressBook[] which holds all registered address books
  135. */
  136. private $addressBooks = [];
  137. /**
  138. * @var \Closure[] to call to load/register address books
  139. */
  140. private $addressBookLoaders = [];
  141. /**
  142. * In order to improve lazy loading a closure can be registered which will be called in case
  143. * address books are actually requested
  144. *
  145. * @param \Closure $callable
  146. */
  147. public function register(\Closure $callable) {
  148. $this->addressBookLoaders[] = $callable;
  149. }
  150. /**
  151. * Get (and load when needed) the address book for $key
  152. */
  153. protected function getAddressBook(string $addressBookKey): ?IAddressBook {
  154. $this->loadAddressBooks();
  155. if (!array_key_exists($addressBookKey, $this->addressBooks)) {
  156. return null;
  157. }
  158. return $this->addressBooks[$addressBookKey];
  159. }
  160. /**
  161. * Load all address books registered with 'register'
  162. */
  163. protected function loadAddressBooks() {
  164. foreach ($this->addressBookLoaders as $callable) {
  165. $callable($this);
  166. }
  167. $this->addressBookLoaders = [];
  168. }
  169. }