1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* EGroupware - InfoLog - Business object |
4
|
|
|
* |
5
|
|
|
* @link http://www.egroupware.org |
6
|
|
|
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de> |
7
|
|
|
* @author Joerg Lehrke <[email protected]> |
8
|
|
|
* @package infolog |
9
|
|
|
* @copyright (c) 2003-17 by Ralf Becker <RalfBecker-AT-outdoor-training.de> |
10
|
|
|
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License |
11
|
|
|
*/ |
12
|
|
|
|
13
|
|
|
use EGroupware\Api; |
14
|
|
|
use EGroupware\Api\Link; |
15
|
|
|
use EGroupware\Api\Acl; |
16
|
|
|
use EGroupware\Api\Vfs; |
17
|
|
|
|
18
|
|
|
/** |
19
|
|
|
* This class is the BO-layer of InfoLog |
20
|
|
|
*/ |
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) |
1697
|
|
|
{ |
1698
|
|
|
if (!is_object($this->categories)) |
1699
|
|
|
{ |
1700
|
|
|
$this->categories = new Api\Categories($this->user,'infolog'); |
1701
|
|
|
} |
1702
|
|
|
|
1703
|
|
|
if (!is_array($cat_id_list)) |
1704
|
|
|
{ |
1705
|
|
|
$cat_id_list = explode(',',$cat_id_list); |
1706
|
|
|
} |
1707
|
|
|
$cat_list = array(); |
1708
|
|
|
foreach($cat_id_list as $cat_id) |
1709
|
|
|
{ |
1710
|
|
|
if ($cat_id && $this->categories->check_perms(Acl::READ, $cat_id) && |
1711
|
|
|
($cat_name = $this->categories->id2name($cat_id)) && $cat_name != '--') |
1712
|
|
|
{ |
1713
|
|
|
$cat_list[] = $cat_name; |
1714
|
|
|
} |
1715
|
|
|
} |
1716
|
|
|
|
1717
|
|
|
return $cat_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) |
1930
|
|
|
{ |
1931
|
|
|
#Horde::logMessage("getParentID($_guid)", __FILE__, __LINE__, PEAR_LOG_DEBUG); |
1932
|
|
|
|
1933
|
|
|
$parentID = False; |
1934
|
|
|
$myfilter = array('col_filter' => array('info_uid'=>$_guid)) ; |
1935
|
|
|
if ($_guid && ($found=$this->search($myfilter)) && ($uidmatch = array_shift($found))) |
1936
|
|
|
{ |
1937
|
|
|
$parentID = $uidmatch['info_id']; |
1938
|
|
|
} |
1939
|
|
|
return $parentID; |
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) |
1953
|
|
|
{ |
1954
|
|
|
$foundInfoLogs = array(); |
1955
|
|
|
$filter = array(); |
1956
|
|
|
|
1957
|
|
|
if ($this->log) |
1958
|
|
|
{ |
1959
|
|
|
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__ |
1960
|
|
|
. '('. ($relax ? 'RELAX, ': 'EXACT, ') . $tzid . ')[InfoData]:' |
1961
|
|
|
. array2string($infoData)); |
1962
|
|
|
} |
1963
|
|
|
|
1964
|
|
|
if ($infoData['info_id'] |
1965
|
|
|
&& ($egwData = $this->read($infoData['info_id'], true, 'server'))) |
1966
|
|
|
{ |
1967
|
|
|
// we only do a simple consistency check |
1968
|
|
|
if (!$relax || strpos($egwData['info_subject'], $infoData['info_subject']) === 0) |
1969
|
|
|
{ |
1970
|
|
|
return array($egwData['info_id']); |
1971
|
|
|
} |
1972
|
|
|
if (!$relax) return array(); |
|
|
|
|
1973
|
|
|
} |
1974
|
|
|
unset($infoData['info_id']); |
1975
|
|
|
|
1976
|
|
|
if (!$relax && !empty($infoData['info_uid'])) |
1977
|
|
|
{ |
1978
|
|
|
$filter = array('col_filter' => array('info_uid' => $infoData['info_uid'])); |
1979
|
|
|
foreach($this->so->search($filter) as $egwData) |
1980
|
|
|
{ |
1981
|
|
|
if (!$this->check_access($egwData,Acl::READ)) continue; |
1982
|
|
|
$foundInfoLogs[$egwData['info_id']] = $egwData['info_id']; |
1983
|
|
|
} |
1984
|
|
|
return $foundInfoLogs; |
1985
|
|
|
} |
1986
|
|
|
unset($infoData['info_uid']); |
1987
|
|
|
|
1988
|
|
|
if (empty($infoData['info_des'])) |
1989
|
|
|
{ |
1990
|
|
|
$description = false; |
1991
|
|
|
} |
1992
|
|
|
else |
1993
|
|
|
{ |
1994
|
|
|
// ignore meta information appendices |
1995
|
|
|
$description = trim(preg_replace('/\s*\[[A-Z_]+:.*\].*/im', '', $infoData['info_des'])); |
1996
|
|
|
$text = trim(preg_replace('/\s*\[[A-Z_]+:.*\]/im', '', $infoData['info_des'])); |
1997
|
|
|
if ($this->log) |
1998
|
|
|
{ |
1999
|
|
|
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__ |
2000
|
|
|
. "()[description]: $description"); |
2001
|
|
|
} |
2002
|
|
|
// Avoid quotation problems |
2003
|
|
|
$matches = null; |
2004
|
|
|
if (preg_match_all('/[\x20-\x7F]*/m', $text, $matches, PREG_SET_ORDER)) |
2005
|
|
|
{ |
2006
|
|
|
$text = ''; |
2007
|
|
|
foreach ($matches as $chunk) |
2008
|
|
|
{ |
2009
|
|
|
if (strlen($text) < strlen($chunk[0])) |
2010
|
|
|
{ |
2011
|
|
|
$text = $chunk[0]; |
2012
|
|
|
} |
2013
|
|
|
} |
2014
|
|
|
if ($this->log) |
2015
|
|
|
{ |
2016
|
|
|
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__ |
2017
|
|
|
. "()[search]: $text"); |
2018
|
|
|
} |
2019
|
|
|
$filter['search'] = $text; |
2020
|
|
|
} |
2021
|
|
|
} |
2022
|
|
|
$this->time2time($infoData, $tzid, false); |
2023
|
|
|
|
2024
|
|
|
$filter['col_filter'] = $infoData; |
2025
|
|
|
// priority does not need to match |
2026
|
|
|
unset($filter['col_filter']['info_priority']); |
2027
|
|
|
// we ignore description and location first |
2028
|
|
|
unset($filter['col_filter']['info_des']); |
2029
|
|
|
unset($filter['col_filter']['info_location']); |
2030
|
|
|
|
2031
|
|
|
foreach ($this->so->search($filter) as $itemID => $egwData) |
2032
|
|
|
{ |
2033
|
|
|
if (!$this->check_access($egwData,Acl::READ)) continue; |
2034
|
|
|
|
2035
|
|
|
switch ($infoData['info_type']) |
2036
|
|
|
{ |
2037
|
|
|
case 'task': |
2038
|
|
|
if (!empty($egwData['info_location'])) |
2039
|
|
|
{ |
2040
|
|
|
$egwData['info_location'] = str_replace("\r\n", "\n", $egwData['info_location']); |
2041
|
|
|
} |
2042
|
|
|
if (!$relax && |
2043
|
|
|
!empty($infoData['info_location']) && (empty($egwData['info_location']) |
2044
|
|
|
|| strpos($egwData['info_location'], $infoData['info_location']) !== 0)) |
2045
|
|
|
{ |
2046
|
|
|
if ($this->log) |
2047
|
|
|
{ |
2048
|
|
|
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__ |
2049
|
|
|
. '()[location mismatch]: ' |
2050
|
|
|
. $infoData['info_location'] . ' <> ' . $egwData['info_location']); |
2051
|
|
|
} |
2052
|
|
|
continue 2; // +1 for switch |
2053
|
|
|
} |
2054
|
|
|
default: |
2055
|
|
|
if (!empty($egwData['info_des'])) |
2056
|
|
|
{ |
2057
|
|
|
$egwData['info_des'] = str_replace("\r\n", "\n", $egwData['info_des']); |
2058
|
|
|
} |
2059
|
|
|
if (!$relax && ($description && empty($egwData['info_des']) |
2060
|
|
|
|| !empty($egwData['info_des']) && empty($infoData['info_des']) |
2061
|
|
|
|| strpos($egwData['info_des'], $description) === false)) |
2062
|
|
|
{ |
2063
|
|
|
if ($this->log) |
2064
|
|
|
{ |
2065
|
|
|
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__ |
2066
|
|
|
. '()[description mismatch]: ' |
2067
|
|
|
. $infoData['info_des'] . ' <> ' . $egwData['info_des']); |
2068
|
|
|
} |
2069
|
|
|
continue 2; // +1 for switch |
2070
|
|
|
} |
2071
|
|
|
// no further criteria to match |
2072
|
|
|
$foundInfoLogs[$egwData['info_id']] = $egwData['info_id']; |
2073
|
|
|
} |
2074
|
|
|
} |
2075
|
|
|
|
2076
|
|
|
if (!$relax && !empty($foundInfoLogs)) |
2077
|
|
|
{ |
2078
|
|
|
if ($this->log) |
2079
|
|
|
{ |
2080
|
|
|
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__ |
2081
|
|
|
. '()[FOUND]:' . array2string($foundInfoLogs)); |
2082
|
|
|
} |
2083
|
|
|
return $foundInfoLogs; |
2084
|
|
|
} |
2085
|
|
|
|
2086
|
|
|
if ($relax) |
2087
|
|
|
{ |
2088
|
|
|
unset($filter['search']); |
2089
|
|
|
} |
2090
|
|
|
|
2091
|
|
|
// search for matches by date only |
2092
|
|
|
unset($filter['col_filter']['info_startdate']); |
2093
|
|
|
unset($filter['col_filter']['info_enddate']); |
2094
|
|
|
unset($filter['col_filter']['info_datecompleted']); |
2095
|
|
|
// Some devices support lesser stati |
2096
|
|
|
unset($filter['col_filter']['info_status']); |
2097
|
|
|
|
2098
|
|
|
// try tasks without category |
2099
|
|
|
unset($filter['col_filter']['info_cat']); |
2100
|
|
|
|
2101
|
|
|
// Horde::logMessage("findVTODO Filter\n" |
2102
|
|
|
// . print_r($filter, true), |
2103
|
|
|
// __FILE__, __LINE__, PEAR_LOG_DEBUG); |
2104
|
|
|
foreach ($this->so->search($filter) as $itemID => $egwData) |
2105
|
|
|
{ |
2106
|
|
|
if (!$this->check_access($egwData,Acl::READ)) continue; |
2107
|
|
|
// Horde::logMessage("findVTODO Trying\n" |
2108
|
|
|
// . print_r($egwData, true), |
2109
|
|
|
// __FILE__, __LINE__, PEAR_LOG_DEBUG); |
2110
|
|
|
if (isset($infoData['info_cat']) |
2111
|
|
|
&& isset($egwData['info_cat']) && $egwData['info_cat'] |
2112
|
|
|
&& $infoData['info_cat'] != $egwData['info_cat']) |
2113
|
|
|
{ |
2114
|
|
|
if ($this->log) |
2115
|
|
|
{ |
2116
|
|
|
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__ |
2117
|
|
|
. '()[category mismatch]: ' |
2118
|
|
|
. $infoData['info_cat'] . ' <> ' . $egwData['info_cat']); |
2119
|
|
|
} |
2120
|
|
|
continue; |
2121
|
|
|
} |
2122
|
|
|
if (isset($infoData['info_startdate']) && $infoData['info_startdate']) |
2123
|
|
|
{ |
2124
|
|
|
// We got a startdate from client |
2125
|
|
|
if (isset($egwData['info_startdate']) && $egwData['info_startdate']) |
2126
|
|
|
{ |
2127
|
|
|
// We compare the date only |
2128
|
|
|
$taskTime = new Api\DateTime($infoData['info_startdate'],Api\DateTime::$server_timezone); |
2129
|
|
|
$egwTime = new Api\DateTime($egwData['info_startdate'],Api\DateTime::$server_timezone); |
2130
|
|
|
if ($taskTime->format('Ymd') != $egwTime->format('Ymd')) |
2131
|
|
|
{ |
2132
|
|
|
if ($this->log) |
2133
|
|
|
{ |
2134
|
|
|
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__ |
2135
|
|
|
. '()[start mismatch]: ' |
2136
|
|
|
. $taskTime->format('Ymd') . ' <> ' . $egwTime->format('Ymd')); |
2137
|
|
|
} |
2138
|
|
|
continue; |
2139
|
|
|
} |
2140
|
|
|
} |
2141
|
|
|
elseif (!$relax) |
2142
|
|
|
{ |
2143
|
|
|
if ($this->log) |
2144
|
|
|
{ |
2145
|
|
|
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__ |
2146
|
|
|
. '()[start mismatch]'); |
2147
|
|
|
} |
2148
|
|
|
continue; |
2149
|
|
|
} |
2150
|
|
|
} |
2151
|
|
|
if ($infoData['info_type'] == 'task') |
2152
|
|
|
{ |
2153
|
|
|
if (isset($infoData['info_status']) && isset($egwData['info_status']) |
|
|
|
|
2154
|
|
|
&& $egwData['info_status'] == 'done' |
2155
|
|
|
&& $infoData['info_status'] != 'done' || |
2156
|
|
|
$egwData['info_status'] != 'done' |
2157
|
|
|
&& $infoData['info_status'] == 'done') |
2158
|
|
|
{ |
2159
|
|
|
if ($this->log) |
2160
|
|
|
{ |
2161
|
|
|
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__ |
2162
|
|
|
. '()[status mismatch]: ' |
2163
|
|
|
. $infoData['info_status'] . ' <> ' . $egwData['info_status']); |
2164
|
|
|
} |
2165
|
|
|
continue; |
2166
|
|
|
} |
2167
|
|
|
if (isset($infoData['info_enddate']) && $infoData['info_enddate']) |
2168
|
|
|
{ |
2169
|
|
|
// We got a enddate from client |
2170
|
|
|
if (isset($egwData['info_enddate']) && $egwData['info_enddate']) |
2171
|
|
|
{ |
2172
|
|
|
// We compare the date only |
2173
|
|
|
$taskTime = new Api\DateTime($infoData['info_enddate'],Api\DateTime::$server_timezone); |
2174
|
|
|
$egwTime = new Api\DateTime($egwData['info_enddate'],Api\DateTime::$server_timezone); |
2175
|
|
|
if ($taskTime->format('Ymd') != $egwTime->format('Ymd')) |
2176
|
|
|
{ |
2177
|
|
|
if ($this->log) |
2178
|
|
|
{ |
2179
|
|
|
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__ |
2180
|
|
|
. '()[DUE mismatch]: ' |
2181
|
|
|
. $taskTime->format('Ymd') . ' <> ' . $egwTime->format('Ymd')); |
2182
|
|
|
} |
2183
|
|
|
continue; |
2184
|
|
|
} |
2185
|
|
|
} |
2186
|
|
|
elseif (!$relax) |
2187
|
|
|
{ |
2188
|
|
|
if ($this->log) |
2189
|
|
|
{ |
2190
|
|
|
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__ |
2191
|
|
|
. '()[DUE mismatch]'); |
2192
|
|
|
} |
2193
|
|
|
continue; |
2194
|
|
|
} |
2195
|
|
|
} |
2196
|
|
|
if (isset($infoData['info_datecompleted']) && $infoData['info_datecompleted']) |
2197
|
|
|
{ |
2198
|
|
|
// We got a completed date from client |
2199
|
|
|
if (isset($egwData['info_datecompleted']) && $egwData['info_datecompleted']) |
2200
|
|
|
{ |
2201
|
|
|
// We compare the date only |
2202
|
|
|
$taskTime = new Api\DateTime($infoData['info_datecompleted'],Api\DateTime::$server_timezone); |
2203
|
|
|
$egwTime = new Api\DateTime($egwData['info_datecompleted'],Api\DateTime::$server_timezone); |
2204
|
|
|
if ($taskTime->format('Ymd') != $egwTime->format('Ymd')) |
2205
|
|
|
{ |
2206
|
|
|
if ($this->log) |
2207
|
|
|
{ |
2208
|
|
|
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__ |
2209
|
|
|
. '()[completed mismatch]: ' |
2210
|
|
|
. $taskTime->format('Ymd') . ' <> ' . $egwTime->format('Ymd')); |
2211
|
|
|
} |
2212
|
|
|
continue; |
2213
|
|
|
} |
2214
|
|
|
} |
2215
|
|
|
elseif (!$relax) |
2216
|
|
|
{ |
2217
|
|
|
if ($this->log) |
2218
|
|
|
{ |
2219
|
|
|
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__ |
2220
|
|
|
. '()[completed mismatch]'); |
2221
|
|
|
} |
2222
|
|
|
continue; |
2223
|
|
|
} |
2224
|
|
|
} |
2225
|
|
|
elseif (!$relax && isset($egwData['info_datecompleted']) && $egwData['info_datecompleted']) |
2226
|
|
|
{ |
2227
|
|
|
if ($this->log) |
2228
|
|
|
{ |
2229
|
|
|
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__ |
2230
|
|
|
. '()[completed mismatch]'); |
2231
|
|
|
} |
2232
|
|
|
continue; |
2233
|
|
|
} |
2234
|
|
|
} |
2235
|
|
|
$foundInfoLogs[$itemID] = $itemID; |
2236
|
|
|
} |
2237
|
|
|
if ($this->log) |
2238
|
|
|
{ |
2239
|
|
|
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__ |
2240
|
|
|
. '()[FOUND]:' . array2string($foundInfoLogs)); |
2241
|
|
|
} |
2242
|
|
|
return $foundInfoLogs; |
2243
|
|
|
} |
2244
|
|
|
} |
2245
|
|
|
|