1<?php 2/** 3 * The Turba_List:: class provides an interface for dealing with a 4 * list of Turba_Objects. 5 * 6 * Copyright 2000-2017 Horde LLC (http://www.horde.org/) 7 * 8 * See the enclosed file LICENSE for license information (ASL). If you did 9 * did not receive this file, see http://www.horde.org/licenses/apache. 10 * 11 * @author Chuck Hagenbuch <chuck@horde.org> 12 * @author Jon Parise <jon@csh.rit.edu> 13 * @category Horde 14 * @license http://www.horde.org/licenses/apache ASL 15 * @package Turba 16 */ 17class Turba_List implements Countable 18{ 19 /** 20 * The array containing the Turba_Objects represented in this list. 21 * 22 * @var array 23 */ 24 public $objects = array(); 25 26 /** 27 * The field to compare objects by. 28 * 29 * @var string 30 */ 31 protected $_usortCriteria; 32 33 /** 34 * Constructor. 35 */ 36 public function __construct(array $ids = array()) 37 { 38 foreach ($ids as $value) { 39 list($source, $key) = explode(':', $value); 40 try { 41 $driver = $GLOBALS['injector']->getInstance('Turba_Factory_Driver')->create($source); 42 $this->insert($driver->getObject($key)); 43 } catch (Horde_Exception $e) { 44 } 45 } 46 } 47 48 /** 49 * Inserts a new object into the list. 50 * 51 * @param Turba_Object $object The object to insert. 52 */ 53 public function insert(Turba_Object $object) 54 { 55 if ($object instanceof Turba_Object) { 56 $key = $object->getSource() . ':' . $object->getValue('__key'); 57 if (!isset($this->objects[$key])) { 58 $this->objects[$key] = $object; 59 } 60 } 61 } 62 63 /** 64 * Resets our internal pointer to the beginning of the list. Use this to 65 * hide the internal storage (array, list, etc.) from client objects. 66 * 67 * @return Turba_Object The next object in the list. 68 */ 69 public function reset() 70 { 71 return reset($this->objects); 72 } 73 74 /** 75 * Returns the next Turba_Object in the list. Use this to hide internal 76 * implementation details from client objects. 77 * 78 * @return Turba_Object The next object in the list. 79 */ 80 public function next() 81 { 82 list(,$tmp) = each($this->objects); 83 return $tmp; 84 } 85 86 /** 87 * Filters/Sorts the list based on the specified sort routine. 88 * The default sort order is by last name, ascending. 89 * 90 * @param array $order Array of hashes describing sort fields. Each 91 * hash has the following fields: 92 * <pre> 93 * ascending - (boolean) Sort direction. 94 * field - (string) Sort field. 95 * </pre> 96 */ 97 public function sort($order = null) 98 { 99 global $attributes, $prefs; 100 101 if (!$order) { 102 $order = array( 103 array( 104 'ascending' => true, 105 'field' => 'lastname' 106 ) 107 ); 108 } 109 110 $need_lastname = $need_firstname = false; 111 $name_format = $prefs->getValue('name_format'); 112 $name_sort = $prefs->getValue('name_sort'); 113 foreach ($order as &$field) { 114 if ($field['field'] == 'name') { 115 if ($name_sort == 'last_first') { 116 $field['field'] = 'lastname'; 117 } elseif ($name_sort == 'first_last') { 118 $field['field'] = 'firstname'; 119 } 120 } 121 122 if ($field['field'] == 'lastname') { 123 $field['field'] = '__lastname'; 124 $need_lastname = true; 125 break; 126 } 127 if ($field['field'] == 'firstname') { 128 $field['field'] = '__firstname'; 129 $need_firstname = true; 130 break; 131 } 132 } 133 134 if ($need_firstname || $need_lastname) { 135 $sorted_objects = array(); 136 foreach ($this->objects as $key => $object) { 137 $name = $object->getValue('name'); 138 $firstname = $object->getValue('firstname'); 139 $lastname = $object->getValue('lastname'); 140 if (!$lastname) { 141 $lastname = Turba::guessLastname($name); 142 } 143 if (!$firstname) { 144 switch ($name_format) { 145 case 'last_first': 146 $firstname = preg_replace('/' . preg_quote($lastname, '/') . ',\s*/', '', $name); 147 break; 148 case 'first_last': 149 $firstname = preg_replace('/\s+' . preg_quote($lastname, '/') . '/', '', $name); 150 break; 151 default: 152 $firstname = preg_replace('/\s*' . preg_quote($lastname, '/') . '(,\s*)?/', '', $name); 153 break; 154 } 155 } 156 $object->setValue('__lastname', $lastname); 157 $object->setValue('__firstname', $firstname); 158 $sorted_objects[$key] = $object; 159 } 160 } else { 161 $sorted_objects = $this->objects; 162 } 163 164 // Set the comparison type based on the type of attribute we're 165 // sorting by. 166 foreach ($order as &$val) { 167 $sm = 'text'; 168 169 if (isset($attributes[$val['field']])) { 170 $f = $attributes[$val['field']]; 171 if (!empty($f['cmptype'])) { 172 $sm = $f['cmptype']; 173 } elseif (in_array($f['type'], array('int', 'intlist', 'number'))) { 174 $sm = 'int'; 175 } 176 } 177 178 $val['sortmethod'] = $sm; 179 } 180 $this->_usortCriteria = $order; 181 182 /* Exceptions thrown inside a sort incorrectly cause an error. See 183 * Bug #9202. */ 184 @usort($sorted_objects, array($this, '_cmp')); 185 186 $this->objects = $sorted_objects; 187 } 188 189 /** 190 * Usort helper function. 191 * 192 * Compares two Turba_Objects based on the member variable 193 * $_usortCriteria, taking care to sort numerically if it is an integer 194 * field. 195 * 196 * @param Turba_Object $a The first Turba_Object to compare. 197 * @param Turba_Object $b The second Turba_Object to compare. 198 * 199 * @return integer Comparison of the two field values. 200 */ 201 protected function _cmp(Turba_Object $a, Turba_Object $b) 202 { 203 foreach ($this->_usortCriteria as $field) { 204 $f = $field['field']; 205 206 switch ($field['sortmethod']) { 207 case 'int': 208 $result = ($a->getValue($f) > $b->getValue($f)) ? 1 : -1; 209 break; 210 211 case 'text': 212 if (!isset($a->sortValue[$f])) { 213 $a->sortValue[$f] = Horde_String::lower($a->getValue($f), true, 'UTF-8'); 214 } 215 if (!isset($b->sortValue[$f])) { 216 $b->sortValue[$f] = Horde_String::lower($b->getValue($f), true, 'UTF-8'); 217 } 218 219 // Use strcoll for locale-safe comparisons. 220 $result = strcoll($a->sortValue[$f], $b->sortValue[$f]); 221 break; 222 } 223 224 if ($result != 0) { 225 return (($field['ascending'] ? 1 : -1) * $result); 226 } 227 } 228 229 return 0; 230 } 231 232 /* Countable methods. */ 233 234 public function count() 235 { 236 return count($this->objects); 237 } 238 239} 240