1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16:
17: class Turba_Driver_Sql extends Turba_Driver
18: {
19: 20: 21: 22: 23:
24: protected $_capabilities = array(
25: 'delete_addressbook' => true,
26: 'delete_all' => true
27: );
28:
29: 30: 31: 32: 33:
34: protected $_countCache = array();
35:
36: 37: 38: 39: 40:
41: protected $_db;
42:
43: 44: 45: 46: 47: 48: 49: 50: 51:
52: public function __construct($name = '', array $params = array())
53: {
54: if (empty($params['db'])) {
55: throw new InvalidArgumentException('Missing required Horde_Db_Adapter object');
56: }
57: $this->_db = $params['db'];
58: unset($params['db']);
59:
60: parent::__construct($name, $params);
61: }
62:
63: 64: 65: 66: 67:
68: public function count()
69: {
70: $test = $this->getContactOwner();
71: if (!isset($this->_countCache[$test])) {
72:
73: $query = 'SELECT COUNT(*) FROM ' . $this->_params['table'] .
74: ' WHERE ' . $this->toDriver('__owner') . ' = ?';
75: $values = array($test);
76:
77:
78: try {
79: $this->_countCache[$test] = $this->_db->selectValue($query, $values);
80: } catch (Horde_Db_Exception $e) {
81: $this->_countCache[$test] = 0;
82: }
83: }
84:
85: return $this->_countCache[$test];
86: }
87:
88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101:
102: protected function _search(array $criteria, array $fields, array $blobFields = array(), $count_only = false)
103: {
104: return $this->_internalSearch($criteria, $fields, $blobFields, array(), $count_only);
105: }
106:
107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124:
125: protected function _internalSearch(array $criteria, array $fields, $blobFields = array(), $appendWhere = array(), $count_only = false)
126: {
127:
128: $where = '';
129: $values = array();
130:
131: if (count($criteria) || !empty($this->_params['filter'])) {
132: foreach ($criteria as $key => $vals) {
133: if ($key == 'OR' || $key == 'AND') {
134: if (!empty($where)) {
135: $where .= ' ' . $key . ' ';
136: }
137: $binds = $this->_buildSearchQuery($key, $vals);
138: $where .= '(' . $binds[0] . ')';
139: $values += $binds[1];
140: }
141: }
142: $where = ' WHERE ' . $where;
143: if (count($criteria) && !empty($this->_params['filter'])) {
144: $where .= ' AND ';
145: }
146: if (!empty($this->_params['filter'])) {
147: $where .= $this->_params['filter'];
148: }
149: if (count($appendWhere)) {
150: $where .= ' AND ' . $appendWhere['sql'];
151: $values = array_merge($values, $appendWhere['params']);
152: }
153: } elseif (count($appendWhere)) {
154: $where = ' WHERE ' . $appendWhere['sql'];
155: $values = array_merge($values, $appendWhere['params']);
156: }
157:
158:
159: if ($count_only) {
160: $query = 'SELECT COUNT(*) FROM ' . $this->_params['table'] . $where;
161: try {
162: return $this->_db->selectValue($query, $values);
163: } catch (Horde_Db_Exception $e) {
164: throw new Turba_Exception($e);
165: }
166: } else {
167: $query = 'SELECT ' . implode(', ', $fields) . ' FROM ' . $this->_params['table'] . $where;
168: try {
169: return $this->_parseRead($blobFields, $this->_db->selectAll($query, $values));
170: } catch (Horde_Db_Exception $e) {
171: throw new Turba_Exception($e);
172: }
173: }
174: }
175:
176: protected function _parseRead($blobFields, $result)
177: {
178: $results = array();
179: $columns = $this->_db->columns($this->_params['table']);
180:
181: foreach ($result as $row) {
182: $entry = array();
183:
184: foreach ($row as $field => $val) {
185: if (isset($blobFields[$field])) {
186: $entry[$field] = $columns[$field]->binaryToString($val);
187: } else {
188: $entry[$field] = $this->_convertFromDriver($val);
189: }
190: }
191:
192: $results[] = $entry;
193: }
194:
195: return $results;
196: }
197:
198: 199: 200: 201: 202: 203: 204:
205: protected function _buildFields($array)
206: {
207: foreach ($array as &$entry) {
208: $entry = is_array($entry)
209: ? implode(',', $this->_buildFields($entry))
210: : 'a1.' . $entry;
211: }
212:
213: return $array;
214: }
215:
216: 217: 218: 219: 220: 221: 222:
223: protected function _buildWhere($array)
224: {
225: foreach ($array as &$entry) {
226: if (is_array($entry)) {
227: $entry = reset($entry);
228: }
229: $entry = 'a1.' . $entry . ' IS NOT NULL AND a1.' . $entry . ' <> \'\'';
230: }
231:
232: return $array;
233: }
234:
235: 236: 237: 238: 239: 240: 241:
242: protected function _buildJoin($array)
243: {
244: foreach ($array as &$entry) {
245: $entry = is_array($entry)
246: ? implode(' AND ', $this->_buildJoin($entry))
247: : 'a1.' . $entry . ' = a2.' . $entry;
248: }
249:
250: return $array;
251: }
252:
253: 254: 255: 256: 257: 258: 259: 260: 261: 262: 263: 264:
265: public function searchDuplicates()
266: {
267: $owner = $this->getContactOwner();
268: $fields = array();
269: if (is_array($this->map['name'])) {
270: if (in_array('lastname', $this->map['name']['fields']) &&
271: isset($this->map['lastname'])) {
272: $field = array($this->map['lastname']);
273: if (in_array('firstname', $this->map['name']['fields']) &&
274: isset($this->map['firstname'])) {
275: $field[] = $this->map['firstname'];
276: }
277: $fields[] = $field;
278: }
279: } else {
280: $fields[] = $this->map['name'];
281: }
282: if (isset($this->map['email'])) {
283: $fields[] = $this->map['email'];
284: }
285: $nameFormat = $GLOBALS['prefs']->getValue('name_format');;
286: if ($nameFormat != 'first_last' && $nameFormat != 'last_first') {
287: $nameFormat = 'first_last';
288: }
289:
290: $order = $this->_buildFields($fields);
291: $joins = $this->_buildJoin($fields);
292: $where = $this->_buildWhere($fields);
293:
294: $duplicates = array();
295: for ($i = 0; $i < count($joins); $i++) {
296:
297: $query = sprintf('SELECT DISTINCT a1.%s,%s FROM %s a1 JOIN %s a2 ON %s AND a1.%s <> a2.%s WHERE a1.%s = ? AND a2.%s = ? AND %s ORDER BY %s',
298: $this->map['__key'],
299: $order[$i],
300: $this->_params['table'],
301: $this->_params['table'],
302: $joins[$i],
303: $this->map['__key'],
304: $this->map['__key'],
305: $this->map['__owner'],
306: $this->map['__owner'],
307: $where[$i],
308: $order[$i]);
309:
310:
311: try {
312: $ids = $this->_db->selectValues($query, array($owner, $owner));
313: } catch (Horde_Db_Exception $e) {
314: throw new Turba_Exception($e);
315: }
316:
317: $field = ($i == 0)
318: ? 'name'
319: : array_search($fields[$i], $this->map);
320:
321: $contacts = array();
322: foreach ($ids as $id) {
323: $contact = $this->getObject($id);
324: $value = $contact->getValue($field);
325: if ($field == 'name') {
326: $value = Turba::formatName($contact, $nameFormat);
327: }
328:
329: if ($field == 'email') {
330: $value = Horde_String::lower($value);
331: }
332: if (!isset($contacts[$value])) {
333: $contacts[$value] = new Turba_List();
334: }
335: $contacts[$value]->insert($contact);
336: }
337: if ($contacts) {
338: $duplicates[$field] = $contacts;
339: }
340: }
341:
342: return $duplicates;
343: }
344:
345: 346: 347: 348: 349: 350: 351: 352: 353: 354: 355: 356:
357: protected function _read($key, $ids, $owner, array $fields,
358: array $blobFields = array())
359: {
360: $values = array();
361:
362: $in = '';
363: if (is_array($ids)) {
364: if (!count($ids)) {
365: return array();
366: }
367:
368: foreach ($ids as $id) {
369: $in .= empty($in) ? '?' : ', ?';
370: $values[] = $this->_convertToDriver($id);
371: }
372: $where = $key . ' IN (' . $in . ')';
373: } else {
374: $where = $key . ' = ?';
375: $values[] = $this->_convertToDriver($ids);
376: }
377: if (isset($this->map['__owner'])) {
378: $where .= ' AND ' . $this->map['__owner'] . ' = ?';
379: $values[] = $this->_convertToDriver($owner);
380: }
381: if (!empty($this->_params['filter'])) {
382: $where .= ' AND ' . $this->_params['filter'];
383: }
384:
385: $query = 'SELECT ' . implode(', ', $fields) . ' FROM '
386: . $this->_params['table'] . ' WHERE ' . $where;
387:
388: try {
389: return $this->_parseRead($blobFields, $this->_db->selectAll($query, $values));
390: } catch (Horde_Db_Exception $e) {
391: throw new Turba_Exception($e);
392: }
393: }
394:
395: 396: 397: 398: 399: 400: 401:
402: protected function _add(array $attributes, array $blob_fields = array())
403: {
404: list($fields, $values) = $this->_prepareWrite($attributes, $blob_fields);
405: $query = 'INSERT INTO ' . $this->_params['table']
406: . ' (' . implode(', ', $fields) . ')'
407: . ' VALUES (' . str_repeat('?, ', count($values) - 1) . '?)';
408:
409: try {
410: $this->_db->insert($query, $values);
411: } catch (Horde_Db_Exception $e) {
412: throw new Turba_Exception($e);
413: }
414: }
415:
416: protected function _prepareWrite($attributes, $blob_fields)
417: {
418: $fields = $values = array();
419:
420: foreach ($attributes as $field => $value) {
421: $fields[] = $field;
422:
423: if (!empty($value) && isset($blob_fields[$field])) {
424: $values[] = new Horde_Db_Value_Binary($value);
425: } else {
426: $values[] = $this->_convertToDriver($value);
427: }
428: }
429:
430: return array($fields, $values);
431: }
432:
433: 434: 435:
436: protected function _canAdd()
437: {
438: return true;
439: }
440:
441: 442: 443: 444: 445:
446: protected function _delete($object_key, $object_id)
447: {
448: $query = 'DELETE FROM ' . $this->_params['table'] .
449: ' WHERE ' . $object_key . ' = ?';
450: $values = array($object_id);
451:
452: try {
453: $this->_db->delete($query, $values);
454: } catch (Horde_Db_Exception $e) {
455: throw new Turba_Exception($e);
456: }
457: }
458:
459: 460: 461: 462: 463:
464: protected function _deleteAll($sourceName = null)
465: {
466: if (!$GLOBALS['registry']->getAuth()) {
467: throw new Turba_Exception('Permission denied');
468: }
469:
470:
471: $values = empty($sourceName)
472: ? array($GLOBALS['registry']->getAuth())
473: : array($sourceName);
474:
475:
476: $query = 'SELECT '. $this->map['__uid'] . ' FROM ' . $this->_params['table'] . ' WHERE owner_id = ?';
477:
478: try {
479: $ids = $this->_db->selectValues($query, $values);
480: } catch (Horde_Db_Exception $e) {
481: throw new Turba_Exception($e);
482: }
483:
484:
485: $query = 'DELETE FROM ' . $this->_params['table'] . ' WHERE owner_id = ?';
486:
487: try {
488: $this->_db->delete($query, $values);
489: } catch (Horde_Db_Exception $e) {
490: throw new Turba_Exception($e);
491: }
492:
493:
494: $history = $GLOBALS['injector']->getInstance('Horde_History');
495: try {
496: foreach ($ids as $id) {
497:
498:
499:
500:
501: $guid = 'turba:' . $this->getName() . ':' . $id;
502: $history->log($guid, array('action' => 'delete'), true);
503: }
504: } catch (Exception $e) {
505: Horde::logMessage($e, 'ERR');
506: }
507: }
508:
509: 510: 511: 512: 513: 514: 515: 516:
517: function _save(Turba_Object $object)
518: {
519: list($object_key, $object_id) = each($this->toDriverKeys(array('__key' => $object->getValue('__key'))));
520: $attributes = $this->toDriverKeys($object->getAttributes());
521: $blob_fields = $this->toDriverKeys($this->getBlobs());
522:
523: $where = $object_key . ' = ?';
524: unset($attributes[$object_key]);
525:
526: list($fields, $values) = $this->_prepareWrite($attributes, $blob_fields);
527:
528: $values[] = $object_id;
529:
530: $query = 'UPDATE ' . $this->_params['table'] . ' SET ' . implode(' = ?, ', $fields) . ' = ? WHERE ' . $where;
531:
532: try {
533: $this->_db->update($query, $values);
534: } catch (Horde_Db_Exception $e) {
535: throw new Turba_Exception($e);
536: }
537:
538: return $object_id;
539: }
540:
541: 542: 543: 544: 545: 546: 547: 548:
549: protected function _makeKey(array $attributes)
550: {
551: return strval(new Horde_Support_Randomid());
552: }
553:
554: 555: 556: 557: 558: 559: 560: 561: 562:
563: protected function _buildSearchQuery($glue, array $criteria)
564: {
565: $clause = '';
566: $values = array();
567:
568: foreach ($criteria as $key => $vals) {
569: if (!empty($vals['OR']) || !empty($vals['AND'])) {
570: if (!empty($clause)) {
571: $clause .= ' ' . $glue . ' ';
572: }
573: $binds = $this->_buildSearchQuery(!empty($vals['OR']) ? 'OR' : 'AND', $vals);
574: $clause .= '(' . $binds[0] . ')';
575: $values = array_merge($values, $binds[1]);
576: } else {
577: if (isset($vals['field'])) {
578: if (!empty($clause)) {
579: $clause .= ' ' . $glue . ' ';
580: }
581: $rhs = $this->_convertToDriver($vals['test']);
582: $binds = $this->_db->buildClause($vals['field'], $vals['op'], $rhs, true, array('begin' => !empty($vals['begin'])));
583: if (is_array($binds)) {
584: $clause .= $binds[0];
585: $values = array_merge($values, $binds[1]);
586: } else {
587: $clause .= $binds;
588: }
589: } else {
590: foreach ($vals as $test) {
591: if (!empty($test['OR']) || !empty($test['AND'])) {
592: if (!empty($clause)) {
593: $clause .= ' ' . $glue . ' ';
594: }
595: $binds = $this->_buildSearchQuery(!empty($vals['OR']) ? 'OR' : 'AND', $test);
596: $clause .= '(' . $binds[0] . ')';
597: $values = array_merge($values, $binds[1]);
598: } else {
599: if (!empty($clause)) {
600: $clause .= ' ' . $key . ' ';
601: }
602: $rhs = $this->_convertToDriver($test['test']);
603: if ($rhs == '' && $test['op'] == '=') {
604: $clause .= '(' . $this->_db->buildClause($test['field'], '=', $rhs) . ' OR ' . $test['field'] . ' IS NULL)';
605: } else {
606: $binds = $this->_db->buildClause($test['field'], $test['op'], $rhs, true, array('begin' => !empty($test['begin'])));
607: if (is_array($binds)) {
608: $clause .= $binds[0];
609: $values = array_merge($values, $binds[1]);
610: } else {
611: $clause .= $binds;
612: }
613: }
614: }
615: }
616: }
617: }
618: }
619:
620: return array($clause, $values);
621: }
622:
623: 624: 625: 626: 627: 628: 629:
630: protected function _convertFromDriver($value)
631: {
632: return Horde_String::convertCharset($value, $this->_db->getOption('charset'), 'UTF-8');
633: }
634:
635: 636: 637: 638: 639: 640: 641:
642: protected function _convertToDriver($value)
643: {
644: return Horde_String::convertCharset($value, 'UTF-8', $this->_db->getOption('charset'));
645: }
646:
647: 648: 649: 650: 651: 652: 653:
654: public function removeUserData($user)
655: {
656:
657: if (!$GLOBALS['registry']->isAdmin()) {
658: throw new Horde_Exception_PermissionDenied(_("Permission denied"));
659: }
660:
661: $this->_deleteAll($user);
662: }
663:
664: 665: 666: 667: 668: 669: 670: 671: 672: 673: 674:
675: public function getTimeObjectTurbaList(Horde_Date $start, Horde_Date $end, $field)
676: {
677: $t_object = $this->toDriver($field);
678: $criteria = $this->makesearch(
679: array('__owner' => $this->getContactOwner()),
680: 'AND',
681: array($this->toDriver('__owner') => true),
682: false);
683:
684:
685:
686: $criteria['AND'][] = array(
687: 'field' => $t_object,
688: 'op' => '<>',
689: 'test' => ''
690: );
691:
692: if ($start->year == $end->year) {
693: $start = sprintf('%02d-%02d', $start->month, $start->mday);
694: $end = sprintf('%02d-%02d', $end->month, $end->mday);
695: $where = array('sql' => $t_object . ' IS NOT NULL AND SUBSTR('
696: . $t_object . ', 6, 5) BETWEEN ? AND ?',
697: 'params' => array($start, $end));
698: } else {
699: $months = array();
700: $diff = ($end->month + 12) - $start->month;
701: $newDate = new Horde_Date(array(
702: 'month' => $start->month,
703: 'mday' => $start->mday,
704: 'year' => $start->year
705: ));
706: for ($i = 0; $i <= $diff; ++$i) {
707: $months[] = sprintf('%02d', $newDate->month++);
708: }
709: $where = array('sql' => $t_object . ' IS NOT NULL AND SUBSTR('
710: . $t_object . ', 6, 2) IN ('
711: . str_repeat('?,', count($months) - 1) . '?)',
712: 'params' => $months);
713: }
714:
715: $fields_pre = array(
716: '__key', '__type', '__owner', 'name', 'birthday', 'category',
717: 'anniversary'
718: );
719:
720: $fields = array();
721: foreach ($fields_pre as $field) {
722: $result = $this->toDriver($field);
723: if (is_array($result)) {
724: foreach ($result as $composite_field) {
725: $composite_result = $this->toDriver($composite_field);
726: if ($composite_result) {
727: $fields[] = $composite_result;
728: }
729: }
730: } elseif ($result) {
731: $fields[] = $result;
732: }
733: }
734:
735: return $this->_toTurbaObjects($this->_internalSearch($criteria, $fields, array(), $where));
736: }
737:
738: }
739: