Total Complexity | 66 |
Total Lines | 511 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like GContacts often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use GContacts, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
30 | class GContacts |
||
31 | { |
||
32 | /** full access to the users contacts */ |
||
33 | public const CONTACTS = "https://www.googleapis.com/auth/contacts"; |
||
34 | /** readonly access to the users contacts */ |
||
35 | public const CONTACTS_READONLY = "https://www.googleapis.com/auth/contacts.readonly"; |
||
36 | /** readonly access to the users other contacts */ |
||
37 | public const CONTACTS_OTHER_READONLY = "https://www.googleapis.com/auth/contacts.other.readonly"; |
||
38 | |||
39 | /** Sort people by when they were changed; older entries first. */ |
||
40 | public const SO_LAST_MODIFIED_ASCENDING = 'LAST_MODIFIED_ASCENDING'; |
||
41 | /** Sort people by when they were changed; newer entries first. */ |
||
42 | public const SO_LAST_MODIFIED_DESCENDING = 'LAST_MODIFIED_DESCENDING'; |
||
43 | /** Sort people by first name. */ |
||
44 | public const SO_FIRST_NAME_ASCENDING = 'FIRST_NAME_ASCENDING'; |
||
45 | /** Sort people by last name. */ |
||
46 | public const SO_LAST_NAME_ASCENDING = 'LAST_NAME_ASCENDING'; |
||
47 | |||
48 | /** the default personFields for detail view */ |
||
49 | public const DEF_DETAIL_PERSON_FIELDS = [ |
||
50 | GContact::PF_NAMES, |
||
51 | GContact::PF_ORGANIZATIONS, |
||
52 | GContact::PF_NICKNAMES, |
||
53 | GContact::PF_BIRTHDAYS, |
||
54 | GContact::PF_PHOTOS, |
||
55 | GContact::PF_ADDRESSES, |
||
56 | GContact::PF_EMAIL_ADDRESSES, |
||
57 | GContact::PF_PHONE_NUMBERS, |
||
58 | GContact::PF_GENDERS, |
||
59 | GContact::PF_MEMBERSHIPS, |
||
60 | GContact::PF_METADATA, |
||
61 | GContact::PF_BIOGRAPHIES, |
||
62 | GContact::PF_URLS, |
||
63 | ]; |
||
64 | |||
65 | /** the default personFields for detail view */ |
||
66 | public const DEF_LIST_PERSON_FIELDS = [ |
||
67 | GContact::PF_NAMES, |
||
68 | GContact::PF_ORGANIZATIONS, |
||
69 | GContact::PF_NICKNAMES, |
||
70 | GContact::PF_BIRTHDAYS, |
||
71 | GContact::PF_ADDRESSES, |
||
72 | GContact::PF_EMAIL_ADDRESSES, |
||
73 | GContact::PF_PHONE_NUMBERS, |
||
74 | GContact::PF_MEMBERSHIPS, |
||
75 | GContact::PF_METADATA, |
||
76 | ]; |
||
77 | |||
78 | /** max. pagesize for the list request */ |
||
79 | protected const CONTACTS_MAX_PAGESIZE = 1000; |
||
80 | /** max. pagesize for a search */ |
||
81 | protected const SEARCH_MAX_PAGESIZE = 30; |
||
82 | |||
83 | /** @var array<string> personFields/readMask to be returned by the request */ |
||
84 | protected array $aPersonFields = []; |
||
85 | /** pagesize for a request */ |
||
86 | protected int $iPageSize = 200; |
||
87 | |||
88 | /** @var GClient clients to perform the requests to the api */ |
||
89 | protected GClient $oClient; |
||
90 | |||
91 | /** |
||
92 | * Create instance an pass the clinet for the requests. |
||
93 | * @param GClient $oClient |
||
94 | */ |
||
95 | public function __construct(GClient $oClient) |
||
98 | } |
||
99 | |||
100 | /** |
||
101 | * Add personFields/readMask for next request. |
||
102 | * Can be called multiple and/or by passing an array of personFields. All |
||
103 | * const `GContact::PF_xxxx` can be specified. |
||
104 | * If no personFields specified before an API call, as default setting |
||
105 | * - `self::DEF_DETAIL_PERSON_FIELDS` for the getContact() and |
||
106 | * - `self::DEF_LIST_PERSON_FIELDS` for the list() and search() call is used |
||
107 | * @param string|array<string>|null $fields; if set to null, the internal array is cleared |
||
108 | * @param bool $bReset rest current set personFields |
||
109 | */ |
||
110 | public function addPersonFields($fields, bool $bReset = false) : void |
||
121 | } |
||
122 | } |
||
123 | |||
124 | /** |
||
125 | * Set the pagesize for reading lists. |
||
126 | * May be limited to the max. pgaesize for the request. |
||
127 | * - contact list: max. pagesize is 1000 |
||
128 | * - search: max. pagesize is 30 |
||
129 | * @param int $iPageSize |
||
130 | */ |
||
131 | public function setPageSize(int $iPageSize) : void |
||
132 | { |
||
133 | $this->iPageSize = $iPageSize; |
||
134 | } |
||
135 | |||
136 | /** |
||
137 | * Get the whole contact list. |
||
138 | * @link https://developers.google.com/people/api/rest/v1/people.connections/list |
||
139 | * @param string $strSortOrder one of the self::SO_xxx constants |
||
140 | * @param string $strGroupResourceName |
||
141 | * @return array<mixed>|false |
||
142 | */ |
||
143 | public function list(string $strSortOrder = self::SO_LAST_NAME_ASCENDING, string $strGroupResourceName = '') |
||
144 | { |
||
145 | $aHeader = [$this->oClient->getAuthHeader()]; |
||
146 | |||
147 | if (count($this->aPersonFields) == 0) { |
||
148 | $this->aPersonFields = self::DEF_LIST_PERSON_FIELDS; |
||
149 | } |
||
150 | |||
151 | $aParams = [ |
||
152 | 'personFields' => implode(',', $this->aPersonFields), |
||
153 | 'sortOrder' => $strSortOrder, |
||
154 | 'pageSize' => ($this->iPageSize > self::CONTACTS_MAX_PAGESIZE ? self::CONTACTS_MAX_PAGESIZE : $this->iPageSize), |
||
155 | ]; |
||
156 | |||
157 | $bEndOfList = false; |
||
158 | $aContactList = []; |
||
159 | while ($bEndOfList === false) { |
||
160 | $strURI = 'https://people.googleapis.com/v1/people/me/connections?' . http_build_query($aParams); |
||
161 | $strResponse = $this->oClient->fetchJsonResponse($strURI, GClient::GET, $aHeader); |
||
162 | if ($strResponse === false) { |
||
163 | return false; |
||
164 | } |
||
165 | $oResponse = json_decode($strResponse, true); |
||
166 | if (is_array($oResponse)) { |
||
167 | if (isset($oResponse['nextPageToken']) && !empty($oResponse['nextPageToken'])) { |
||
168 | $aParams['pageToken'] = $oResponse['nextPageToken']; |
||
169 | } else { |
||
170 | $bEndOfList = true; |
||
171 | } |
||
172 | foreach ($oResponse['connections'] as $aContact) { |
||
173 | if (empty($strGroupResourceName) || GContact::fromArray($aContact)->belongsToGroup($strGroupResourceName)) { |
||
174 | $aContactList[] = $aContact; |
||
175 | } |
||
176 | } |
||
177 | } else { |
||
178 | break; |
||
179 | } |
||
180 | } |
||
181 | return $aContactList; |
||
182 | } |
||
183 | |||
184 | /** |
||
185 | * Search within the contacts. |
||
186 | * The query matches on a contact's `names`, `nickNames`, `emailAddresses`, |
||
187 | * `phoneNumbers`, and `organizations` fields. The search for phone numbers only |
||
188 | * works, if leading '+' and contained '(', ')' or spaces are omitted in the query! |
||
189 | * The query is used to match <b>prefix</B> phrases of the fields on a person. |
||
190 | * For example, a person with name "foo name" matches queries such as "f", "fo", |
||
191 | * "foo", "foo n", "nam", etc., but not "oo n". |
||
192 | * > <b>Note:</b> |
||
193 | * > The count of contacts, the search request returns is limitetd to the |
||
194 | * > pageSize (which is limited itself to max. 30 at all). If there are |
||
195 | * > more contacts in the list that matches the query, unfortunately NO |
||
196 | * > further information about that additional contacts - and how many - are |
||
197 | * > available! |
||
198 | * @link https://developers.google.com/people/api/rest/v1/people/searchContacts |
||
199 | * @param string $strQuery the query to search for |
||
200 | * @return array<mixed>|false |
||
201 | */ |
||
202 | public function search(string $strQuery) |
||
203 | { |
||
204 | $aHeader = [$this->oClient->getAuthHeader()]; |
||
205 | |||
206 | if (count($this->aPersonFields) == 0) { |
||
207 | $this->aPersonFields = self::DEF_LIST_PERSON_FIELDS; |
||
208 | } |
||
209 | |||
210 | $aParams = [ |
||
211 | 'query' => '', |
||
212 | 'readMask' => implode(',', $this->aPersonFields), |
||
213 | 'pageSize' => ($this->iPageSize > self::SEARCH_MAX_PAGESIZE ? self::SEARCH_MAX_PAGESIZE : $this->iPageSize), |
||
214 | ]; |
||
215 | $strURI = 'https://people.googleapis.com/v1/people:searchContacts?' . http_build_query($aParams); |
||
216 | |||
217 | // 'warmup' request |
||
218 | // Note from google documentation: |
||
219 | // Before searching, clients should send a warmup request with an empty query to update the cache. |
||
220 | // https://developers.google.com/people/v1/contacts#search_the_users_contacts |
||
221 | $this->oClient->fetchJsonResponse($strURI, GClient::GET, $aHeader); |
||
222 | $aParams['query'] = $strQuery; |
||
223 | $strURI = 'https://people.googleapis.com/v1/people:searchContacts?' . http_build_query($aParams); |
||
224 | |||
225 | $aContactList = false; |
||
226 | if (($strResponse = $this->oClient->fetchJsonResponse($strURI, GClient::GET, $aHeader)) !== false) { |
||
227 | $aContactList = []; |
||
228 | $oResponse = json_decode($strResponse, true); |
||
229 | if (is_array($oResponse) && isset($oResponse['results']) && count($oResponse['results']) > 0) { |
||
230 | foreach ($oResponse['results'] as $aContact) { |
||
231 | $aContactList[] = $aContact['person']; |
||
232 | } |
||
233 | } |
||
234 | } |
||
235 | return $aContactList; |
||
236 | } |
||
237 | |||
238 | /** |
||
239 | * Get the contact specified by its resourceName. |
||
240 | * Note that only the person fields specified with `addPersonFields()` are included |
||
241 | * in the result. |
||
242 | * @link https://developers.google.com/people/api/rest/v1/people/get |
||
243 | * @see GContacts::addPersonFields() |
||
244 | * @param string $strResourceName |
||
245 | * @return GContact|false |
||
246 | */ |
||
247 | public function getContact(string $strResourceName) |
||
248 | { |
||
249 | $aHeader = [$this->oClient->getAuthHeader()]; |
||
250 | |||
251 | if (count($this->aPersonFields) == 0) { |
||
252 | $this->aPersonFields = self::DEF_DETAIL_PERSON_FIELDS; |
||
253 | } |
||
254 | |||
255 | $aParams = [ |
||
256 | 'personFields' => implode(',', $this->aPersonFields), |
||
257 | ]; |
||
258 | |||
259 | $strURI = 'https://people.googleapis.com/v1/' . $strResourceName . '?' . http_build_query($aParams); |
||
260 | $strResponse = $this->oClient->fetchJsonResponse($strURI, GClient::GET, $aHeader); |
||
261 | |||
262 | $result = false; |
||
263 | if ($strResponse !== false) { |
||
264 | $result = GContact::fromJSON($strResponse, $this->aPersonFields); |
||
265 | } |
||
266 | return $result; |
||
267 | } |
||
268 | |||
269 | /** |
||
270 | * Creates a new contact. |
||
271 | * @link https://developers.google.com/people/api/rest/v1/people/createContact |
||
272 | * @param GContact $oContact |
||
273 | * @return GContact|false |
||
274 | */ |
||
275 | public function createContact(GContact $oContact) |
||
276 | { |
||
277 | $aHeader = [ |
||
278 | $this->oClient->getAuthHeader(), |
||
279 | 'Content-Type: application/json', |
||
280 | ]; |
||
281 | |||
282 | if (count($this->aPersonFields) == 0) { |
||
283 | $this->aPersonFields = self::DEF_DETAIL_PERSON_FIELDS; |
||
284 | } |
||
285 | $aParams = ['personFields' => implode(',', $this->getUpdatePersonFields())]; |
||
286 | |||
287 | $result = false; |
||
288 | $data = json_encode($oContact->getArrayCopy()); |
||
289 | if ($data !== false) { |
||
290 | $strURI = 'https://people.googleapis.com/v1/people:createContact/?' . http_build_query($aParams); |
||
291 | $strResponse = $this->oClient->fetchJsonResponse($strURI, GClient::POST, $aHeader, $data); |
||
292 | |||
293 | if ($strResponse !== false) { |
||
294 | $result = GContact::fromJSON($strResponse, $this->aPersonFields); |
||
295 | } |
||
296 | } else { |
||
297 | $this->oClient->setError(json_last_error(), json_last_error_msg(), 'ERROR_JSON_ENCODE'); |
||
298 | } |
||
299 | return $result; |
||
300 | } |
||
301 | |||
302 | /** |
||
303 | * Updates an existing contact specified by resourceName. |
||
304 | * To prevent the user from data loss, this request fails with an 400 response |
||
305 | * code, if the contact has changed on the server, since it was loaded. |
||
306 | * Reload the data from the server and make the changes again! |
||
307 | * @link https://developers.google.com/people/api/rest/v1/people/updateContact |
||
308 | * @param string $strResourceName |
||
309 | * @param GContact $oContact |
||
310 | * @return GContact|false |
||
311 | */ |
||
312 | public function updateContact(string $strResourceName, GContact $oContact) |
||
313 | { |
||
314 | $aHeader = [ |
||
315 | $this->oClient->getAuthHeader(), |
||
316 | 'Content-Type: application/json', |
||
317 | ]; |
||
318 | |||
319 | if (count($this->aPersonFields) == 0) { |
||
320 | $this->aPersonFields = self::DEF_DETAIL_PERSON_FIELDS; |
||
321 | } |
||
322 | $aParams = ['updatePersonFields' => implode(',', $this->getUpdatePersonFields())]; |
||
323 | |||
324 | $result = false; |
||
325 | $data = json_encode($oContact); |
||
326 | if ($data !== false) { |
||
327 | $strURI = 'https://people.googleapis.com/v1/' . $strResourceName . ':updateContact/?' . http_build_query($aParams); |
||
328 | $strResponse = $this->oClient->fetchJsonResponse($strURI, GClient::PATCH, $aHeader, $data); |
||
329 | |||
330 | if ($strResponse !== false) { |
||
331 | $result = GContact::fromJSON($strResponse, $this->aPersonFields); |
||
332 | } |
||
333 | } |
||
334 | return $result; |
||
335 | } |
||
336 | |||
337 | /** |
||
338 | * Delete the requested contact. |
||
339 | * @link https://developers.google.com/people/api/rest/v1/people/deleteContact |
||
340 | * @param string $strResourceName |
||
341 | * @return bool |
||
342 | */ |
||
343 | public function deleteContact(string $strResourceName) : bool |
||
344 | { |
||
345 | $aHeader = [$this->oClient->getAuthHeader()]; |
||
346 | |||
347 | $strURI = 'https://people.googleapis.com/v1/' . $strResourceName . ':deleteContact'; |
||
348 | $strResponse = $this->oClient->fetchJsonResponse($strURI, GClient::DELETE, $aHeader); |
||
349 | return ($strResponse !== false); |
||
350 | } |
||
351 | |||
352 | /** |
||
353 | * Set or unset the 'starred' mark for the specified contact. |
||
354 | * The 'starred' mark just means, the contact belongs to the system group `contactGroups/starred`. |
||
355 | * @see GContactGroups::addContactsToGroup() |
||
356 | * @see GContactGroups::removeContactsFromGroup() |
||
357 | * @param string $strResourceName |
||
358 | * @param bool $bSetStarred |
||
359 | * @return bool |
||
360 | */ |
||
361 | public function setContactStarred(string $strResourceName, bool $bSetStarred = true) : bool |
||
362 | { |
||
363 | $oContactGroups = new GContactGroups($this->oClient); |
||
364 | if ($bSetStarred) { |
||
365 | $result = $oContactGroups->addContactsToGroup(GContactGroups::GRP_STARRED, [$strResourceName]); |
||
366 | } else { |
||
367 | $result = $oContactGroups->removeContactsFromGroup(GContactGroups::GRP_STARRED, [$strResourceName]); |
||
368 | } |
||
369 | return $result !== false; |
||
370 | } |
||
371 | |||
372 | |||
373 | /** |
||
374 | * Set contact photo from image file. |
||
375 | * Supported types are JPG, PNG, GIF and BMP. |
||
376 | * @link https://developers.google.com/people/api/rest/v1/people/updateContactPhoto |
||
377 | * @param string $strResourceName |
||
378 | * @param string $strFilename |
||
379 | * @return bool |
||
380 | */ |
||
381 | public function setContactPhotoFile(string $strResourceName, string $strFilename) : bool |
||
382 | { |
||
383 | $blobPhoto = ''; |
||
384 | if (filter_var($strFilename, FILTER_VALIDATE_URL)) { |
||
385 | $blobPhoto = $this->loadImageFromURL($strFilename); |
||
386 | } elseif (file_exists($strFilename)) { |
||
387 | $blobPhoto = $this->loadImageFromFile($strFilename); |
||
388 | } else { |
||
389 | $this->oClient->setError(0, 'File not found: ' . $strFilename, 'INVALID_ARGUMENT'); |
||
390 | } |
||
391 | $result = false; |
||
392 | if (!empty($blobPhoto)) { |
||
393 | $aHeader = [ |
||
394 | $this->oClient->getAuthHeader(), |
||
395 | 'Content-Type: application/json', |
||
396 | ]; |
||
397 | $data = json_encode(['photoBytes' => $blobPhoto]); |
||
398 | if ($data !== false) { |
||
399 | $strURI = 'https://people.googleapis.com/v1/' . $strResourceName . ':updateContactPhoto'; |
||
400 | $strResponse = $this->oClient->fetchJsonResponse($strURI, GClient::PATCH, $aHeader, $data); |
||
401 | $result = ($strResponse !== false); |
||
402 | } |
||
403 | } |
||
404 | return $result; |
||
405 | } |
||
406 | |||
407 | /** |
||
408 | * Set contact photo from base64 encoded image data. |
||
409 | * @link https://developers.google.com/people/api/rest/v1/people/updateContactPhoto |
||
410 | * @param string $strResourceName |
||
411 | * @param string $blobPhoto base64 encoded image |
||
412 | * @return bool |
||
413 | */ |
||
414 | public function setContactPhoto(string $strResourceName, string $blobPhoto) : bool |
||
415 | { |
||
416 | $result = false; |
||
417 | // check for valid base64 encoded imagedata |
||
418 | $img = base64_decode($blobPhoto); |
||
419 | if ($img !== false && imagecreatefromstring(base64_decode($blobPhoto)) !== false) { |
||
420 | $aHeader = [ |
||
421 | $this->oClient->getAuthHeader(), |
||
422 | 'Content-Type: application/json', |
||
423 | ]; |
||
424 | $data = json_encode(['photoBytes' => $blobPhoto]); |
||
425 | if ($data !== false) { |
||
426 | $strURI = 'https://people.googleapis.com/v1/' . $strResourceName . ':updateContactPhoto'; |
||
427 | $strResponse = $this->oClient->fetchJsonResponse($strURI, GClient::PATCH, $aHeader, $data); |
||
428 | $result = ($strResponse !== false); |
||
429 | } |
||
430 | } else { |
||
431 | $this->oClient->setError(0, 'Invalid base64 encoded image data', 'INVALID_ARGUMENT'); |
||
432 | } |
||
433 | return $result; |
||
434 | } |
||
435 | |||
436 | /** |
||
437 | * Delete contact photo for given contact. |
||
438 | * @link https://developers.google.com/people/api/rest/v1/people/deleteContactPhoto |
||
439 | * @param string $strResourceName |
||
440 | * @return bool |
||
441 | */ |
||
442 | public function deleteContactPhoto(string $strResourceName) : bool |
||
443 | { |
||
444 | $aHeader = [$this->oClient->getAuthHeader()]; |
||
445 | |||
446 | $strURI = 'https://people.googleapis.com/v1/' . $strResourceName . ':deleteContactPhoto'; |
||
447 | $strResponse = $this->oClient->fetchJsonResponse($strURI, GClient::DELETE, $aHeader); |
||
448 | |||
449 | return ($strResponse !== false); |
||
450 | } |
||
451 | |||
452 | /** |
||
453 | * Get updateable personFields. |
||
454 | * @return array<string> |
||
455 | */ |
||
456 | private function getUpdatePersonFields() : array |
||
457 | { |
||
458 | $aReadonlyPersonFields = [ |
||
459 | GContact::PF_PHOTOS, |
||
460 | GContact::PF_COVER_PHOTOS, |
||
461 | GContact::PF_AGE_RANGES, |
||
462 | GContact::PF_METADATA, |
||
463 | ]; |
||
464 | $aUpdatePersonFields = $this->aPersonFields; |
||
465 | foreach ($aReadonlyPersonFields as $strReadonly) { |
||
466 | if (($key = array_search($strReadonly, $aUpdatePersonFields)) !== false) { |
||
467 | unset($aUpdatePersonFields[$key]); |
||
468 | } |
||
469 | } |
||
470 | return $aUpdatePersonFields; |
||
471 | } |
||
472 | |||
473 | /** |
||
474 | * Load an image from an URL. |
||
475 | * The method uses curl to be independet of [allow_url_fopen] enabled on the system. |
||
476 | * @param string $strURL |
||
477 | * @return string base64 encoded imagedata |
||
478 | */ |
||
479 | private function loadImageFromURL(string $strURL) : string |
||
514 | } |
||
515 | |||
516 | /** |
||
517 | * Load an image from an file. |
||
518 | * In most cases an uploaded imagefile. |
||
519 | * @param string $strFilename |
||
520 | * @return string base64 encoded imagedata |
||
521 | */ |
||
522 | private function loadImageFromFile(string $strFilename) : string |
||
541 | } |
||
542 | } |
||
543 |