Issues (4868)

infolog/inc/class.infolog_tracking.inc.php (8 issues)

1
<?php
2
/**
3
 * InfoLog - history and notifications
4
 *
5
 * @link http://www.egroupware.org
6
 * @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
7
 * @package tracker
8
 * @copyright (c) 2007-16 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
9
 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
10
 * @version $Id$
11
 */
12
13
use EGroupware\Api;
14
use EGroupware\Api\Link;
15
16
/**
17
 * Tracker - tracking object for the tracker
18
 */
19
class infolog_tracking extends Api\Storage\Tracking
20
{
21
	/**
22
	 * Application we are tracking (required!)
23
	 *
24
	 * @var string
25
	 */
26
	var $app = 'infolog';
27
	/**
28
	 * Name of the id-field, used as id in the history log (required!)
29
	 *
30
	 * @var string
31
	 */
32
	var $id_field = 'info_id';
33
	/**
34
	 * Name of the field with the creator id, if the creator of an entry should be notified
35
	 *
36
	 * @var string
37
	 */
38
	var $creator_field = 'info_owner';
39
	/**
40
	 * Name of the field with the id(s) of assinged users, if they should be notified
41
	 *
42
	 * @var string
43
	 */
44
	var $assigned_field = 'info_responsible';
45
	/**
46
	 * Translate field-names to 2-char history status
47
	 *
48
	 * @var array
49
	 */
50
	var $field2history = array(
51
		'info_type'          => 'Ty',
52
		'info_from'          => 'Fr',
53
		'info_link_id'       => 'Li',
54
		'info_id_parent'     => 'parent',
55
		'info_cat'           => 'Ca',
56
		'info_priority'      => 'Pr',
57
		'info_owner'         => 'Ow',
58
		'info_access'        => 'Ac',
59
		'info_status'        => 'St',
60
		'info_percent'       => 'Pe',
61
		'info_datecompleted' => 'Co',
62
		'info_location'      => 'Lo',
63
		'info_startdate'     => 'st',
64
		'info_enddate'       => 'En',
65
		'info_responsible'   => 'Re',
66
		'info_cc'            => 'cc',
67
		'info_subject'       => 'Su',
68
		'info_des'           => 'De',
69
		'info_location'      => 'Lo',
70
		// PM fields
71
		'info_planned_time'  => 'pT',
72
		'info_replanned_time'  => 'replanned',
73
		'info_used_time'     => 'uT',
74
		'pl_id'              => 'pL',
75
		'info_price'         => 'pr',
76
		// all custom fields together
77
		'custom'             => '#c',
78
	);
79
	/**
80
	 * Translate field-names to labels
81
	 *
82
	 * @note The order of these fields is used to determine the order for CSV export
83
	 * @var array
84
	 */
85
	var $field2label = array(
86
		'info_type'      => 'Type',
87
		'info_from'      => 'Contact',
88
		'info_subject'   => 'Subject',
89
		'info_des'       => 'Description',
90
		'info_link_id'   => 'primary link',
91
		'info_id_parent' => 'Parent',
92
		'info_cat'       => 'Category',
93
		'info_priority'  => 'Priority',
94
		'info_owner'     => 'Owner',
95
		'info_modifier'  => 'Modifier',
96
		'info_access'    => 'Access',
97
		'info_status'    => 'Status',
98
		'info_percent'   => 'Completed',
99
		'info_datecompleted' => 'Date completed',
100
		'info_datemodified' => 'Last changed',
101
		'info_location'  => 'Location',
102
		'info_startdate' => 'Start date',
103
		'info_enddate'   => 'Enddate',
104
		'info_responsible' => 'Responsible',
105
		'info_cc'        => 'Cc',
106
		// PM fields
107
		'info_planned_time'  => 'planned time',
108
		'info_replanned_time'  => 're-planned time',
109
		'info_used_time'     => 'used time',
110
		'pl_id'              => 'pricelist',
111
		'info_price'         => 'price',
112
		// custom fields
113
		'custom'             => 'custom fields'
114
	);
115
116
	/**
117
	 * Instance of the infolog_bo class calling us
118
	 *
119
	 * @var infolog_bo
120
	 */
121
	private $infolog;
122
123
	/**
124
	 * Constructor
125
	 *
126
	 * @param botracker $botracker
0 ignored issues
show
The type botracker was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
127
	 * @return tracker_tracking
0 ignored issues
show
The type tracker_tracking was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
128
	 */
129
	function __construct(&$infolog_bo)
130
	{
131
		parent::__construct('infolog');	// add custom fields from infolog
132
133
		$this->infolog =& $infolog_bo;
134
	}
135
136
	/**
137
	 * Get the subject for a given entry
138
	 *
139
	 * Reimpleneted to use a New|deleted|modified prefix.
140
	 *
141
	 * @param array $data
142
	 * @param array $old
143
	 * @return string
144
	 */
145
	function get_subject($data,$old)
146
	{
147
		if ($data['prefix'])
148
		{
149
			$prefix = $data['prefix'];	// async notification
150
		}
151
		elseif (!$old || $old['info_status'] == 'deleted')
0 ignored issues
show
Bug Best Practice introduced by
The expression $old 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...
152
		{
153
			$prefix = lang('New %1',lang($this->infolog->enums['type'][$data['info_type']]));
0 ignored issues
show
The call to lang() has too many arguments starting with lang($this->infolog->enu...'][$data['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

153
			$prefix = /** @scrutinizer ignore-call */ lang('New %1',lang($this->infolog->enums['type'][$data['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...
154
		}
155
		elseif($data['info_status'] == 'deleted')
156
		{
157
			$prefix = lang('%1 deleted',lang($this->infolog->enums['type'][$data['info_type']]));
158
		}
159
		else
160
		{
161
			$prefix = lang('%1 modified',lang($this->infolog->enums['type'][$data['info_type']]));
162
		}
163
		return $prefix.': '.$data['info_subject'];
164
	}
165
166
	/**
167
	 * Get the modified / new message (1. line of mail body) for a given entry, can be reimplemented
168
	 *
169
	 * @param array $data
170
	 * @param array $old
171
	 * @return string
172
	 */
173
	function get_message($data,$old)
174
	{
175
		if ($data['message']) return $data['message'];	// async notification
176
177
		if (!$old || $old['info_status'] == 'deleted')
0 ignored issues
show
Bug Best Practice introduced by
The expression $old 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...
178
		{
179
			return lang('New %1 created by %2 at %3',lang($this->infolog->enums['type'][$data['info_type']]),
0 ignored issues
show
The call to lang() has too many arguments starting with lang($this->infolog->enu...'][$data['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

179
			return /** @scrutinizer ignore-call */ lang('New %1 created by %2 at %3',lang($this->infolog->enums['type'][$data['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...
180
				Api\Accounts::username($this->infolog->user),$this->datetime('now'));
181
		}
182
		elseif($data['info_status'] == 'deleted')
183
		{
184
			return lang('%1 deleted by %2 at %3',lang($this->infolog->enums['type'][$data['info_type']]),
185
				Api\Accounts::username($data['info_modifier']),
186
				$this->datetime($data['info_datemodified']));
187
		}
188
		return lang('%1 modified by %2 at %3',lang($this->infolog->enums['type'][$data['info_type']]),
189
			Api\Accounts::username($data['info_modifier']),
190
			$this->datetime($data['info_datemodified']));
191
	}
192
193
	/**
194
	 * Get the details of an entry
195
	 *
196
	 * @param array|object $data
197
	 * @param int|string $receiver nummeric account_id or email address
198
	 * @return array of details as array with values for keys 'label','value','type'
199
	 */
200
	function get_details($data,$receiver=null)
201
	{
202
		//error_log(__METHOD__.__LINE__.' Data:'.array2string($data));
203
		$responsible = array();
204
		if ($data['info_responsible'])
205
		{
206
			foreach($data['info_responsible'] as $uid)
207
			{
208
				$responsible[] = Api\Accounts::username($uid);
209
			}
210
		}
211
		if ($GLOBALS['egw_info']['user']['preferences']['infolog']['show_id'])
212
		{
213
			$id = ' #'.$data['info_id'];
214
		}
215
		foreach(array(
216
			'info_type'      => lang($this->infolog->enums['type'][$data['info_type']]).$id,
217
			'info_from'      => $data['info_from'],
218
			'info_cat'       => $data['info_cat'] ? $GLOBALS['egw']->categories->id2name($data['info_cat']) : '',
219
			'info_priority'  => lang($this->infolog->enums['priority'][$data['info_priority']]),
220
			'info_owner'     => Api\Accounts::username($data['info_owner']),
221
			'info_status'    => lang($data['info_status']=='deleted'?'deleted':$this->infolog->status[$data['info_type']][$data['info_status']]),
222
			'info_percent'   => (int)$data['info_percent'].'%',
223
			'info_datecompleted' => $data['info_datecompleted'] ? $this->datetime($data['info_datecompleted']) : '',
224
			'info_location'  => $data['info_location'],
225
			'info_startdate' => $data['info_startdate'] ? $this->datetime($data['info_startdate'],null) : '',
226
			'info_enddate'   => $data['info_enddate'] ? $this->datetime($data['info_enddate'],null) : '',
227
			'info_responsible' => implode(', ',$responsible),
228
			'info_subject'   => $data['info_subject'],
229
		) as $name => $value)
230
		{
231
			//error_log(__METHOD__.__LINE__.' Key:'.$name.' val:'.array2string($value));
232
			if ($name=='info_from' && empty($value))
233
			{
234
				if(!empty($data['info_contact']) && is_array($data['link_to']['to_id']))
235
				{
236
					$lkeys = array_keys($data['link_to']['to_id']);
237
					if (in_array($data['info_contact'],$lkeys))
238
					{
239
						list($app,$id) = explode(':',$data['info_contact']);
240
						if (!empty($app)&&!empty($id)) $value = Link::title($app,$id);
241
					}
242
				}
243
				else if ($data['info_link_id'])
244
				{
245
					$this->infolog->link_id2from($data);
246
					if (is_array($data['info_contact'])) $value = $data['info_contact']['title'];
247
				}
248
			}
249
			$details[$name] = array(
250
				'label' => lang($this->field2label[$name]),
251
				'value' => $value,
252
			);
253
			if ($name == 'info_subject') $details[$name]['type'] = 'summary';
254
		}
255
		$details['info_des'] = array(
256
			'value' => $data['info_des'],
257
			'type'  => 'multiline',
258
		);
259
		// add custom fields for given type
260
		$details += $this->get_customfields($data, $data['info_type'], $receiver);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $details seems to be defined by a foreach iteration on line 215. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
261
		//error_log(__METHOD__."(".array2string($data).", $receiver) returning ".array2string($details));
262
		return $details;
263
	}
264
265
	/**
266
	 * Track changes
267
	 *
268
	 * Overrides parent to log the modified date in the history, but not to send a notification
269
	 *
270
	 * @param array $data current entry
271
	 * @param array $old = null old/last state of the entry or null for a new entry
272
	 * @param int $user = null user who made the changes, default to current user
273
	 * @param boolean $deleted = null can be set to true to let the tracking know the item got deleted or undeleted
274
	 * @param array $changed_fields = null changed fields from ealier call to $this->changed_fields($data,$old), to not compute it again
275
	 * @param boolean $skip_notification = false do NOT send any notification
276
	 * @return int|boolean false on error, integer number of changes logged or true for new entries ($old == null)
277
	 */
278
	public function track(array $data,array $old=null,$user=null,$deleted=null,array $changed_fields=null,$skip_notification=false)
279
	{
280
		//error_log(__METHOD__.__LINE__.' notify?'.($skip_notification?'no':'yes').function_backtrace());
281
		$this->user = !is_null($user) ? $user : $GLOBALS['egw_info']['user']['account_id'];
282
283
		$changes = true;
284
285
		if ($old && $this->field2history)
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->field2history 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...
286
		{
287
			$changes = $this->save_history($data,$old,$deleted,$changed_fields);
288
		}
289
290
		// Don't notify if the only change was to the modified date
291
		if(is_null($changed_fields))
292
		{
293
			$changed_fields = $this->changed_fields($data, $old);
294
			$changes = count($changed_fields); // we need that since TRUE evaluates to 1
295
		}
296
		//error_log(__METHOD__.__LINE__.array2string($changed_fields));
297
		if(is_array($changed_fields) && $changes == 1 && in_array('info_datemodified', $changed_fields))
298
		{
299
			return count($changes);
300
		}
301
302
		// do not run do_notifications if we have no changes
303
		if ($changes && !$skip_notification && !$this->do_notifications($data,$old,$deleted))
304
		{
305
			$changes = false;
306
		}
307
		return $changes;
308
	}
309
310
	/**
311
	 * Get a notification-config value
312
	 *
313
	 * @param string $name
314
	 *  - 'copy' array of email addresses notifications should be copied too, can depend on $data
315
	 *  - 'lang' string lang code for copy mail
316
	 *  - 'sender' string send email address
317
	 * @param array $data current entry
318
	 * @param array $old = null old/last state of the entry or null for a new entry
319
	 * @return mixed
320
	 */
321
	function get_config($name,$data,$old=null)
322
	{
323
		unset($old);	// not used, but required function signature
324
		switch($name)
325
		{
326
			case 'copy':	// include the info_cc addresses
327
				if ($data['info_access'] == 'private') return array();	// no copies for private entries
328
				if ($data['info_cc'])
329
				{
330
					$config = preg_split('/, ?/',$data['info_cc']);
331
				}
332
				else
333
				{
334
					$config = array();
335
				}
336
				break;
337
			case self::CUSTOM_NOTIFICATION:
338
				$info_config = Api\Config::read('infolog');
339
				if(!$info_config[self::CUSTOM_NOTIFICATION])
340
				{
341
					return '';
342
				}
343
				// Per-type notification
344
				$type_config = $info_config[self::CUSTOM_NOTIFICATION][$data['info_type']];
345
				$global = $info_config[self::CUSTOM_NOTIFICATION]['~global~'];
346
347
				// Disabled
348
				if(!$type_config['use_custom'] && !$global['use_custom']) return '';
349
350
				// Type or globabl
351
				$config = trim(strip_tags($type_config['message'])) != '' && $type_config['use_custom'] ? $type_config['message'] : $global['message'];
352
				break;
353
		}
354
		return $config;
355
	}
356
}
357