1 | <?php |
||||
2 | /** |
||||
3 | * EGroupware - addressbook |
||||
4 | * |
||||
5 | * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License |
||||
6 | * @package addressbook |
||||
7 | * @subpackage importexport |
||||
8 | * @link http://www.egroupware.org |
||||
9 | * @author Cornelius Weiss <[email protected]> |
||||
10 | * @copyright Cornelius Weiss <[email protected]> |
||||
11 | * @version $Id$ |
||||
12 | */ |
||||
13 | |||||
14 | use EGroupware\Api; |
||||
15 | use EGroupware\Api\Acl; |
||||
16 | |||||
17 | /** |
||||
18 | * export plugin of addressbook |
||||
19 | */ |
||||
20 | class addressbook_export_contacts_csv implements importexport_iface_export_plugin |
||||
21 | { |
||||
22 | /** |
||||
23 | * Constants used for exploding categories & multi-selectboxes into seperate fields |
||||
24 | */ |
||||
25 | const NO_EXPLODE = False; |
||||
26 | const MAIN_CATS = 'main_cats'; // Only the top-level categories get their own field |
||||
27 | const EACH_CAT = 'each_cat'; // Every category gets its own field |
||||
28 | const EXPLODE = 'explode'; // For [custom] multi-selects, each option gets its own field |
||||
29 | |||||
30 | public function __construct() |
||||
31 | { |
||||
32 | $this->ui= new addressbook_ui(); |
||||
0 ignored issues
–
show
Bug
Best Practice
introduced
by
Loading history...
|
|||||
33 | $this->get_selects(); |
||||
34 | } |
||||
35 | |||||
36 | /** |
||||
37 | * Exports records as defined in $_definition |
||||
38 | * |
||||
39 | * @param egw_record $_definition |
||||
0 ignored issues
–
show
The type
egw_record was not found. Maybe you did not declare it correctly or list all dependencies?
The issue could also be caused by a filter entry in the build configuration.
If the path has been excluded in your configuration, e.g. filter:
dependency_paths: ["lib/*"]
For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths
Loading history...
|
|||||
40 | */ |
||||
41 | public function export( $_stream, importexport_definition $_definition) { |
||||
42 | |||||
43 | $options = $_definition->plugin_options; |
||||
0 ignored issues
–
show
The property
plugin_options does not exist on importexport_definition . Since you implemented __get , consider adding a @property annotation.
Loading history...
|
|||||
44 | $this->export_object = $export_object = new importexport_export_csv($_stream, (array)$options); |
||||
0 ignored issues
–
show
|
|||||
45 | |||||
46 | $selection = array(); |
||||
47 | |||||
48 | // Addressbook defines its own export imits |
||||
49 | $limit_exception = Api\Storage\Merge::is_export_limit_excepted(); |
||||
50 | $export_limit = Api\Storage\Merge::getExportLimit($app='addressbook'); |
||||
51 | if (!$limit_exception) $export_object->export_limit = $export_limit; // we may not need that after all |
||||
0 ignored issues
–
show
It seems like
$export_limit can also be of type false . However, the property $export_limit is declared as type integer . Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
Loading history...
|
|||||
52 | if($export_limit == 'no' && !$limit_exception) { |
||||
53 | return; |
||||
54 | } |
||||
55 | |||||
56 | // Need to switch the app to get the same results |
||||
57 | $old_app = $GLOBALS['egw_info']['flags']['currentapp']; |
||||
58 | $GLOBALS['egw_info']['flags']['currentapp'] = 'addressbook'; |
||||
59 | |||||
60 | if ($options['selection'] == 'search') { |
||||
61 | // uicontacts selection with checkbox 'use_all' |
||||
62 | $query = Api\Cache::getSession('addressbook', 'index'); |
||||
63 | $query['num_rows'] = -1; // all |
||||
64 | $query['csv_export'] = true; // so get_rows method _can_ produce different content or not store state in the session |
||||
65 | $query['order'] = 'contact_id'; |
||||
66 | if(!array_key_exists('filter',$query)) $query['filter'] = $GLOBALS['egw_info']['user']['account_id']; |
||||
67 | $readonlys = null; |
||||
68 | $this->ui->get_rows($query,$selection,$readonlys, true); // only return the ids |
||||
69 | } |
||||
70 | elseif ( $options['selection'] == 'all' ) { |
||||
71 | if ($GLOBALS['egw_info']['user']['preferences']['addressbook']['hide_accounts'] === '1') { |
||||
72 | $col_filter['account_id'] = null; |
||||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||||
73 | } |
||||
74 | $selection = ExecMethod2('addressbook.addressbook_bo.search', array(), true, '', '','',false,'AND',false,$col_filter); |
||||
0 ignored issues
–
show
The function
ExecMethod2() has been deprecated: use autoloadable class-names, instanciate and call method or use static methods
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This function has been deprecated. The supplier of the function has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.
Loading history...
|
|||||
75 | //$uicontacts->get_rows($query,$selection,$readonlys,true); |
||||
76 | } |
||||
77 | elseif ($options['selection'] == 'filter') |
||||
78 | { |
||||
79 | $filter = $_definition->filter; |
||||
0 ignored issues
–
show
The property
filter does not exist on importexport_definition . Since you implemented __get , consider adding a @property annotation.
Loading history...
|
|||||
80 | $query = array(); |
||||
81 | |||||
82 | // Handle ranges |
||||
83 | foreach($filter as $field => $value) |
||||
84 | { |
||||
85 | if($field == 'cat_id') |
||||
86 | { |
||||
87 | $query['col_filter'][$field] = implode(',',$value); |
||||
88 | continue; |
||||
89 | } |
||||
90 | |||||
91 | // Birthdays in addressbook are formatted Y-m-d |
||||
92 | if($field == 'bday') |
||||
93 | { |
||||
94 | if($value['from']) |
||||
95 | { |
||||
96 | $query['col_filter'][] = "contact_bday >= " . $GLOBALS['egw']->db->quote(date('Y-m-d', (int)$value['from'])); |
||||
97 | } |
||||
98 | if($value['to']) |
||||
99 | { |
||||
100 | $query['col_filter'][] = "contact_bday <= " . $GLOBALS['egw']->db->quote(date('Y-m-d', (int)$value['to'])); |
||||
101 | } |
||||
102 | continue; |
||||
103 | } |
||||
104 | // Custom fields & listed exceptions are not filtered with contact_ prefix |
||||
105 | if(strpos($field, '#') !== 0 && !in_array($field, array('tid','owner'))) |
||||
106 | { |
||||
107 | $field = 'contact_'.$field; |
||||
108 | } |
||||
109 | $query['col_filter'][$field] = $value; |
||||
110 | if(!is_array($value) || (!$value['from'] && !$value['to'])) continue; |
||||
111 | |||||
112 | // Ranges are inclusive, so should be provided that way (from 2 to 10 includes 2 and 10) |
||||
113 | if($value['from']) $query['col_filter'][] = "$field >= " . (int)$value['from']; |
||||
114 | if($value['to']) $query['col_filter'][] = "$field <= " . (int)$value['to']; |
||||
115 | unset($query['col_filter'][$field]); |
||||
116 | } |
||||
117 | $selection = ExecMethod2('addressbook.addressbook_bo.search', array(), true, '', '','',false,'AND',false,$query['col_filter']); |
||||
0 ignored issues
–
show
The function
ExecMethod2() has been deprecated: use autoloadable class-names, instanciate and call method or use static methods
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This function has been deprecated. The supplier of the function has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.
Loading history...
|
|||||
118 | } |
||||
119 | else |
||||
120 | { |
||||
121 | $selection = explode(',',$options['selection']); |
||||
122 | } |
||||
123 | if(!is_array($selection)) |
||||
124 | { |
||||
125 | $selection = array(); |
||||
126 | } |
||||
127 | $GLOBALS['egw_info']['flags']['currentapp'] = $old_app; |
||||
128 | |||||
129 | if(Api\Storage\Merge::hasExportLimit($export_limit) && !$limit_exception) { |
||||
130 | $selection = array_slice($selection, 0, $export_limit); |
||||
131 | } |
||||
132 | |||||
133 | if($options['explode_multiselects']) { |
||||
134 | $customfields = Api\Storage\Customfields::get('addressbook'); |
||||
135 | $additional_fields = array(); |
||||
136 | $cat_obj = new Api\Categories('', 'addressbook'); |
||||
137 | foreach($options['explode_multiselects'] as $field => $explode) { |
||||
138 | switch($explode['explode']) { |
||||
139 | case self::MAIN_CATS: |
||||
140 | $cats = $cat_obj->return_array('mains', 0, false,'','ASC','',true); |
||||
141 | foreach($cats as $settings) { |
||||
142 | $additional_fields[$field][$settings['id']] = array( |
||||
143 | 'count' => 0, |
||||
144 | 'label' => $settings['name'], |
||||
145 | 'subs' => array(), |
||||
146 | ); |
||||
147 | $subs = $cat_obj->return_sorted_array(0, False, '', 'ASC', 'cat_name', True, $settings['id']); |
||||
148 | foreach($subs as $sub) { |
||||
149 | $name = $sub['name']; |
||||
150 | $path = $sub; |
||||
151 | while($path['parent'] != $settings['id']) { |
||||
152 | $path = $cat_obj->read($path['parent']); |
||||
153 | $name = $path['name'] . '/' . $name; |
||||
154 | } |
||||
155 | $additional_fields[$field][$settings['id']]['subs'][$sub['id']] = $name; |
||||
156 | } |
||||
157 | } |
||||
158 | break; |
||||
159 | case self::EACH_CAT: |
||||
160 | $cats = $cat_obj->return_array('all', 0, false,'','ASC','',true); |
||||
161 | foreach($cats as $settings) { |
||||
162 | $name = $settings['name']; |
||||
163 | $path = $settings; |
||||
164 | while($path['level'] != 0) { |
||||
165 | $path = $cat_obj->read($path['parent']); |
||||
166 | $name = $path['name'] . '/' . $name; |
||||
167 | } |
||||
168 | $additional_fields[$field][$settings['id']] = array( |
||||
169 | 'count' => 0, |
||||
170 | 'label' => $name |
||||
171 | ); |
||||
172 | } |
||||
173 | break; |
||||
174 | case self::EXPLODE: |
||||
175 | // Only works for custom fields |
||||
176 | $index = substr($field, 1); |
||||
177 | foreach($customfields[$index]['values'] as $key => $value) { |
||||
178 | $additional_fields[$field][$key] = array( |
||||
179 | 'count' => 0, |
||||
180 | 'label' => $customfields[$index]['label'] . ': ' . $value, |
||||
181 | ); |
||||
182 | } |
||||
183 | break; |
||||
184 | } |
||||
185 | } |
||||
186 | |||||
187 | // Check records to see if additional fields are actually used |
||||
188 | foreach ($selection as $_contact) { |
||||
189 | if(is_array($_contact) && array_key_exists('photo', $_contact)) { |
||||
190 | unset($_contact['photo']); |
||||
191 | } |
||||
192 | if(is_array($_contact) && count($_contact) == 1 && $_contact['id']) { |
||||
193 | $_contact = $_contact['id']; |
||||
194 | } |
||||
195 | if(is_array($_contact) && $_contact['id']) { |
||||
196 | $contact = new addressbook_egw_record(); |
||||
197 | $contact->set_record($_contact); |
||||
198 | } else { |
||||
199 | $contact = new addressbook_egw_record($_contact); |
||||
200 | } |
||||
201 | foreach($additional_fields as $field => &$values) { |
||||
202 | if(!$contact->$field) continue; |
||||
203 | foreach($values as $value => &$settings) { |
||||
204 | if(!is_array($contact->$field)) { |
||||
205 | $contact->$field = explode(',', $contact->$field); |
||||
206 | } |
||||
207 | if(is_array($contact->$field) && in_array($value, $contact->$field)) { |
||||
208 | $settings['count']++; |
||||
209 | } elseif($contact->$field == $value) { |
||||
210 | $settings['count']++; |
||||
211 | } elseif($options['explode_multiselects'][$field]['explode'] == self::MAIN_CATS && array_intersect($contact->$field, array_keys($settings['subs']))) { |
||||
212 | $settings['count']++; |
||||
213 | } |
||||
214 | } |
||||
215 | } |
||||
216 | } |
||||
217 | |||||
218 | unset($field); |
||||
219 | unset($value); |
||||
220 | unset($settings); |
||||
221 | |||||
222 | // Add additional columns |
||||
223 | foreach($additional_fields as $field => $additional_values) { |
||||
224 | // Remove original |
||||
225 | unset($options['mapping'][$field]); |
||||
226 | // Add exploded |
||||
227 | $field_count = 0; |
||||
228 | foreach($additional_values as $value => $settings) { |
||||
229 | if($settings['count'] > 0) { |
||||
230 | $field_count += $settings['count']; |
||||
231 | $options['mapping'][$field.'-'.$value] = $settings['label']; |
||||
232 | } |
||||
233 | } |
||||
234 | if($field_count > 0) { |
||||
235 | // Set some options for converting |
||||
236 | $options['explode_multiselects'][$field]['values'] = $additional_values; |
||||
237 | } else { |
||||
238 | // Don't need this anymore |
||||
239 | unset($options['explode_multiselects'][$field]); |
||||
240 | } |
||||
241 | } |
||||
242 | } |
||||
243 | |||||
244 | $export_object->set_mapping($options['mapping']); |
||||
245 | |||||
246 | // Add in last/next event, if needed |
||||
247 | if($options['mapping']['last_date'] || $options['mapping']['next_date']) |
||||
248 | { |
||||
249 | $contact_ids = array(); |
||||
250 | foreach($selection as $_contact) |
||||
251 | { |
||||
252 | if(is_array($_contact) && $_contact['id']) |
||||
253 | { |
||||
254 | $contact_ids[] = $_contact['account_id'] ? $_contact['account_id'] : 'c'.$_contact['id']; |
||||
255 | } |
||||
256 | else |
||||
257 | { |
||||
258 | $contact_ids[] = 'c'.$contact; |
||||
259 | } |
||||
260 | } |
||||
261 | $events = $this->ui->read_calendar($contact_ids, false); |
||||
262 | } |
||||
263 | |||||
264 | // $options['selection'] is array of identifiers as this plugin doesn't |
||||
265 | // support other selectors atm. |
||||
266 | foreach ($selection as $_contact) { |
||||
267 | if(is_array($_contact) && array_key_exists('photo', $_contact)) { |
||||
268 | unset($_contact['photo']); |
||||
269 | } |
||||
270 | if(is_array($_contact) && count($_contact) == 1 && $_contact['id']) { |
||||
271 | $_contact = $_contact['id']; |
||||
272 | } |
||||
273 | if(is_array($_contact) && $_contact['id']) { |
||||
274 | $contact = new addressbook_egw_record(); |
||||
275 | $contact->set_record($_contact); |
||||
276 | } else { |
||||
277 | $contact = new addressbook_egw_record($_contact); |
||||
278 | } |
||||
279 | |||||
280 | if($events && $events[$contact->id]) |
||||
0 ignored issues
–
show
The property
id does not exist on addressbook_egw_record . Since you implemented __get , consider adding a @property annotation.
Loading history...
|
|||||
281 | { |
||||
282 | // NB: last_date and next_date are used instead of last_event & next_event |
||||
283 | // to avoid automatic conversion - we want to export link title, not date-time |
||||
284 | $contact->last_date = $events[$contact->id]['last_link']['title']; |
||||
0 ignored issues
–
show
The property
last_date does not exist on addressbook_egw_record . Since you implemented __set , consider adding a @property annotation.
Loading history...
|
|||||
285 | $contact->next_date = $events[$contact->id]['next_link']['title']; |
||||
0 ignored issues
–
show
The property
next_date does not exist on addressbook_egw_record . Since you implemented __set , consider adding a @property annotation.
Loading history...
|
|||||
286 | } |
||||
287 | // Some conversion |
||||
288 | $this->convert($contact, $options); |
||||
289 | if($options['convert']) { |
||||
290 | importexport_export_csv::convert($contact, addressbook_egw_record::$types, 'addressbook',$this->selects); |
||||
291 | } else { |
||||
292 | // Implode arrays, so they don't say 'Array' |
||||
293 | foreach($contact->get_record_array() as $key => $value) { |
||||
294 | if(is_array($value)) $contact->$key = implode(',', $value); |
||||
295 | } |
||||
296 | } |
||||
297 | |||||
298 | $export_object->export_record($contact); |
||||
299 | unset($contact); |
||||
300 | } |
||||
301 | return $export_object; |
||||
302 | } |
||||
303 | |||||
304 | /** |
||||
305 | * returns translated name of plugin |
||||
306 | * |
||||
307 | * @return string name |
||||
308 | */ |
||||
309 | public static function get_name() { |
||||
310 | return lang('Addressbook CSV export'); |
||||
311 | } |
||||
312 | |||||
313 | /** |
||||
314 | * returns translated (user) description of plugin |
||||
315 | * |
||||
316 | * @return string descriprion |
||||
317 | */ |
||||
318 | public static function get_description() { |
||||
319 | return lang("Exports contacts from your Addressbook into a CSV File."); |
||||
320 | } |
||||
321 | |||||
322 | /** |
||||
323 | * retruns file suffix for exported file |
||||
324 | * |
||||
325 | * @return string suffix |
||||
326 | */ |
||||
327 | public static function get_filesuffix() { |
||||
328 | return 'csv'; |
||||
329 | } |
||||
330 | |||||
331 | public static function get_mimetype() { |
||||
332 | return 'text/csv'; |
||||
333 | } |
||||
334 | |||||
335 | /** |
||||
336 | * Suggest a file name for the downloaded file |
||||
337 | * No suffix |
||||
338 | */ |
||||
339 | public function get_filename() |
||||
340 | { |
||||
341 | if(is_object($this->export_object) && $this->export_object->get_num_of_records() == 1) |
||||
342 | { |
||||
343 | return $this->export_object->record->get_title(); |
||||
344 | } |
||||
345 | return false; |
||||
346 | } |
||||
347 | |||||
348 | /** |
||||
349 | * return html for options. |
||||
350 | * this way the plugin has all opertunities for options tab |
||||
351 | * |
||||
352 | * @param $definition Specific definition |
||||
0 ignored issues
–
show
The type
Specific was not found. Maybe you did not declare it correctly or list all dependencies?
The issue could also be caused by a filter entry in the build configuration.
If the path has been excluded in your configuration, e.g. filter:
dependency_paths: ["lib/*"]
For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths
Loading history...
|
|||||
353 | * |
||||
354 | * @return array ( |
||||
355 | * name => string, |
||||
356 | * content => array, |
||||
357 | * sel_options => array, |
||||
358 | * readonlys => array, |
||||
359 | * preserv => array, |
||||
360 | * ) |
||||
361 | */ |
||||
362 | public function get_options_etpl(importexport_definition &$definition = NULL) |
||||
363 | { |
||||
364 | return false; |
||||
365 | } |
||||
366 | |||||
367 | /** |
||||
368 | * returns slectors of this plugin via xajax |
||||
369 | * |
||||
370 | */ |
||||
371 | public function get_selectors_etpl() { |
||||
372 | return array( |
||||
373 | 'name' => 'importexport.export_csv_selectors', |
||||
374 | ); |
||||
375 | } |
||||
376 | |||||
377 | /** |
||||
378 | * Convert some internal data to something with more meaning |
||||
379 | * |
||||
380 | * Dates, times, user IDs, category IDs |
||||
381 | */ |
||||
382 | public static function convert(addressbook_egw_record &$record, $options) { |
||||
383 | |||||
384 | if ($record->tel_prefer) { |
||||
0 ignored issues
–
show
The property
tel_prefer does not exist on addressbook_egw_record . Since you implemented __get , consider adding a @property annotation.
Loading history...
|
|||||
385 | $field = $record->tel_prefer; |
||||
386 | $record->tel_prefer = $record->$field; |
||||
0 ignored issues
–
show
The property
tel_prefer does not exist on addressbook_egw_record . Since you implemented __set , consider adding a @property annotation.
Loading history...
|
|||||
387 | } |
||||
388 | |||||
389 | if(!is_array($options['explode_multiselects'])) |
||||
390 | { |
||||
391 | return; |
||||
392 | } |
||||
393 | foreach((array)$options['explode_multiselects'] as $field => $explode_settings) { |
||||
394 | if(!is_array($record->$field)) $record->$field = explode(',', $record->$field); |
||||
395 | foreach((array)$explode_settings['values'] as $value => $settings) { |
||||
396 | $field_name = "$field-$value"; |
||||
397 | $record->$field_name = array(); |
||||
398 | if(is_array($record->$field) && in_array($value, $record->$field) || $record->$field == $value) { |
||||
0 ignored issues
–
show
|
|||||
399 | if($explode_settings['explode'] != self::MAIN_CATS) { |
||||
400 | $record->$field_name = $options['convert'] ? lang('Yes') : true; |
||||
401 | } elseif($options['convert']) { |
||||
402 | // 3 part assign due to magic get method |
||||
403 | $record_value = $record->$field_name; |
||||
404 | $record_value[] = $settings['label']; |
||||
405 | $record->$field_name = $record_value; |
||||
406 | } else { |
||||
407 | $record->$field_name = $value; |
||||
408 | } |
||||
409 | } |
||||
410 | if($explode_settings['explode'] == self::MAIN_CATS && count(array_intersect($record->$field, array_keys($settings['subs'])))) { |
||||
411 | // 3 part assign due to magic get method |
||||
412 | $record_value = $record->$field_name; |
||||
413 | if(!is_array($record_value)) $record_value = array($record_value); |
||||
414 | foreach(array_intersect($record->$field, array_keys($settings['subs'])) as $sub_id) { |
||||
415 | $record_value[] = $options['convert'] ? $settings['subs'][$sub_id] : $sub_id; |
||||
416 | } |
||||
417 | $record->$field_name = $record_value; |
||||
418 | } |
||||
419 | if(is_array($record->$field_name)) $record->$field_name = implode(($options['convert'] ? ', ' : ','), $record->$field_name); |
||||
420 | } |
||||
421 | } |
||||
422 | } |
||||
423 | |||||
424 | |||||
425 | protected function get_selects() |
||||
426 | { |
||||
427 | $this->selects = array( |
||||
0 ignored issues
–
show
|
|||||
428 | 'tid' => array('n' => 'Contact') |
||||
429 | ); |
||||
430 | foreach($this->ui->content_types as $tid => $data) |
||||
431 | { |
||||
432 | $this->selects['tid'][$tid] = $data['name']; |
||||
433 | } |
||||
434 | } |
||||
435 | /** |
||||
436 | * Get the class name for the egw_record to use while exporting |
||||
437 | * |
||||
438 | * @return string; |
||||
439 | */ |
||||
440 | public static function get_egw_record_class() |
||||
441 | { |
||||
442 | return 'addressbook_egw_record'; |
||||
443 | } |
||||
444 | |||||
445 | /** |
||||
446 | * Adjust automatically generated filter fields |
||||
447 | */ |
||||
448 | public function get_filter_fields(Array &$filters) |
||||
449 | { |
||||
450 | unset($filters['last_event']); |
||||
451 | unset($filters['next_event']); |
||||
452 | foreach($filters as $field_name => &$settings) |
||||
453 | { |
||||
454 | if($this->selects[$field_name]) $settings['values'] = $this->selects[$field_name]; |
||||
455 | } |
||||
456 | $filters['owner'] = array( |
||||
457 | 'name' => 'owner', |
||||
458 | 'label' => 'addressbook', |
||||
459 | 'type' => 'select', |
||||
460 | 'rows' => 5, |
||||
461 | 'tags' => true, |
||||
462 | 'values' => $this->ui->get_addressbooks(Acl::READ) |
||||
463 | ); |
||||
464 | } |
||||
465 | } |
||||
466 |