0011522: improve handling of group-lists
[tine20] / tine20 / Tinebase / Record / RecordSet.php
1 <?php
2 /**
3  * Tine 2.0
4  * 
5  * @package     Tinebase
6  * @subpackage  Record
7  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
8  * @copyright   Copyright (c) 2007-2011 Metaways Infosystems GmbH (http://www.metaways.de)
9  * @author      Cornelius Weiss <c.weiss@metaways.de>
10  */
11
12 /**
13  * class to hold a list of records
14  * 
15  * records are held as a unsorted set with a autoasigned numeric index.
16  * NOTE: the index of an record is _not_ related to the record and/or its identifier!
17  * 
18  * @package     Tinebase
19  * @subpackage  Record
20  *
21  */
22 class Tinebase_Record_RecordSet implements IteratorAggregate, Countable, ArrayAccess
23 {
24     /**
25      * class name of records this instance can hold
26      * @var string
27      */
28     protected $_recordClass;
29     
30     /**
31      * Holds records
32      * @var array
33      */
34     protected $_listOfRecords = array();
35     
36     /**
37      * holds mapping id -> offset in $_listOfRecords
38      * @var array
39      */
40     protected $_idMap = array();
41     
42     /**
43      * holds offsets of idless (new) records in $_listOfRecords
44      * @var array
45      */
46     protected $_idLess = array();
47     
48     /**
49      * Holds validation errors
50      * @var array
51      */
52     protected $_validationErrors = array();
53
54     /**
55      * creates new Tinebase_Record_RecordSet
56      *
57      * @param string $_className the required classType
58      * @param array|Tinebase_Record_RecordSet $_records array of record objects
59      * @param bool $_bypassFilters {@see Tinebase_Record_Interface::__construct}
60      * @param bool $_convertDates {@see Tinebase_Record_Interface::__construct}
61      * @return void
62      * @throws Tinebase_Exception_InvalidArgument
63      */
64     public function __construct($_className, $_records = array(), $_bypassFilters = false, $_convertDates = true)
65     {
66         if (! class_exists($_className)) {
67             throw new Tinebase_Exception_InvalidArgument('Class ' . $_className . ' does not exist');
68         }
69         $this->_recordClass = $_className;
70         
71         foreach ($_records as $record) {
72             $toAdd = $record instanceof Tinebase_Record_Abstract ? $record : new $this->_recordClass($record, $_bypassFilters, $_convertDates);
73             $this->addRecord($toAdd);
74         }
75     }
76     
77     /**
78      * clone records
79      */
80     public function __clone()
81     {
82         foreach ($this->_listOfRecords as $key => $record) {
83             $this->_listOfRecords[$key] = clone $record;
84         }
85     }
86     
87     /**
88      * returns name of record class this recordSet contains
89      * 
90      * @returns string
91      */
92     public function getRecordClassName()
93     {
94         return $this->_recordClass;
95     }
96     
97     /**
98      * add Tinebase_Record_Interface like object to internal list (it is not inserted if record is already in set)
99      *
100      * @param Tinebase_Record_Interface $_record
101      * @param integer $_index
102      * @return int index in set of inserted record or index of existing record
103      */
104     public function addRecord(Tinebase_Record_Interface $_record, $_index = NULL)
105     {
106         if (! $_record instanceof $this->_recordClass) {
107             throw new Tinebase_Exception_Record_NotAllowed('Attempt to add/set record of wrong record class ('
108                 . get_class($_record) . ') Should be ' . $this->_recordClass);
109         }
110
111         $recordId = $_record->getId();
112
113         if ($recordId && isset($this->_idMap[$recordId]) && isset($this->_listOfRecords[$this->_idMap[$recordId]])) {
114             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
115                 . ' Record (id ' . $recordId . ') already in set - we don\'t want duplicates)');
116             return $this->_idMap[$recordId];
117         }
118
119         $this->_listOfRecords[] = $_record;
120         end($this->_listOfRecords);
121         $index = ($_index !== NULL) ? $_index : key($this->_listOfRecords);
122         
123         // maintain indices
124         if ($recordId) {
125             $this->_idMap[$recordId] = $index;
126         } else {
127             $this->_idLess[] = $index;
128         }
129         
130         return $index;
131     }
132     
133     /**
134      * removes all records from this set
135      */
136     public function removeAll()
137     {
138         foreach ($this->_listOfRecords as $record) {
139             $this->removeRecord($record);
140         }
141     }
142     
143     /**
144      * remove record from set
145      * 
146      * @param Tinebase_Record_Interface $_record
147      */
148     public function removeRecord(Tinebase_Record_Interface $_record)
149     {
150         $idx = $this->indexOf($_record);
151         if ($idx !== false) {
152             $this->offsetUnset($idx);
153         }
154     }
155
156     /**
157      * remove records from set
158      * 
159      * @param Tinebase_Record_RecordSet $_records
160      */
161     public function removeRecords(Tinebase_Record_RecordSet $_records)
162     {
163         foreach ($_records as $record) {
164             $this->removeRecord($record);
165         }
166     }
167     
168     /**
169      * get index of given record
170      * 
171      * @param Tinebase_Record_Interface $_record
172      * @return (int) index of record of false if not found
173      */
174     public function indexOf(Tinebase_Record_Interface $_record)
175     {
176         return array_search($_record, $this->_listOfRecords);
177     }
178     
179     /**
180      * checks if each member record of this set is valid
181      * 
182      * @return bool
183      */
184     public function isValid()
185     {
186         foreach ($this->_listOfRecords as $index => $record) {
187             if (!$record->isValid()) {
188                 $this->_validationErrors[$index] = $record->getValidationErrors();
189             }
190         }
191         return !(bool)count($this->_validationErrors);
192     }
193     
194     /**
195      * returns array of array of fields with validation errors 
196      *
197      * @return array index => validationErrors
198      */
199     public function getValidationErrors()
200     {
201         return $this->_validationErrors;
202     }
203     
204     /**
205      * converts RecordSet to array
206      * NOTE: keys of the array are numeric and have _noting_ to do with internal indexes or identifiers
207      * 
208      * @return array 
209      */
210     public function toArray()
211     {
212         $resultArray = array();
213         foreach($this->_listOfRecords as $index => $record) {
214             $resultArray[$index] = $record->toArray();
215         }
216          
217         return array_values($resultArray);
218     }
219     
220     /**
221      * returns index of record identified by its id
222      * 
223      * @param  string $_id id of record
224      * @return int|bool    index of record or false if not in set
225      */
226     public function getIndexById($_id)
227     {
228         return (isset($this->_idMap[$_id]) || array_key_exists($_id, $this->_idMap)) ? $this->_idMap[$_id] : false;
229     }
230     
231     /**
232      * returns record identified by its id
233      * 
234      * @param  string $_id id of record
235      * @return Tinebase_Record_Abstract::|bool    record or false if not in set
236      */
237     public function getById($_id)
238     {
239         $idx = $this->getIndexById($_id);
240         
241         return $idx !== false ? $this[$idx] : false;
242     }
243
244     /**
245      * returns record identified by its id
246      * 
247      * @param  integer $index of record
248      * @return Tinebase_Record_Abstract::|bool    record or false if not in set
249      */
250     public function getByIndex($index)
251     {
252         return (isset($this->_listOfRecords[$index])) ? $this->_listOfRecords[$index] : false;
253     }
254     
255     /**
256      * returns array of ids
257      */
258     public function getArrayOfIds()
259     {
260         return array_keys($this->_idMap);
261     }
262     
263     /**
264      * returns array of ids
265      */
266     public function getArrayOfIdsAsString()
267     {
268         $ids = array_keys($this->_idMap);
269         foreach($ids as $key => $id) {
270             $ids[$key] = (string) $id;
271         }
272         return $ids;
273     }
274
275     /**
276      * returns array with idless (new) records in this set
277      * 
278      * @return array
279      */
280     public function getIdLessIndexes()
281     {
282         return array_values($this->_idLess);
283     }
284     
285     /**
286      * sets given property in all records with data from given values identified by their indices
287      *
288      * @param string $_name property name
289      * @param array  $_values index => property value
290      * @param boolean $skipMissing
291      * @throws Tinebase_Exception_Record_NotDefined
292      */
293     public function setByIndices($_name, array $_values, $skipMissing = false)
294     {
295         foreach ($_values as $index => $value) {
296             if (! (isset($this->_listOfRecords[$index]) || array_key_exists($index, $this->_listOfRecords))) {
297                 if ($skipMissing) {
298                     if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ 
299                         . ' Skip missing record ' . $index . ' => ' . $value . ' property: ' . $_name);
300                     continue;
301                 } else {
302                     throw new Tinebase_Exception_Record_NotDefined('Could not find record with index ' . $index);
303                 }
304             }
305             $this->_listOfRecords[$index]->$_name = $value;
306         }
307     }
308     
309     /**
310      * Sets timezone of $this->_datetimeFields
311      * 
312      * @see Tinebase_DateTime::setTimezone()
313      * @param  string $_timezone
314      * @param  bool   $_recursive
315      * @return  void
316      * @throws Tinebase_Exception_Record_Validation
317      */
318     public function setTimezone($_timezone, $_recursive = TRUE)
319     {
320         $returnValues = array();
321         foreach ($this->_listOfRecords as $index => $record) {
322             $returnValues[$index] = $record->setTimezone($_timezone, $_recursive);
323         }
324         
325         return $returnValues;
326     }
327     
328     /**
329      * sets given property in all member records of this set
330      * 
331      * @param string $_name
332      * @param mixed $_value
333      * @return void
334      */
335     public function __set($_name, $_value)
336     {
337         foreach ($this->_listOfRecords as $record) {
338             $record->$_name = $_value;
339         }
340     }
341     
342     /**
343      * returns an array with the properties of all records in this set
344      * 
345      * @param  string $_name property
346      * @return array index => property
347      */
348     public function __get($_name)
349     {
350         $propertiesArray = array();
351         
352         foreach ($this->_listOfRecords as $index => $record) {
353             $propertiesArray[$index] = $record->$_name;
354         }
355         
356         return $propertiesArray;
357     }
358     
359     /**
360      * executes given function in all records
361      *
362      * @param string $_fname
363      * @param array $_arguments
364      * @return array array index => return value
365      */
366     public function __call($_fname, $_arguments)
367     {
368         $returnValues = array();
369         foreach ($this->_listOfRecords as $index => $record) {
370             $returnValues[$index] = call_user_func_array(array($record, $_fname), $_arguments);
371         }
372         
373         return $returnValues;
374     }
375     
376    /** convert this to string
377     *
378     * @return string
379     */
380     public function __toString()
381     {
382        return print_r($this->toArray(), TRUE);
383     }
384     
385     /**
386      * Returns the number of elements in the recordSet.
387      * required by interface Countable
388      *
389      * @return int
390      */
391     public function count()
392     {
393         return count($this->_listOfRecords);
394     }
395
396     /**
397      * required by IteratorAggregate interface
398      * 
399      * @return iterator
400      */
401     public function getIterator()
402     {
403         return new ArrayIterator($this->_listOfRecords);
404     }
405
406     /**
407      * required by ArrayAccess interface
408      */
409     public function offsetExists($_offset)
410     {
411         return isset($this->_listOfRecords[$_offset]);
412     }
413     
414     /**
415      * required by ArrayAccess interface
416      */
417     public function offsetGet($_offset)
418     {
419         if (! is_int($_offset)) {
420             throw new Tinebase_Exception_UnexpectedValue("index must be of type integer (". gettype($_offset) .") " . $_offset .  ' given');
421         }
422         if (! (isset($this->_listOfRecords[$_offset]) || array_key_exists($_offset, $this->_listOfRecords))) {
423             throw new Tinebase_Exception_NotFound("No such entry with index $_offset in this record set");
424         }
425         
426         return $this->_listOfRecords[$_offset];
427     }
428     
429     /**
430      * required by ArrayAccess interface
431      */
432     public function offsetSet($_offset, $_value)
433     {
434         if (! $_value instanceof $this->_recordClass) {
435             throw new Tinebase_Exception_Record_NotAllowed('Attempt to add/set record of wrong record class. Should be ' . $this->_recordClass);
436         }
437         
438         if (!is_int($_offset)) {
439             $this->addRecord($_value);
440         } else {
441             if (!(isset($this->_listOfRecords[$_offset]) || array_key_exists($_offset, $this->_listOfRecords))) {
442                 throw new Tinebase_Exception_Record_NotAllowed('adding a record is only allowd via the addRecord method');
443             }
444             $this->_listOfRecords[$_offset] = $_value;
445             $id = $_value->getId();
446             if ($id) {
447                 if(! (isset($this->_idMap[$id]) || array_key_exists($id, $this->_idMap))) {
448                     $this->_idMap[$id] = $_offset;
449                     $idLessIdx = array_search($_offset, $this->_idLess);
450                     unset($this->_idLess[$idLessIdx]);
451                 }
452             } else {
453                 if (array_search($_offset, $this->_idLess) === false) {
454                     $this->_idLess[] = $_offset;
455                     $idMapIdx = array_search($_offset, $this->_idMap);
456                     unset($this->_idMap[$idMapIdx]);
457                 }
458             }
459         }
460     }
461     
462     /**
463      * required by ArrayAccess interface
464      */
465     public function offsetUnset($_offset)
466     {
467         $id = $this->_listOfRecords[$_offset]->getId();
468         if ($id) {
469             unset($this->_idMap[$id]);
470         } else {
471             $idLessIdx = array_search($_offset, $this->_idLess);
472             unset($this->_idLess[$idLessIdx]);
473         }
474         
475         unset($this->_listOfRecords[$_offset]);
476     }
477     
478     /**
479      * Returns an array with ids of records to delete, to create or to update
480      *
481      * @param array $_toCompareWithRecordsIds Array to compare this record sets ids with
482      * @return array An array with sub array indices 'toDeleteIds', 'toCreateIds' and 'toUpdateIds'
483      * 
484      * @deprecated please use diff() as this returns wrong result when idless records have been added
485      * @see 0007492: replace getMigration() with diff() when comparing Tinebase_Record_RecordSets
486      */
487     public function getMigration(array $_toCompareWithRecordsIds)
488     {
489         $existingRecordsIds = $this->getArrayOfIds();
490         
491         $result = array();
492         
493         $result['toDeleteIds'] = array_diff($existingRecordsIds, $_toCompareWithRecordsIds);
494         $result['toCreateIds'] = array_diff($_toCompareWithRecordsIds, $existingRecordsIds);
495         $result['toUpdateIds'] = array_intersect($existingRecordsIds, $_toCompareWithRecordsIds);
496         
497         return $result;
498     }
499
500     /**
501      * adds indices to this record set
502      *
503      * @param array $_properties
504      * @return $this
505      *
506      * TODO implement
507      */
508     public function addIndices(array $_properties)
509     {
510         return $this;
511     }
512     
513     /**
514      * filter recordset and return subset
515      *
516      * @param string $_field
517      * @param string $_value
518      * @return Tinebase_Record_RecordSet
519      */
520     public function filter($_field, $_value = NULL, $_valueIsRegExp = FALSE)
521     {
522         $matchingRecords = $this->_getMatchingRecords($_field, $_value, $_valueIsRegExp);
523         
524         $result = new Tinebase_Record_RecordSet($this->_recordClass, $matchingRecords);
525         
526         return $result;
527     }
528
529     /**
530      * returns new set with records of this set
531      *
532      * @param  bool $recordsByRef
533      * @return Tinebase_Record_RecordSet
534      */
535     public function getClone($recordsByRef=false)
536     {
537         if ($recordsByRef) {
538             $result = new Tinebase_Record_RecordSet($this->_recordClass, $this->_listOfRecords);
539         } else {
540             $result = clone $this;
541         }
542
543         return $result;
544     }
545
546     /**
547      * Finds the first matching record in this store by a specific property/value.
548      *
549      * @param string $_field
550      * @param string $_value
551      * @return Tinebase_Record_Abstract
552      */
553     public function find($_field, $_value, $_valueIsRegExp = FALSE)
554     {
555         $matchingRecords = array_values($this->_getMatchingRecords($_field, $_value, $_valueIsRegExp));
556         return count($matchingRecords) > 0 ? $matchingRecords[0] : NULL;
557     }
558     
559     /**
560      * filter recordset and return matching records
561      *
562      * @param string|function $_field
563      * @param string $_value
564      * @param boolean $_valueIsRegExp
565      * @return array
566      */
567     protected function _getMatchingRecords($_field, $_value, $_valueIsRegExp = FALSE)
568     {
569         if (!is_string($_field) && is_callable($_field)) {
570             $matchingRecords = array_filter($this->_listOfRecords, $_field);
571         } else {
572             if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . " Filtering field '$_field' of '{$this->_recordClass}' without indices, expecting slow results");
573             $valueMap = $this->$_field;
574             
575             if ($_valueIsRegExp) {
576                 $matchingMap = preg_grep($_value,  $valueMap);
577             } else {
578                 $matchingMap = array_flip((array)array_keys($valueMap, $_value));
579             }
580             
581             $matchingRecords = array_intersect_key($this->_listOfRecords, $matchingMap);
582         }
583         
584         return $matchingRecords;
585     }
586     
587     /**
588      * returns first record of this set
589      *
590      * @return Tinebase_Record_Abstract|NULL
591      */
592     public function getFirstRecord()
593     {
594         if (count($this->_listOfRecords) > 0) {
595             foreach ($this->_listOfRecords as $idx => $record) {
596                 return $record;
597             }
598         } else {
599             return NULL;
600         }
601     }
602     
603     /**
604      * compares two recordsets / only compares the ids / returns all records that are different in an array:
605      *  - removed  -> all records that are in $this but not in $_recordSet
606      *  - added    -> all records that are in $_recordSet but not in $this
607      *  - modified -> array of diffs  for all different records that are in both record sets
608      * 
609      * @param Tinebase_Record_RecordSet $recordSet
610      * @return Tinebase_Record_RecordSetDiff
611      */
612     public function diff($recordSet)
613     {
614         if (! $recordSet instanceof Tinebase_Record_RecordSet) {
615             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
616                 . ' Did not get Tinebase_Record_RecordSet, skipping diff(' . $this->_recordClass . ')');
617             return new Tinebase_Record_RecordSetDiff(array(
618                 'model'    => $this->getRecordClassName()
619             ));
620         }
621         
622         if ($this->getRecordClassName() !== $recordSet->getRecordClassName()) {
623             throw new Tinebase_Exception_InvalidArgument('can only compare recordsets with the same type of records');
624         }
625         
626         $existingRecordsIds = $this->getArrayOfIds();
627         $toCompareWithRecordsIds = $recordSet->getArrayOfIds();
628         
629         $removedIds = array_diff($existingRecordsIds, $toCompareWithRecordsIds);
630         $addedIds = array_diff($toCompareWithRecordsIds, $existingRecordsIds);
631         $modifiedIds = array_intersect($existingRecordsIds, $toCompareWithRecordsIds);
632         
633         $removed = new Tinebase_Record_RecordSet($this->getRecordClassName());
634         $added = new Tinebase_Record_RecordSet($this->getRecordClassName());
635         $modified = new Tinebase_Record_RecordSet('Tinebase_Record_Diff');
636         
637         foreach ($addedIds as $id) {
638             $added->addRecord($recordSet->getById($id));
639         }
640         // consider records without id, too
641         foreach ($recordSet->getIdLessIndexes() as $index) {
642             $added->addRecord($recordSet->getByIndex($index));
643         }
644         foreach ($removedIds as $id) {
645             $removed->addRecord($this->getById($id));
646         }
647         // consider records without id, too
648         foreach ($this->getIdLessIndexes() as $index) {
649             $removed->addRecord($this->getByIndex($index));
650         }
651         foreach ($modifiedIds as $id) {
652             $diff = $this->getById($id)->diff($recordSet->getById($id));
653             if (! $diff->isEmpty()) {
654                 $modified->addRecord($diff);
655             }
656         }
657         
658         $result = new Tinebase_Record_RecordSetDiff(array(
659             'model'    => $this->getRecordClassName(),
660             'added'    => $added,
661             'removed'  => $removed,
662             'modified' => $modified,
663         ));
664         
665         return $result;
666     }
667     
668     /**
669      * merges records from given record set
670      * 
671      * @param Tinebase_Record_RecordSet $_recordSet
672      * @return void
673      */
674     public function merge(Tinebase_Record_RecordSet $_recordSet)
675     {
676         foreach ($_recordSet as $record) {
677             if (! in_array($record, $this->_listOfRecords, true)) {
678                 $this->addRecord($record);
679             }
680         }
681         
682         return $this;
683     }
684
685     /**
686      * merges records from given record set if id not yet present in current record set
687      *
688      * @param Tinebase_Record_RecordSet $_recordSet
689      * @return void
690      */
691     public function mergeById(Tinebase_Record_RecordSet $_recordSet)
692     {
693         foreach ($_recordSet as $record) {
694             if (false === $this->getIndexById($record->getId())) {
695                 $this->addRecord($record);
696             }
697         }
698
699         return $this;
700     }
701     
702     /**
703      * sorts this recordset
704      *
705      * @param string $_field
706      * @param string $_direction
707      * @param string $_sortFunction
708      * @param int $_flags sort flags for asort/arsort
709      * @return $this
710      */
711     public function sort($_field, $_direction = 'ASC', $_sortFunction = 'asort', $_flags = SORT_REGULAR)
712     {
713         if (! is_string($_field) && is_callable($_field)) {
714             $_sortFunction = 'function';
715         } else {
716             $offsetToSortFieldMap = $this->__get($_field);
717         }
718
719         switch ($_sortFunction) {
720             case 'asort':
721                 $fn = $_direction == 'ASC' ? 'asort' : 'arsort';
722                 $fn($offsetToSortFieldMap, $_flags);
723                 break;
724             case 'natcasesort':
725                 natcasesort($offsetToSortFieldMap);
726                 if ($_direction == 'DESC') {
727                     // @todo check if this is working
728                     $offsetToSortFieldMap = array_reverse($offsetToSortFieldMap);
729                 }
730                 break;
731             case 'function':
732                 uasort ($this->_listOfRecords , $_field);
733                 $offsetToSortFieldMap = $this->_listOfRecords;
734                 break;
735             default:
736                 throw new Tinebase_Exception_InvalidArgument('Sort function unknown.');
737         }
738         
739         // tmp records
740         $oldListOfRecords = $this->_listOfRecords;
741         
742         // reset indexes and records
743         $this->_idLess        = array();
744         $this->_idMap         = array();
745         $this->_listOfRecords = array();
746         
747         foreach (array_keys($offsetToSortFieldMap) as $oldOffset) {
748             $this->addRecord($oldListOfRecords[$oldOffset]);
749         }
750         
751         return $this;
752     }
753
754     /**
755     * sorts this recordset by pagination sort info
756     *
757     * @param Tinebase_Model_Pagination $_pagination
758     * @return $this
759     */
760     public function sortByPagination($_pagination)
761     {
762         if ($_pagination !== NULL && $_pagination->sort) {
763             $sortField = is_array($_pagination->sort) ? $_pagination->sort[0] : $_pagination->sort;
764             $this->sort($sortField, ($_pagination->dir) ? $_pagination->dir : 'ASC');
765         }
766         
767         return $this;
768     }
769     
770     /**
771      * limits this recordset by pagination
772      * sorting should always be applied before to get the desired sequence
773      * @param Tinebase_Model_Pagination $_pagination
774      * @return $this
775      */
776     public function limitByPagination($_pagination)
777     {
778         if ($_pagination !== NULL && $_pagination->limit) {
779             $indices = range($_pagination->start, $_pagination->start + $_pagination->limit - 1);
780             foreach($this as $index => &$record) {
781                 if(! in_array($index, $indices)) {
782                     $this->offsetUnset($index);
783                 }
784             }
785         }
786         return $this;
787     }
788     
789     /**
790      * translate all member records of this set
791      */
792     public function translate()
793     {
794         foreach ($this->_listOfRecords as $record) {
795             $record->translate();
796         }
797     }
798
799     /**
800      * convert recordset, array of ids or records to array of ids
801      *
802      * @param  mixed  $_mixed
803      * @return array
804      */
805     public static function getIdsFromMixed($_mixed)
806     {
807         if ($_mixed instanceof Tinebase_Record_RecordSet) { // Record set
808             $ids = $_mixed->getArrayOfIds();
809
810         } elseif (is_array($_mixed)) { // array
811             foreach ($_mixed as $mixed) {
812                 if ($mixed instanceof Tinebase_Record_Abstract) {
813                     $ids[] = $mixed->getId();
814                 } else {
815                     $ids[] = $mixed;
816                 }
817             }
818
819         } else { // string
820             $ids[] = $_mixed instanceof Tinebase_Record_Abstract ? $_mixed->getId() : $_mixed;
821         }
822
823         return $ids;
824     }
825 }