Total Complexity | 505 |
Total Lines | 2222 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like infolog_bo 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.
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 infolog_bo, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
21 | class infolog_bo |
||
22 | { |
||
23 | /** |
||
24 | * Undelete right |
||
25 | */ |
||
26 | const ACL_UNDELETE = Acl::CUSTOM1; |
||
27 | |||
28 | var $enums; |
||
29 | var $status; |
||
30 | /** |
||
31 | * Instance of our so class |
||
32 | * |
||
33 | * @var infolog_so |
||
34 | */ |
||
35 | var $so; |
||
36 | /** |
||
37 | * Total from last search call |
||
38 | * @var int |
||
39 | */ |
||
40 | var $total; |
||
41 | var $vfs; |
||
42 | var $vfs_basedir='/infolog'; |
||
43 | /** |
||
44 | * Set Logging |
||
45 | * |
||
46 | * @var boolean |
||
47 | */ |
||
48 | var $log = false; |
||
49 | /** |
||
50 | * Cached timezone data |
||
51 | * |
||
52 | * @var array id => data |
||
53 | */ |
||
54 | protected static $tz_cache = array(); |
||
55 | /** |
||
56 | * current time as timestamp in user-time and server-time |
||
57 | * |
||
58 | * @var int |
||
59 | */ |
||
60 | var $user_time_now; |
||
61 | var $now; |
||
62 | /** |
||
63 | * name of timestamps in an InfoLog entry |
||
64 | * |
||
65 | * @var array |
||
66 | */ |
||
67 | var $timestamps = array('info_startdate','info_enddate','info_datemodified','info_datecompleted','info_created'); |
||
68 | /** |
||
69 | * fields the responsible user can change |
||
70 | * |
||
71 | * @var array |
||
72 | */ |
||
73 | var $responsible_edit=array('info_status','info_percent','info_datecompleted'); |
||
74 | /** |
||
75 | * Fields to exclude from copy, if an entry is copied, the ones below are excluded by default. |
||
76 | * |
||
77 | * @var array |
||
78 | */ |
||
79 | var $copy_excludefields = array('info_id', 'info_uid', 'info_etag', 'caldav_name', 'info_created', 'info_creator', 'info_datemodified', 'info_modifier'); |
||
80 | /** |
||
81 | * Fields to exclude from copy, if a sub-entry is created, the ones below are excluded by default. |
||
82 | * |
||
83 | * @var array |
||
84 | */ |
||
85 | var $sub_excludefields = array('info_id', 'info_uid', 'info_etag', 'caldav_name', 'info_created', 'info_creator', 'info_datemodified', 'info_modifier'); |
||
86 | /** |
||
87 | * Additional fields to $sub_excludefields to exclude, if no config stored |
||
88 | * |
||
89 | * @var array |
||
90 | */ |
||
91 | var $default_sub_excludefields = array('info_des'); |
||
92 | /** |
||
93 | * implicit ACL rights of the responsible user: read or edit |
||
94 | * |
||
95 | * @var string |
||
96 | */ |
||
97 | var $implicit_rights='read'; |
||
98 | /** |
||
99 | * Custom fields read from the infolog config |
||
100 | * |
||
101 | * @var array |
||
102 | */ |
||
103 | var $customfields=array(); |
||
104 | /** |
||
105 | * Group owners for certain types read from the infolog config |
||
106 | * |
||
107 | * @var array |
||
108 | */ |
||
109 | var $group_owners=array(); |
||
110 | /** |
||
111 | * Current user |
||
112 | * |
||
113 | * @var int |
||
114 | */ |
||
115 | var $user; |
||
116 | /** |
||
117 | * History loggin: ''=no, 'history'=history & delete allowed, 'history_admin_delete', 'history_no_delete' |
||
118 | * |
||
119 | * @var string |
||
120 | */ |
||
121 | var $history; |
||
122 | /** |
||
123 | * Instance of infolog_tracking, only instaciated if needed! |
||
124 | * |
||
125 | * @var infolog_tracking |
||
126 | */ |
||
127 | var $tracking; |
||
128 | /** |
||
129 | * Maximum number of line characters (-_+=~) allowed in a mail, to not stall the layout. |
||
130 | * Longer lines / biger number of these chars are truncated to that max. number or chars. |
||
131 | * |
||
132 | * @var int |
||
133 | */ |
||
134 | var $max_line_chars = 40; |
||
135 | |||
136 | /** |
||
137 | * Available filters |
||
138 | * |
||
139 | * @var array filter => label pairs |
||
140 | */ |
||
141 | var $filters = array( |
||
142 | '' => 'no Filter', |
||
143 | 'done' => 'done', |
||
144 | 'responsible' => 'responsible', |
||
145 | 'responsible-open-today' => 'responsible open', |
||
146 | 'responsible-open-overdue' => 'responsible overdue', |
||
147 | 'responsible-upcoming' => 'responsible upcoming', |
||
148 | 'responsible-open-upcoming'=> 'responsible open and upcoming', |
||
149 | 'delegated' => 'delegated', |
||
150 | 'delegated-open-today' => 'delegated open', |
||
151 | 'delegated-open-overdue' => 'delegated overdue', |
||
152 | 'delegated-upcoming' => 'delegated upcomming', |
||
153 | 'delegated-open-upcoming' => 'delegated open and upcoming', |
||
154 | 'own' => 'own', |
||
155 | 'own-open-today' => 'own open', |
||
156 | 'own-open-overdue' => 'own overdue', |
||
157 | 'own-upcoming' => 'own upcoming', |
||
158 | 'own-open-upcoming' => 'own open and upcoming', |
||
159 | 'private' => 'private', |
||
160 | 'open-today' => 'open(status)', |
||
161 | 'open-overdue' => 'overdue', |
||
162 | 'upcoming' => 'upcoming', |
||
163 | 'open-upcoming' => 'open and upcoming', |
||
164 | 'bydate' => 'startdate', |
||
165 | 'duedate' => 'enddate' |
||
166 | ); |
||
167 | |||
168 | /** |
||
169 | * Constructor Infolog BO |
||
170 | * |
||
171 | * @param int $info_id |
||
172 | */ |
||
173 | function __construct($info_id = 0) |
||
174 | { |
||
175 | $this->enums = $this->stock_enums = array( |
||
|
|||
176 | 'priority' => array ( |
||
177 | 3 => 'urgent', |
||
178 | 2 => 'high', |
||
179 | 1 => 'normal', |
||
180 | 0 => 'low' |
||
181 | ), |
||
182 | 'confirm' => array( |
||
183 | 'not' => 'not','accept' => 'accept','finish' => 'finish', |
||
184 | 'both' => 'both' ), |
||
185 | 'type' => array( |
||
186 | 'task' => 'task','phone' => 'phone','note' => 'note','email' => 'email' |
||
187 | /* ,'confirm' => 'confirm','reject' => 'reject','fax' => 'fax' not implemented so far */ ) |
||
188 | ); |
||
189 | $this->status = $this->stock_status = array( |
||
190 | 'defaults' => array( |
||
191 | 'task' => 'not-started', 'phone' => 'not-started', 'note' => 'done','email' => 'done'), |
||
192 | 'task' => array( |
||
193 | 'offer' => 'offer', // --> NEEDS-ACTION |
||
194 | 'not-started' => 'not-started', // iCal NEEDS-ACTION |
||
195 | 'ongoing' => 'ongoing', // iCal IN-PROCESS |
||
196 | 'done' => 'done', // iCal COMPLETED |
||
197 | 'cancelled' => 'cancelled', // iCal CANCELLED |
||
198 | 'billed' => 'billed', // --> DONE |
||
199 | 'template' => 'template', // --> cancelled |
||
200 | 'nonactive' => 'nonactive', // --> cancelled |
||
201 | 'archive' => 'archive' ), // --> cancelled |
||
202 | 'phone' => array( |
||
203 | 'not-started' => 'call', // iCal NEEDS-ACTION |
||
204 | 'ongoing' => 'will-call', // iCal IN-PROCESS |
||
205 | 'done' => 'done', // iCal COMPLETED |
||
206 | 'billed' => 'billed' ), // --> DONE |
||
207 | 'note' => array( |
||
208 | 'ongoing' => 'ongoing', // iCal has no status on notes |
||
209 | 'done' => 'done' ), |
||
210 | 'email' => array( |
||
211 | 'ongoing' => 'ongoing', // iCal has no status on notes |
||
212 | 'done' => 'done' ), |
||
213 | ); |
||
214 | if (($config_data = Api\Config::read('infolog'))) |
||
215 | { |
||
216 | $this->allow_past_due_date = $config_data['allow_past_due_date'] === null ? 1 : $config_data['allow_past_due_date']; |
||
217 | if (isset($config_data['status']) && is_array($config_data['status'])) |
||
218 | { |
||
219 | foreach(array_keys($config_data['status']) as $key) |
||
220 | { |
||
221 | if (!is_array($this->status[$key])) |
||
222 | { |
||
223 | $this->status[$key] = array(); |
||
224 | } |
||
225 | $this->status[$key] = array_merge($this->status[$key],(array)$config_data['status'][$key]); |
||
226 | } |
||
227 | } |
||
228 | if (isset($config_data['types']) && is_array($config_data['types'])) |
||
229 | { |
||
230 | //echo "stock-types:<pre>"; print_r($this->enums['type']); echo "</pre>\n"; |
||
231 | //echo "config-types:<pre>"; print_r($config_data['types']); echo "</pre>\n"; |
||
232 | $this->enums['type'] += $config_data['types']; |
||
233 | //echo "types:<pre>"; print_r($this->enums['type']); echo "</pre>\n"; |
||
234 | } |
||
235 | if ($config_data['group_owners']) $this->group_owners = $config_data['group_owners']; |
||
236 | |||
237 | $this->customfields = Api\Storage\Customfields::get('infolog'); |
||
238 | if ($this->customfields) |
||
239 | { |
||
240 | foreach($this->customfields as $name => $field) |
||
241 | { |
||
242 | // old infolog customefield record |
||
243 | if(empty($field['type'])) |
||
244 | { |
||
245 | if (count($field['values'])) $field['type'] = 'select'; // selectbox |
||
246 | elseif ($field['rows'] > 1) $field['type'] = 'textarea'; // textarea |
||
247 | elseif (intval($field['len']) > 0) $field['type'] = 'text'; // regular input field |
||
248 | else $field['type'] = 'label'; // header-row |
||
249 | $field['type2'] = $field['typ']; |
||
250 | unset($field['typ']); |
||
251 | $this->customfields[$name] = $field; |
||
252 | $save_config = true; |
||
253 | } |
||
254 | } |
||
255 | if ($save_config) Api\Config::save_value('customfields',$this->customfields,'infolog'); |
||
256 | } |
||
257 | if (is_array($config_data['responsible_edit'])) |
||
258 | { |
||
259 | $this->responsible_edit = array_merge($this->responsible_edit,$config_data['responsible_edit']); |
||
260 | } |
||
261 | if (is_array($config_data['copy_excludefields'])) |
||
262 | { |
||
263 | $this->copy_excludefields = array_merge($this->copy_excludefields,$config_data['copy_excludefields']); |
||
264 | } |
||
265 | if (is_array($config_data['sub_excludefields']) && $config_data['sub_excludefields']) |
||
266 | { |
||
267 | $this->sub_excludefields = array_merge($this->sub_excludefields,$config_data['sub_excludefields']); |
||
268 | } |
||
269 | else |
||
270 | { |
||
271 | $this->sub_excludefields = array_merge($this->sub_excludefields,$this->default_sub_excludefields); |
||
272 | } |
||
273 | if ($config_data['implicit_rights'] == 'edit') |
||
274 | { |
||
275 | $this->implicit_rights = 'edit'; |
||
276 | } |
||
277 | $this->history = $config_data['history']; |
||
278 | } |
||
279 | // sort types by there translation |
||
280 | foreach($this->enums['type'] as $key => $val) |
||
281 | { |
||
282 | if (($val = lang($key)) != $key.'*') $this->enums['type'][$key] = lang($key); |
||
283 | } |
||
284 | natcasesort($this->enums['type']); |
||
285 | |||
286 | $this->user = $GLOBALS['egw_info']['user']['account_id']; |
||
287 | |||
288 | $this->now = time(); |
||
289 | $this->user_time_now = Api\DateTime::server2user($this->now,'ts'); |
||
290 | |||
291 | $this->grants = $GLOBALS['egw']->acl->get_grants('infolog',$this->group_owners ? $this->group_owners : true); |
||
292 | $this->so = new infolog_so($this->grants); |
||
293 | |||
294 | if ($info_id) |
||
295 | { |
||
296 | $this->read( $info_id ); |
||
297 | } |
||
298 | else |
||
299 | { |
||
300 | $this->init(); |
||
301 | } |
||
302 | } |
||
303 | |||
304 | /** |
||
305 | * checks if there are customfields for typ $typ |
||
306 | * |
||
307 | * @param string $type |
||
308 | * @return boolean True if there are customfields for $typ, else False |
||
309 | */ |
||
310 | function has_customfields($type) |
||
311 | { |
||
312 | foreach($this->customfields as $field) |
||
313 | { |
||
314 | if ((!$type || empty($field['type2']) || in_array($type,is_array($field['type2']) ? $field['type2'] : explode(',',$field['type2'])))) |
||
315 | { |
||
316 | return True; |
||
317 | } |
||
318 | } |
||
319 | return False; |
||
320 | } |
||
321 | |||
322 | /** |
||
323 | * check's if user has the requiered rights on entry $info_id |
||
324 | * |
||
325 | * @param int|array $info data or info_id of infolog entry to check |
||
326 | * @param int $required_rights ACL::{READ|EDIT|ADD|DELETE}|infolog_bo::ACL_UNDELETE |
||
327 | * @param int $other uid to check (if info==0) or 0 to check against $this->user |
||
328 | * @param int $user = null user whos rights to check, default current user |
||
329 | * @return boolean |
||
330 | */ |
||
331 | function check_access($info,$required_rights,$other=0,$user=null) |
||
332 | { |
||
333 | static $cache = array(); |
||
334 | |||
335 | $info_id = is_array($info) ? $info['info_id'] : $info; |
||
336 | |||
337 | if (!$user) $user = $this->user; |
||
338 | if ($user == $this->user) |
||
339 | { |
||
340 | $grants = $this->grants; |
||
341 | if ($info_id) $access =& $cache[$info_id][$required_rights]; // we only cache the current user! |
||
342 | } |
||
343 | else |
||
344 | { |
||
345 | $grants = $GLOBALS['egw']->acl->get_grants('infolog',$this->group_owners ? $this->group_owners : true,$user); |
||
346 | } |
||
347 | if (!$info) |
||
348 | { |
||
349 | $owner = $other ? $other : $user; |
||
350 | $grant = $grants[$owner]; |
||
351 | return $grant & $required_rights; |
||
352 | } |
||
353 | |||
354 | |||
355 | if (!isset($access)) |
||
356 | { |
||
357 | // handle delete for the various history modes |
||
358 | if ($this->history) |
||
359 | { |
||
360 | if (!is_array($info) && !($info = $this->so->read(array('info_id' => $info_id)))) return false; |
||
361 | |||
362 | if ($info['info_status'] == 'deleted' && |
||
363 | ($required_rights == Acl::EDIT || // no edit rights for deleted entries |
||
364 | $required_rights == Acl::ADD || // no add rights for deleted entries |
||
365 | $required_rights == Acl::DELETE && ($this->history == 'history_no_delete' || // no delete at all! |
||
366 | $this->history == 'history_admin_delete' && (!isset($GLOBALS['egw_info']['user']['apps']['admin']) || $user!=$this->user)))) // delete only for admins |
||
367 | { |
||
368 | $access = false; |
||
369 | } |
||
370 | elseif ($required_rights == self::ACL_UNDELETE) |
||
371 | { |
||
372 | if ($info['info_status'] != 'deleted') |
||
373 | { |
||
374 | $access = false; // can only undelete deleted items |
||
375 | } |
||
376 | else |
||
377 | { |
||
378 | // undelete requires edit rights |
||
379 | $access = $this->so->check_access( $info,Acl::EDIT,$this->implicit_rights == 'edit',$grants,$user ); |
||
380 | } |
||
381 | } |
||
382 | } |
||
383 | elseif ($required_rights == self::ACL_UNDELETE) |
||
384 | { |
||
385 | $access = false; |
||
386 | } |
||
387 | if (!isset($access)) |
||
388 | { |
||
389 | $access = $this->so->check_access( $info,$required_rights,$this->implicit_rights == 'edit',$grants,$user ); |
||
390 | } |
||
391 | } |
||
392 | // else $cached = ' (from cache)'; |
||
393 | // error_log(__METHOD__."($info_id,$required_rights,$other,$user) returning$cached ".array2string($access)); |
||
394 | return $access; |
||
395 | } |
||
396 | |||
397 | /** |
||
398 | * Check if user is responsible for an entry: he or one of his memberships is in responsible |
||
399 | * |
||
400 | * @param array $info infolog entry as array |
||
401 | * @return boolean |
||
402 | */ |
||
403 | function is_responsible($info) |
||
404 | { |
||
405 | return $this->so->is_responsible($info); |
||
406 | } |
||
407 | |||
408 | /** |
||
409 | * init internal data to be empty |
||
410 | */ |
||
411 | function init() |
||
412 | { |
||
413 | $this->so->init(); |
||
414 | } |
||
415 | |||
416 | /** |
||
417 | * convert a link_id value into an info_from text |
||
418 | * |
||
419 | * @param array &$info infolog entry, key info_from gets set by this function |
||
420 | * @param string $not_app = '' app to exclude |
||
421 | * @param string $not_id = '' id to exclude |
||
422 | * @return boolean True if we have a linked item, False otherwise |
||
423 | */ |
||
424 | function link_id2from(&$info,$not_app='',$not_id='') |
||
425 | { |
||
426 | //error_log(__METHOD__ . "(subject='{$info['info_subject']}', link_id='{$info['info_link_id']}', from='{$info['info_from']}', not_app='$not_app', not_id='$not_id')"); |
||
427 | |||
428 | if ($info['info_link_id'] > 0 && |
||
429 | (isset($info['links']) && ($link = $info['links'][$info['info_link_id']]) || // use supplied links info |
||
430 | ($link = Link::get_link($info['info_link_id'])) !== False)) // if link not found in supplied links, we always search! |
||
431 | { |
||
432 | if (isset($info['links']) && isset($link['app'])) |
||
433 | { |
||
434 | $app = $link['app']; |
||
435 | $id = $link['id']; |
||
436 | } |
||
437 | else |
||
438 | { |
||
439 | $nr = $link['link_app1'] == 'infolog' && $link['link_id1'] == $info['info_id'] ? '2' : '1'; |
||
440 | $app = $link['link_app'.$nr]; |
||
441 | $id = $link['link_id'.$nr]; |
||
442 | } |
||
443 | $title = Link::title($app,$id); |
||
444 | |||
445 | if ((string)$info['info_custom_from'] === '') // old entry |
||
446 | { |
||
447 | $info['info_custom_from'] = (int) ($title != $info['info_from'] && @htmlentities($title) != $info['info_from']); |
||
448 | } |
||
449 | if (!$info['info_custom_from']) |
||
450 | { |
||
451 | $info['info_from'] = ''; |
||
452 | $info['info_custom_from'] = 0; |
||
453 | } |
||
454 | if ($app == $not_app && $id == $not_id) |
||
455 | { |
||
456 | return False; |
||
457 | } |
||
458 | // if link is a project and no other project selected, also add as project |
||
459 | if ($app == 'projectmanager' && $id && !$info['pm_id']) |
||
460 | { |
||
461 | $info['old_pm_id'] = $info['pm_id'] = $id; |
||
462 | } |
||
463 | else |
||
464 | { |
||
465 | // Link might be contact, check others |
||
466 | $this->get_pm_id($info); |
||
467 | } |
||
468 | $info['info_link'] = $info['info_contact'] = array( |
||
469 | 'app' => $app, |
||
470 | 'id' => $id, |
||
471 | 'title' => (!empty($info['info_from']) ? $info['info_from'] : $title), |
||
472 | ); |
||
473 | |||
474 | //echo " title='$title'</p>\n"; |
||
475 | return $info['blur_title'] = $title; |
||
476 | } |
||
477 | |||
478 | // Set ID to 'none' instead of unset to make it seem like there's a value |
||
479 | $info['info_link'] = $info['info_contact'] = $info['info_from'] ? array('id' => 'none', 'title' => $info['info_from']) : null; |
||
480 | $info['info_link_id'] = 0; // link might have been deleted |
||
481 | $info['info_custom_from'] = (int)!!$info['info_from']; |
||
482 | |||
483 | $this->get_pm_id($info); |
||
484 | |||
485 | return False; |
||
486 | } |
||
487 | |||
488 | /** |
||
489 | * Find projectmanager ID from linked project(s) |
||
490 | * |
||
491 | * @param Array $info |
||
492 | */ |
||
493 | public function get_pm_id(&$info) |
||
494 | { |
||
495 | $pm_links = Link::get_links('infolog',$info['info_id'],'projectmanager'); |
||
496 | |||
497 | $old_pm_id = is_array($pm_links) ? array_shift($pm_links) : $info['old_pm_id']; |
||
498 | if (!isset($info['pm_id']) && $old_pm_id) $info['pm_id'] = $old_pm_id; |
||
499 | return $old_pm_id; |
||
500 | } |
||
501 | |||
502 | /** |
||
503 | * Create a subject from a description: truncate it and add ' ...' |
||
504 | */ |
||
505 | static function subject_from_des($des) |
||
506 | { |
||
507 | return substr($des,0,60).' ...'; |
||
508 | } |
||
509 | |||
510 | /** |
||
511 | * Convert the timestamps from given timezone to another and keep dates. |
||
512 | * The timestamps are mostly expected to be in server-time |
||
513 | * and $fromTZId is only used to qualify dates. |
||
514 | * |
||
515 | * @param array $values to modify |
||
516 | * @param string $fromTZId = null |
||
517 | * @param string $toTZId = false |
||
518 | * TZID timezone name e.g. 'UTC' |
||
519 | * or NULL for timestamps in user-time |
||
520 | * or false for timestamps in server-time |
||
521 | */ |
||
522 | function time2time(&$values, $fromTZId=false, $toTZId=null) |
||
523 | { |
||
524 | |||
525 | if ($fromTZId === $toTZId) return; |
||
526 | |||
527 | $tz = Api\DateTime::$server_timezone; |
||
528 | |||
529 | if ($fromTZId) |
||
530 | { |
||
531 | if (!isset(self::$tz_cache[$fromTZId])) |
||
532 | { |
||
533 | self::$tz_cache[$fromTZId] = calendar_timezones::DateTimeZone($fromTZId); |
||
534 | } |
||
535 | $fromTZ = self::$tz_cache[$fromTZId]; |
||
536 | } |
||
537 | elseif (is_null($fromTZId)) |
||
538 | { |
||
539 | $tz = Api\DateTime::$user_timezone; |
||
540 | $fromTZ = Api\DateTime::$user_timezone; |
||
541 | } |
||
542 | else |
||
543 | { |
||
544 | $fromTZ = Api\DateTime::$server_timezone; |
||
545 | } |
||
546 | if ($toTZId) |
||
547 | { |
||
548 | if (!isset(self::$tz_cache[$toTZId])) |
||
549 | { |
||
550 | self::$tz_cache[$toTZId] = calendar_timezones::DateTimeZone($toTZId); |
||
551 | } |
||
552 | $toTZ = self::$tz_cache[$toTZId]; |
||
553 | } |
||
554 | elseif (is_null($toTZId)) |
||
555 | { |
||
556 | $toTZ = Api\DateTime::$user_timezone; |
||
557 | } |
||
558 | else |
||
559 | { |
||
560 | $toTZ = Api\DateTime::$server_timezone; |
||
561 | } |
||
562 | //error_log(__METHOD__.'(values[info_enddate]='.date('Y-m-d H:i:s',$values['info_enddate']).", from=".array2string($fromTZId).", to=".array2string($toTZId).") tz=".$tz->getName().', fromTZ='.$fromTZ->getName().', toTZ='.$toTZ->getName().', userTZ='.Api\DateTime::$user_timezone->getName()); |
||
563 | foreach($this->timestamps as $key) |
||
564 | { |
||
565 | if ($values[$key]) |
||
566 | { |
||
567 | $time = new Api\DateTime($values[$key], $tz); |
||
568 | $time->setTimezone($fromTZ); |
||
569 | if ($time->format('Hi') == '0000') |
||
570 | { |
||
571 | // we keep dates the same in new timezone |
||
572 | $arr = Api\DateTime::to($time,'array'); |
||
573 | $time = new Api\DateTime($arr, $toTZ); |
||
574 | } |
||
575 | else |
||
576 | { |
||
577 | $time->setTimezone($toTZ); |
||
578 | } |
||
579 | $values[$key] = Api\DateTime::to($time,'ts'); |
||
580 | } |
||
581 | } |
||
582 | //error_log(__METHOD__.'() --> values[info_enddate]='.date('Y-m-d H:i:s',$values['info_enddate'])); |
||
583 | } |
||
584 | |||
585 | /** |
||
586 | * convert a date from server to user-time |
||
587 | * |
||
588 | * @param int $ts timestamp in server-time |
||
589 | * @param string $date_format = 'ts' date-formats: 'ts'=timestamp, 'server'=timestamp in server-time, 'array'=array or string with date-format |
||
590 | * @return mixed depending of $date_format |
||
591 | */ |
||
592 | function date2usertime($ts,$date_format='ts') |
||
593 | { |
||
594 | if (empty($ts) || $date_format == 'server') return $ts; |
||
595 | |||
596 | return Api\DateTime::server2user($ts,$date_format); |
||
597 | } |
||
598 | |||
599 | /** |
||
600 | * Read an infolog entry specified by $info_id |
||
601 | * |
||
602 | * @param int|array $info_id integer id or array with id's or array with column=>value pairs of the entry to read |
||
603 | * @param boolean $run_link_id2from = true should link_id2from run, default yes, |
||
604 | * need to be set to false if called from link-title to prevent an infinit recursion |
||
605 | * @param string $date_format = 'ts' date-formats: 'ts'=timestamp, 'server'=timestamp in server-time, |
||
606 | * 'array'=array or string with date-format |
||
607 | * @param boolean $ignore_acl = false if true, do NOT check access, default false |
||
608 | * |
||
609 | * @return array|boolean infolog entry, null if not found or false if no permission to read it |
||
610 | */ |
||
611 | function &read($info_id,$run_link_id2from=true,$date_format='ts',$ignore_acl=false) |
||
612 | { |
||
613 | //error_log(__METHOD__.'('.array2string($info_id).', '.array2string($run_link_id2from).", '$date_format') ".function_backtrace()); |
||
614 | if (is_scalar($info_id) || isset($info_id[count($info_id)-1])) |
||
615 | { |
||
616 | if (is_scalar($info_id) && !is_numeric($info_id)) |
||
617 | { |
||
618 | $info_id = array('info_uid' => $info_id); |
||
619 | } |
||
620 | else |
||
621 | { |
||
622 | $info_id = array('info_id' => $info_id); |
||
623 | } |
||
624 | } |
||
625 | |||
626 | if (!$info_id || ($data = $this->so->read($info_id)) === False) |
||
627 | { |
||
628 | return null; |
||
629 | } |
||
630 | |||
631 | if (!$ignore_acl && !$this->check_access($data,Acl::READ)) // check behind read, to prevent a double read |
||
632 | { |
||
633 | return False; |
||
634 | } |
||
635 | |||
636 | if ($data['info_subject'] == $this->subject_from_des($data['info_des'])) |
||
637 | { |
||
638 | $data['info_subject'] = ''; |
||
639 | } |
||
640 | if ($run_link_id2from) |
||
641 | { |
||
642 | $this->link_id2from($data); |
||
643 | } |
||
644 | // convert server- to user-time |
||
645 | if ($date_format == 'ts') |
||
646 | { |
||
647 | $this->time2time($data); |
||
648 | |||
649 | // pre-cache title and file access |
||
650 | self::set_link_cache($data); |
||
651 | } |
||
652 | |||
653 | return $data; |
||
654 | } |
||
655 | |||
656 | /** |
||
657 | * Delete an infolog entry, evtl. incl. it's children / subs |
||
658 | * |
||
659 | * @param int|array $info_id int id |
||
660 | * @param boolean $delete_children should the children be deleted |
||
661 | * @param int|boolean $new_parent parent to use for not deleted children if > 0 |
||
662 | * @param boolean $skip_notification Do not send notification of delete |
||
663 | * @return boolean True if delete was successful, False otherwise ($info_id does not exist or no rights) |
||
664 | */ |
||
665 | function delete($info_id,$delete_children=False,$new_parent=False, $skip_notification=False) |
||
666 | { |
||
667 | if (is_array($info_id)) |
||
668 | { |
||
669 | $info_id = (int)(isset($info_id[0]) ? $info_id[0] : (isset($info_id['info_id']) ? $info_id['info_id'] : $info_id['info_id'])); |
||
670 | } |
||
671 | if (($info = $this->so->read(array('info_id' => $info_id), true, 'server')) === False) |
||
672 | { |
||
673 | return False; |
||
674 | } |
||
675 | if (!$this->check_access($info,Acl::DELETE)) |
||
676 | { |
||
677 | return False; |
||
678 | } |
||
679 | // check if we have children and delete or re-parent them |
||
680 | if (($children = $this->so->get_children($info_id))) |
||
681 | { |
||
682 | foreach($children as $id => $owner) |
||
683 | { |
||
684 | if ($delete_children && $this->so->grants[$owner] & Acl::DELETE) |
||
685 | { |
||
686 | $this->delete($id,$delete_children,$new_parent,$skip_notification); // call ourself recursive to delete the child |
||
687 | } |
||
688 | else // dont delete or no rights to delete the child --> re-parent it |
||
689 | { |
||
690 | $this->so->write(array( |
||
691 | 'info_id' => $id, |
||
692 | 'info_parent_id' => $new_parent, |
||
693 | )); |
||
694 | } |
||
695 | } |
||
696 | } |
||
697 | $deleted = $info; |
||
698 | $deleted['info_status'] = 'deleted'; |
||
699 | $deleted['info_datemodified'] = time(); |
||
700 | $deleted['info_modifier'] = $this->user; |
||
701 | |||
702 | // if we have history switched on and not an already deleted item --> set only status deleted |
||
703 | if ($this->history && $info['info_status'] != 'deleted') |
||
704 | { |
||
705 | if ($info['info_status'] == 'deleted') return false; // entry already deleted |
||
706 | |||
707 | $this->so->write($deleted); |
||
708 | |||
709 | Link::unlink(0,'infolog',$info_id,'','!file','',true); // keep the file attachments, hide the rest |
||
710 | } |
||
711 | else |
||
712 | { |
||
713 | $this->so->delete($info_id,false); // we delete the children via bo to get all notifications! |
||
714 | |||
715 | Link::unlink(0,'infolog',$info_id); |
||
716 | } |
||
717 | if ($info['info_status'] != 'deleted') // dont notify of final purge of already deleted items |
||
718 | { |
||
719 | // send email notifications and do the history logging |
||
720 | if(!$skip_notification) |
||
721 | { |
||
722 | if (!is_object($this->tracking)) |
||
723 | { |
||
724 | $this->tracking = new infolog_tracking($this); |
||
725 | } |
||
726 | $this->tracking->track($deleted,$info,$this->user,true); |
||
727 | } |
||
728 | } |
||
729 | return True; |
||
730 | } |
||
731 | |||
732 | /** |
||
733 | * writes the given $values to InfoLog, a new entry gets created if info_id is not set or 0 |
||
734 | * |
||
735 | * checks and asures ACL |
||
736 | * |
||
737 | * @param array &$values values to write |
||
738 | * @param boolean $check_defaults = true check and set certain defaults |
||
739 | * @param boolean|int $touch_modified = true touch the modification date and sets the modifier's user-id, 2: only modifier |
||
740 | * @param boolean $user2server = true conversion between user- and server-time necessary |
||
741 | * @param boolean $skip_notification = false true = do NOT send notification, false (default) = send notifications |
||
742 | * @param boolean $throw_exception = false Throw an exception (if required fields are not set) |
||
743 | * @param string $purge_cfs = null null=dont, 'ical'=only iCal X-properties (cfs name starting with "#"), 'all'=all cfs |
||
744 | * @param boolean $ignore_acl =true |
||
745 | * |
||
746 | * @return int|boolean info_id on a successfull write or false |
||
747 | */ |
||
748 | function write(&$values_in, $check_defaults=true, $touch_modified=true, $user2server=true, |
||
749 | $skip_notification=false, $throw_exception=false, $purge_cfs=null, $ignore_acl=false) |
||
750 | { |
||
751 | $values = $values_in; |
||
752 | //echo "boinfolog::write()values="; _debug_array($values); |
||
753 | if (!$ignore_acl && (!$values['info_id'] && !$this->check_access(0,Acl::EDIT,$values['info_owner']) && |
||
754 | !$this->check_access(0,Acl::ADD,$values['info_owner']))) |
||
755 | { |
||
756 | return false; |
||
757 | } |
||
758 | // we need to get the old values to update the links in customfields and for the tracking |
||
759 | if ($values['info_id']) |
||
760 | { |
||
761 | $old = $this->read($values['info_id'], false, 'server', $ignore_acl); |
||
762 | } |
||
763 | |||
764 | if (($status_only = !$ignore_acl && $values['info_id'] && !$this->check_access($values,Acl::EDIT))) |
||
765 | { |
||
766 | if (!isset($values['info_responsible'])) |
||
767 | { |
||
768 | $responsible = $old['info_responsible']; |
||
769 | } |
||
770 | else |
||
771 | { |
||
772 | $responsible = $values['info_responsible']; |
||
773 | } |
||
774 | if (!($status_only = in_array($this->user, (array)$responsible))) // responsible has implicit right to change status |
||
775 | { |
||
776 | $status_only = !!array_intersect((array)$responsible,array_keys($GLOBALS['egw']->accounts->memberships($this->user))); |
||
777 | } |
||
778 | if (!$status_only && $values['info_status'] != 'deleted') |
||
779 | { |
||
780 | $status_only = $undelete = $this->check_access($values['info_id'],self::ACL_UNDELETE); |
||
781 | } |
||
782 | } |
||
783 | if (!$ignore_acl && ($values['info_id'] && !$this->check_access($values['info_id'],Acl::EDIT) && !$status_only || |
||
784 | !$values['info_id'] && $values['info_id_parent'] && !$this->check_access($values['info_id_parent'],Acl::ADD))) |
||
785 | { |
||
786 | return false; |
||
787 | } |
||
788 | |||
789 | // Make sure status is still valid if the type changes |
||
790 | if($old['info_type'] != $values['info_type'] && $values['info_status']) |
||
791 | { |
||
792 | if (isset($this->status[$values['info_type']]) && |
||
793 | !in_array($values['info_status'], array_keys($this->status[$values['info_type']]))) |
||
794 | { |
||
795 | $values['info_status'] = $this->status['defaults'][$values['info_type']]; |
||
796 | } |
||
797 | } |
||
798 | if ($status_only && !$undelete) // make sure only status gets writen |
||
799 | { |
||
800 | $set_completed = !$values['info_datecompleted'] && // set date completed of finished job, only if its not already set |
||
801 | in_array($values['info_status'],array('done','billed','cancelled')); |
||
802 | |||
803 | $values = $old; |
||
804 | // only overwrite explicitly allowed fields |
||
805 | $values['info_datemodified'] = $values_in['info_datemodified']; |
||
806 | foreach ($this->responsible_edit as $name) |
||
807 | { |
||
808 | if (isset($values_in[$name])) $values[$name] = $values_in[$name]; |
||
809 | } |
||
810 | if ($set_completed) |
||
811 | { |
||
812 | $values['info_datecompleted'] = $user2server ? $this->user_time_now : $this->now; |
||
813 | $values['info_percent'] = 100; |
||
814 | $forcestatus = true; |
||
815 | $status = 'done'; |
||
816 | if (isset($values['info_type']) && !in_array($values['info_status'],array('done','billed','cancelled'))) { |
||
817 | $forcestatus = false; |
||
818 | //echo "set_completed:"; _debug_array($this->status[$values['info_type']]); |
||
819 | if (isset($this->status[$values['info_type']]['done'])) { |
||
820 | $forcestatus = true; |
||
821 | $status = 'done'; |
||
822 | } elseif (isset($this->status[$values['info_type']]['billed'])) { |
||
823 | $forcestatus = true; |
||
824 | $status = 'billed'; |
||
825 | } elseif (isset($this->status[$values['info_type']]['cancelled'])) { |
||
826 | $forcestatus = true; |
||
827 | $status = 'cancelled'; |
||
828 | } |
||
829 | } |
||
830 | if ($forcestatus && !in_array($values['info_status'],array('done','billed','cancelled'))) $values['info_status'] = $status; |
||
831 | } |
||
832 | $check_defaults = false; |
||
833 | } |
||
834 | if ($check_defaults) |
||
835 | { |
||
836 | if (!$values['info_datecompleted'] && |
||
837 | (in_array($values['info_status'],array('done','billed')))) |
||
838 | { |
||
839 | $values['info_datecompleted'] = $user2server ? $this->user_time_now : $this->now; // set date completed to today if status == done |
||
840 | } |
||
841 | // Check for valid status / percent combinations |
||
842 | if (in_array($values['info_status'],array('done','billed'))) |
||
843 | { |
||
844 | $values['info_percent'] = 100; |
||
845 | } |
||
846 | else if (in_array($values['info_status'], array('not-started'))) |
||
847 | { |
||
848 | $values['info_percent'] = 0; |
||
849 | } |
||
850 | else if (($values['info_status'] != 'archive' && $values['info_status'] != 'cancelled' && |
||
851 | isset($this->stock_status[$values['info_type']]) && in_array($values['info_status'], $this->stock_status[$values['info_type']])) && |
||
852 | ((int)$values['info_percent'] == 100 || $values['info_percent'] == 0)) |
||
853 | { |
||
854 | // We change percent to match status, not status to match percent |
||
855 | $values['info_percent'] = 10; |
||
856 | } |
||
857 | if ((int)$values['info_percent'] == 100 && !in_array($values['info_status'],array('done','billed','cancelled','archive'))) |
||
858 | { |
||
859 | //echo "check_defaults:"; _debug_array($this->status[$values['info_type']]); |
||
860 | //$values['info_status'] = 'done'; |
||
861 | $status = 'done'; |
||
862 | if (isset($values['info_type'])) { |
||
863 | if (isset($this->status[$values['info_type']]['done'])) { |
||
864 | $status = 'done'; |
||
865 | } elseif (isset($this->status[$values['info_type']]['billed'])) { |
||
866 | $status = 'billed'; |
||
867 | } elseif (isset($this->status[$values['info_type']]['cancelled'])) { |
||
868 | $status = 'cancelled'; |
||
869 | } else { |
||
870 | // since the comlete stati above do not exist for that type, dont change it |
||
871 | $status = $values['info_status']; |
||
872 | } |
||
873 | } |
||
874 | $values['info_status'] = $status; |
||
875 | } |
||
876 | if ($values['info_responsible'] && $values['info_status'] == 'offer') |
||
877 | { |
||
878 | $values['info_status'] = 'not-started'; // have to match if not finished |
||
879 | } |
||
880 | if (isset($values['info_subject']) && empty($values['info_subject'])) |
||
881 | { |
||
882 | $values['info_subject'] = $this->subject_from_des($values['info_des']); |
||
883 | } |
||
884 | |||
885 | // Check required custom fields |
||
886 | if($throw_exception) |
||
887 | { |
||
888 | $custom = Api\Storage\Customfields::get('infolog'); |
||
889 | foreach($custom as $c_name => $c_field) |
||
890 | { |
||
891 | if($c_field['type2']) $type2 = is_array($c_field['type2']) ? $c_field['type2'] : explode(',',$c_field['type2']); |
||
892 | if($c_field['needed'] && (!$c_field['type2'] || $c_field['type2'] && in_array($values['info_type'],$type2))) |
||
893 | { |
||
894 | // Required custom field |
||
895 | if(!$values['#'.$c_name]) |
||
896 | { |
||
897 | throw new Api\Exception\WrongUserinput(lang('For infolog type %1, %2 is required',lang($values['info_type']),$c_field['label'])); |
||
898 | } |
||
899 | } |
||
900 | } |
||
901 | } |
||
902 | } |
||
903 | if (isset($this->group_owners[$values['info_type']])) |
||
904 | { |
||
905 | $values['info_owner'] = $this->group_owners[$values['info_type']]; |
||
906 | if (!$ignore_acl && !($this->grants[$this->group_owners[$values['info_type']]] & Acl::EDIT)) |
||
907 | { |
||
908 | if (!$this->check_access($values['info_id'],Acl::EDIT) || |
||
909 | !$values['info_id'] && !$this->check_access($values,Acl::ADD) |
||
910 | ) |
||
911 | { |
||
912 | return false; // no edit rights from the group-owner and no implicit rights (delegated and sufficient rights) |
||
913 | } |
||
914 | } |
||
915 | $values['info_access'] = 'public'; // group-owners are allways public |
||
916 | } |
||
917 | elseif (!$values['info_id'] && !$values['info_owner'] || $GLOBALS['egw']->accounts->get_type($values['info_owner']) == 'g') |
||
918 | { |
||
919 | $values['info_owner'] = $this->so->user; |
||
920 | } |
||
921 | |||
922 | $to_write = $values; |
||
923 | if ($user2server) |
||
924 | { |
||
925 | // convert user- to server-time |
||
926 | $this->time2time($to_write, null, false); |
||
927 | } |
||
928 | else |
||
929 | { |
||
930 | // convert server- to user-time |
||
931 | $this->time2time($values); |
||
932 | } |
||
933 | |||
934 | if ($touch_modified && $touch_modified !== 2 || !$values['info_datemodified']) |
||
935 | { |
||
936 | // Should only an entry be updated which includes the original modification date? |
||
937 | // Used in the web-GUI to check against a modification by an other user while editing the entry. |
||
938 | // It's now disabled for xmlrpc, as otherwise the xmlrpc code need to be changed! |
||
939 | $xmlrpc = is_object($GLOBALS['server']) && $GLOBALS['server']->last_method; |
||
940 | $check_modified = $values['info_datemodified'] && !$xmlrpc ? $to_write['info_datemodified'] : false; |
||
941 | $values['info_datemodified'] = $this->user_time_now; |
||
942 | $to_write['info_datemodified'] = $this->now; |
||
943 | } |
||
944 | if ($touch_modified || !$values['info_modifier']) |
||
945 | { |
||
946 | $values['info_modifier'] = $to_write['info_modifier'] = $this->so->user; |
||
947 | } |
||
948 | |||
949 | // set created and creator for new entries |
||
950 | if (!$values['info_id']) |
||
951 | { |
||
952 | $values['info_created'] = $this->user_time_now; |
||
953 | $to_write['info_created'] = $this->now; |
||
954 | $values['info_creator'] = $to_write['info_creator'] = $this->so->user; |
||
955 | } |
||
956 | //_debug_array($values); |
||
957 | // error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."()\n".array2string($values)."\n",3,'/tmp/infolog'); |
||
958 | |||
959 | if (($info_id = $this->so->write($to_write, $check_modified, $purge_cfs, !isset($old)))) |
||
960 | { |
||
961 | if (!isset($values['info_type']) || $status_only || empty($values['caldav_url'])) |
||
962 | { |
||
963 | $values = $this->read($info_id, true, 'server', $ignore_acl); |
||
964 | } |
||
965 | |||
966 | $values['info_id'] = $info_id; |
||
967 | $to_write['info_id'] = $info_id; |
||
968 | |||
969 | // if the info responbsible array is not passed, fetch it from old. |
||
970 | if (!array_key_exists('info_responsible',$values)) $values['info_responsible'] = $old['info_responsible']; |
||
971 | if (!is_array($values['info_responsible'])) // this should not happen, bug it does ;-) |
||
972 | { |
||
973 | $values['info_responsible'] = $values['info_responsible'] ? explode(',',$values['info_responsible']) : array(); |
||
974 | $to_write['info_responsible'] = $values['info_responsible']; |
||
975 | } |
||
976 | |||
977 | // writing links for a new entry |
||
978 | if (!$old && is_array($to_write['link_to']['to_id']) && count($to_write['link_to']['to_id'])) |
||
979 | { |
||
980 | //echo "<p>writing links for new entry $info_id</p>\n"; _debug_array($content['link_to']['to_id']); |
||
981 | Link::link('infolog',$info_id,$to_write['link_to']['to_id']); |
||
982 | $values['link_to']['to_id'] = $info_id; |
||
983 | } |
||
984 | $this->write_check_links($to_write); |
||
985 | if(!$values['info_link_id'] || $values['info_link_id'] != $to_write['info_link_id']) |
||
986 | { |
||
987 | // Just got a link ID, need to save it |
||
988 | $this->so->write($to_write); |
||
989 | $values['info_link_id'] = $to_write['info_link_id']; |
||
990 | $values['info_contact'] = $to_write['info_contact']; |
||
991 | $values['info_from'] = $to_write['info_from']; |
||
992 | $this->link_id2from($values); |
||
993 | } |
||
994 | $values['pm_id'] = $to_write['pm_id']; |
||
995 | |||
996 | if (($info_from_set = ($values['info_link_id'] && isset($values['info_from']) && empty($values['info_from'])))) |
||
997 | { |
||
998 | $values['info_from'] = $to_write['info_from'] = $this->link_id2from($values); |
||
999 | } |
||
1000 | |||
1001 | // create (and remove) links in custom fields |
||
1002 | if(!is_array($old)) |
||
1003 | { |
||
1004 | $old = array(); |
||
1005 | } |
||
1006 | Api\Storage\Customfields::update_links('infolog',$values,$old,'info_id'); |
||
1007 | |||
1008 | // Check for restore of deleted entry, restore held links |
||
1009 | if($old['info_status'] == 'deleted' && $values['info_status'] != 'deleted') |
||
1010 | { |
||
1011 | Link::restore('infolog', $info_id); |
||
1012 | } |
||
1013 | |||
1014 | // notify the link-class about the update, as other apps may be subscribt to it |
||
1015 | Link::notify_update('infolog',$info_id,$values); |
||
1016 | |||
1017 | // pre-cache the new values |
||
1018 | self::set_link_cache($values); |
||
1019 | |||
1020 | // send email notifications and do the history logging |
||
1021 | if (!is_object($this->tracking)) |
||
1022 | { |
||
1023 | $this->tracking = new infolog_tracking($this); |
||
1024 | } |
||
1025 | |||
1026 | if ($old && ($missing_fields = array_diff_key($old,$values))) |
||
1027 | { |
||
1028 | // Some custom fields (multiselect with nothing selected) will be missing, |
||
1029 | // and that's OK. Don't put them back. |
||
1030 | foreach(array_keys($missing_fields) as $field) |
||
1031 | { |
||
1032 | if(array_key_exists($field, $values_in)) |
||
1033 | { |
||
1034 | unset($missing_fields[$field]); |
||
1035 | } |
||
1036 | } |
||
1037 | $values = array_merge($values,$missing_fields); |
||
1038 | } |
||
1039 | // Add keys missing in the $to_write array |
||
1040 | if (($missing_fields = array_diff_key($values,$to_write))) |
||
1041 | { |
||
1042 | $to_write = array_merge($to_write,$missing_fields); |
||
1043 | } |
||
1044 | $this->tracking->track($to_write,$old,$this->user,$values['info_status'] == 'deleted' || $old['info_status'] == 'deleted', |
||
1045 | null,$skip_notification); |
||
1046 | |||
1047 | if ($info_from_set) $values['info_from'] = ''; |
||
1048 | |||
1049 | // Change new values back to user time before sending them back |
||
1050 | if($user2server) |
||
1051 | { |
||
1052 | $this->time2time($values); |
||
1053 | } |
||
1054 | // merge changes (keeping extra values from the UI) |
||
1055 | $values_in = array_merge($values_in,$values); |
||
1056 | |||
1057 | // Update modified timestamp of parent |
||
1058 | if($values['info_id_parent'] && $touch_modified) |
||
1059 | { |
||
1060 | $parent = $this->read($values['info_id_parent'], true, 'server', true); |
||
1061 | $this->write($parent, false, true, false, true, false, null, $ignore_acl); |
||
1062 | } |
||
1063 | } |
||
1064 | return $info_id; |
||
1065 | } |
||
1066 | |||
1067 | /** |
||
1068 | * Check links when writing an infolog entry |
||
1069 | * |
||
1070 | * Checks for info_contact properly linked, project properly linked and |
||
1071 | * adds or removes to correct. |
||
1072 | * |
||
1073 | * @param Array $values |
||
1074 | */ |
||
1075 | protected function write_check_links(&$values) |
||
1076 | { |
||
1077 | $old_link_id = (int)$values['info_link_id']; |
||
1078 | $from = $values['info_from']; |
||
1079 | |||
1080 | if($values['info_contact'] && !( |
||
1081 | is_array($values['info_contact']) && $values['info_contact']['id'] == 'none' |
||
1082 | ) || ( |
||
1083 | is_array($values['info_contact']) && $values['info_contact']['id'] == 'none' && |
||
1084 | array_key_exists('search', $values['info_contact']) |
||
1085 | )) |
||
1086 | { |
||
1087 | if(is_array($values['info_contact'])) |
||
1088 | { |
||
1089 | // eTemplate2 returns the array all ready |
||
1090 | $app = $values['info_contact']['app']; |
||
1091 | $id = (int)$values['info_contact']['id']; |
||
1092 | $from = $values['info_contact']['search']; |
||
1093 | } |
||
1094 | else if ($values['info_contact']) |
||
1095 | { |
||
1096 | list($app, $id) = explode(':', $values['info_contact'], 2); |
||
1097 | } |
||
1098 | // if project has been removed, but is still info_contact --> also remove it |
||
1099 | if ($app == 'projectmanager' && $id && $id == $values['old_pm_id'] && !$values['pm_id']) |
||
1100 | { |
||
1101 | unset($values['info_link_id'], $id, $values['info_contact']); |
||
1102 | } |
||
1103 | else if ($app && $id) |
||
1104 | { |
||
1105 | if(!is_array($values['link_to'])) |
||
1106 | { |
||
1107 | $values['link_to'] = array(); |
||
1108 | } |
||
1109 | $values['info_link_id'] = (int)($info_link_id = Link::link( |
||
1110 | 'infolog', |
||
1111 | $values['info_id'], |
||
1112 | $app,$id |
||
1113 | )); |
||
1114 | $values['info_from'] = Link::title($app, $id); |
||
1115 | if($values['pm_id']) |
||
1116 | { |
||
1117 | // They just changed the contact, don't clear the project |
||
1118 | unset($old_link_id); |
||
1119 | } |
||
1120 | } |
||
1121 | else if ($from) |
||
1122 | { |
||
1123 | $values['info_from'] = $from; |
||
1124 | } |
||
1125 | else |
||
1126 | { |
||
1127 | unset($values['info_link_id']); |
||
1128 | $values['info_from'] = null; |
||
1129 | } |
||
1130 | } |
||
1131 | else if ($values['pm_id'] && $values['info_id'] && !$values['old_pm_id']) |
||
1132 | { |
||
1133 | // Set for new entry with no contact |
||
1134 | $app = 'projectmanager'; |
||
1135 | $id = $values['pm_id']; |
||
1136 | $values['info_link_id'] = (int)($info_link_id = Link::link( |
||
1137 | 'infolog', |
||
1138 | $values['info_id'], |
||
1139 | $app,$id |
||
1140 | )); |
||
1141 | } |
||
1142 | else |
||
1143 | { |
||
1144 | unset($values['info_link_id']); |
||
1145 | unset($values['info_contact']); |
||
1146 | $values['info_from'] = $from ? $from : null; |
||
1147 | } |
||
1148 | if($values['info_id'] && $values['old_pm_id'] !== $values['pm_id']) |
||
1149 | { |
||
1150 | Link::unlink(0,'infolog',$values['info_id'],0,'projectmanager',$values['old_pm_id']); |
||
1151 | // Project has changed, but link is not to project |
||
1152 | if($values['pm_id']) |
||
1153 | { |
||
1154 | $link_id = Link::link('infolog', $values['info_id'], 'projectmanager', $values['pm_id']); |
||
1155 | if(!$values['info_link_id']) |
||
1156 | { |
||
1157 | $values['info_link_id'] = $link_id; |
||
1158 | } |
||
1159 | } |
||
1160 | else |
||
1161 | { |
||
1162 | // Project removed, but primary link is not to project |
||
1163 | $values['pm_id'] = null; |
||
1164 | } |
||
1165 | } |
||
1166 | if ($old_link_id && $old_link_id != $values['info_link_id']) |
||
1167 | { |
||
1168 | $link = Link::get_link($old_link_id); |
||
1169 | // remove selected project, if removed link is that project |
||
1170 | if($link['link_app2'] == 'projectmanager' && $link['link_id2'] == $values['old_pm_id']) |
||
1171 | { |
||
1172 | unset($values['pm_id'], $values['old_pm_id']); |
||
1173 | } |
||
1174 | Link::unlink($old_link_id); |
||
1175 | } |
||
1176 | // if linked to a project and no other project selected, also add as project |
||
1177 | $links = Link::get_links('infolog', $values['info_id'], 'projectmanager'); |
||
1178 | if (!$values['pm_id'] && count($links)) |
||
1179 | { |
||
1180 | $values['old_pm_id'] = $values['pm_id'] = array_pop($links); |
||
1181 | } |
||
1182 | } |
||
1183 | |||
1184 | /** |
||
1185 | * Query the number of children / subs for one or more info_id's |
||
1186 | * |
||
1187 | * @param int|array $info_id id |
||
1188 | * @return int|array number of subs |
||
1189 | */ |
||
1190 | function anzSubs( $info_id ) |
||
1191 | { |
||
1192 | return $this->so->anzSubs( $info_id ); |
||
1193 | } |
||
1194 | |||
1195 | /** |
||
1196 | * searches InfoLog for a certain pattern in $query |
||
1197 | * |
||
1198 | * @param $query[order] column-name to sort after |
||
1199 | * @param $query[sort] sort-order DESC or ASC |
||
1200 | * @param $query[filter] string with combination of acl-, date- and status-filters, eg. 'own-open-today' or '' |
||
1201 | * @param $query[cat_id] category to use or 0 or unset |
||
1202 | * @param $query[search] pattern to search, search is done in info_from, info_subject and info_des |
||
1203 | * @param $query[action] / $query[action_id] if only entries linked to a specified app/entry show be used |
||
1204 | * @param &$query[start], &$query[total] nextmatch-parameters will be used and set if query returns less entries |
||
1205 | * @param $query[col_filter] array with column-name - data pairs, data == '' means no filter (!) |
||
1206 | * @param boolean $no_acl =false true: ignore all acl |
||
1207 | * @return array with id's as key of the matching log-entries |
||
1208 | */ |
||
1209 | function &search(&$query, $no_acl=false) |
||
1210 | { |
||
1211 | //error_log(__METHOD__.'('.array2string($query).')'); |
||
1212 | |||
1213 | if($query['filter'] == 'bydate') |
||
1214 | { |
||
1215 | if (is_int($query['startdate'])) $query['col_filter'][] = 'info_startdate >= '.$GLOBALS['egw']->db->quote($query['startdate']); |
||
1216 | if (is_int($query['enddate'])) $query['col_filter'][] = 'info_startdate <= '.$GLOBALS['egw']->db->quote($query['enddate']+(60*60*24)-1); |
||
1217 | } |
||
1218 | elseif ($query['filter'] == 'duedate') |
||
1219 | { |
||
1220 | if (is_int($query['startdate'])) $query['col_filter'][] = 'info_enddate >= '.$GLOBALS['egw']->db->quote($query['startdate']); |
||
1221 | if (is_int($query['enddate'])) $query['col_filter'][] = 'info_enddate <= '.$GLOBALS['egw']->db->quote($query['enddate']+(60*60*24)-1); |
||
1222 | } |
||
1223 | elseif ($query['filter'] == 'private') |
||
1224 | { |
||
1225 | $query['col_filter'][] = 'info_access = ' . $GLOBALS['egw']->db->quote('private'); |
||
1226 | } |
||
1227 | if (!isset($query['date_format']) || $query['date_format'] != 'server') |
||
1228 | { |
||
1229 | if (isset($query['col_filter'])) |
||
1230 | { |
||
1231 | foreach ($this->timestamps as $key) |
||
1232 | { |
||
1233 | if (!empty($query['col_filter'][$key])) |
||
1234 | { |
||
1235 | $query['col_filter'][$key] = Api\DateTime::user2server($query['col_filter'][$key],'ts'); |
||
1236 | } |
||
1237 | } |
||
1238 | } |
||
1239 | } |
||
1240 | |||
1241 | $ret = $this->so->search($query, $no_acl); |
||
1242 | $this->total = $query['total']; |
||
1243 | |||
1244 | if (is_array($ret)) |
||
1245 | { |
||
1246 | foreach ($ret as $id => &$data) |
||
1247 | { |
||
1248 | if (!$no_acl && !$this->check_access($data,Acl::READ)) |
||
1249 | { |
||
1250 | unset($ret[$id]); |
||
1251 | continue; |
||
1252 | } |
||
1253 | // convert system- to user-time |
||
1254 | foreach ($this->timestamps as $key) |
||
1255 | { |
||
1256 | if ($data[$key]) |
||
1257 | { |
||
1258 | $time = new Api\DateTime($data[$key], Api\DateTime::$server_timezone); |
||
1259 | if (!isset($query['date_format']) || $query['date_format'] != 'server') |
||
1260 | { |
||
1261 | if ($time->format('Hi') == '0000') |
||
1262 | { |
||
1263 | // we keep dates the same in user-time |
||
1264 | $arr = Api\DateTime::to($time,'array'); |
||
1265 | $time = new Api\DateTime($arr, Api\DateTime::$user_timezone); |
||
1266 | } |
||
1267 | else |
||
1268 | { |
||
1269 | $time->setTimezone(Api\DateTime::$user_timezone); |
||
1270 | } |
||
1271 | } |
||
1272 | $data[$key] = Api\DateTime::to($time,'ts'); |
||
1273 | } |
||
1274 | } |
||
1275 | // pre-cache title and file access |
||
1276 | self::set_link_cache($data); |
||
1277 | } |
||
1278 | } |
||
1279 | //echo "<p>boinfolog::search(".print_r($query,True).")=<pre>".print_r($ret,True)."</pre>\n"; |
||
1280 | return $ret; |
||
1281 | } |
||
1282 | |||
1283 | /** |
||
1284 | * Query ctag for infolog |
||
1285 | * |
||
1286 | * @param array $filter = array('filter'=>'own','info_type'=>'task') |
||
1287 | * @return string |
||
1288 | */ |
||
1289 | public function getctag(array $filter=array('filter'=>'own','info_type'=>'task')) |
||
1290 | { |
||
1291 | $filter += array( |
||
1292 | 'order' => 'info_datemodified', |
||
1293 | 'sort' => 'DESC', |
||
1294 | 'date_format' => 'server', |
||
1295 | 'start' => 0, |
||
1296 | 'num_rows' => 1, |
||
1297 | ); |
||
1298 | // we need to query deleted entries too for a ctag! |
||
1299 | $filter['filter'] .= '+deleted'; |
||
1300 | |||
1301 | $result =& $this->search($filter); |
||
1302 | |||
1303 | if (empty($result)) return 'EGw-empty-wGE'; |
||
1304 | |||
1305 | $entry = array_shift($result); |
||
1306 | |||
1307 | return $entry['info_datemodified']; |
||
1308 | } |
||
1309 | |||
1310 | /** |
||
1311 | * imports a mail identified by uid as infolog |
||
1312 | * |
||
1313 | * @author Cornelius Weiss <[email protected]> |
||
1314 | * @todo search if infolog with from and subject allready exists ->appned body & inform user |
||
1315 | * @param array $_addresses array of addresses |
||
1316 | * - array (email,name) |
||
1317 | * @param string $_subject |
||
1318 | * @param string $_message |
||
1319 | * @param array $_attachments |
||
1320 | * @param string $_date |
||
1321 | * @return array $content array for uiinfolog |
||
1322 | */ |
||
1323 | function import_mail($_addresses,$_subject,$_message,$_attachments,$_date) |
||
1324 | { |
||
1325 | foreach($_addresses as $address) |
||
1326 | { |
||
1327 | $names[] = $address['name']; |
||
1328 | $emails[] =$address['email']; |
||
1329 | } |
||
1330 | |||
1331 | $type = isset($this->enums['type']['email']) ? 'email' : 'note'; |
||
1332 | $status = isset($this->status['defaults'][$type]) ? $this->status['defaults'][$type] : 'done'; |
||
1333 | $info = array( |
||
1334 | 'info_id' => 0, |
||
1335 | 'info_type' => $type, |
||
1336 | 'info_from' => implode(', ',$names) . implode(', ', $emails), |
||
1337 | 'info_subject' => $_subject, |
||
1338 | 'info_des' => $_message, |
||
1339 | 'info_startdate' => Api\DateTime::server2user($_date), |
||
1340 | 'info_status' => $status, |
||
1341 | 'info_priority' => 1, |
||
1342 | 'info_percent' => $status == 'done' ? 100 : 0, |
||
1343 | 'referer' => false, |
||
1344 | 'link_to' => array( |
||
1345 | 'to_app' => 'infolog', |
||
1346 | 'to_id' => 0, |
||
1347 | ), |
||
1348 | ); |
||
1349 | if ($GLOBALS['egw_info']['user']['preferences']['infolog']['cat_add_default']) $info['info_cat'] = $GLOBALS['egw_info']['user']['preferences']['infolog']['cat_add_default']; |
||
1350 | // find the addressbookentry to link with |
||
1351 | $addressbook = new Api\Contacts(); |
||
1352 | $contacts = array(); |
||
1353 | foreach ($emails as $mailadr) |
||
1354 | { |
||
1355 | $contacts = array_merge($contacts,(array)$addressbook->search( |
||
1356 | array( |
||
1357 | 'email' => $mailadr, |
||
1358 | 'email_home' => $mailadr |
||
1359 | ),True,'','','',false,'OR',false,null,'',false)); |
||
1360 | } |
||
1361 | if (!$contacts || !is_array($contacts) || !is_array($contacts[0])) |
||
1362 | { |
||
1363 | $info['msg'] = lang('Attention: No Contact with address %1 found.',$info['info_from']); |
||
1364 | $info['info_custom_from'] = true; // show the info_from line and NOT only the link |
||
1365 | } |
||
1366 | else |
||
1367 | { |
||
1368 | // create the first address as info_contact |
||
1369 | $contact = array_shift($contacts); |
||
1370 | $info['info_contact'] = 'addressbook:'.$contact['id']; |
||
1371 | // create the rest a "ordinary" links |
||
1372 | foreach ($contacts as $contact) |
||
1373 | { |
||
1374 | Link::link('infolog',$info['link_to']['to_id'],'addressbook',$contact['id']); |
||
1375 | } |
||
1376 | } |
||
1377 | if (is_array($_attachments)) |
||
1378 | { |
||
1379 | foreach ($_attachments as $attachment) |
||
1380 | { |
||
1381 | if($attachment['egw_data']) |
||
1382 | { |
||
1383 | Link::link('infolog',$info['link_to']['to_id'],Link::DATA_APPNAME, $attachment); |
||
1384 | } |
||
1385 | else if(is_readable($attachment['tmp_name']) || |
||
1386 | (Vfs::is_readable($attachment['tmp_name']) && parse_url($attachment['tmp_name'], PHP_URL_SCHEME) === 'vfs')) |
||
1387 | { |
||
1388 | Link::link('infolog',$info['link_to']['to_id'],'file', $attachment); |
||
1389 | } |
||
1390 | } |
||
1391 | } |
||
1392 | return $info; |
||
1393 | } |
||
1394 | |||
1395 | /** |
||
1396 | * get title for an infolog entry identified by $info |
||
1397 | * |
||
1398 | * Is called as hook to participate in the linking |
||
1399 | * |
||
1400 | * @param int|array $info int info_id or array with infolog entry |
||
1401 | * @return string|boolean string with the title, null if $info not found, false if no perms to view |
||
1402 | */ |
||
1403 | function link_title($info) |
||
1404 | { |
||
1405 | if (!is_array($info)) |
||
1406 | { |
||
1407 | $info = $this->read( $info,false ); |
||
1408 | } |
||
1409 | if (!$info) |
||
1410 | { |
||
1411 | return $info; |
||
1412 | } |
||
1413 | $title = !empty($info['info_subject']) ? $info['info_subject'] :self::subject_from_des($info['info_descr']); |
||
1414 | return $title.($GLOBALS['egw_info']['user']['preferences']['infolog']['show_id']?' (#'.$info['info_id'].')':''); |
||
1415 | } |
||
1416 | |||
1417 | /** |
||
1418 | * Return multiple titles fetched by a single query |
||
1419 | * |
||
1420 | * @param array $ids |
||
1421 | */ |
||
1422 | function link_titles(array $ids) |
||
1423 | { |
||
1424 | $titles = array(); |
||
1425 | foreach ($this->search($params=array( |
||
1426 | 'col_filter' => array('info_id' => $ids), |
||
1427 | )) as $info) |
||
1428 | { |
||
1429 | $titles[$info['info_id']] = $this->link_title($info); |
||
1430 | } |
||
1431 | foreach (array_diff($ids,array_keys($titles)) as $id) |
||
1432 | { |
||
1433 | $titles[$id] = false; // we assume every not returned entry to be not readable, as we notify the link class about all deletes |
||
1434 | } |
||
1435 | return $titles; |
||
1436 | } |
||
1437 | |||
1438 | /** |
||
1439 | * query infolog for entries matching $pattern |
||
1440 | * |
||
1441 | * Is called as hook to participate in the linking |
||
1442 | * |
||
1443 | * @param string $pattern pattern to search |
||
1444 | * @param array $options Array of options for the search |
||
1445 | * @return array with info_id - title pairs of the matching entries |
||
1446 | */ |
||
1447 | function link_query($pattern, Array &$options = array()) |
||
1448 | { |
||
1449 | $query = array( |
||
1450 | 'search' => $pattern, |
||
1451 | 'start' => $options['start'], |
||
1452 | 'num_rows' => $options['num_rows'], |
||
1453 | 'subs' => true, |
||
1454 | ); |
||
1455 | $ids = $this->search($query); |
||
1456 | $options['total'] = $query['total']; |
||
1457 | $content = array(); |
||
1458 | if (is_array($ids)) |
||
1459 | { |
||
1460 | foreach(array_keys($ids) as $id) |
||
1461 | { |
||
1462 | $content[$id] = $this->link_title($id); |
||
1463 | } |
||
1464 | } |
||
1465 | return $content; |
||
1466 | } |
||
1467 | |||
1468 | /** |
||
1469 | * Check access to the file store |
||
1470 | * |
||
1471 | * @param int|array $id id of entry or entry array |
||
1472 | * @param int $check Acl::READ for read and Acl::EDIT for write or delete access |
||
1473 | * @param string $rel_path = null currently not used in InfoLog |
||
1474 | * @param int $user = null for which user to check, default current user |
||
1475 | * @return boolean true if access is granted or false otherwise |
||
1476 | */ |
||
1477 | function file_access($id,$check,$rel_path=null,$user=null) |
||
1478 | { |
||
1479 | unset($rel_path); // not used |
||
1480 | return $this->check_access($id,$check,0,$user); |
||
1481 | } |
||
1482 | |||
1483 | /** |
||
1484 | * Set the cache of the link class (title, file_access) for the given infolog entry |
||
1485 | * |
||
1486 | * @param array $info |
||
1487 | */ |
||
1488 | function set_link_cache(array $info) |
||
1489 | { |
||
1490 | Link::set_cache('infolog',$info['info_id'], |
||
1491 | $this->link_title($info), |
||
1492 | $this->file_access($info,Acl::EDIT) ? EGW_ACL_READ|EGW_ACL_EDIT : |
||
1493 | ($this->file_access($info,Acl::READ) ? Acl::READ : 0)); |
||
1494 | } |
||
1495 | |||
1496 | /** |
||
1497 | * hook called be calendar to include events or todos in the cal-dayview |
||
1498 | * |
||
1499 | * @param int $args[year], $args[month], $args[day] date of the events |
||
1500 | * @param int $args[owner] owner of the events |
||
1501 | * @param string $args[location] calendar_include_{events|todos} |
||
1502 | * @return array of events (array with keys starttime, endtime, title, view, icon, content) |
||
1503 | */ |
||
1504 | function cal_to_include($args) |
||
1505 | { |
||
1506 | //echo "<p>cal_to_include("; print_r($args); echo ")</p>\n"; |
||
1507 | $user = (int) $args['owner']; |
||
1508 | if ($user <= 0 && !checkdate($args['month'],$args['day'],$args['year'])) |
||
1509 | { |
||
1510 | return False; |
||
1511 | } |
||
1512 | Api\Translation::add_app('infolog'); |
||
1513 | |||
1514 | $do_events = $args['location'] == 'calendar_include_events'; |
||
1515 | $to_include = array(); |
||
1516 | $date_wanted = sprintf('%04d/%02d/%02d',$args['year'],$args['month'],$args['day']); |
||
1517 | $query = array( |
||
1518 | 'order' => $args['order'] ? $args['order'] : 'info_startdate', |
||
1519 | 'sort' => $args['sort'] ? $args['sort'] : ($do_events ? 'ASC' : 'DESC'), |
||
1520 | 'filter'=> "user$user".($do_events ? 'date' : 'opentoday').$date_wanted, |
||
1521 | 'start' => 0, |
||
1522 | ); |
||
1523 | if ($GLOBALS['egw_info']['user']['preferences']['infolog']['cal_show'] || $GLOBALS['egw_info']['user']['preferences']['infolog']['cal_show'] === '0') |
||
1524 | { |
||
1525 | $query['col_filter']['info_type'] = explode(',',$GLOBALS['egw_info']['user']['preferences']['infolog']['cal_show']); |
||
1526 | } |
||
1527 | elseif ($this->customfields && !$GLOBALS['egw_info']['user']['preferences']['infolog']['cal_show_custom']) |
||
1528 | { |
||
1529 | $query['col_filter']['info_type'] = array('task','phone','note','email'); |
||
1530 | } |
||
1531 | while ($infos = $this->search($query)) |
||
1532 | { |
||
1533 | foreach ($infos as $info) |
||
1534 | { |
||
1535 | $start = new Api\DateTime($info['info_startdate'],Api\DateTime::$user_timezone); |
||
1536 | $title = ($do_events ? $start->format(false).' ' : ''). |
||
1537 | $info['info_subject']; |
||
1538 | $view = Link::view('infolog',$info['info_id']); |
||
1539 | $size = null; |
||
1540 | $edit = Link::edit('infolog',$info['info_id'], $size); |
||
1541 | $edit['size'] = $size; |
||
1542 | $content=array(); |
||
1543 | $status = $this->status[$info['info_type']][$info['info_status']]; |
||
1544 | $icons = array(); |
||
1545 | foreach(array( |
||
1546 | $info['info_type'] => 'navbar', |
||
1547 | $status => 'status' |
||
1548 | ) as $icon => $default) |
||
1549 | { |
||
1550 | $icons[Api\Image::find('infolog',$icon) ? $icon : $default] = $icon; |
||
1551 | } |
||
1552 | $content[] = Api\Html::a_href($title,$view); |
||
1553 | $html = Api\Html::table(array(1 => $content)); |
||
1554 | |||
1555 | $to_include[] = array( |
||
1556 | 'starttime' => $info['info_startdate'], |
||
1557 | 'endtime' => ($info['info_enddate'] ? $info['info_enddate'] : $info['info_startdate']), |
||
1558 | 'title' => $title, |
||
1559 | 'view' => $view, |
||
1560 | 'edit' => $edit, |
||
1561 | 'icons' => $icons, |
||
1562 | 'content' => $html, |
||
1563 | ); |
||
1564 | } |
||
1565 | if ($query['total'] <= ($query['start']+=count($infos))) |
||
1566 | { |
||
1567 | break; // no more availible |
||
1568 | } |
||
1569 | } |
||
1570 | //echo "boinfolog::cal_to_include("; print_r($args); echo ")<pre>"; print_r($to_include); echo "</pre>\n"; |
||
1571 | return $to_include; |
||
1572 | } |
||
1573 | |||
1574 | /** |
||
1575 | * Returm InfoLog (custom) information for projectmanager: status icon, type icon, css class |
||
1576 | * |
||
1577 | * @param array $args array with id's in $args['infolog'] |
||
1578 | * @return array with id => array with values for keys 'status', 'icon', 'class' |
||
1579 | */ |
||
1580 | function pm_icons($args) |
||
1581 | { |
||
1582 | if (isset($args['infolog']) && count($args['infolog'])) |
||
1583 | { |
||
1584 | $query = array( |
||
1585 | 'col_filter' => array('info_id' => $args['infolog']), |
||
1586 | 'subs' => true, |
||
1587 | 'cols' => 'main.info_id,info_type,info_status,info_percent,info_id_parent', |
||
1588 | ); |
||
1589 | $infos = array(); |
||
1590 | foreach($this->search($query) as $row) |
||
1591 | { |
||
1592 | $infos[$row['info_id']] = array( |
||
1593 | 'status' => $row['info_type'] != 'phone' && $row['info_status'] == 'ongoing' ? |
||
1594 | $row['info_percent'].'%' : 'infolog/'.$this->status[$row['info_type']][$row['info_status']], |
||
1595 | 'status_icon' => $row['info_type'] != 'phone' && $row['info_status'] == 'ongoing' ? |
||
1596 | 'ongoing' : 'infolog/'.$row['info_status'], |
||
1597 | 'class' => $row['info_id_parent'] ? 'infolog_rowHasParent' : null, |
||
1598 | ); |
||
1599 | if (Api\Image::find('infolog', $icon=$row['info_type'].'_element') || |
||
1600 | Api\Image::find('infolog', $icon=$row['info_type'])) |
||
1601 | { |
||
1602 | $infos[$row['info_id']]['icon'] = 'infolog/'.$icon; |
||
1603 | } |
||
1604 | } |
||
1605 | $anzSubs = $this->anzSubs(array_keys($infos)); |
||
1606 | if($anzSubs && is_array($anzSubs)) |
||
1607 | { |
||
1608 | foreach($anzSubs as $info_id => $subs) |
||
1609 | { |
||
1610 | if ($subs) $infos[$info_id]['class'] .= ' infolog_rowHasSubs'; |
||
1611 | } |
||
1612 | } |
||
1613 | } |
||
1614 | return $infos; |
||
1615 | } |
||
1616 | |||
1617 | var $categories; |
||
1618 | |||
1619 | /** |
||
1620 | * Find existing categories in database by name or add categories that do not exist yet |
||
1621 | * currently used for ical/sif import |
||
1622 | * |
||
1623 | * @param array $catname_list names of the categories which should be found or added |
||
1624 | * @param int $info_id = -1 match against existing infolog and expand the returned category ids |
||
1625 | * by the ones the user normally does not see due to category permissions - used to preserve categories |
||
1626 | * @return array category ids (found, added and preserved categories) |
||
1627 | */ |
||
1628 | function find_or_add_categories($catname_list, $info_id=-1) |
||
1629 | { |
||
1630 | if (!is_object($this->categories)) |
||
1631 | { |
||
1632 | $this->categories = new Api\Categories($this->user,'infolog'); |
||
1633 | } |
||
1634 | $old_cats_preserve = array(); |
||
1635 | if ($info_id && $info_id > 0) |
||
1636 | { |
||
1637 | // preserve Api\Categories without users read access |
||
1638 | $old_infolog = $this->read($info_id); |
||
1639 | $old_categories = explode(',',$old_infolog['info_cat']); |
||
1640 | if (is_array($old_categories) && count($old_categories) > 0) |
||
1641 | { |
||
1642 | foreach ($old_categories as $cat_id) |
||
1643 | { |
||
1644 | if ($cat_id && !$this->categories->check_perms(Acl::READ, $cat_id)) |
||
1645 | { |
||
1646 | $old_cats_preserve[] = $cat_id; |
||
1647 | } |
||
1648 | } |
||
1649 | } |
||
1650 | } |
||
1651 | |||
1652 | $cat_id_list = array(); |
||
1653 | foreach ((array)$catname_list as $cat_name) |
||
1654 | { |
||
1655 | $cat_name = trim($cat_name); |
||
1656 | $cat_id = $this->categories->name2id($cat_name, 'X-'); |
||
1657 | |||
1658 | if (!$cat_id) |
||
1659 | { |
||
1660 | // some SyncML clients (mostly phones) add an X- to the category names |
||
1661 | if (strncmp($cat_name, 'X-', 2) == 0) |
||
1662 | { |
||
1663 | $cat_name = substr($cat_name, 2); |
||
1664 | } |
||
1665 | $cat_id = $this->categories->add(array('name' => $cat_name, 'descr' => $cat_name, 'access' => 'private')); |
||
1666 | } |
||
1667 | |||
1668 | if ($cat_id) |
||
1669 | { |
||
1670 | $cat_id_list[] = $cat_id; |
||
1671 | } |
||
1672 | } |
||
1673 | |||
1674 | if (count($old_cats_preserve) > 0) |
||
1675 | { |
||
1676 | $cat_id_list = array_merge($old_cats_preserve, $cat_id_list); |
||
1677 | } |
||
1678 | |||
1679 | if (count($cat_id_list) > 1) |
||
1680 | { |
||
1681 | $cat_id_list = array_unique($cat_id_list); |
||
1682 | // disable sorting until infolog supports multiple categories |
||
1683 | // to make sure that the preserved category takes precedence over a new one from the client |
||
1684 | /* sort($cat_id_list, SORT_NUMERIC); */ |
||
1685 | } |
||
1686 | |||
1687 | return $cat_id_list; |
||
1688 | } |
||
1689 | |||
1690 | /** |
||
1691 | * Get names for categories specified by their id's |
||
1692 | * |
||
1693 | * @param array|string $cat_id_list array or comma-sparated list of id's |
||
1694 | * @return array with names |
||
1695 | */ |
||
1696 | function get_categories($cat_id_list) |
||
1718 | } |
||
1719 | |||
1720 | /** |
||
1721 | * Send all async infolog notification |
||
1722 | * |
||
1723 | * Called via the async service job 'infolog-async-notification' |
||
1724 | */ |
||
1725 | function async_notification() |
||
1726 | { |
||
1727 | if (!($users = $this->so->users_with_open_entries())) |
||
1728 | { |
||
1729 | return; |
||
1730 | } |
||
1731 | //error_log(__METHOD__."() users with open entries: ".implode(', ',$users)); |
||
1732 | |||
1733 | $save_account_id = $GLOBALS['egw_info']['user']['account_id']; |
||
1734 | $save_prefs = $GLOBALS['egw_info']['user']['preferences']; |
||
1735 | foreach($users as $user) |
||
1736 | { |
||
1737 | if (!($email = $GLOBALS['egw']->accounts->id2name($user,'account_email'))) continue; |
||
1738 | // create the enviroment for $user |
||
1739 | $this->user = $GLOBALS['egw_info']['user']['account_id'] = $user; |
||
1740 | $GLOBALS['egw']->preferences->__construct($user); |
||
1741 | $GLOBALS['egw_info']['user']['preferences'] = $GLOBALS['egw']->preferences->read_repository(); |
||
1742 | $GLOBALS['egw']->acl->__construct($user); |
||
1743 | $this->grants = $GLOBALS['egw']->acl->get_grants('infolog',$this->group_owners ? $this->group_owners : true); |
||
1744 | $this->so = new infolog_so($this->grants); // so caches it's filters |
||
1745 | |||
1746 | $notified_info_ids = array(); |
||
1747 | foreach(array( |
||
1748 | 'notify_due_responsible' => 'open-responsible-enddate', |
||
1749 | 'notify_due_delegated' => 'open-delegated-enddate', |
||
1750 | 'notify_start_responsible' => 'open-responsible-date', |
||
1751 | 'notify_start_delegated' => 'open-delegated-date', |
||
1752 | ) as $pref => $filter) |
||
1753 | { |
||
1754 | if (!($pref_value = $GLOBALS['egw_info']['user']['preferences']['infolog'][$pref])) continue; |
||
1755 | |||
1756 | $filter .= date('Y-m-d',time()+24*60*60*(int)$pref_value); |
||
1757 | //error_log(__METHOD__."() checking with filter '$filter' ($pref_value) for user $user ($email)"); |
||
1758 | |||
1759 | $params = array('filter' => $filter, 'custom_fields' => true, 'subs' => true); |
||
1760 | foreach($this->so->search($params) as $info) |
||
1761 | { |
||
1762 | // check if we already send a notification for that infolog entry, eg. starting and due on same day |
||
1763 | if (in_array($info['info_id'],$notified_info_ids)) continue; |
||
1764 | |||
1765 | if (is_null($this->tracking) || $this->tracking->user != $user) |
||
1766 | { |
||
1767 | $this->tracking = new infolog_tracking($this); |
||
1768 | } |
||
1769 | switch($pref) |
||
1770 | { |
||
1771 | case 'notify_due_responsible': |
||
1772 | $info['prefix'] = lang('Due %1',$this->enums['type'][$info['info_type']]); |
||
1773 | $info['message'] = lang('%1 you are responsible for is due at %2',$this->enums['type'][$info['info_type']], |
||
1774 | $this->tracking->datetime($info['info_enddate'],false)); |
||
1775 | break; |
||
1776 | case 'notify_due_delegated': |
||
1777 | $info['prefix'] = lang('Due %1',$this->enums['type'][$info['info_type']]); |
||
1778 | $info['message'] = lang('%1 you delegated is due at %2',$this->enums['type'][$info['info_type']], |
||
1779 | $this->tracking->datetime($info['info_enddate'],false)); |
||
1780 | break; |
||
1781 | case 'notify_start_responsible': |
||
1782 | $info['prefix'] = lang('Starting %1',$this->enums['type'][$info['info_type']]); |
||
1783 | $info['message'] = lang('%1 you are responsible for is starting at %2',$this->enums['type'][$info['info_type']], |
||
1784 | $this->tracking->datetime($info['info_startdate'],null)); |
||
1785 | break; |
||
1786 | case 'notify_start_delegated': |
||
1787 | $info['prefix'] = lang('Starting %1',$this->enums['type'][$info['info_type']]); |
||
1788 | $info['message'] = lang('%1 you delegated is starting at %2',$this->enums['type'][$info['info_type']], |
||
1789 | $this->tracking->datetime($info['info_startdate'],null)); |
||
1790 | break; |
||
1791 | } |
||
1792 | //error_log("notifiying $user($email) about $info[info_subject]: $info[message]"); |
||
1793 | $this->tracking->send_notification($info,null,$email,$user,$pref); |
||
1794 | |||
1795 | $notified_info_ids[] = $info['info_id']; |
||
1796 | } |
||
1797 | } |
||
1798 | } |
||
1799 | |||
1800 | $GLOBALS['egw_info']['user']['account_id'] = $save_account_id; |
||
1801 | $GLOBALS['egw_info']['user']['preferences'] = $save_prefs; |
||
1802 | } |
||
1803 | |||
1804 | /** conversion of infolog status to vtodo status |
||
1805 | * @private |
||
1806 | * @var array |
||
1807 | */ |
||
1808 | var $_status2vtodo = array( |
||
1809 | 'offer' => 'NEEDS-ACTION', |
||
1810 | 'not-started' => 'NEEDS-ACTION', |
||
1811 | 'ongoing' => 'IN-PROCESS', |
||
1812 | 'done' => 'COMPLETED', |
||
1813 | 'cancelled' => 'CANCELLED', |
||
1814 | 'billed' => 'COMPLETED', |
||
1815 | 'template' => 'CANCELLED', |
||
1816 | 'nonactive' => 'CANCELLED', |
||
1817 | 'archive' => 'CANCELLED', |
||
1818 | 'deferred' => 'NEEDS-ACTION', |
||
1819 | 'waiting' => 'IN-PROCESS', |
||
1820 | ); |
||
1821 | |||
1822 | /** conversion of vtodo status to infolog status |
||
1823 | * @private |
||
1824 | * @var array |
||
1825 | */ |
||
1826 | var $_vtodo2status = array( |
||
1827 | 'NEEDS-ACTION' => 'not-started', |
||
1828 | 'NEEDS ACTION' => 'not-started', |
||
1829 | 'IN-PROCESS' => 'ongoing', |
||
1830 | 'IN PROCESS' => 'ongoing', |
||
1831 | 'COMPLETED' => 'done', |
||
1832 | 'CANCELLED' => 'cancelled', |
||
1833 | ); |
||
1834 | |||
1835 | /** |
||
1836 | * Converts an infolog status into a vtodo status |
||
1837 | * |
||
1838 | * @param string $status see $this->status |
||
1839 | * @return string {CANCELLED|NEEDS-ACTION|COMPLETED|IN-PROCESS} |
||
1840 | */ |
||
1841 | function status2vtodo($status) |
||
1842 | { |
||
1843 | return isset($this->_status2vtodo[$status]) ? $this->_status2vtodo[$status] : 'NEEDS-ACTION'; |
||
1844 | } |
||
1845 | |||
1846 | /** |
||
1847 | * Converts a vtodo status into an infolog status using the optional X-INFOLOG-STATUS |
||
1848 | * |
||
1849 | * X-INFOLOG-STATUS is only used, if translated to the vtodo-status gives the identical vtodo status |
||
1850 | * --> the user did not changed it |
||
1851 | * |
||
1852 | * @param string $_vtodo_status {CANCELLED|NEEDS-ACTION|COMPLETED|IN-PROCESS} |
||
1853 | * @param string $x_infolog_status preserved original infolog status |
||
1854 | * @return string |
||
1855 | */ |
||
1856 | function vtodo2status($_vtodo_status,$x_infolog_status=null) |
||
1857 | { |
||
1858 | $vtodo_status = strtoupper($_vtodo_status); |
||
1859 | |||
1860 | if ($x_infolog_status && $this->status2vtodo($x_infolog_status) == $vtodo_status) |
||
1861 | { |
||
1862 | $status = $x_infolog_status; |
||
1863 | } |
||
1864 | else |
||
1865 | { |
||
1866 | $status = isset($this->_vtodo2status[$vtodo_status]) ? $this->_vtodo2status[$vtodo_status] : 'not-started'; |
||
1867 | } |
||
1868 | return $status; |
||
1869 | } |
||
1870 | |||
1871 | /** |
||
1872 | * Get status of a single or all types |
||
1873 | * |
||
1874 | * As status value can have different translations depending on type, we list all translations |
||
1875 | * |
||
1876 | * @param string $type = null |
||
1877 | * @param array &$icons = null on return name of icons |
||
1878 | * @return array value => (commaseparated) translations |
||
1879 | */ |
||
1880 | function get_status($type=null, array &$icons=null) |
||
1881 | { |
||
1882 | // if filtered by type, show only the stati of the filtered type |
||
1883 | if ($type && isset($this->status[$type])) |
||
1884 | { |
||
1885 | $statis = $icons = $this->status[$type]; |
||
1886 | } |
||
1887 | else // show all stati |
||
1888 | { |
||
1889 | $statis = $icons = array(); |
||
1890 | foreach($this->status as $t => $stati) |
||
1891 | { |
||
1892 | if ($t === 'defaults') continue; |
||
1893 | foreach($stati as $val => $label) |
||
1894 | { |
||
1895 | $statis[$val][$label] = lang($label); |
||
1896 | if (!isset($icons[$val])) $icons[$val] = $label; |
||
1897 | } |
||
1898 | } |
||
1899 | foreach($statis as $val => &$labels) |
||
1900 | { |
||
1901 | $labels = implode(', ', $labels); |
||
1902 | } |
||
1903 | } |
||
1904 | return $statis; |
||
1905 | } |
||
1906 | |||
1907 | /** |
||
1908 | * Activates an InfoLog entry (setting it's status from template or inactive depending on the completed percentage) |
||
1909 | * |
||
1910 | * @param array $info |
||
1911 | * @return string new status |
||
1912 | */ |
||
1913 | function activate($info) |
||
1914 | { |
||
1915 | switch((int)$info['info_percent']) |
||
1916 | { |
||
1917 | case 0: return 'not-started'; |
||
1918 | case 100: return 'done'; |
||
1919 | } |
||
1920 | return 'ongoing'; |
||
1921 | } |
||
1922 | |||
1923 | /** |
||
1924 | * Get the Parent ID of an InfoLog entry |
||
1925 | * |
||
1926 | * @param string $_guid |
||
1927 | * @return string parentID |
||
1928 | */ |
||
1929 | function getParentID($_guid) |
||
1940 | } |
||
1941 | |||
1942 | /** |
||
1943 | * Try to find a matching db entry |
||
1944 | * This expects timestamps to be in server-time. |
||
1945 | * |
||
1946 | * @param array $infoData the infolog data we try to find |
||
1947 | * @param boolean $relax = false if asked to relax, we only match against some key fields |
||
1948 | * @param string $tzid = null timezone, null => user time |
||
1949 | * |
||
1950 | * @return array of infolog_ids of matching entries |
||
1951 | */ |
||
1952 | function findInfo($infoData, $relax=false, $tzid=null) |
||
2243 | } |
||
2244 | } |
||
2245 |