infolog_bo   F
last analyzed

Complexity

Total Complexity 505

Size/Duplication

Total Lines 2222
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 974
dl 0
loc 2222
rs 1.6258
c 0
b 0
f 0
wmc 505

34 Methods

Rating   Name   Duplication   Size   Complexity  
B get_categories() 0 22 8
F write() 0 317 117
D write_check_links() 0 106 32
A date2usertime() 0 5 3
A getParentID() 0 11 4
A has_customfields() 0 10 6
A file_access() 0 4 1
A status2vtodo() 0 3 2
D cal_to_include() 0 68 18
F check_access() 0 64 24
A vtodo2status() 0 13 4
A is_responsible() 0 3 1
D search() 0 72 22
C delete() 0 65 16
C read() 0 43 12
F findInfo() 0 291 83
A set_link_cache() 0 6 3
D link_id2from() 0 62 19
A link_titles() 0 14 3
A subject_from_des() 0 3 1
F __construct() 0 128 26
A getctag() 0 19 2
A link_query() 0 19 3
C async_notification() 0 77 15
A get_pm_id() 0 7 4
A init() 0 3 1
A anzSubs() 0 3 1
C pm_icons() 0 35 15
B time2time() 0 58 11
C import_mail() 0 70 17
C find_or_add_categories() 0 60 15
B get_status() 0 25 8
A link_title() 0 12 5
A activate() 0 8 3

How to fix   Complexity   

Complex Class

Complex classes like infolog_bo often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use infolog_bo, and based on these observations, apply Extract Interface, too.

1
<?php
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(
0 ignored issues
show
Bug Best Practice introduced by
The property stock_enums does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
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(
0 ignored issues
show
Bug Best Practice introduced by
The property stock_status does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
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'];
0 ignored issues
show
Bug Best Practice introduced by
The property allow_past_due_date does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
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)
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->customfields of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
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);
0 ignored issues
show
Unused Code introduced by
The assignment to $val is dead and can be removed.
Loading history...
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');
0 ignored issues
show
Documentation Bug introduced by
It seems like EGroupware\Api\DateTime:...2user($this->now, 'ts') of type EGroupware\Api\datetime is incompatible with the declared type integer of property $user_time_now.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
290
291
		$this->grants = $GLOBALS['egw']->acl->get_grants('infolog',$this->group_owners ? $this->group_owners : true);
0 ignored issues
show
Bug Best Practice introduced by
The property grants does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
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;
0 ignored issues
show
Bug Best Practice introduced by
The expression $user of type integer|null is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
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'];
0 ignored issues
show
introduced by
The condition is_array($pm_links) is always true.
Loading history...
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))
0 ignored issues
show
introduced by
The condition is_null($fromTZId) is always false.
Loading history...
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))
0 ignored issues
show
introduced by
The condition is_numeric($info_id) is always true.
Loading history...
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);
0 ignored issues
show
Bug Best Practice introduced by
The method infolog_bo::set_link_cache() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

650
			self::/** @scrutinizer ignore-call */ 
651
         set_link_cache($data);
Loading history...
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)
0 ignored issues
show
Unused Code introduced by
The call to infolog_so::read() has too many arguments starting with true. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

671
		if (($info = $this->so->/** @scrutinizer ignore-call */ read(array('info_id' => $info_id), true, 'server')) === False)

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
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']));
0 ignored issues
show
Unused Code introduced by
The call to lang() has too many arguments starting with lang($values['info_type']). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

897
							throw new Api\Exception\WrongUserinput(/** @scrutinizer ignore-call */ lang('For infolog type %1, %2 is required',lang($values['info_type']),$c_field['label']));

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
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')
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: (! $values['info_id'] &&...s['info_owner']) == 'g', Probably Intended Meaning: ! $values['info_id'] && ...['info_owner']) == 'g')
Loading history...
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'])
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: ($touch_modified && $tou...es['info_datemodified'], Probably Intended Meaning: $touch_modified && ($tou...s['info_datemodified'])
Loading history...
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);
0 ignored issues
show
Bug Best Practice introduced by
The method infolog_bo::set_link_cache() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1018
			self::/** @scrutinizer ignore-call */ 
1019
         set_link_cache($values);
Loading history...
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'] && !(
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: ($values['info_contact']...values['info_contact']), Probably Intended Meaning: $values['info_contact'] ...alues['info_contact']))
Loading history...
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(
0 ignored issues
show
Unused Code introduced by
The assignment to $info_link_id is dead and can be removed.
Loading history...
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
0 ignored issues
show
Documentation Bug introduced by
The doc comment column-name at position 0 could not be parsed: Unknown type name 'column-name' at position 0 in column-name.
Loading history...
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))
0 ignored issues
show
introduced by
The condition is_array($ret) is always true.
Loading history...
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);
0 ignored issues
show
Bug Best Practice introduced by
The method infolog_bo::set_link_cache() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1276
				self::/** @scrutinizer ignore-call */ 
1277
          set_link_cache($data);
Loading history...
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),
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $emails seems to be defined by a foreach iteration on line 1325. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
Comprehensibility Best Practice introduced by
The variable $names seems to be defined by a foreach iteration on line 1325. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
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']);
0 ignored issues
show
Unused Code introduced by
The call to lang() has too many arguments starting with $info['info_from']. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1363
			$info['msg'] = /** @scrutinizer ignore-call */ lang('Attention: No Contact with address %1 found.',$info['info_from']);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
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))
0 ignored issues
show
introduced by
The condition is_array($_attachments) is always true.
Loading history...
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(
0 ignored issues
show
Bug introduced by
$params = array('col_fil...ray('info_id' => $ids)) cannot be passed to infolog_bo::search() as the parameter $query expects a reference. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1425
		foreach ($this->search(/** @scrutinizer ignore-type */ $params=array(
Loading history...
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))
0 ignored issues
show
introduced by
The condition is_array($ids) is always true.
Loading history...
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'])
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->customfields of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
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);
0 ignored issues
show
Bug Best Practice introduced by
The property grants does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
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']]);
0 ignored issues
show
Unused Code introduced by
The call to lang() has too many arguments starting with $this->enums['type'][$info['info_type']]. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1772
							$info['prefix'] = /** @scrutinizer ignore-call */ lang('Due %1',$this->enums['type'][$info['info_type']]);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
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();
0 ignored issues
show
introduced by
The condition $relax is always true.
Loading history...
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'])
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: (IssetNode && IssetNode ...info_status'] == 'done', Probably Intended Meaning: IssetNode && IssetNode &...nfo_status'] == 'done')
Loading history...
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