1616use OCP \IConfig ;
1717use OCP \IGroupManager ;
1818use OCP \IUser ;
19+ use OCP \IUserManager ;
1920use OCP \IUserSession ;
2021use OCP \Mail \IEmailValidator ;
2122use OCP \Share \IShare ;
23+ use RuntimeException ;
2224
2325class MailPlugin implements ISearchPlugin {
2426 protected bool $ shareWithGroupOnly ;
@@ -41,6 +43,7 @@ public function __construct(
4143 private KnownUserService $ knownUserService ,
4244 private IUserSession $ userSession ,
4345 private IEmailValidator $ emailValidator ,
46+ private IUserManager $ userManager ,
4447 private mixed $ shareWithGroupOnlyExcludeGroupsList ,
4548 private int $ shareType ,
4649 ) {
@@ -66,83 +69,125 @@ public function search($search, $limit, $offset, ISearchResult $searchResult): b
6669 }
6770
6871 // Extract the email address from "Foo Bar <foo.bar@example.tld>" and then search with "foo.bar@example.tld" instead
69- $ result = preg_match ('/<([^@]+@.+)>$/ ' , $ search , $ matches );
70- if ($ result && filter_var ($ matches [1 ], FILTER_VALIDATE_EMAIL )) {
71- return $ this ->search ($ matches [1 ], $ limit , $ offset , $ searchResult );
72+ if (preg_match ('/<([^@]+@.+)>$/ ' , $ search , $ matches ) && filter_var ($ matches [1 ], FILTER_VALIDATE_EMAIL )) {
73+ $ search = $ matches [1 ];
7274 }
7375
7476 $ currentUserId = $ this ->userSession ->getUser ()->getUID ();
77+ $ userGroups = null ;
7578
76- $ result = $ userResults = ['wide ' => [], 'exact ' => []];
77- $ userType = new SearchResultType ('users ' );
78- $ emailType = new SearchResultType ('emails ' );
79+ $ hasMore = false ;
80+ $ count = 0 ;
81+ $ results = ['wide ' => [], 'exact ' => []];
82+ $ type = match ($ this ->shareType ) {
83+ IShare::TYPE_USER => new SearchResultType ('users ' ),
84+ IShare::TYPE_EMAIL => new SearchResultType ('emails ' ),
85+ default => throw new RuntimeException (),
86+ };
7987
8088 // Search in contacts
8189 $ addressBookContacts = $ this ->contactsManager ->search (
8290 $ search ,
8391 ['EMAIL ' , 'FN ' ],
8492 [
85- 'limit ' => $ limit ,
93+ // We request one more, so we can check if there are more results available
94+ 'limit ' => $ limit + 1 ,
8695 'offset ' => $ offset ,
8796 'enumeration ' => $ this ->shareeEnumeration ,
8897 'fullmatch ' => $ this ->shareeEnumerationFullMatch ,
8998 ]
9099 );
91100 $ lowerSearch = strtolower ($ search );
92101 foreach ($ addressBookContacts as $ contact ) {
93- if (isset ($ contact ['EMAIL ' ])) {
94- $ emailAddresses = $ contact ['EMAIL ' ];
95- if (\is_string ($ emailAddresses )) {
96- $ emailAddresses = [$ emailAddresses ];
102+ if (!isset ($ contact ['EMAIL ' ])) {
103+ continue ;
104+ }
105+
106+ $ emailAddresses = $ contact ['EMAIL ' ];
107+ if (\is_string ($ emailAddresses )) {
108+ $ emailAddresses = [$ emailAddresses ];
109+ }
110+ foreach ($ emailAddresses as $ emailAddress ) {
111+ $ displayName = $ emailAddress ;
112+ $ emailAddressType = null ;
113+ if (\is_array ($ emailAddress )) {
114+ $ emailAddressData = $ emailAddress ;
115+ $ emailAddress = $ emailAddressData ['value ' ];
116+ $ emailAddressType = $ emailAddressData ['type ' ];
117+ }
118+
119+ if (!filter_var ($ emailAddress , FILTER_VALIDATE_EMAIL )) {
120+ continue ;
97121 }
98- foreach ($ emailAddresses as $ type => $ emailAddress ) {
99- $ displayName = $ emailAddress ;
100- $ emailAddressType = null ;
101- if (\is_array ($ emailAddress )) {
102- $ emailAddressData = $ emailAddress ;
103- $ emailAddress = $ emailAddressData ['value ' ];
104- $ emailAddressType = $ emailAddressData ['type ' ];
105- }
106122
107- if (!filter_var ($ emailAddress , FILTER_VALIDATE_EMAIL )) {
123+ if (isset ($ contact ['FN ' ])) {
124+ $ displayName = $ contact ['FN ' ] . ' ( ' . $ emailAddress . ') ' ;
125+ }
126+ $ exactEmailMatch = strtolower ($ emailAddress ) === $ lowerSearch ;
127+
128+ if (isset ($ contact ['isLocalSystemBook ' ])) {
129+ $ contactUser = $ this ->userManager ->get ($ contact ['UID ' ]);
130+ if ($ contactUser === null ) {
108131 continue ;
109132 }
110133
111- if (isset ($ contact ['FN ' ])) {
112- $ displayName = $ contact ['FN ' ] . ' ( ' . $ emailAddress . ') ' ;
134+ $ contactGroups = $ this ->groupManager ->getUserGroupIds ($ contactUser );
135+ if ($ this ->shareWithGroupOnly ) {
136+ $ userGroups ??= $ this ->groupManager ->getUserGroupIds ($ this ->userSession ->getUser ());
137+ if (array_intersect ($ contactGroups , array_diff ($ userGroups , $ this ->shareWithGroupOnlyExcludeGroupsList )) === []) {
138+ continue ;
139+ }
113140 }
114- $ exactEmailMatch = strtolower ($ emailAddress ) === $ lowerSearch ;
115-
116- if (isset ($ contact ['isLocalSystemBook ' ])) {
117- if ($ this ->shareWithGroupOnly ) {
118- /*
119- * Check if the user may share with the user associated with the e-mail of the just found contact
120- */
121- $ userGroups = $ this ->groupManager ->getUserGroupIds ($ this ->userSession ->getUser ());
122-
123- // ShareWithGroupOnly filtering
124- $ userGroups = array_diff ($ userGroups , $ this ->shareWithGroupOnlyExcludeGroupsList );
125-
126- $ found = false ;
127- foreach ($ userGroups as $ userGroup ) {
128- if ($ this ->groupManager ->isInGroup ($ contact ['UID ' ], $ userGroup )) {
129- $ found = true ;
130- break ;
131- }
132- }
133- if (!$ found ) {
134- continue ;
135- }
141+
142+ if ($ exactEmailMatch && $ this ->shareeEnumerationFullMatch ) {
143+ try {
144+ $ cloud = $ this ->cloudIdManager ->resolveCloudId ($ contact ['CLOUD ' ][0 ] ?? '' );
145+ } catch (\InvalidArgumentException $ e ) {
146+ continue ;
147+ }
148+
149+ if ($ this ->shareType === IShare::TYPE_USER && !$ this ->isCurrentUser ($ cloud ) && !$ searchResult ->hasResult ($ type , $ cloud ->getUser ())) {
150+ $ singleResult = [[
151+ 'label ' => $ displayName ,
152+ 'uuid ' => $ contact ['UID ' ] ?? $ emailAddress ,
153+ 'name ' => $ contact ['FN ' ] ?? $ displayName ,
154+ 'value ' => [
155+ 'shareType ' => IShare::TYPE_USER ,
156+ 'shareWith ' => $ cloud ->getUser (),
157+ ],
158+ 'shareWithDisplayNameUnique ' => !empty ($ emailAddress ) ? $ emailAddress : $ cloud ->getUser ()
159+ ]];
160+ $ searchResult ->addResultSet ($ type , [], $ singleResult );
161+ $ searchResult ->markExactIdMatch ($ type );
136162 }
137- if ($ exactEmailMatch && $ this ->shareeEnumerationFullMatch ) {
138- try {
139- $ cloud = $ this ->cloudIdManager ->resolveCloudId ($ contact ['CLOUD ' ][0 ] ?? '' );
140- } catch (\InvalidArgumentException $ e ) {
163+ return false ;
164+ }
165+
166+ if ($ this ->shareeEnumeration && $ this ->shareType === IShare::TYPE_USER ) {
167+ try {
168+ if (!isset ($ contact ['CLOUD ' ])) {
141169 continue ;
142170 }
171+ $ cloud = $ this ->cloudIdManager ->resolveCloudId ($ contact ['CLOUD ' ][0 ] ?? '' );
172+ } catch (\InvalidArgumentException $ e ) {
173+ continue ;
174+ }
175+ $ addToWide = !($ this ->shareeEnumerationInGroupOnly || $ this ->shareeEnumerationPhone );
143176
144- if ($ this ->shareType === IShare::TYPE_USER && !$ this ->isCurrentUser ($ cloud ) && !$ searchResult ->hasResult ($ userType , $ cloud ->getUser ())) {
145- $ singleResult = [[
177+ if (!$ addToWide && $ this ->shareeEnumerationPhone && $ this ->knownUserService ->isKnownToUser ($ currentUserId , $ contact ['UID ' ])) {
178+ $ addToWide = true ;
179+ }
180+
181+ if (!$ addToWide && $ this ->shareeEnumerationInGroupOnly ) {
182+ $ userGroups ??= $ this ->groupManager ->getUserGroupIds ($ this ->userSession ->getUser ());
183+ $ addToWide = array_intersect ($ contactGroups , $ userGroups ) !== [];
184+ }
185+
186+ if ($ addToWide && !$ this ->isCurrentUser ($ cloud ) && !$ searchResult ->hasResult ($ type , $ cloud ->getUser ())) {
187+ if ($ count ++ >= $ limit ) {
188+ $ hasMore = true ;
189+ } else {
190+ $ results ['wide ' ][] = [
146191 'label ' => $ displayName ,
147192 'uuid ' => $ contact ['UID ' ] ?? $ emailAddress ,
148193 'name ' => $ contact ['FN ' ] ?? $ displayName ,
@@ -151,122 +196,69 @@ public function search($search, $limit, $offset, ISearchResult $searchResult): b
151196 'shareWith ' => $ cloud ->getUser (),
152197 ],
153198 'shareWithDisplayNameUnique ' => !empty ($ emailAddress ) ? $ emailAddress : $ cloud ->getUser ()
154-
155- ]];
156- $ searchResult ->addResultSet ($ userType , [], $ singleResult );
157- $ searchResult ->markExactIdMatch ($ emailType );
199+ ];
158200 }
159- return false ;
160201 }
202+ }
161203
162- if ($ this ->shareeEnumeration ) {
163- try {
164- if (!isset ($ contact ['CLOUD ' ])) {
165- continue ;
166- }
167- $ cloud = $ this ->cloudIdManager ->resolveCloudId ($ contact ['CLOUD ' ][0 ] ?? '' );
168- } catch (\InvalidArgumentException $ e ) {
169- continue ;
170- }
171-
172- $ addToWide = !($ this ->shareeEnumerationInGroupOnly || $ this ->shareeEnumerationPhone );
173- if (!$ addToWide && $ this ->shareeEnumerationPhone && $ this ->knownUserService ->isKnownToUser ($ currentUserId , $ contact ['UID ' ])) {
174- $ addToWide = true ;
175- }
204+ continue ;
205+ }
176206
177- if (!$ addToWide && $ this ->shareeEnumerationInGroupOnly ) {
178- $ addToWide = false ;
179- $ userGroups = $ this ->groupManager ->getUserGroupIds ($ this ->userSession ->getUser ());
180- foreach ($ userGroups as $ userGroup ) {
181- if ($ this ->groupManager ->isInGroup ($ contact ['UID ' ], $ userGroup )) {
182- $ addToWide = true ;
183- break ;
184- }
185- }
186- }
187- if ($ addToWide && !$ this ->isCurrentUser ($ cloud ) && !$ searchResult ->hasResult ($ userType , $ cloud ->getUser ())) {
188- if ($ this ->shareType === IShare::TYPE_USER ) {
189- $ userResults ['wide ' ][] = [
190- 'label ' => $ displayName ,
191- 'uuid ' => $ contact ['UID ' ] ?? $ emailAddress ,
192- 'name ' => $ contact ['FN ' ] ?? $ displayName ,
193- 'value ' => [
194- 'shareType ' => IShare::TYPE_USER ,
195- 'shareWith ' => $ cloud ->getUser (),
196- ],
197- 'shareWithDisplayNameUnique ' => !empty ($ emailAddress ) ? $ emailAddress : $ cloud ->getUser ()
198- ];
199- }
200- continue ;
201- }
202- }
203- continue ;
204- }
207+ if ($ this ->shareType !== IShare::TYPE_EMAIL ) {
208+ continue ;
209+ }
205210
206- if ($ this ->shareType !== IShare::TYPE_EMAIL ) {
207- continue ;
211+ if ($ count ++ >= $ limit ) {
212+ $ hasMore = true ;
213+ } elseif ($ exactEmailMatch || (isset ($ contact ['FN ' ]) && strtolower ($ contact ['FN ' ]) === $ lowerSearch )) {
214+ if ($ exactEmailMatch ) {
215+ $ searchResult ->markExactIdMatch ($ type );
208216 }
209217
210- if ($ exactEmailMatch
211- || (isset ($ contact ['FN ' ]) && strtolower ($ contact ['FN ' ]) === $ lowerSearch )) {
212- if ($ exactEmailMatch ) {
213- $ searchResult ->markExactIdMatch ($ emailType );
214- }
215- $ result ['exact ' ][] = [
216- 'label ' => $ displayName ,
217- 'uuid ' => $ contact ['UID ' ] ?? $ emailAddress ,
218- 'name ' => $ contact ['FN ' ] ?? $ displayName ,
219- 'type ' => $ emailAddressType ?? '' ,
220- 'value ' => [
221- 'shareType ' => IShare::TYPE_EMAIL ,
222- 'shareWith ' => $ emailAddress ,
223- ],
224- ];
225- } else {
226- $ result ['wide ' ][] = [
227- 'label ' => $ displayName ,
228- 'uuid ' => $ contact ['UID ' ] ?? $ emailAddress ,
229- 'name ' => $ contact ['FN ' ] ?? $ displayName ,
230- 'type ' => $ emailAddressType ?? '' ,
231- 'value ' => [
232- 'shareType ' => IShare::TYPE_EMAIL ,
233- 'shareWith ' => $ emailAddress ,
234- ],
235- ];
236- }
218+ $ results ['exact ' ][] = [
219+ 'label ' => $ displayName ,
220+ 'uuid ' => $ contact ['UID ' ] ?? $ emailAddress ,
221+ 'name ' => $ contact ['FN ' ] ?? $ displayName ,
222+ 'type ' => $ emailAddressType ?? '' ,
223+ 'value ' => [
224+ 'shareType ' => IShare::TYPE_EMAIL ,
225+ 'shareWith ' => $ emailAddress ,
226+ ],
227+ ];
228+ } else {
229+ $ results ['wide ' ][] = [
230+ 'label ' => $ displayName ,
231+ 'uuid ' => $ contact ['UID ' ] ?? $ emailAddress ,
232+ 'name ' => $ contact ['FN ' ] ?? $ displayName ,
233+ 'type ' => $ emailAddressType ?? '' ,
234+ 'value ' => [
235+ 'shareType ' => IShare::TYPE_EMAIL ,
236+ 'shareWith ' => $ emailAddress ,
237+ ],
238+ ];
237239 }
238240 }
239241 }
240242
241- $ reachedEnd = true ;
242- if ($ this ->shareeEnumeration ) {
243- $ reachedEnd = (count ($ result ['wide ' ]) < $ offset + $ limit )
244- && (count ($ userResults ['wide ' ]) < $ offset + $ limit );
245-
246- $ result ['wide ' ] = array_slice ($ result ['wide ' ], $ offset , $ limit );
247- $ userResults ['wide ' ] = array_slice ($ userResults ['wide ' ], $ offset , $ limit );
248- }
249-
250243 if ($ this ->shareType === IShare::TYPE_EMAIL
251- && !$ searchResult ->hasExactIdMatch ($ emailType ) && $ this ->emailValidator ->isValid ($ search )) {
252- $ result ['exact ' ][] = [
253- 'label ' => $ search ,
254- 'uuid ' => $ search ,
255- 'value ' => [
256- 'shareType ' => IShare::TYPE_EMAIL ,
257- 'shareWith ' => $ search ,
258- ],
259- ];
244+ && !$ searchResult ->hasExactIdMatch ($ type ) && $ this ->emailValidator ->isValid ($ search )) {
245+ if ($ count ++ >= $ limit ) {
246+ $ hasMore = true ;
247+ } else {
248+ $ results ['exact ' ][] = [
249+ 'label ' => $ search ,
250+ 'uuid ' => $ search ,
251+ 'value ' => [
252+ 'shareType ' => IShare::TYPE_EMAIL ,
253+ 'shareWith ' => $ search ,
254+ ],
255+ ];
256+ }
260257 }
261258
262- if ($ this ->shareType === IShare::TYPE_USER && !empty ($ userResults ['wide ' ])) {
263- $ searchResult ->addResultSet ($ userType , $ userResults ['wide ' ], []);
264- }
265- if ($ this ->shareType === IShare::TYPE_EMAIL ) {
266- $ searchResult ->addResultSet ($ emailType , $ result ['wide ' ], $ result ['exact ' ]);
267- }
259+ $ searchResult ->addResultSet ($ type , $ results ['wide ' ], $ results ['exact ' ]);
268260
269- return ! $ reachedEnd ;
261+ return $ hasMore ;
270262 }
271263
272264 public function isCurrentUser (ICloudId $ cloud ): bool {
0 commit comments