Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like Database 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
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 Database, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
34 | class Database extends AbstractBackend { |
||
35 | |||
36 | static private $preparedQueries = array(); |
||
37 | |||
38 | /** |
||
39 | * The name of the backend. |
||
40 | * |
||
41 | * @var string |
||
42 | */ |
||
43 | public $name = 'local'; |
||
44 | |||
45 | /** |
||
46 | * The cached address books. |
||
47 | * |
||
48 | * @var array[] |
||
49 | */ |
||
50 | public $addressBooks; |
||
51 | |||
52 | /** |
||
53 | * The table that holds the address books. |
||
54 | * |
||
55 | * @var string |
||
56 | */ |
||
57 | public $addressBooksTableName; |
||
58 | |||
59 | /** |
||
60 | * The table that holds the contact vCards. |
||
61 | * |
||
62 | * @var string |
||
63 | */ |
||
64 | public $cardsTableName; |
||
65 | |||
66 | /** |
||
67 | * The table that holds the indexed vCard properties. |
||
68 | * |
||
69 | * @var string |
||
70 | */ |
||
71 | public $indexTableName; |
||
72 | |||
73 | /** |
||
74 | * Sets up the backend |
||
75 | * |
||
76 | */ |
||
77 | 2 | public function __construct( |
|
78 | $userid = null, |
||
79 | $options = array( |
||
80 | 'addressBooksTableName' => '*PREFIX*contacts_addressbooks', |
||
81 | 'cardsTableName' => '*PREFIX*contacts_cards', |
||
82 | 'indexTableName' => '*PREFIX*contacts_cards_properties' |
||
83 | ) |
||
84 | ) { |
||
85 | 2 | $this->userid = $userid ? $userid : \OCP\User::getUser(); |
|
86 | 2 | $this->addressBooksTableName = $options['addressBooksTableName']; |
|
87 | 2 | $this->cardsTableName = $options['cardsTableName']; |
|
88 | 2 | $this->indexTableName = $options['indexTableName']; |
|
89 | 2 | $this->addressBooks = array(); |
|
90 | 2 | } |
|
91 | |||
92 | /** |
||
93 | * {@inheritdoc} |
||
94 | */ |
||
95 | public function getAddressBooksForUser(array $options = array()) { |
||
96 | |||
97 | try { |
||
98 | $result = $this->getPreparedQuery('getaddressbooksforuser') |
||
99 | ->execute(array($this->userid)); |
||
100 | |||
101 | View Code Duplication | if (\OCP\DB::isError($result)) { |
|
|
|||
102 | \OCP\Util::writeLog('contacts', __METHOD__. 'DB error: ' |
||
103 | . \OC_DB::getErrorMessage($result), \OCP\Util::ERROR); |
||
104 | return $this->addressBooks; |
||
105 | } |
||
106 | |||
107 | } catch(\Exception $e) { |
||
108 | \OCP\Util::writeLog('contacts', __METHOD__.' exception: ' |
||
109 | . $e->getMessage(), \OCP\Util::ERROR); |
||
110 | return $this->addressBooks; |
||
111 | } |
||
112 | |||
113 | while ($row = $result->fetchRow()) { |
||
114 | $row['permissions'] = \OCP\PERMISSION_ALL; |
||
115 | $this->addressBooks[$row['id']] = $row; |
||
116 | } |
||
117 | |||
118 | // Create default address book if the list is empty |
||
119 | if (count($this->addressBooks) === 0) { |
||
120 | $l10n = \OCP\Util::getL10N('contacts'); |
||
121 | $id = $this->createAddressBook(array('displayname' => $l10n->t('Contacts'))); |
||
122 | if ($id !== false) { |
||
123 | $addressBook = $this->getAddressBook($id); |
||
124 | $this->addressBooks[$id] = $addressBook; |
||
125 | } else { |
||
126 | \OCP\Util::writeLog( |
||
127 | 'contacts', |
||
128 | __METHOD__ . ', Error creating default address book', |
||
129 | \OCP\Util::ERROR |
||
130 | ); |
||
131 | } |
||
132 | } |
||
133 | |||
134 | return $this->addressBooks; |
||
135 | } |
||
136 | |||
137 | /** |
||
138 | * {@inheritdoc} |
||
139 | */ |
||
140 | 2 | public function getAddressBook($addressBookId, array $options = array()) { |
|
141 | 2 | $owner = isset($options['shared_by']) ? $options['shared_by'] : $this->userid; |
|
142 | //\OCP\Util::writeLog('contacts', __METHOD__.' id: ' |
||
143 | // . $addressBookId, \OCP\Util::DEBUG); |
||
144 | 2 | if ($this->addressBooks && isset($this->addressBooks[$addressBookId])) { |
|
145 | //print(__METHOD__ . ' ' . __LINE__ .' addressBookInfo: ' . print_r($this->addressBooks[$addressBookId], true)); |
||
146 | return $this->addressBooks[$addressBookId]; |
||
147 | } |
||
148 | |||
149 | // Hmm, not found. Lets query the db. |
||
150 | try { |
||
151 | 2 | $result = $this->getPreparedQuery('getaddressbook')->execute(array($addressBookId, $owner)); |
|
152 | |||
153 | 2 | View Code Duplication | if (\OCP\DB::isError($result)) { |
154 | \OCP\Util::writeLog('contacts', __METHOD__. 'DB error: ' |
||
155 | . \OC_DB::getErrorMessage($result), \OCP\Util::ERROR); |
||
156 | return null; |
||
157 | } |
||
158 | |||
159 | 2 | $row = $result->fetchRow(); |
|
160 | |||
161 | 2 | if (!$row) { |
|
162 | return null; |
||
163 | } |
||
164 | |||
165 | 2 | $row['permissions'] = \OCP\PERMISSION_ALL; |
|
166 | 2 | $row['backend'] = $this->name; |
|
167 | 2 | $this->addressBooks[$addressBookId] = $row; |
|
168 | 2 | return $row; |
|
169 | |||
170 | } catch(\Exception $e) { |
||
171 | \OCP\Util::writeLog('contacts', __METHOD__.' exception: ' |
||
172 | . $e->getMessage(), \OCP\Util::ERROR); |
||
173 | return null; |
||
174 | } |
||
175 | |||
176 | } |
||
177 | |||
178 | /** |
||
179 | * {@inheritdoc} |
||
180 | * @param string|false $addressBookId |
||
181 | */ |
||
182 | public function hasAddressBook($addressBookId) { |
||
183 | |||
184 | // First check if it's already cached |
||
185 | if ($this->addressBooks && isset($this->addressBooks[$addressBookId])) { |
||
186 | return true; |
||
187 | } |
||
188 | |||
189 | return count($this->getAddressBook($addressBookId)) > 0; |
||
190 | } |
||
191 | |||
192 | /** |
||
193 | * Updates an addressbook's properties |
||
194 | * |
||
195 | * @param string $addressBookId |
||
196 | * @param array $changes |
||
197 | * @return bool |
||
198 | */ |
||
199 | 2 | public function updateAddressBook($addressBookId, array $changes) { |
|
200 | |||
201 | if (count($changes) === 0) { |
||
202 | return false; |
||
203 | } |
||
204 | |||
205 | $query = 'UPDATE `' . $this->addressBooksTableName . '` SET '; |
||
206 | |||
207 | $updates = array(); |
||
208 | |||
209 | View Code Duplication | if (isset($changes['displayname'])) { |
|
210 | $query .= '`displayname` = ?, '; |
||
211 | $updates[] = $changes['displayname']; |
||
212 | |||
213 | if ($this->addressBooks && isset($this->addressBooks[$addressBookId])) { |
||
214 | $this->addressBooks[$addressBookId]['displayname'] = $changes['displayname']; |
||
215 | } |
||
216 | |||
217 | } |
||
218 | |||
219 | View Code Duplication | if (isset($changes['description'])) { |
|
220 | |||
221 | $query .= '`description` = ?, '; |
||
222 | $updates[] = $changes['description']; |
||
223 | |||
224 | if ($this->addressBooks && isset($this->addressBooks[$addressBookId])) { |
||
225 | $this->addressBooks[$addressBookId]['description'] = $changes['description']; |
||
226 | } |
||
227 | |||
228 | } |
||
229 | |||
230 | $query .= '`ctag` = ? + 1 WHERE `id` = ?'; |
||
231 | $now = time(); |
||
232 | $updates[] = $now; |
||
233 | $updates[] = $addressBookId; |
||
234 | |||
235 | View Code Duplication | if ($this->addressBooks && isset($this->addressBooks[$addressBookId])) { |
|
236 | $this->addressBooks[$addressBookId]['lastmodified'] = $now; |
||
237 | } |
||
238 | |||
239 | try { |
||
240 | |||
241 | $stmt = \OCP\DB::prepare($query); |
||
242 | $result = $stmt->execute($updates); |
||
243 | |||
244 | View Code Duplication | if (\OCP\DB::isError($result)) { |
|
245 | \OCP\Util::writeLog('contacts', |
||
246 | 2 | __METHOD__. 'DB error: ' |
|
247 | . \OC_DB::getErrorMessage($result), \OCP\Util::ERROR); |
||
248 | return false; |
||
249 | } |
||
250 | |||
251 | } catch(\Exception $e) { |
||
252 | \OCP\Util::writeLog('contacts', |
||
253 | __METHOD__ . ', exception: ' |
||
254 | . $e->getMessage(), \OCP\Util::ERROR); |
||
255 | return false; |
||
256 | } |
||
257 | |||
258 | return true; |
||
259 | } |
||
260 | |||
261 | /** |
||
262 | * Creates a new address book |
||
263 | * |
||
264 | * Supported properties are 'displayname', 'description' and 'uri'. |
||
265 | * 'uri' is supported to allow to add from CardDAV requests, and MUST |
||
266 | * be used for the 'uri' database field if present. |
||
267 | * 'displayname' MUST be present. |
||
268 | * |
||
269 | * @param array $properties |
||
270 | * @return string|false The ID if the newly created AddressBook or false on error. |
||
271 | */ |
||
272 | 2 | public function createAddressBook(array $properties) { |
|
273 | |||
274 | 2 | if (count($properties) === 0 || !isset($properties['displayname'])) { |
|
275 | return false; |
||
276 | 2 | } |
|
277 | |||
278 | 2 | $updates = array($this->userid, $properties['displayname']); |
|
279 | 2 | $updates[] = isset($properties['uri']) |
|
280 | 2 | ? $properties['uri'] |
|
281 | 2 | : $this->createAddressBookURI($properties['displayname']); |
|
282 | 2 | $updates[] = isset($properties['description']) ? $properties['description'] : ''; |
|
283 | 2 | $ctag = time(); |
|
284 | 2 | $updates[] = $ctag; |
|
285 | |||
286 | try { |
||
287 | 2 | $result = $this->getPreparedQuery('createaddressbook')->execute($updates); |
|
288 | |||
289 | 2 | View Code Duplication | if (\OCP\DB::isError($result)) { |
290 | \OCP\Util::writeLog('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OCP\Util::ERROR); |
||
291 | return false; |
||
292 | } |
||
293 | |||
294 | 2 | } catch(\Exception $e) { |
|
295 | \OCP\Util::writeLog('contacts', __METHOD__ . ', exception: ' . $e->getMessage(), \OCP\Util::ERROR); |
||
296 | return false; |
||
297 | } |
||
298 | |||
299 | 2 | $newid = \OCP\DB::insertid($this->addressBooksTableName); |
|
300 | |||
301 | 2 | if ($this->addressBooks) { |
|
302 | $updates['id'] = $newid; |
||
303 | $updates['ctag'] = $ctag; |
||
304 | $updates['lastmodified'] = $ctag; |
||
305 | $updates['permissions'] = \OCP\PERMISSION_ALL; |
||
306 | $this->addressBooks[$newid] = $updates; |
||
307 | } |
||
308 | |||
309 | 2 | return $newid; |
|
310 | } |
||
311 | |||
312 | /** |
||
313 | * Get all contact ids from the address book to run pre_deleteAddressBook hook |
||
314 | * |
||
315 | * @param string $addressBookId |
||
316 | */ |
||
317 | 2 | protected function preDeleteAddressBook($addressBookId) { |
|
318 | // Get all contact ids for this address book |
||
319 | $ids = array(); |
||
320 | $result = null; |
||
321 | |||
322 | try { |
||
323 | |||
324 | $result = $this->getPreparedQuery('getcontactids') |
||
325 | ->execute(array($addressBookId)); |
||
326 | |||
327 | View Code Duplication | if (\OCP\DB::isError($result)) { |
|
328 | \OCP\Util::writeLog('contacts', __METHOD__. 'DB error: ' |
||
329 | . \OC_DB::getErrorMessage($result), \OCP\Util::ERROR); |
||
330 | return false; |
||
331 | } |
||
332 | |||
333 | } catch(\Exception $e) { |
||
334 | \OCP\Util::writeLog('contacts', __METHOD__. |
||
335 | 2 | ', exception: ' . $e->getMessage(), \OCP\Util::ERROR); |
|
336 | return false; |
||
337 | } |
||
338 | |||
339 | if (!is_null($result)) { |
||
340 | while ($id = $result->fetchOne()) { |
||
341 | $ids[] = $id; |
||
342 | 2 | } |
|
343 | |||
344 | \OCP\Util::emitHook('OCA\Contacts', 'pre_deleteAddressBook', |
||
345 | array('addressbookid' => $addressBookId, 'contactids' => $ids) |
||
346 | ); |
||
347 | } |
||
348 | } |
||
349 | |||
350 | /** |
||
351 | * Deletes an entire addressbook and all its contents |
||
352 | * |
||
353 | * NOTE: For efficience this method bypasses the cleanup hooks and deletes |
||
354 | * property indexes and category/group relations by itself. |
||
355 | * |
||
356 | * @param string $addressBookId |
||
357 | * @return bool |
||
358 | */ |
||
359 | public function deleteAddressBook($addressBookId) { |
||
360 | |||
361 | $this->preDeleteAddressBook($addressBookId); |
||
362 | |||
363 | try { |
||
364 | $this->getPreparedQuery('deleteaddressbookcontacts') |
||
365 | ->execute(array($addressBookId)); |
||
366 | } catch(\Exception $e) { |
||
367 | \OCP\Util::writeLog('contacts', __METHOD__. |
||
368 | ', exception: ' . $e->getMessage(), \OCP\Util::ERROR); |
||
369 | return false; |
||
370 | } |
||
371 | |||
372 | try { |
||
373 | $this->getPreparedQuery('deleteaddressbook') |
||
374 | ->execute(array($addressBookId)); |
||
375 | } catch(\Exception $e) { |
||
376 | \OCP\Util::writeLog('contacts', __METHOD__. |
||
377 | ', exception: ' . $e->getMessage(), \OCP\Util::ERROR); |
||
378 | return false; |
||
379 | } |
||
380 | |||
381 | if ($this->addressBooks && isset($this->addressBooks[$addressBookId])) { |
||
382 | unset($this->addressBooks[$addressBookId]); |
||
383 | } |
||
384 | |||
385 | return true; |
||
386 | } |
||
387 | |||
388 | /** |
||
389 | * @brief Updates ctag for addressbook |
||
390 | * @param integer $id |
||
391 | * @return boolean |
||
392 | */ |
||
393 | 2 | public function setModifiedAddressBook($id) { |
|
394 | 2 | $ctag = time(); |
|
395 | 2 | $this->getPreparedQuery('touchaddressbook')->execute(array($ctag, $id)); |
|
396 | |||
397 | 2 | return true; |
|
398 | } |
||
399 | |||
400 | /** |
||
401 | * {@inheritdoc} |
||
402 | */ |
||
403 | 2 | public function lastModifiedAddressBook($addressBookId) { |
|
404 | |||
405 | 2 | View Code Duplication | if ($this->addressBooks && isset($this->addressBooks[$addressBookId])) { |
406 | 2 | return $this->addressBooks[$addressBookId]['lastmodified']; |
|
407 | } |
||
408 | |||
409 | $addressBook = $this->getAddressBook($addressBookId); |
||
410 | if($addressBook) { |
||
411 | $this->addressBooks[$addressBookId] = $addressBook; |
||
412 | } |
||
413 | return $addressBook ? $addressBook['lastmodified'] : null; |
||
414 | } |
||
415 | |||
416 | /** |
||
417 | * Returns the number of contacts in a specific address book. |
||
418 | * |
||
419 | * @param string $addressBookId |
||
420 | * @return null|integer |
||
421 | */ |
||
422 | public function numContacts($addressBookId) { |
||
423 | |||
424 | $result = $this->getPreparedQuery('numcontacts')->execute(array($addressBookId)); |
||
425 | |||
426 | View Code Duplication | if (\OCP\DB::isError($result)) { |
|
427 | \OCP\Util::writeLog('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OCP\Util::ERROR); |
||
428 | return null; |
||
429 | } |
||
430 | |||
431 | return (int)$result->fetchOne(); |
||
432 | } |
||
433 | |||
434 | /** |
||
435 | * {@inheritdoc} |
||
436 | */ |
||
437 | public function getContacts($addressBookId, array $options = array()) { |
||
438 | //\OCP\Util::writeLog('contacts', __METHOD__.' addressbookid: ' . $addressBookId, \OCP\Util::DEBUG); |
||
439 | $cards = array(); |
||
440 | try { |
||
441 | $queryIdentifier = (isset($options['omitdata']) && $options['omitdata'] === true) |
||
442 | ? 'getcontactsomitdata' |
||
443 | : 'getcontacts'; |
||
444 | |||
445 | $result = $this->getPreparedQuery($queryIdentifier, $options)->execute(array($addressBookId)); |
||
446 | |||
447 | View Code Duplication | if (\OCP\DB::isError($result)) { |
|
448 | \OCP\Util::writeLog('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OCP\Util::ERROR); |
||
449 | return $cards; |
||
450 | } |
||
451 | |||
452 | } catch(\Exception $e) { |
||
453 | \OCP\Util::writeLog('contacts', __METHOD__.', exception: '.$e->getMessage(), \OCP\Util::ERROR); |
||
454 | return $cards; |
||
455 | } |
||
456 | |||
457 | if (!is_null($result)) { |
||
458 | |||
459 | while ($row = $result->fetchRow()) { |
||
460 | $row['permissions'] = \OCP\PERMISSION_ALL; |
||
461 | $cards[] = $row; |
||
462 | } |
||
463 | |||
464 | } |
||
465 | |||
466 | return $cards; |
||
467 | } |
||
468 | |||
469 | /** |
||
470 | * Returns a specific contact. |
||
471 | * |
||
472 | * NOTE: The contact $id for Database and Shared backends can be an array containing |
||
473 | * either 'id' or 'uri' to be able to play seamlessly with the |
||
474 | * CardDAV backend. |
||
475 | * NOTE: $addressbookid isn't always used in the query, so there's no access control. |
||
476 | * This is because the groups backend - \OCP\Tags - doesn't no about parent collections |
||
477 | * only object IDs. Hence a hack is made with an optional 'noCollection'. |
||
478 | * |
||
479 | * @param string $addressBookId |
||
480 | * @param string|array $id Contact ID |
||
481 | * @param array $options - Optional (backend specific options) |
||
482 | * @return array|null |
||
483 | */ |
||
484 | 2 | public function getContact($addressBookId, $id, array $options = array()) { |
|
485 | //\OCP\Util::writeLog('contacts', __METHOD__.' identifier: ' . $addressBookId . ' / ' . $id, \OCP\Util::DEBUG); |
||
486 | |||
487 | // When dealing with tags we have no idea if which address book it's in |
||
488 | // but since they're all in the same table they have unique IDs anyway |
||
489 | 2 | $noCollection = isset($options['noCollection']) ? $options['noCollection'] : false; |
|
490 | |||
491 | 2 | $queryIdentifier = 'getcontact'; |
|
492 | 2 | $queries = array(); |
|
493 | |||
494 | // When querying from CardDAV we don't have the ID, only the uri |
||
495 | 2 | if (is_array($id)) { |
|
496 | if (isset($id['id'])) { |
||
497 | $queries[] = $id['id']; |
||
498 | $queryIdentifier .= 'byid'; |
||
499 | } elseif (isset($id['uri'])) { |
||
500 | $queries[] = $id['uri']; |
||
501 | $queryIdentifier .= 'byuri'; |
||
502 | } else { |
||
503 | throw new \Exception( |
||
504 | __METHOD__ . ' If second argument is an array, either \'id\' or \'uri\' has to be set.' |
||
505 | ); |
||
506 | } |
||
507 | } else { |
||
508 | 2 | if (!trim($id)) { |
|
509 | throw new \Exception( |
||
510 | __METHOD__ . ' Missing or empty second argument \'$id\'.' |
||
511 | ); |
||
512 | } |
||
513 | 2 | $queries[] = $id; |
|
514 | 2 | $queryIdentifier .= 'byid'; |
|
515 | } |
||
516 | |||
517 | 2 | if ($noCollection) { |
|
518 | $queryIdentifier .= 'nocollection'; |
||
519 | } else { |
||
520 | 2 | $queries[] = $addressBookId; |
|
521 | } |
||
522 | |||
523 | try { |
||
524 | //\OCP\Util::writeLog('contacts', __METHOD__.', identifier: '. $queryIdentifier . ', queries: ' . implode(',', $queries), \OCP\Util::DEBUG); |
||
525 | 2 | $result = $this->getPreparedQuery($queryIdentifier)->execute($queries); |
|
526 | |||
527 | 2 | View Code Duplication | if (\OCP\DB::isError($result)) { |
528 | \OCP\Util::writeLog('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OCP\Util::ERROR); |
||
529 | return null; |
||
530 | } |
||
531 | |||
532 | 2 | } catch (\Exception $e) { |
|
533 | \OCP\Util::writeLog('contacts', __METHOD__.', exception: '.$e->getMessage(), \OCP\Util::ERROR); |
||
534 | \OCP\Util::writeLog('contacts', __METHOD__.', id: '. $id, \OCP\Util::DEBUG); |
||
535 | return null; |
||
536 | } |
||
537 | |||
538 | 2 | $row = $result->fetchRow(); |
|
539 | |||
540 | 2 | if (!$row) { |
|
541 | if (is_array($id)) { |
||
542 | $idstr = implode(", ", $id); |
||
543 | } else { |
||
544 | $idstr = $id; |
||
545 | } |
||
546 | \OCP\Util::writeLog('contacts', __METHOD__.', Not found, id: '. $idstr, \OCP\Util::DEBUG); |
||
547 | return null; |
||
548 | } |
||
549 | |||
550 | 2 | $row['permissions'] = \OCP\PERMISSION_ALL; |
|
551 | 2 | return $row; |
|
552 | } |
||
553 | |||
554 | /** |
||
555 | * @param string|false $addressBookId |
||
556 | * @param false|string $id |
||
557 | */ |
||
558 | public function hasContact($addressBookId, $id) { |
||
559 | try { |
||
560 | return $this->getContact($addressBookId, $id) !== null; |
||
561 | } catch (\Exception $e) { |
||
562 | \OCP\Util::writeLog('contacts', __METHOD__.', exception: '.$e->getMessage(), \OCP\Util::ERROR); |
||
563 | return false; |
||
564 | } |
||
565 | } |
||
566 | |||
567 | /** |
||
568 | * Creates a new contact |
||
569 | * |
||
570 | * In the Database and Shared backends contact be either a Contact object or a string |
||
571 | * with carddata to be able to play seamlessly with the CardDAV backend. |
||
572 | * If this method is called by the CardDAV backend, the carddata is already validated. |
||
573 | * NOTE: It's assumed that this method is called either from the CardDAV backend, the |
||
574 | * import script, or from the ownCloud web UI in which case either the uri parameter is |
||
575 | * set, or the contact has a UID. If neither is set, it will fail. |
||
576 | * |
||
577 | * @param string $addressBookId |
||
578 | * @param string $contact |
||
579 | * @param array $options - Optional (backend specific options) |
||
580 | * @return false|string The identifier for the new contact or false on error. |
||
581 | */ |
||
582 | 2 | public function createContact($addressBookId, $contact, array $options = array()) { |
|
583 | //\OCP\Util::writeLog('contacts', __METHOD__.' addressBookId: ' . $addressBookId, \OCP\Util::DEBUG); |
||
584 | |||
585 | 2 | $uri = isset($options['uri']) ? $options['uri'] : null; |
|
586 | |||
587 | 2 | View Code Duplication | if (!$contact instanceof VCard) { |
588 | try { |
||
589 | $contact = Reader::read($contact); |
||
590 | } catch(\Exception $e) { |
||
591 | \OCP\Util::writeLog('contacts', __METHOD__.', exception: '.$e->getMessage(), \OCP\Util::ERROR); |
||
592 | return false; |
||
593 | } |
||
594 | } |
||
595 | |||
596 | try { |
||
597 | 2 | $contact->validate(VCard::REPAIR|VCard::UPGRADE); |
|
598 | 2 | } catch (\Exception $e) { |
|
599 | \OCP\Util::writeLog('contacts', __METHOD__ . ' ' . |
||
600 | 'Error validating vcard: ' . $e->getMessage(), \OCP\Util::ERROR); |
||
601 | return false; |
||
602 | } |
||
603 | |||
604 | 2 | $uri = is_null($uri) ? $this->uniqueURI($addressBookId, $contact->UID . '.vcf') : $uri; |
|
605 | 2 | $now = new \DateTime; |
|
606 | 2 | $contact->REV = $now->format(\DateTime::W3C); |
|
607 | |||
608 | 2 | $appinfo = \OCP\App::getAppInfo('contacts'); |
|
609 | 2 | $appversion = \OCP\App::getAppVersion('contacts'); |
|
610 | 2 | $prodid = '-//ownCloud//NONSGML ' . $appinfo['name'] . ' ' . $appversion.'//EN'; |
|
611 | 2 | $contact->PRODID = $prodid; |
|
612 | |||
613 | try { |
||
614 | 2 | $result = $this->getPreparedQuery('createcontact') |
|
615 | 2 | ->execute( |
|
616 | array( |
||
617 | 2 | $addressBookId, |
|
618 | 2 | (string)$contact->FN, |
|
619 | 2 | $contact->serialize(), |
|
620 | 2 | $uri, |
|
621 | 2 | time() |
|
622 | 2 | ) |
|
623 | 2 | ); |
|
624 | |||
625 | 2 | View Code Duplication | if (\OCP\DB::isError($result)) { |
626 | \OCP\Util::writeLog('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OCP\Util::ERROR); |
||
627 | return false; |
||
628 | } |
||
629 | |||
630 | 2 | } catch(\Exception $e) { |
|
631 | \OCP\Util::writeLog('contacts', __METHOD__.', exception: '.$e->getMessage(), \OCP\Util::ERROR); |
||
632 | return false; |
||
633 | } |
||
634 | 2 | $newid = \OCP\DB::insertid($this->cardsTableName); |
|
635 | |||
636 | 2 | $this->setModifiedAddressBook($addressBookId); |
|
637 | 2 | \OCP\Util::emitHook('OCA\Contacts', 'post_createContact', |
|
638 | 2 | array('id' => $newid, 'addressBookId' => $addressBookId, 'backend' => $this->name, 'contact' => $contact) |
|
639 | 2 | ); |
|
640 | 2 | return (string)$newid; |
|
641 | } |
||
642 | |||
643 | /** |
||
644 | * Updates a contact |
||
645 | * |
||
646 | * @param string $addressBookId |
||
647 | * @param false|string $id Contact ID |
||
648 | * @param string $contact |
||
649 | * @param array $options - Optional (backend specific options) |
||
650 | * @see getContact |
||
651 | * @return bool |
||
652 | * @throws \Exception if $contact is a string but can't be parsed as a VCard |
||
653 | * @throws \Exception if the Contact to update couldn't be found |
||
654 | */ |
||
655 | public function updateContact($addressBookId, $id, $contact, array $options = array()) { |
||
656 | //\OCP\Util::writeLog('contacts', __METHOD__.' identifier: ' . $addressBookId . ' / ' . $id, \OCP\Util::DEBUG); |
||
657 | $noCollection = isset($options['noCollection']) ? $options['noCollection'] : false; |
||
658 | $isBatch = isset($options['isBatch']) ? $options['isBatch'] : false; |
||
659 | |||
660 | $updateRevision = true; |
||
661 | $isCardDAV = false; |
||
662 | |||
663 | View Code Duplication | if (!$contact instanceof VCard) { |
|
664 | try { |
||
665 | $contact = Reader::read($contact); |
||
666 | } catch(\Exception $e) { |
||
667 | \OCP\Util::writeLog('contacts', __METHOD__.', exception: '.$e->getMessage(), \OCP\Util::ERROR); |
||
668 | return false; |
||
669 | } |
||
670 | } |
||
671 | |||
672 | if (is_array($id)) { |
||
673 | |||
674 | if (isset($id['id'])) { |
||
675 | $id = $id['id']; |
||
676 | } elseif (isset($id['uri'])) { |
||
677 | $updateRevision = false; |
||
678 | $isCardDAV = true; |
||
679 | $id = $this->getIdFromUri($addressBookId,$id['uri']); |
||
680 | |||
681 | if (is_null($id)) { |
||
682 | \OCP\Util::writeLog('contacts', __METHOD__ . ' Couldn\'t find contact', \OCP\Util::ERROR); |
||
683 | return false; |
||
684 | } |
||
685 | |||
686 | } else { |
||
687 | throw new \Exception( |
||
688 | __METHOD__ . ' If second argument is an array, either \'id\' or \'uri\' has to be set.' |
||
689 | ); |
||
690 | } |
||
691 | } |
||
692 | |||
693 | if ($updateRevision || !isset($contact->REV)) { |
||
694 | $now = new \DateTime; |
||
695 | $contact->REV = $now->format(\DateTime::W3C); |
||
696 | } |
||
697 | |||
698 | $data = $contact->serialize(); |
||
699 | |||
700 | if ($noCollection) { |
||
701 | $me = $this->getContact(null, $id, $options); |
||
702 | $addressBookId = $me['parent']; |
||
703 | } |
||
704 | |||
705 | $updates = array($contact->FN, $data, time(), $id, $addressBookId); |
||
706 | |||
707 | try { |
||
708 | |||
709 | $result = $this->getPreparedQuery('updatecontact')->execute($updates); |
||
710 | |||
711 | View Code Duplication | if (\OCP\DB::isError($result)) { |
|
712 | \OCP\Util::writeLog('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OCP\Util::ERROR); |
||
713 | return false; |
||
714 | } |
||
715 | |||
716 | } catch(\Exception $e) { |
||
717 | \OCP\Util::writeLog('contacts', __METHOD__.', exception: ' |
||
718 | . $e->getMessage(), \OCP\Util::ERROR); |
||
719 | \OCP\Util::writeLog('contacts', __METHOD__.', id' . $id, \OCP\Util::DEBUG); |
||
720 | return false; |
||
721 | } |
||
722 | |||
723 | $this->setModifiedAddressBook($addressBookId); |
||
724 | |||
725 | if (!$isBatch) { |
||
726 | |||
727 | \OCP\Util::emitHook('OCA\Contacts', 'post_updateContact', |
||
728 | array( |
||
729 | 'backend' => $this->name, |
||
730 | 'addressBookId' => $addressBookId, |
||
731 | 'contactId' => $id, |
||
732 | 'contact' => $contact, |
||
733 | 'carddav' => $isCardDAV |
||
734 | ) |
||
735 | ); |
||
736 | |||
737 | } |
||
738 | |||
739 | return true; |
||
740 | } |
||
741 | |||
742 | /** |
||
743 | * Deletes a contact |
||
744 | * |
||
745 | * @param string $addressBookId |
||
746 | * @param false|string $id |
||
747 | * @param array $options - Optional (backend specific options) |
||
748 | * @see getContact |
||
749 | * @return bool |
||
750 | */ |
||
751 | public function deleteContact($addressBookId, $id, array $options = array()) { |
||
752 | // TODO: pass the uri in $options instead. |
||
753 | |||
754 | $noCollection = isset($options['noCollection']) ? $options['noCollection'] : false; |
||
755 | $isBatch = isset($options['isBatch']) ? $options['isBatch'] : false; |
||
756 | |||
757 | if (is_array($id)) { |
||
758 | |||
759 | if (isset($id['id'])) { |
||
760 | $id = $id['id']; |
||
761 | } elseif (isset($id['uri'])) { |
||
762 | |||
763 | $id = $this->getIdFromUri($addressBookId,$id['uri']); |
||
764 | |||
765 | if (is_null($id)) { |
||
766 | \OCP\Util::writeLog('contacts', __METHOD__ . ' Couldn\'t find contact', \OCP\Util::ERROR); |
||
767 | return false; |
||
768 | } |
||
769 | |||
770 | } else { |
||
771 | throw new \Exception( |
||
772 | __METHOD__ . ' If second argument is an array, either \'id\' or \'uri\' has to be set.' |
||
773 | ); |
||
774 | } |
||
775 | } |
||
776 | |||
777 | if (!$isBatch) { |
||
778 | \OCP\Util::emitHook('OCA\Contacts', 'pre_deleteContact', |
||
779 | array('id' => $id) |
||
780 | ); |
||
781 | } |
||
782 | |||
783 | if ($noCollection) { |
||
784 | $me = $this->getContact(null, $id, $options); |
||
785 | $addressBookId = $me['parent']; |
||
786 | } |
||
787 | |||
788 | try { |
||
789 | |||
790 | $result = $this->getPreparedQuery('deletecontact') |
||
791 | ->execute(array($id, $addressBookId)); |
||
792 | |||
793 | View Code Duplication | if (\OCP\DB::isError($result)) { |
|
794 | \OCP\Util::writeLog('contacts', __METHOD__. 'DB error: ' |
||
795 | . \OC_DB::getErrorMessage($result), \OCP\Util::ERROR); |
||
796 | return false; |
||
797 | } |
||
798 | |||
799 | } catch(\Exception $e) { |
||
800 | \OCP\Util::writeLog('contacts', __METHOD__. |
||
801 | ', exception: ' . $e->getMessage(), \OCP\Util::ERROR); |
||
802 | \OCP\Util::writeLog('contacts', __METHOD__.', id: ' |
||
803 | . $id, \OCP\Util::DEBUG); |
||
804 | return false; |
||
805 | } |
||
806 | |||
807 | $this->setModifiedAddressBook($addressBookId); |
||
808 | return true; |
||
809 | } |
||
810 | |||
811 | /** |
||
812 | * @brief Get the last modification time for a contact. |
||
813 | * |
||
814 | * Must return a UNIX time stamp or null if the backend |
||
815 | * doesn't support it. |
||
816 | * |
||
817 | * @param string $addressBookId |
||
818 | * @param mixed $id |
||
819 | * @returns int | null |
||
820 | */ |
||
821 | public function lastModifiedContact($addressBookId, $id) { |
||
822 | |||
823 | $contact = $this->getContact($addressBookId, $id); |
||
824 | return ($contact ? $contact['lastmodified'] : null); |
||
825 | |||
826 | } |
||
827 | |||
828 | /** |
||
829 | * @brief Get the contact id from the uri. |
||
830 | * |
||
831 | * @param string $addressBookId |
||
832 | * @returns int | null |
||
833 | */ |
||
834 | public function getIdFromUri($addressBookId,$uri) { |
||
835 | |||
836 | $stmt = $this->getPreparedQuery('contactidfromuri'); |
||
837 | $result = $stmt->execute(array($addressBookId,$uri)); |
||
838 | |||
839 | View Code Duplication | if (\OCP\DB::isError($result)) { |
|
840 | \OCP\Util::writeLog('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OCP\Util::ERROR); |
||
841 | return null; |
||
842 | } |
||
843 | |||
844 | $one = $result->fetchOne(); |
||
845 | |||
846 | View Code Duplication | if (!$one) { |
|
847 | \OCP\Util::writeLog('contacts', __METHOD__.', Not found, uri: '. $uri, \OCP\Util::DEBUG); |
||
848 | return null; |
||
849 | } |
||
850 | |||
851 | return $one; |
||
852 | } |
||
853 | |||
854 | /** |
||
855 | * Create a unique URI based on the display name. |
||
856 | * |
||
857 | * @param string $displayName |
||
858 | * @return string |
||
859 | */ |
||
860 | 2 | private function createAddressBookURI($displayName) { |
|
861 | |||
862 | 2 | $name = str_replace(' ', '_', strtolower($displayName)); |
|
863 | |||
864 | try { |
||
865 | 2 | $stmt = $this->getPreparedQuery('addressbookuris'); |
|
866 | 2 | $result = $stmt->execute(array($this->userid)); |
|
867 | |||
868 | 2 | View Code Duplication | if (\OCP\DB::isError($result)) { |
869 | \OCP\Util::writeLog('contacts', |
||
870 | __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), |
||
871 | \OCP\Util::ERROR |
||
872 | ); |
||
873 | return $name; |
||
874 | } |
||
875 | |||
876 | 2 | } catch(\Exception $e) { |
|
877 | \OCP\Util::writeLog('contacts', __METHOD__ . ' exception: ' . $e->getMessage(), \OCP\Util::ERROR); |
||
878 | return $name; |
||
879 | } |
||
880 | 2 | $uris = array(); |
|
881 | 2 | while ($row = $result->fetchRow()) { |
|
882 | $uris[] = $row['uri']; |
||
883 | } |
||
884 | |||
885 | 2 | $newname = $name; |
|
886 | $i = 1 |
||
887 | 2 | ; |
|
888 | 2 | while (in_array($newname, $uris)) { |
|
889 | $newname = $name.$i; |
||
890 | $i = $i + 1; |
||
891 | } |
||
892 | 2 | return $newname; |
|
893 | } |
||
894 | |||
895 | /** |
||
896 | * @brief Checks if a contact with the same URI already exist in the address book. |
||
897 | * @param string $addressBookId Address book ID. |
||
898 | * @param string $uri |
||
899 | * @returns string Unique URI |
||
900 | */ |
||
901 | 2 | protected function uniqueURI($addressBookId, $uri) { |
|
902 | 2 | $stmt = $this->getPreparedQuery('counturi'); |
|
903 | |||
904 | 2 | $result = $stmt->execute(array($addressBookId, $uri)); |
|
905 | 2 | $result = $result->fetchRow(); |
|
906 | |||
907 | 2 | if (is_array($result) && count($result) > 0 && $result['count'] > 0) { |
|
908 | |||
909 | while (true) { |
||
910 | $uri = Properties::generateUID() . '.vcf'; |
||
911 | $result = $stmt->execute(array($addressBookId, $uri)); |
||
912 | |||
913 | if (is_array($result) && count($result) > 0 && $result['count'] > 0) { |
||
914 | continue; |
||
915 | } else { |
||
916 | return $uri; |
||
917 | } |
||
918 | |||
919 | } |
||
920 | } |
||
921 | |||
922 | 2 | return $uri; |
|
923 | } |
||
924 | |||
925 | /** |
||
926 | * Collect (nearly) all queries in one place |
||
927 | * |
||
928 | * @param string $identifier |
||
929 | * @param array $options Can be used for e.g. offset/limit |
||
930 | * @throws \Exception If $identifier isn't known |
||
931 | * @return \OC_DB_StatementWrapper |
||
932 | */ |
||
933 | 2 | protected function getPreparedQuery($identifier, array $options = array()) { |
|
934 | |||
935 | 2 | if (isset(self::$preparedQueries[$identifier])) { |
|
936 | 2 | return self::$preparedQueries[$identifier]; |
|
937 | } |
||
938 | |||
939 | 1 | $args = array(); |
|
940 | |||
941 | switch ($identifier) { |
||
942 | |||
943 | 1 | case 'getaddressbooksforuser': |
|
944 | $args[] = 'SELECT `id`, `displayname`, `description`, `ctag`' |
||
945 | . ' AS `lastmodified`, `userid` AS `owner`, `uri` FROM `' |
||
946 | . $this->addressBooksTableName |
||
947 | . '` WHERE `userid` = ? ORDER BY `displayname`'; |
||
948 | break; |
||
949 | 1 | case 'getaddressbook': |
|
950 | 1 | $args[] = 'SELECT `id`, `displayname`, `description`, ' |
|
951 | . '`userid` AS `owner`, `ctag` AS `lastmodified`, `uri` FROM `' |
||
952 | 1 | . $this->addressBooksTableName |
|
953 | 1 | . '` WHERE `id` = ? AND `userid` = ?'; |
|
954 | 1 | break; |
|
955 | 1 | case 'createaddressbook': |
|
956 | 1 | $args[] = 'INSERT INTO `' |
|
957 | 1 | . $this->addressBooksTableName . '` ' |
|
958 | 1 | . '(`userid`,`displayname`,`uri`,`description`,`ctag`) ' |
|
959 | 1 | . 'VALUES(?,?,?,?,?)'; |
|
960 | 1 | break; |
|
961 | 1 | case 'deleteaddressbookcontacts': |
|
962 | $args[] = 'DELETE FROM `' . $this->cardsTableName |
||
963 | . '` WHERE `addressbookid` = ?'; |
||
964 | break; |
||
965 | 1 | case 'deleteaddressbook': |
|
966 | $args[] = 'DELETE FROM `' |
||
967 | . $this->addressBooksTableName . '` WHERE `id` = ?'; |
||
968 | break; |
||
969 | 1 | case 'touchaddressbook': |
|
970 | 1 | $args[] = 'UPDATE `' . $this->addressBooksTableName |
|
971 | 1 | . '` SET `ctag` = ? + 1 WHERE `id` = ?'; |
|
972 | 1 | break; |
|
973 | 1 | case 'counturi': |
|
974 | 1 | $args[] = 'SELECT COUNT(*) AS `count` FROM `' |
|
975 | 1 | . $this->cardsTableName |
|
976 | 1 | . '` WHERE `addressbookid` = ? AND `uri` = ?'; |
|
977 | 1 | break; |
|
978 | 1 | case 'addressbookuris': |
|
979 | 1 | $args[] = 'SELECT `uri` FROM `' |
|
980 | 1 | . $this->addressBooksTableName . '` WHERE `userid` = ? '; |
|
981 | 1 | break; |
|
982 | 1 | case 'contactidfromuri': |
|
983 | $args[] = 'SELECT `id` FROM `' |
||
984 | . $this->cardsTableName |
||
985 | . '` WHERE `addressbookid` = ? AND `uri` = ? '; |
||
986 | break; |
||
987 | 1 | case 'deletecontact': |
|
988 | $args[] = 'DELETE FROM `' |
||
989 | . $this->cardsTableName |
||
990 | . '` WHERE `id` = ? AND `addressbookid` = ?'; |
||
991 | break; |
||
992 | 1 | case 'updatecontact': |
|
993 | $args[] = 'UPDATE `' . $this->cardsTableName |
||
994 | . '` SET `fullname` = ?,`carddata` = ?, `lastmodified` = ?' |
||
995 | . ' WHERE `id` = ? AND `addressbookid` = ?'; |
||
996 | break; |
||
997 | 1 | case 'createcontact': |
|
998 | 1 | $args[] = 'INSERT INTO `' |
|
999 | 1 | . $this->cardsTableName |
|
1000 | 1 | . '` (`addressbookid`,`fullname`,`carddata`,`uri`,`lastmodified`) ' |
|
1001 | 1 | . ' VALUES(?,?,?,?,?)'; |
|
1002 | 1 | break; |
|
1003 | 1 | case 'getcontactbyid': |
|
1004 | 1 | $args[] = 'SELECT `id`, `uri`, `carddata`, `lastmodified`, ' |
|
1005 | . '`addressbookid` AS `parent`, `fullname` AS `displayname` FROM `' |
||
1006 | 1 | . $this->cardsTableName |
|
1007 | 1 | . '` WHERE `id` = ? AND `addressbookid` = ?'; |
|
1008 | 1 | break; |
|
1009 | case 'getcontactbyuri': |
||
1010 | $args[] = 'SELECT `id`, `uri`, `carddata`, `lastmodified`, ' |
||
1011 | . '`addressbookid` AS `parent`, `fullname` AS `displayname` FROM `' |
||
1012 | . $this->cardsTableName |
||
1013 | . '` WHERE `uri` = ? AND `addressbookid` = ?'; |
||
1014 | break; |
||
1015 | case 'getcontactbyidnocollection': |
||
1016 | $args[] = 'SELECT `id`, `uri`, `carddata`, `lastmodified`, ' |
||
1017 | . '`addressbookid` AS `parent`, `fullname` AS `displayname` FROM `' |
||
1018 | . $this->cardsTableName . '` WHERE `id` = ?'; |
||
1019 | break; |
||
1020 | case 'getcontactbyurinocollection': |
||
1021 | $args[] = 'SELECT `id`, `uri`, `carddata`, `lastmodified`, ' |
||
1022 | . '`addressbookid` AS `parent`, `fullname` AS `displayname` FROM `' |
||
1023 | . $this->cardsTableName . '` WHERE `uri` = ?'; |
||
1024 | break; |
||
1025 | case 'getcontactids': |
||
1026 | $args[] = 'SELECT `id` FROM `' |
||
1027 | . $this->cardsTableName . '` WHERE `addressbookid` = ?'; |
||
1028 | break; |
||
1029 | View Code Duplication | case 'getcontacts': |
|
1030 | $args[] = 'SELECT `id`, `uri`, `carddata`, `lastmodified`, ' |
||
1031 | . '`addressbookid` AS `parent`, `fullname` AS `displayname` FROM `' |
||
1032 | . $this->cardsTableName . '` WHERE `addressbookid` = ?'; |
||
1033 | $args[] = isset($options['limit']) ? $options['limit'] : null; |
||
1034 | $args[] = isset($options['offset']) ? $options['offset'] : null; |
||
1035 | break; |
||
1036 | View Code Duplication | case 'getcontactsomitdata': |
|
1037 | $args[] = 'SELECT `id`, `uri`, `lastmodified`, ' |
||
1038 | . '`addressbookid` AS `parent`, `fullname` AS `displayname` FROM `' |
||
1039 | . $this->cardsTableName . '` WHERE `addressbookid` = ?'; |
||
1040 | $args[] = isset($options['limit']) ? $options['limit'] : null; |
||
1041 | $args[] = isset($options['offset']) ? $options['offset'] : null; |
||
1042 | break; |
||
1043 | case 'numcontacts': |
||
1044 | $args[] = 'SELECT COUNT(*) AS `count` FROM `' |
||
1045 | . $this->cardsTableName . '` WHERE `addressbookid` = ?'; |
||
1046 | break; |
||
1047 | default: |
||
1048 | throw new \Exception('Unknown query identifier: ' . $identifier); |
||
1049 | |||
1050 | } |
||
1051 | |||
1052 | 1 | self::$preparedQueries[$identifier] = call_user_func_array('\OCP\DB::prepare', $args); |
|
1053 | |||
1054 | 1 | return self::$preparedQueries[$identifier]; |
|
1055 | } |
||
1056 | |||
1057 | public function getSearchProvider($addressbook) { |
||
1060 | |||
1061 | } |
||
1062 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.