infolog_ui   F
last analyzed

Complexity

Total Complexity 603

Size/Duplication

Total Lines 2623
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 1430
dl 0
loc 2623
rs 0.8
c 0
b 0
f 0
wmc 603

20 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 51 6
F get_info() 0 111 63
A get_validtypes() 0 16 4
A filter() 0 13 5
F get_rows() 0 273 91
B timesheet_set() 0 30 10
A cal_to_include() 0 10 2
C calendar_set() 0 80 17
F action() 0 253 53
C close() 0 41 12
D create_copy() 0 101 40
B icon() 0 18 9
D delete() 0 55 19
F edit() 0 540 160
F index() 0 271 71
C get_actions() 0 269 12
C admin() 0 126 12
B hook_view() 0 44 8
A mail_import() 0 15 3
B csv_export_fields() 0 42 6

How to fix   Complexity   

Complex Class

Complex classes like infolog_ui 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_ui, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * InfoLog - User interface
4
 *
5
 * @link http://www.egroupware.org
6
 * @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
7
 * @package infolog
8
 * @copyright (c) 2003-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
use EGroupware\Api\Framework;
16
use EGroupware\Api\Egw;
17
use EGroupware\Api\Acl;
18
use EGroupware\Api\Etemplate;
19
20
/**
21
 * This class is the UI-layer (user interface) of InfoLog
22
 */
23
class infolog_ui
24
{
25
	var $public_functions = array(
26
		'index'       => True,
27
		'edit'        => True,
28
		'delete'      => True,
29
		'close'       => True,
30
		'admin'       => True,
31
		'hook_view'   => True,
32
		'writeLangFile' => True,
33
		'mail_import' => True
34
	);
35
	/**
36
	 * reference to the infolog preferences of the user
37
	 *
38
	 * @var array
39
	 */
40
	var $prefs;
41
	/**
42
	 * instance of the bo-class
43
	 *
44
	 * @var infolog_bo
45
	 */
46
	var $bo;
47
	/**
48
	 * instance of the etemplate class
49
	 *
50
	 * @var Etemplate
51
	 */
52
	var $tmpl;
53
	/**
54
	 * allowed units and hours per day, can be overwritten by the projectmanager configuration, default all units, 8h
55
	 *
56
	 * @var string
57
	 */
58
	var $duration_format = ',';	// comma is necessary!
59
60
	var $icons = array(
61
		'type' => array(
62
			'task'      => 'task.gif',      'task_alt'      => 'Task',
63
			'phone'     => 'phone.gif',     'phone_alt'     => 'Phonecall',
64
			'note'      => 'note.gif',      'note_alt'      => 'Note',
65
			'confirm'   => 'confirm.gif',   'confirm_alt'   => 'Confirmation',
66
			'reject'    => 'reject.gif',    'reject_alt'    => 'Reject',
67
			'email'     => 'email.gif',     'email_alt'     => 'Email' ),
68
		'action' => array(
69
			'new'       => 'new.gif',       'new_alt'       => 'Add Sub',
70
			'view'      => 'view.gif',      'view_alt'      => 'View Subs',
71
			'parent'    => 'parent.gif',    'parent_alt'    => 'View other Subs',
72
			'edit'      => 'edit.gif',      'edit_alt'      => 'Edit',
73
			'addfile'   => 'addfile.gif',   'addfile_alt'   => 'Add a file',
74
			'delete'    => 'delete.gif',    'delete_alt'    => 'Delete',
75
			'close'     => 'done.gif',      'close_alt'     => 'Close' ,
76
			'close_all' => 'done_all.gif',  'close_all_alt' => 'Close' ),
77
		'status' => array(
78
			'billed'    => 'billed.gif',    'billed_alt'    => 'billed',
79
			'done'      => 'done.gif',      'done_alt'      => 'done',
80
			'will-call' => 'will-call.gif', 'will-call_alt' => 'will-call',
81
			'call'      => 'call.gif',      'call_alt'      => 'call',
82
			'ongoing'   => 'ongoing.gif',   'ongoing_alt'   => 'ongoing',
83
			'offer'     => 'offer.gif',     'offer_alt'     => 'offer' )
84
	);
85
	var $filters;
86
	var $messages = array(
87
		'edit'    => 'InfoLog - Edit',
88
		'add'     => 'InfoLog - New',
89
		'add_sub' => 'InfoLog - New Subproject',
90
		'sp'      => '- Subprojects from',
91
	);
92
93
	/**
94
	 * Constructor
95
	 *
96
	 * @return infolog_ui
97
	 */
98
	function __construct(Etemplate $etemplate = null)
99
	{
100
		if ($GLOBALS['egw_info']['flags']['currentapp'] != 'infolog') Api\Translation::add_app('infolog');
101
102
		// Make sure Global category is infolog - on first load, it may not be
103
		if($GLOBALS['egw_info']['flags']['currentapp'] == 'infolog' && !$GLOBALS['egw']->categories->app_name)
104
		{
105
			$GLOBALS['egw']->categories = new Api\Categories();
106
		}
107
108
		$this->bo = new infolog_bo();
109
110
		if($etemplate === null)
111
		{
112
			$etemplate = new Etemplate();
113
		}
114
		$this->tmpl = $etemplate;
115
116
		$this->user = $GLOBALS['egw_info']['user']['account_id'];
0 ignored issues
show
Bug Best Practice introduced by
The property user does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
117
118
		$this->prefs =& $GLOBALS['egw_info']['user']['preferences']['infolog'];
119
120
		// read the duration format from project-manager
121
		if ($GLOBALS['egw_info']['apps']['projectmanager'])
122
		{
123
			$pm_config = Api\Config::read('projectmanager');
124
			$this->duration_format = str_replace(',','',implode('', (array)$pm_config['duration_units']));
125
			//error_log(__METHOD__."() ".__LINE__." duration_format=$this->duration_format, duration_unit=".array2string($pm_config['duration_units']));
126
			$this->hours_per_workday = $pm_config['hours_per_workday'];
0 ignored issues
show
Bug Best Practice introduced by
The property hours_per_workday does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
127
			unset($pm_config);
128
		}
129
		$this->filters =& $this->bo->filters;
130
		/* these are just for testing of the notifications
131
		for($i = -1; $i <= 3; ++$i)
132
		{
133
			$this->filters['delegated-open-enddate'.date('Y-m-d',time()+$i*24*60*60)] = "delegated due in $i day(s)";
134
		}
135
		for($i = -1; $i <= 3; ++$i)
136
		{
137
			$this->filters['responsible-open-enddate'.date('Y-m-d',time()+$i*24*60*60)] = "responsible due in $i day(s)";
138
		}
139
		for($i = -1; $i <= 3; ++$i)
140
		{
141
			$this->filters['delegated-open-date'.date('Y-m-d',time()+$i*24*60*60)] = "delegated starting in $i day(s)";
142
		}
143
		for($i = -1; $i <= 3; ++$i)
144
		{
145
			$this->filters['responsible-open-date'.date('Y-m-d',time()+$i*24*60*60)] = "responsible starting in $i day(s)";
146
		}
147
		*/
148
		$GLOBALS['infolog_ui'] =& $this;	// make ourself availible for ExecMethod of get_rows function
149
	}
150
151
	/**
152
	 * Sets additional fields for one infolog entry, which are not persistent in the DB
153
	 *
154
	 * @param array $info infolog entry read from the db
155
	 * @param array &$readonlys ACL specific settings for the buttons
156
	 * @param string $action
157
	 * @param string/int $action_id
0 ignored issues
show
Documentation Bug introduced by
The doc comment string/int at position 0 could not be parsed: Unknown type name 'string/int' at position 0 in string/int.
Loading history...
158
	 * @param boolean $show_links
159
	 * @param int $details
160
	 * @return array
161
	 */
162
	function get_info($info,&$readonlys,$action='',$action_id='',$show_links=false,$details = 1)
163
	{
164
		if (!is_array($info))
165
		{
166
			$info = $this->bo->read($info);
167
		}
168
		$id = $info['info_id'];
169
		$done = $info['info_status'] == 'done' || $info['info_status'] == 'billed' || $info['info_status'] == 'cancelled'; //cancelled is regarded as a completed status as well in bo
170
		// regard an infolog as done/billed/cancelled if its percentage is 100% when there is to status like the above for that type
171
		if (!$done && !isset($this->bo->status[$info['info_type']]['done']) && !isset($this->bo->status[$info['info_type']]['billed']) &&
172
			!isset($this->bo->status[$info['info_type']]['cancelled']) && (int)$info['info_percent']==100) $done = true ;
173
		$info['sub_class'] = $this->bo->enums['priority'][$info['info_priority']] . ($done ? '_done' : '');
174
		if (!$done && $info['info_enddate'] < $this->bo->user_time_now)
175
		{
176
			$info['end_class'] = 'infolog_overdue';
177
		}
178
		if (!isset($info['info_anz_subs'])) $info['info_anz_subs'] = $this->bo->anzSubs($id);
179
		$this->bo->link_id2from($info,$action,$action_id);	// unset from for $action:$action_id
180
		$info['info_percent'] = (int) $info['info_percent'].'%';
181
		$editrights = $this->bo->check_access($info,Acl::EDIT);
182
		$isresposible = $this->bo->is_responsible($info);
183
		if ((!($editrights || // edit rights or more then standard responsible rights
184
			$isresposible && array_diff($this->bo->responsible_edit,array('info_status','info_percent','info_datecompleted')))))
185
		{
186
			$info['class'] .= 'rowNoEdit ';
187
		}
188
		if ($info['status'] == 'deleted' && !$this->bo->check_access($info, infolog_bo::ACL_UNDELETE))
189
		{
190
			$info['class'] .= 'rowNoUndelete ';
191
		}
192
		if (($done || (!($editrights || $isresposible))))
193
		{
194
			$info['class'] .= 'rowNoClose ';
195
		}
196
		// this one is supressed, when you are not allowed to edit, or not responsible, or the entry is closed
197
		// and has no children. If you want that this one is shown if there are children regardless of the status of the current or its childs,
198
		// then modify ($done) to ($done && !$info['info_anz_subs'])
199
		if ($done || !$info['info_anz_subs'] || (!($editrights || $isresposible)))
200
		{
201
			$info['class'] .= 'rowNoCloseAll ';
202
		}
203
		if (!$this->bo->check_access($info,Acl::DELETE))
204
		{
205
			$info['class'] .= 'rowNoDelete ';
206
		}
207
		if (!$this->bo->check_access($info,Acl::ADD))
208
		{
209
			$info['class'] .= 'rowNoSubs ';
210
		}
211
		if ($info['info_id_parent']) $info['class'] .= 'infolog_rowHasParent ';
212
		if ($info['info_anz_subs'] > 0) $info['class'] .= 'infolog_rowHasSubs ';
213
214
		$info['row_mod'] = $info['info_datemodified'];
215
216
		if (!$show_links) $show_links = $this->prefs['show_links'];
217
		if (($show_links != 'none' && $show_links != 'no_describtion' ||
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: ($show_links != 'none' &...tmod DESC', true, true), Probably Intended Meaning: $show_links != 'none' &&...mod DESC', true, true))
Loading history...
218
			 $this->prefs['show_times'] || isset($GLOBALS['egw_info']['user']['apps']['timesheet'])) &&
219
			(isset($info['links']) || ($info['links'] = Link::get_links('infolog',$info['info_id'],'','link_lastmod DESC',true,true))))
220
		{
221
			$timesheets = array();
222
			foreach ($info['links'] as $link)
223
			{
224
				// incl. link modification time into row_mod (link's lastmod is always in server-time!)
225
				$link_mod = Api\DateTime::server2user($link['lastmod']);
226
				if ($info['row_mod'] < $link_mod) $info['row_mod'] = $link_mod;
227
228
				if ($link['deleted']) continue;	// skip deleted links, but incl. them in row_mod!
229
230
				if ($show_links != 'none' && $show_links != 'no_describtion' &&
231
					$link['link_id'] != $info['info_link_id'] &&
232
				    ($link['app'] != $action || $link['id'] != $action_id) &&
233
					($show_links == 'all' || ($show_links == 'links') === ($link['app'] != Link::VFS_APPNAME)))
234
				{
235
					$info['filelinks'][] = $link;
236
				}
237
				if (!$info['pm_id'] && $link['app'] == 'projectmanager')
238
				{
239
					$info['pm_id'] = $link['id'];
240
				}
241
				if ($link['app'] == 'timesheet') $timesheets[] = $link['id'];
242
			}
243
			if ($this->prefs['show_times'] && isset($GLOBALS['egw_info']['user']['apps']['timesheet']) && $timesheets)
244
			{
245
				$sum = ExecMethod('timesheet.timesheet_bo.sum',$timesheets);
0 ignored issues
show
Deprecated Code introduced by
The function ExecMethod() has been deprecated: use autoloadable class-names, instanciate and call method or use static methods ( Ignorable by Annotation )

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

245
				$sum = /** @scrutinizer ignore-deprecated */ ExecMethod('timesheet.timesheet_bo.sum',$timesheets);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
246
				$info['info_sum_timesheets'] = $sum['duration'];
247
				// incl. max timesheet modification in row_mod
248
				if ($info['row_mod'] < $sum['max_modified']) $info['row_mod'] = $sum['max_modified'];
249
			}
250
		}
251
		$info['info_type_label'] = $this->bo->enums['type'][$info['info_type']];
252
		$info['info_status_label'] = isset($this->bo->status[$info['info_type']][$info['info_status']]) ?
253
			$this->bo->status[$info['info_type']][$info['info_status']] : $info['info_status'];
254
255
		if (!$this->prefs['show_percent'] || $this->prefs['show_percent'] == 2 && !$details)
256
		{
257
			if ($info['info_status'] == 'ongoing' && $info['info_type'] != 'phone')
258
			{
259
				$info['info_status'] = $info['info_status_label'] = $info['info_percent'];
260
			}
261
			$readonlys["edit_percent[$id]"] = true;
262
		}
263
		elseif($readonlys["edit_percent[$id]"])	// show percent, but button is switched off
264
		{
265
			$info['info_percent2'] = $info['info_percent'];
266
		}
267
		if ($this->prefs['show_id'] == 1 || $this->prefs['show_id'] == 2 && $details)
268
		{
269
			$info['info_number'] = $info['info_id'];
270
		}
271
		//error_log(__METHOD__."() returning ".array2string($info));
272
		return $info;
273
	}
274
275
	/**
276
	 * Check if no filter is active
277
	 *
278
	 * @param array $query
279
	 * @return string name of 1. filter found or null
280
	 */
281
	protected static function filter(array $query)
282
	{
283
		$filter = $query['filter'] ? 'filter' : ($query['cat_id'] ? 'cat_id' : null);
284
		foreach((array)$query['col_filter'] as $name => $value)
285
		{
286
			if ((string)$value !== '')
287
			{
288
				$filter = $name;
289
				break;
290
			}
291
		}
292
		//error_log(__METHOD__."(col_filter=".array2string($query['col_filter']).") returning ".array2string($filter));
293
		return $filter;
294
	}
295
296
	/**
297
	 * Callback for nextmatch widget
298
	 *
299
	 * @param array &$query
300
	 * @param array &$rows
301
	 * @param array &$readonlys
302
	 * @return int
303
	 */
304
	function get_rows(&$query,&$rows,&$readonlys)
305
	{
306
		//error_log(__METHOD__."() query[csv_export]=".array2string($query['csv_export']).", query[filter]=".array2string($query['filter']).", query[col_filter]=".array2string(array_diff($query['col_filter'],array('',0))).' '.function_backtrace());
307
		if (!$query['csv_export'])
308
		{
309
			unset($query['no_actions']);
310
			if (!$query['col_filter']['parent_id'] && !$query['search'] &&
311
				($this->prefs['listNoSubs'] == '1' || $this->prefs['listNoSubs'] === 'filter' && !self::filter($query)))
312
			{
313
				$parent_id = 0;
314
			}
315
			else
316
			{
317
				$parent_id = $query['col_filter']['parent_id'];
318
			}
319
			//error_log(__METHOD__."() prefs[listNoSubs]=".array2string($this->prefs['listNoSubs'])." --> parent_id=$parent_id");
320
			unset($query['col_filter']['parent_id']);
321
			if(!$query['action'])
322
			{
323
				Api\Cache::setSession('infolog', $query['session_for'].'session_data',
324
					array_diff_key ($query, array_flip(array('rows','actions','action_links','placeholder_actions'))));
325
			}
326
			$query['actions'] = $this->get_actions($query);
327
			$query['row_id'] = 'info_id';
328
			$query['row_modified'] = 'row_mod';
329
			$query['parent_id'] = 'info_id_parent';
330
			$query['is_parent'] = 'info_anz_subs';
331
			$query['action_var'] = 'multi_action';	// as 'action' is already used in infolog
332
		}
333
		// nextmatch opened an infolog containing children --> do not filter them, always show all children
334
		elseif($query['csv_export'] === 'children')
335
		{
336
			$query['filter'] = $query['search'] = $query['cat_id'] = '';
337
			$query['col_filter'] = array('info_id_parent' => $query['col_filter']['info_id_parent']);
338
		}
339
340
		$GLOBALS['egw']->session->commit_session();
341
		$orginal_colfilter = $query['col_filter'];
342
		if (isset($parent_id)) $query['col_filter']['info_id_parent'] = (string)$parent_id;
343
344
		//echo "<p>infolog_ui.get_rows(start=$query[start],search='$query[search]',filter='$query[filter]',cat_id=$query[cat_id],action='$query[action]/$query[action_id]',col_filter=".print_r($query['col_filter'],True).",sort=$query[sort],order=$query[order])</p>\n";
345
		if (!isset($query['start'])) $query['start'] = 0;
346
347
		// handle action and linked filter (show only entries linked to a certain other entry)
348
		$link_filters = array();
349
		$links = array();
350
		if ($query['col_filter']['linked'])
351
		{
352
			$link_filters['linked'] = $query['col_filter']['linked'];
353
			$links['linked'] = array();
354
			unset($query['col_filter']['linked']);
355
		}
356
		if($query['action'] && in_array($query['action'], array_keys($GLOBALS['egw_info']['apps'])) && $query['action_id'])
357
		{
358
			$link_filters['action'] = array('app'=>$query['action'], 'id' => $query['action_id']);
359
			$links['action'] = array();
360
		}
361
		foreach($link_filters as $key => $link)
362
		{
363
			if(!is_array($link))
364
			{
365
				// Legacy string style
366
				list($app,$id) = explode(':',$link);
367
			}
368
			else
369
			{
370
				// Full info
371
				$app = $link['app'];
372
				$id = $link['id'];
373
			}
374
			if(!is_array($id)) $id = explode(',',$id);
375
			if (!($linked = Link::get_links_multiple($app,$id,true,'infolog','',$query['col_filter']['info_status'] == 'deleted')))
376
			{
377
				$rows = array();	// no infologs linked to selected link --> no rows to return
378
				return 0;
379
			}
380
381
382
			foreach($linked as $infos)
383
			{
384
				$links[$key] = array_merge($links[$key],$infos);
385
			}
386
			$links[$key] = array_unique($links[$key]);
387
			if($key == 'linked')
388
			{
389
				$linked = array('app' => $app, 'id' => $id, 'title' => (count($id) == 1 ? Link::title($app, $id) : lang('multiple')));
390
			}
391
		}
392
		if(count($links))
393
		{
394
			$query['col_filter']['info_id'] = count($links) > 1 ? call_user_func_array('array_intersect', $links) : $links[$key];
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $key seems to be defined by a foreach iteration on line 361. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
395
		}
396
397
		// check if we have a custom, type-specific template
398
		$old_template = $query['template'];
399
400
		// Reset custom, type-specific template if type was cleared (without changing it for home)
401
		if(!$query['template'] || stripos($query['template'], 'infolog.index.rows') === 0)
402
		{
403
			$query['template'] = 'infolog.index.rows';
404
		}
405
		unset($query['custom_fields']);
406
		if ($query['col_filter']['info_type'])
407
		{
408
			$tpl = new Etemplate;
409
			if ($tpl->read('infolog.index.rows.'.$query['col_filter']['info_type']))
410
			{
411
				$query['template'] = $tpl->name;
412
				$query['custom_fields'] = true;	// read the custom fields too
413
			}
414
			// If status is not valid for selected type, clear status filter
415
			if($query['col_filter']['info_status'] && $query['col_filter']['info_status'] != 'deleted' &&
416
				!in_array($query['col_filter']['info_status'], array_keys($this->bo->status[$query['col_filter']['info_type']])))
417
			{
418
				$query['col_filter']['info_status'] = '';
419
				$clear_status_filter = true;
420
			}
421
		}
422
		// Framework\Template change forces the UI to do a full update first, no point in getting rows right now
423
		if($old_template && $old_template != $query['template']) return 0;
424
425
		// do we need to read the custom fields, depends on the column is enabled and customfields exist, prefs are filter specific
426
		// so we have to check that as well
427
		$details = $query['filter2'] == 'all';
428
		$columnselection_pref = 'nextmatch-'.($query['action'] ? 'infolog.'.$query['action'] : ($tpl && $tpl->name == $query['template'] ? $query['template'] : 'infolog.index.rows'))
429
			.($details ? '-details' : '');
430
		//error_log(__METHOD__."(start=$query[start], num_rows=$query[num_rows]) query[col_filter][info_type]={$query['col_filter']['info_type']} --> query[template]=$query[template], columselection_pref=$columnselection_pref");
431
432
		$columselection = $this->prefs[$columnselection_pref];
433
434
		if (!$query['selectcols'] && $columselection)
435
		{
436
			$columselection = is_array($columselection) ? $columselection : explode(',',$columselection);
437
		}
438
		else
439
		{
440
			$columselection = $query['selectcols'] ? (is_array($query['selectcols']) ? $query['selectcols'] : explode(',',$query['selectcols'])) : array();
441
		}
442
		// do we need to query the cf's
443
		$query['custom_fields'] = $this->bo->customfields && (!$columselection || in_array('customfields',$columselection));
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->bo->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...
444
445
		$infos = $this->bo->search($query);
446
		$query['col_filter'] = $orginal_colfilter;
447
		if (!is_array($infos))
0 ignored issues
show
introduced by
The condition is_array($infos) is always true.
Loading history...
448
		{
449
			$infos = array( );
450
		}
451
		// add a '-details' to the name of the columnselection pref
452
		if ($details)
453
		{
454
			$query['default_cols'] = '!cat_id,info_used_time_info_planned_time,info_used_time_info_planned_time_info_replanned_time,info_id';
455
		}
456
		else
457
		{
458
			$query['default_cols'] = '!cat_id,info_datemodified,info_used_time_info_planned_time,info_used_time_info_planned_time_info_replanned_time,info_id';
459
		}
460
		// set old show_times pref, that get_info calculates the cumulated time of the timesheets (we only check used&planned to work for both time cols)
461
		$this->prefs['show_times'] = strpos($this->prefs[$query['columnselection_pref']],'info_used_time_info_planned_time') !== false;
462
463
		// query all links and sub counts in one go
464
		if ($infos && (!$query['csv_export'] || !is_array($query['csv_export'])))
465
		{
466
			$links = Link::get_links_multiple('infolog',array_keys($infos),true,'','link_lastmod DESC',true);	// true=incl. deleted
467
			$anzSubs = $this->bo->anzSubs(array_keys($infos));
468
		}
469
		$rows = array();
470
471
		// Don't add parent in if info_id_parent (expanding to show subs)
472
		if ($query['action_id'] && $query['csv_export'] !== 'children')
473
		{
474
			$parents = $query['action'] == 'sp' && $query['action_id'] ? (array)$query['action_id'] : array();
475
			if (!empty($parents) && count($parents) == 1 && is_array($query['action_id']))
476
			{
477
				$query['action_id'] = array_shift($query['action_id']);	// display single parent as app_header
478
			}
479
		}
480
481
		$parent_first = !empty($parents) && count($parents) == 1;
482
		$parent_index = 0;
483
		// et2 nextmatch listens to total, and only displays that many rows, so add parent in or we'll lose the last row
484
		if($parent_first || $query['action'] == 'sp' && is_array($query['action_id'])) $query['total']++;
485
486
		// Check to see if we need to remove description
487
		foreach($infos as $id => $info)
488
		{
489
			if (!$query['csv_export'] || !is_array($query['csv_export']))
490
			{
491
				$info['links'] =& $links[$id];
492
				$info['info_anz_subs'] = (int)$anzSubs[$id];
493
				$info = $this->get_info($info,$readonlys,null,null,false,$details);
494
			}
495
			// for subs view ('sp') add parent(s) in front of subs once(!)
496
			if ( $parent_first && ($main = $this->bo->read($query['action_id'])) ||
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: ($parent_first && $main ...info['info_id_parent']), Probably Intended Meaning: $parent_first && ($main ...nfo['info_id_parent']))
Loading history...
497
				$parents && ($parent_index = array_search($info['info_id_parent'], $parents)) !== false &&
498
				($main = $this->bo->read($info['info_id_parent'])))
499
			{
500
				$main = $this->get_info($main, $readonlys);
501
				$main['class'] .= 'th ';
502
				// if only certain custom-fields are to be displayed, we need to unset the not displayed ones manually
503
				// as read() always read them all, while search() only reads the selected ones
504
				if ($query['custom_fields'])
505
				{
506
					foreach($columselection as $col)
507
					{
508
						if ($col[0] == '#')
509
						{
510
							foreach(array_keys($main) as $n)
511
							{
512
								if ($n[0] == '#' && !in_array($n, $columselection)) unset($main[$n]);
513
							}
514
							break;
515
						}
516
					}
517
				}
518
				$parent_first = false;
519
				if($query['start'] == 0)
520
				{
521
					array_splice($rows, $id, 0, array($main));
522
					unset($parents[$parent_index]);
523
				}
524
			}
525
			$rows[] = $info;
526
		}
527
		unset($links);
528
529
		if ($query['cat_id']) $rows['no_cat_id'] = true;
530
		if ($query['no_actions']) $rows['no_actions'] = true;
531
		$rows['no_timesheet'] = !isset($GLOBALS['egw_info']['user']['apps']['timesheet']);
532
		if($clear_status_filter)
533
		{
534
			$rows['info_status'] = '';
535
		}
536
537
		// switch cf column off, if we have no cf's
538
		$rows['no_customfields'] = !$query['custom_fields'];
539
540
		$rows['no_info_owner_info_responsible'] = (
541
			$GLOBALS['egw_info']['user']['preferences']['common']['account_selection'] == 'none' &&
542
			!isset($GLOBALS['egw_info']['user']['apps']['admin'])
543
		);
544
545
		// if filtered by type, show only the stati of the filtered type
546
		$rows['sel_options']['info_status'] = $this->bo->get_status($query['col_filter']['info_type']);
547
548
		if ($this->bo->history)
549
		{
550
			$rows['sel_options']['info_status']['deleted'] = 'deleted';
551
		}
552
		// Update actions for selected type / status / etc.
553
		$query['actions'] = $this->get_actions($query);
554
555
		if ($GLOBALS['egw_info']['flags']['currentapp'] == 'infolog' && !$this->called_by)
556
		{
557
			$headers = array();
558
			if ($query['filter'] != '' && !empty($this->filters[$query['filter']]))
559
			{
560
				$headers[] = lang($this->filters[$query['filter']]);
561
			}
562
			if ($query['action'] && ($title = $query['action_title'] || is_array($query['action_id']) ?
563
				$query['action_title'] : Link::title($query['action']=='sp'?'infolog':$query['action'],$query['action_id'])))
564
			{
565
				$headers[] = $title;
566
			}
567
			if ($query['search'])
568
			{
569
				 $headers[] = lang("Search for '%1'", $query['search']);
0 ignored issues
show
Unused Code introduced by
The call to lang() has too many arguments starting with $query['search']. ( Ignorable by Annotation )

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

569
				 $headers[] = /** @scrutinizer ignore-call */ lang("Search for '%1'", $query['search']);

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...
570
			}
571
			$GLOBALS['egw_info']['flags']['app_header'] = implode(': ', $headers);
572
		}
573
574
		if (isset($linked)) $query['col_filter']['linked'] = $linked;  // add linked back to the colfilter
575
576
		return $query['total'];
577
	}
578
579
	/**
580
	 * Hook for timesheet to set some extra data and links
581
	 *
582
	 * @param array $data
583
	 * @param int $data[id] info_id
584
	 * @return array with key => value pairs to set in new timesheet and link_app/link_id arrays
585
	 */
586
	function timesheet_set($data)
587
	{
588
		$set = array();
589
		if ((int)$data['id'] && ($info = $this->bo->read($data['id'])))
590
		{
591
			if ($info['info_cat']) $set['cat_id'] = $info['info_cat'];
592
			if ($info['info_used_time'])
593
			{
594
				$set['ts_duration'] = $info['info_used_time'];
595
			}
596
			if ($info['pl_id'])
597
			{
598
				$set['pl_id'] = $info['pl_id'];
599
			}
600
			if ($info['info_price'])
601
			{
602
				$set['ts_unitprice'] = $info['info_price'];
603
			}
604
605
			foreach(Link::get_links('infolog',$info['info_id'],'','link_lastmod DESC',true) as $link)
606
			{
607
				if ($link['app'] != 'timesheet' && $link['app'] != Link::VFS_APPNAME)
608
				{
609
					$set['link_app'][] = $link['app'];
610
					$set['link_id'][]  = $link['id'];
611
				}
612
			}
613
614
		}
615
		return $set;
616
	}
617
618
	/**
619
	 * Hook for calendar to set some extra data and links
620
	 *
621
	 * @param array $data event-array preset by calendar plus
622
	 * @param int $data[entry_id] info_id
623
	 * @return array with key => value pairs to set in new event and link_app/link_id arrays
624
	 */
625
	function calendar_set($data)
626
	{
627
		if (!($infolog = $this->bo->read($data['entry_id'])))
628
		{
629
			return $data;
630
		}
631
		$event = array_merge($data,array(
632
			'category'	=> $GLOBALS['egw']->categories->check_list(Acl::READ, $infolog['info_cat']),
633
			'priority'	=> $infolog['info_priority'] + 1,
634
			'public'	=> $infolog['info_access'] != 'private',
635
			'title'		=> $infolog['info_subject'],
636
			'description'	=> $infolog['info_des'],
637
			'location'	=> $infolog['info_location'],
638
			'start'		=> $infolog['info_startdate'],
639
			'end'		=> $infolog['info_enddate'] ? $infolog['info_enddate'] : $infolog['info_datecompleted']
640
		));
641
		unset($event['entry_id']);
642
		if (!$event['end']) $event['end'] = $event['start'] + (int) $GLOBALS['egw_info']['user']['preferences']['calendar']['defaultlength']*60;
643
644
		// Match Api\Categories by name
645
		$event['category'] = $GLOBALS['egw']->categories->name2id(Api\Categories::id2name($infolog['info_cat']));
646
647
		// make current user the owner of the new event, not the selected calendar, if current user has rights for it
648
		$event['owner'] = $user = $GLOBALS['egw_info']['user']['account_id'];
649
650
		// add/modify participants according to prefs
651
		$prefs = explode(',',$this->prefs['calendar_set'] ? $this->prefs['calendar_set'] : 'responsible,contact,user');
652
653
		// if no default participants (selected calendars) --> remove all
654
		if (!in_array('selected',$prefs))
655
		{
656
			$event['participants'] = $event['participant_types'] = array();
657
		}
658
		// Add responsible as participant
659
		if (in_array('responsible',$prefs))
660
		{
661
			foreach($infolog['info_responsible'] as $responsible)
662
			{
663
				$event['participants'][$responsible] = $event['participant_types']['u'][$responsible] =
664
					calendar_so::combine_status($user==$responsible?'A':'U');
665
			}
666
		}
667
		// Add linked contact as participant
668
		if (in_array('contact',$prefs) && $infolog['info_link']['app'] == 'addressbook')
669
		{
670
			$event['participants'][calendar_so::combine_user('c',$infolog['info_link']['id'])] =
671
				$event['participant_types']['c'][$infolog['info_link']['id']] = calendar_so::combine_status('U');
672
		}
673
		if (in_array('owner',$prefs))
674
		{
675
			$event['participants'][$infolog['info_owner']] = $event['participant_types']['u'][$infolog['info_owner']] =
676
				calendar_so::combine_status('A',1,'CHAIR');
677
		}
678
		// Add current user, if set or no other participants, which is not allowed
679
		if (in_array('user',$prefs))
680
		{
681
			$event['participants'][$user] = $event['participant_types']['u'][$user] =
682
				calendar_so::combine_status('A',1,'CHAIR');
683
		}
684
685
		// Add infolog link to calendar entry
686
		$event['link_app'][] = $infolog['info_link']['app'];
687
		$event['link_id'][]  = $infolog['info_link']['id'];
688
689
		// Copy infolog's links
690
		foreach(Link::get_links('infolog',$infolog['info_id'],'','link_lastmod DESC',true) as $link)
691
		{
692
			if ($link['app'] != Link::VFS_APPNAME)
693
			{
694
				$event['link_app'][] = $link['app'];
695
				$event['link_id'][]  = $link['id'];
696
			}
697
		}
698
		// Copy same custom fields
699
		foreach(array_keys(Api\Storage\Customfields::get('calendar')) as $name)
700
		{
701
			if ($this->bo->customfields[$name]) $event['#'.$name] = $infolog['#'.$name];
702
		}
703
		//error_log(__METHOD__.'('.array2string($data).') infolog='.array2string($infolog).' returning '.array2string($event));
704
		return $event;
705
	}
706
707
	/**
708
	 * hook called be calendar to include events or todos in the cal-dayview
709
	 * Since the hook has no idea about infolog or preferences, we add the user's
710
	 * current sorting for infolog here so they're in the expected order
711
	 *
712
	 * @param int $args[year], $args[month], $args[day] date of the events
713
	 * @param int $args[owner] owner of the events
714
	 * @param string $args[location] calendar_include_{events|todos}
715
	 * @return array of events (array with keys starttime, endtime, title, view, icon, content)
716
	 */
717
	public function cal_to_include($args)
718
	{
719
		$nm = Api\Cache::getSession('infolog', 'session_data');
720
		if($nm)
721
		{
722
			$args['order'] = $nm['order'];
723
			$args['sort'] = $nm['sort'];
724
		}
725
726
		return $this->bo->cal_to_include($args);
727
	}
728
729
	/**
730
	 * Shows the infolog list
731
	 *
732
	 * @param array/string $values etemplate content or 'reset_action_view' if called by index.php to reset an action-view
0 ignored issues
show
Documentation Bug introduced by
The doc comment array/string at position 0 could not be parsed: Unknown type name 'array/string' at position 0 in array/string.
Loading history...
733
	 * @param string $action if set only entries liked to that $action:$action_id are shown
734
	 * @param string $action_id if set only entries liked to that $action:$action_id are shown
735
	 * @param mixed $called_as is how we got called, for a hook eg. the call-params of that page containing the hook
736
	 * @param boolean $e$extra_app_header * @param boolean $return_html=false
737
	 * @param string $own_referer='' this is our own referer
738
	 * @param string $action_title='' app_header for the action, if '' we try the link-title
739
	 */
740
	function index($values = null,$action='',$action_id='',$called_as=0,$extra_app_header=False,$return_html=False,$own_referer='',$action_title='')
741
	{
742
		unset($extra_app_header);	// not used, but dont want to change signature
743
		if (is_array($values))
744
		{
745
			$called_as = $values['called_as'];
746
			$own_referer = $values['own_referer'];
747
		}
748
		elseif ($own_referer === '')
749
		{
750
			$own_referer = Api\Header\Referer::get();
751
			if (strpos($own_referer,'menuaction=infolog.infolog_ui.edit') !== false)
752
			{
753
				$own_referer = Api\Cache::getSession('infolog', 'own_session');
754
			}
755
			else
756
			{
757
				Api\Cache::setSession('infolog', 'own_session', $own_referer);
758
			}
759
		}
760
761
		// Handle legacy buttons like actions
762
		if(is_array($values))
763
		{
764
			foreach(array('document', 'view', 'delete') as $button)
765
			{
766
				if(isset($values['nm']['rows'][$button]))
767
				{
768
					$id = @key($values['nm']['rows'][$button]);
769
					$values['nm']['multi_action'] = $button;
770
					$values['nm']['selected'] = array($id);
771
					break; // Only one can come per submit
772
				}
773
			}
774
		}
775
		if (is_array($values) && !empty($values['nm']['multi_action']))
776
		{
777
			if (empty($values['nm']['selected']) && !$values['nm']['select_all'])
778
			{
779
				$msg = lang('You need to select some entries first');
0 ignored issues
show
Unused Code introduced by
The assignment to $msg is dead and can be removed.
Loading history...
780
			}
781
			else
782
			{
783
				// Some processing to add values in for links and cats
784
				$multi_action = $values['nm']['multi_action'];
785
				// Action has an additional action - add / delete, etc.  Buttons named <multi-action>_action[action_name]
786
				if(in_array($multi_action, array('link', 'responsible', 'startdate', 'enddate')))
787
				{
788
					// eTemplate ignores the _popup namespace, but et2 doesn't
789
					if($values[$multi_action.'_popup'])
790
					{
791
						$popup =& $values[$multi_action.'_popup'];
792
					}
793
					else
794
					{
795
						$popup =& $values;
796
					}
797
					$values['nm']['multi_action'] .= '_' . key($popup[$multi_action . '_action']);
798
					if($multi_action == 'link')
799
					{
800
						$popup[$multi_action] = $popup['link']['app'] . ':'.$popup['link']['id'];
801
					}
802
					else if(is_array($popup[$multi_action]))
803
					{
804
						$popup[$multi_action] = implode(',',$popup[$multi_action]);
805
					}
806
					$values['nm']['multi_action'] .= '_' . $popup[$multi_action];
807
					unset($values[$multi_action.'_popup']);
808
					unset($values[$multi_action]);
809
				}
810
				$success = $failed = $action_msg = null;
811
				if ($this->action($values['nm']['multi_action'], $values['nm']['selected'], $values['nm']['select_all'],
812
					$success, $failed, $action_msg, $values['nm'], $msg, $values['nm']['checkboxes']['no_notifications']))
813
				{
814
					$msg .= lang('%1 entries %2',$success,$action_msg);
0 ignored issues
show
Unused Code introduced by
The call to lang() has too many arguments starting with $success. ( Ignorable by Annotation )

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

814
					$msg .= /** @scrutinizer ignore-call */ lang('%1 entries %2',$success,$action_msg);

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...
815
					Framework::message($msg);
816
				}
817
				elseif(is_null($msg))
818
				{
819
					$msg .= lang('%1 entries %2, %3 failed because of insufficent rights !!!',$success,$action_msg,$failed);
820
					Framework::message($msg,'error');
821
				}
822
				elseif($msg)
823
				{
824
					$msg .= "\n".lang('%1 entries %2, %3 failed.',$success,$action_msg,$failed);
825
					Framework::message($msg,'error');
826
				}
827
				unset($values['nm']['multi_action']);
828
				unset($values['nm']['select_all']);
829
			}
830
		}
831
		if (!$action)
832
		{
833
			$action = is_array($values) && $values['action'] ? $values['action'] : $_REQUEST['action'];
834
			$action_id = is_array($values) && $values['action_id'] ? $values['action_id'] : $_REQUEST['action_id'];
835
			$action_title = is_array($values) && $values['action_title'] ? $values['action_title'] : $_REQUEST['action_title'];
836
		}
837
		//echo "<p>".__METHOD__."(action='$action/$action_id',called_as='$called_as/$values[referer]',own_referer='$own_referer') values=\n"; _debug_array($values);
838
		if (!is_array($values))
839
		{
840
			$nm = Api\Cache::getSession('infolog', $this->called_by.'session_data');
841
			unset($nm['rows']);
842
			if ($values === 'reset_action_view')
843
			{
844
				$action = '';
845
				$action_id = 0;
846
				$action_title = '';
847
			}
848
			if($_GET['ajax'] === 'true')
849
			{
850
				$nm['action'] = '';
851
				$nm['action_id'] = 0;
852
				$nm['action_title'] = '';
853
				// check if action-view reset filter and restore it
854
				if (($filter = Api\Cache::getSession('infolog', 'filter_reset_from')))
855
				{
856
					$nm['filter'] = $filter;
857
					Api\Cache::unsetSession('infolog', 'filter_reset_from');
858
				}
859
			}
860
			$values = array('nm' => $nm);
861
862
			if (isset($_GET['filter']) && $_GET['filter'] != 'default' || !isset($values['nm']['filter']) && !$this->called_by)
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: (IssetNode && $_GET['fil...e && ! $this->called_by, Probably Intended Meaning: IssetNode && ($_GET['fil... && ! $this->called_by)
Loading history...
863
			{
864
				$values['nm']['filter'] = $_GET['filter'] && $_GET['filter'] != 'default' ? $_GET['filter'] :
865
					$this->prefs['defaultFilter'];
866
			}
867
			if (!isset($values['nm']['order']) || !$values['nm']['order'])
868
			{
869
				$values['nm']['order'] = 'info_datemodified';
870
				$values['nm']['sort'] = 'DESC';
871
			}
872
873
			if (!$values['nm']['session_for'] && $this->called_by) $values['nm']['session_for'] = $this->called_by;
874
875
			$action_id = $values['action_id'] = $action ? $action_id : $nm['action_id'];
876
			$action_title = $values['action_title'] = $action ? $action_title : $nm['action_title'];
877
			$action = $values['action'] = $action ? $action : $nm['action'];
878
		}
879
		if($_GET['search']) $values['nm']['search'] = $_GET['search'];
880
881
		if ($values['nm']['add'])
882
		{
883
			$values['add'] = $values['nm']['add'];
884
			unset($values['nm']['add']);
885
		}
886
		unset($values['nm']['rows']['checked']);	// not longer used, but hides button actions
887
888
		switch ($action)
889
		{
890
			case 'sp':
891
				if (!is_array($action_id) && strpos($action_id, 'infolog:') === 0) $action_id = (int)substr($action_id, 8);
892
				if ((is_array($action_id) && !$this->bo->read(current($action_id))) || !$this->bo->read($action_id))
893
				{
894
					$action = '';
895
					$action_id = 0;
896
					break;
897
				}
898
				else
899
				{
900
					$values['nm']['col_filter']['info_id_parent'] = $action_id;
901
				}
902
				break;
903
			default:
904
				// Nothing
905
		}
906
		$readonlys['cancel'] = $action != 'sp';
0 ignored issues
show
Comprehensibility Best Practice introduced by
$readonlys was never initialized. Although not strictly required by PHP, it is generally a good practice to add $readonlys = array(); before regardless.
Loading history...
907
908
		$this->tmpl->read('infolog.index');
909
		$values['nm']['options-filter'] = $this->filters;
910
		$values['nm']['get_rows'] = 'infolog.infolog_ui.get_rows';
911
		$values['nm']['options-filter2'] = (in_array($this->prefs['show_links'],array('all','no_describtion')) ? array() : array(
912
			''               => 'default',
913
		)) + array(
914
			'no_describtion' => 'no details',
915
			'all'            => 'details',
916
		);
917
918
		//apply infolog_filter_change javascript method (hide/show of date filter form) over onchange filter
919
		$values['nm']['filter_onchange'] = "app.infolog.filter_change();";
920
921
		//apply infolog_filter2_change javascript method (show/hide details each rows) over onchange filter2
922
		$values['nm']['filter2_onchange'] = "return app.infolog.filter2_change(ev, widget)";
923
924
		// disable favories dropdown button, if not running as infolog
925
		if ($called_as && $called_as != 'infolog')
926
		{
927
			$values['nm']['favorites'] = false;
928
		}
929
		else
930
		{
931
			// Allow saving parent ID into favorites
932
			$values['nm']['favorites'] = array('action','action_id');
933
		}
934
935
		// Allow add actions even when there's no rows
936
		$values['nm']['placeholder_actions'] = array('new');
937
938
		if(!isset($values['nm']['filter2'])) $values['nm']['filter2'] = $this->prefs['nextmatch-'.($action ? 'infolog.'.$action : 'infolog.index.rows').'-details-pref'];
939
940
		// disable columns for main entry as set in the pref for details or no details
941
		$values['nm']['columnselection_pref'] = 'nextmatch-'.($action ? 'infolog.'.$action : 'infolog.index.rows')
942
			.($values['nm']['filter2']=='all' ? '-details' : '');
943
		if ($action == 'sp')
944
		{
945
			$pref = $values['nm']['columnselection_pref'];
946
			foreach(array('info_used_time_info_planned_time_info_replanned_time','info_datemodified','info_owner_info_responsible','customfields') as $name)
947
			{
948
				$values['main']['no_'.$name] = strpos($this->prefs[$pref],$name) === false;
949
			}
950
			if (!$values['main']['no_customfields'])
951
			{
952
				// set the column-header of the main table for the customfields.
953
				foreach(array_keys($this->bo->customfields) as $lname)
954
				{
955
					$values['main']['customfields'].=$lname."\n";
956
				}
957
			}
958
		}
959
		if ($values['nm']['filter']=='bydate')
960
		{
961
			foreach (array_keys($values['nm']['col_filter']) as $colfk)
962
			{
963
				if (is_int($colfk)) unset($values['nm']['col_filter']);
964
			}
965
		}
966
		$values['action'] = $persist['action'] = $values['nm']['action'] = $action;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$persist was never initialized. Although not strictly required by PHP, it is generally a good practice to add $persist = array(); before regardless.
Loading history...
967
		$values['action_id'] = $persist['action_id'] = $values['nm']['action_id'] = $action_id;
968
		$values['action_title'] = $persist['action_title'] = $values['nm']['action_title'] = $action_title;
969
		$values['duration_format'] = ','.$this->duration_format;
970
		$persist['called_as'] = $called_as;
971
		$persist['own_referer'] = $own_referer;
972
973
		// store whole $values[nm] in etemplate request
974
		unset($values['nm']['rows']);
975
		$persist['nm'] = $values['nm'];
976
977
		if (!$called_as)
978
		{
979
			$GLOBALS['egw_info']['flags']['params']['manual'] = array('page' => 'ManualInfologIndex');
980
		}
981
		else
982
		{
983
			$values['css'] = '<style type="text/css">@import url('.$GLOBALS['egw_info']['server']['webserver_url'].'/infolog/templates/default/app.css);'."</style>";
984
			// Avoid DOM conflicts
985
			$this->tmpl->set_dom_id("{$this->tmpl->name}-$action-$action_id");
986
		}
987
		// add scrollbar to long description, if user choose so in his prefs
988
		if ($this->prefs['limit_des_lines'] > 0 || (string)$this->prefs['limit_des_lines'] == '')
989
		{
990
			$values['css'] .= '<style type="text/css">@media screen { .infoDes {  '.
991
				' max-height: '.
992
				(($this->prefs['limit_des_lines'] ? $this->prefs['limit_des_lines'] : 5) * 1.35).	// dono why em is not real lines
993
				'em; overflow: auto; }}</style>';
994
		}
995
996
		$sel_options = array(
997
			'info_type'     => $this->bo->enums['type'],
998
			'pm_id'      => array(lang('No project')),
999
			'info_priority' => $this->bo->enums['priority'],
1000
		);
1001
1002
		// remove group-types user has not any rights to as filter
1003
		// does not take implicit rights as delegated into account, so they will not be available as filters
1004
		foreach($this->bo->group_owners as $type => $group)
1005
		{
1006
			if (!isset($this->bo->grants[$group])) unset($sel_options['info_type'][$type]);
1007
		}
1008
1009
1010
		return $this->tmpl->exec('infolog.infolog_ui.index',$values,$sel_options,$readonlys,$persist,$return_html ? -1 : 0);
1011
	}
1012
1013
	/**
1014
	 * Get valid types
1015
	 *
1016
	 * @return array - array of valid types
1017
	 */
1018
	private function get_validtypes()
1019
	{
1020
		// Types
1021
		$types = $this->bo->enums['type'];
1022
		if ($this->bo->group_owners)
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->bo->group_owners 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...
1023
		{
1024
			// remove types owned by groups the user has no edit grant
1025
			foreach($this->bo->group_owners as $type => $group)
1026
			{
1027
				if (!($this->bo->grants[$group] & Acl::EDIT))
1028
				{
1029
					unset($types[$type]);
1030
				}
1031
			}
1032
		}
1033
		return $types;
1034
	}
1035
1036
	/**
1037
	 * Get actions / context menu items
1038
	 *
1039
	 * @param array $query
1040
	 * @return array see nextmatch_widget::get_actions()
1041
	 */
1042
	private function get_actions(array $query)
1043
	{
1044
		for($i = 0; $i <= 100; $i += 10)
1045
		{
1046
			$percent[$i] = $i.'%';
1047
		}
1048
		// Types
1049
		$types = $this->get_validtypes();
1050
		$types_add = array();
1051
		// Do not add deleted type to add or change menus
1052
		unset($types['delete']);
1053
		foreach($types as $type => &$data)
1054
		{
1055
			$image_exists = Api\Image::find('infolog',$type);
1056
			$data = array(
1057
				'caption' => $data,
1058
				'icon' => $image_exists ? $type : 'infolog/navbar',
1059
			);
1060
			$types_add[$type] = $data + array(
1061
				'onExecute' => "javaScript:app.infolog.add_action_handler"
1062
			);
1063
		}
1064
1065
		$icons = null;
1066
		$statis = $this->bo->get_status($query['col_filter']['info_type'], $icons);
1067
		foreach($statis as $type => &$data)
1068
		{
1069
			$image_exists = Api\Image::find('infolog',$icons[$type]);
1070
			$data = array(
1071
				'caption' => $data,
1072
				'icon' => $image_exists ? $icons[$type] : 'infolog/status',
1073
			);
1074
		}
1075
1076
		$actions = array(
1077
			'open' => array(
1078
				'caption' => 'Open',
1079
				'default' => true,
1080
				'allowOnMultiple' => false,
1081
				'onExecute' => Api\Header\UserAgent::mobile()?'javaScript:app.infolog.viewEntry':'',
1082
				'url' => 'menuaction=infolog.infolog_ui.edit&info_id=$id',
1083
				'popup' => Link::get_registry('infolog', 'add_popup'),
1084
				'group' => $group=1,
1085
				'mobileViewTemplate' => 'view?'.filemtime(Api\Etemplate\Widget\Template::rel2path('/infolog/templates/mobile/view.xet'))
1086
			),
1087
			'parent' => array(
1088
				'caption' => 'View parent with children',
1089
				'icon' => 'up.grey',
1090
				'allowOnMultiple' => false,
1091
				'enabled' => 'javaScript:app.infolog.has_parent',
1092
				'onExecute' => 'javaScript:app.infolog.view_parent',
1093
				'group' => $group,
1094
				'hideOnMobile' => true
1095
			),
1096
			'add' => array(
1097
				'caption' => 'Add',
1098
				'group' => $group,
1099
				'children' => array(
1100
					'new' => array(
1101
						'caption' => 'New',
1102
						'children' => $types_add,
1103
						'icon' => 'task',
1104
					),
1105
					'sub' => array(
1106
						'caption' => 'Sub-entry',
1107
						'url' => 'menuaction=infolog.infolog_ui.edit&action=sp&action_id=$id',
1108
						'popup' => Link::get_registry('infolog', 'add_popup'),
1109
						'allowOnMultiple' => false,
1110
						'hint' => 'Add a new sub-task, -note, -call to this entry',
1111
						'icon' => 'new',
1112
					),
1113
					'copy' => array(
1114
						'caption' => 'Copy',
1115
						'url' => 'menuaction=infolog.infolog_ui.edit&action=copy&info_id=$id',
1116
						'popup' => Link::get_registry('infolog', 'add_popup'),
1117
						'allowOnMultiple' => false,
1118
						'icon' => 'copy',
1119
					),
1120
				),
1121
				'hideOnMobile' => true
1122
			),
1123
			'no_notifications' => array(
1124
				'caption' => 'Do not notify',
1125
				'checkbox' => true,
1126
				'confirm_mass_selection' => "You are going to change %1 entries: Are you sure you want to send notifications about this change?",
1127
				'hint' => 'Do not notify of these changes',
1128
				'group' => $group,
1129
			),
1130
			// modifying content of one or multiple infolog(s)
1131
			'change' => array(
1132
				'caption' => 'Change',
1133
				'group' => ++$group,
1134
				'icon' => 'edit',
1135
				'disableClass' => 'rowNoEdit',
1136
				'confirm_mass_selection' => true,
1137
				'children' => array(
1138
					'type' => array(
1139
						'caption' => 'Type',
1140
						'prefix' => 'type_',
1141
						'children' => $types,
1142
						'group' => $group,
1143
						'icon' => 'task',
1144
					),
1145
					'status' => array(
1146
						'caption' => 'Status',
1147
						'prefix' => 'status_',
1148
						'children' => $statis,
1149
						'group' => $group,
1150
						'icon' => 'ongoing',
1151
					),
1152
					'completion' => array(
1153
						'caption' => 'Completed',
1154
						'prefix' => 'completion_',
1155
						'children' => $percent,
1156
						'group' => $group,
1157
						'icon' => 'completed',
1158
					),
1159
					'cat' =>  Etemplate\Widget\Nextmatch::category_action(
1160
						'infolog',$group,'Change category','cat_'
1161
					),
1162
					'startdate' => array(
1163
						'caption' => 'Start date',
1164
						'group' => $group,
1165
						'nm_action' => 'open_popup',
1166
					),
1167
					'enddate' => array(
1168
						'caption' => 'Due date',
1169
						'group' => $group,
1170
						'nm_action' => 'open_popup',
1171
					),
1172
					'responsible' => array(
1173
						'caption' => 'Delegation',
1174
						'group' => $group,
1175
						'icon' => 'users',
1176
						'nm_action' => 'open_popup',
1177
						'onExecute' => 'javaScript:app.infolog.change_responsible'
1178
					),
1179
					'link' => array(
1180
						'caption' => 'Links',
1181
						'group' => $group,
1182
						'nm_action' => 'open_popup',
1183
					),
1184
				),
1185
				'hideOnMobile' => true
1186
			),
1187
			'close' => array(
1188
				'caption' => 'Close',
1189
				'icon' => 'done',
1190
				'group' => $group,
1191
				'disableClass' => 'rowNoClose',
1192
				'confirm_mass_selection' => true,
1193
			),
1194
			'close_all' => array(
1195
				'caption' => 'Close all',
1196
				'icon' => 'done_all',
1197
				'group' => $group,
1198
				'hint' => 'Sets the status of this entry and its subs to done',
1199
				'allowOnMultiple' => false,
1200
				'disableClass' => 'rowNoCloseAll',
1201
				'confirm_mass_selection' => true,
1202
			),
1203
			'print' => array(
1204
				'caption' => 'Print',
1205
				'icon' => 'print',
1206
				'group' => $group,
1207
				'onExecute' => 'javaScript:app.infolog.infolog_menu_print'
1208
			)
1209
		);
1210
		++$group;	// integration with other apps
1211
		if ($GLOBALS['egw_info']['user']['apps']['filemanager'])
1212
		{
1213
			$actions['filemanager'] = array(
1214
				'icon' => 'filemanager/navbar',
1215
				'caption' => 'Filemanager',
1216
				'url' => 'menuaction=filemanager.filemanager_ui.index&path=/apps/infolog/$id&ajax=true',
1217
				'allowOnMultiple' => false,
1218
				'group' => $group,
1219
			);
1220
		}
1221
		if ($GLOBALS['egw_info']['user']['apps']['calendar'])
1222
		{
1223
			$actions['calendar'] = array(	// interactive add for a single event
1224
				'icon' => 'calendar/navbar',
1225
				'caption' => 'Schedule appointment',
1226
				'group' => $group,
1227
				'url' => 'menuaction=calendar.calendar_uiforms.edit&'.
1228
					Link::get_registry('calendar', 'add_app') . '[]=infolog&'.Link::get_registry('calendar','add_id').'[]=$id',
1229
				'allowOnMultiple' => false,
1230
				'popup' => Link::get_registry('calendar', 'edit_popup')
1231
			);
1232
		}
1233
		if ($GLOBALS['egw_info']['user']['apps']['timesheet'])
1234
		{
1235
			$actions['timesheet'] = array(	// interactive add for a single event
1236
				'icon' => 'timesheet/navbar',
1237
				'caption' => 'Timesheet',
1238
				'group' => $group,
1239
				'allowOnMultiple' => false,
1240
1241
				'children' => array(
1242
					'timesheet_list' => array(
1243
						'caption' => lang('View linked %1 entries', lang('timesheet')),
0 ignored issues
show
Unused Code introduced by
The call to lang() has too many arguments starting with lang('timesheet'). ( Ignorable by Annotation )

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

1243
						'caption' => /** @scrutinizer ignore-call */ lang('View linked %1 entries', lang('timesheet')),

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...
1244
						'icon' => 'infolog/navbar',
1245
						'onExecute' => 'javaScript:app.infolog.timesheet_list',
1246
						'allowOnMultiple' => false,
1247
						'hideOnDisabled' => true,
1248
					),
1249
					'timesheet_add' => array(
1250
						'icon' => 'timesheet/navbar',
1251
						'caption' => 'Add timesheet entry',
1252
						'url' => 'menuaction=timesheet.timesheet_ui.edit&link_app[]=infolog&link_id[]=$id',
1253
						'popup' => Link::get_registry('timesheet', 'add_popup'),
1254
					)
1255
				)
1256
			);
1257
		}
1258
		if ($GLOBALS['egw_info']['user']['apps']['tracker'])
1259
		{
1260
			$actions['to_tracker'] = array(
1261
				'icon' => 'tracker/navbar',
1262
				'caption' => 'Tracker',
1263
				'hint' => 'Convert to a ticket',
1264
				'group' => $group,
1265
				'url' => 'menuaction=tracker.tracker_ui.edit&'.
1266
					Link::get_registry('tracker', 'add_app') . '[]=infolog&'.Link::get_registry('tracker','add_id').'[]=$id',
1267
				'allowOnMultiple' => false,
1268
				'popup' => Link::get_registry('tracker', 'add_popup'),
1269
			);
1270
		}
1271
1272
		$actions['documents'] = infolog_merge::document_action(
1273
			$this->prefs['document_dir'], ++$group, 'Insert in document', 'document_',
1274
			$this->prefs['default_document']
1275
		);
1276
		$actions['ical'] = array(
1277
			'icon' => 'ical',
1278
			'caption' => 'Export iCal',
1279
			'postSubmit' => true,	// download needs post submit to work
1280
			'group' => $group,
1281
			'allowOnMultiple' => true,
1282
			'hideOnMobile' => true
1283
		);
1284
1285
		$actions['delete'] = array(
1286
			'caption' => 'Delete',
1287
			'group' => ++$group,
1288
			'disableClass' => 'rowNoDelete',
1289
			'onExecute' => 'javaScript:app.infolog.confirm_delete',
1290
			'confirm_mass_selection' => true,
1291
		);
1292
		if ($query['col_filter']['info_status'] == 'deleted')
1293
		{
1294
			$actions['undelete'] = array(
1295
				'caption' => 'Un-Delete',
1296
				'group' => $group,
1297
				'icon' => 'revert',
1298
				'disableClass' => 'rowNoUndelete',
1299
				'confirm_mass_selection' => true,
1300
			);
1301
		}
1302
		$actions['info_drop_mail'] = array(
1303
			'type' => 'drop',
1304
			'caption' => 'Link mail',
1305
			'acceptedTypes' => 'mail',
1306
			'onExecute' => 'javaScript:app.infolog.handle_dropped_mail',
1307
			'hideOnDisabled' => true
1308
		);
1309
		//echo "<p>".__METHOD__."($do_email, $tid_filter, $org_view)</p>\n"; _debug_array($actions);
1310
		return $actions;
1311
	}
1312
1313
	/**
1314
	 * Handles actions on multiple infologs
1315
	 *
1316
	 * @param string $_action
1317
	 * @param array $checked contact id's to use if !$use_all
1318
	 * @param boolean $use_all if true use all entries of the current selection (in the session)
1319
	 * @param int &$success number of succeded actions
1320
	 * @param int &$failed number of failed actions (not enought permissions)
1321
	 * @param string &$action_msg translated verb for the actions, to be used in a message like '%1 entries deleted'
1322
	 * @param array $query get_rows parameter
1323
	 * @param string &$msg on return user feedback
1324
	 * @param boolean $skip_notifications true to NOT notify users about changes
1325
	 * @return boolean true if all actions succeded, false otherwise
1326
	 */
1327
	function action($_action, $checked, $use_all, &$success, &$failed, &$action_msg,
1328
		array $query, &$msg, $skip_notifications = false)
1329
	{
1330
		//echo '<p>'.__METHOD__."('$action',".array2string($checked).','.(int)$use_all.",...)</p>\n";
1331
		$success = $failed = 0;
1332
		if ($use_all)
1333
		{
1334
			@set_time_limit(0);                     // switch off the execution time limit, as it's for big selections to small
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for set_time_limit(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

1334
			/** @scrutinizer ignore-unhandled */ @set_time_limit(0);                     // switch off the execution time limit, as it's for big selections to small

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1335
			$query['num_rows'] = -1;        // all
1336
			$result = $readonlys = null;
1337
			$this->get_rows($query,$result,$readonlys);
1338
			$checked = array();
1339
			foreach($result as $key => $info)
1340
			{
1341
				if(is_numeric($key))
1342
				{
1343
					$checked[] = $info['info_id'];
1344
				}
1345
			}
1346
		}
1347
1348
		// Actions with options in the selectbox
1349
		list($action, $settings) = explode('_', $_action, 2);
1350
1351
		// Actions that can handle a list of IDs
1352
		switch($action)
1353
		{
1354
			case 'link':
1355
				list($add_remove, $link) = explode('_', $settings, 2);
1356
				list($app, $link_id) = explode(strpos($link,':') !== false ? ':' : ',', $link);
1357
				if(!$link_id)
1358
				{
1359
					$action_msg = 'linked';
1360
					$msg = lang('You need to select an entry for linking.');
1361
					break;
1362
				}
1363
				$title = Link::title($app, $link_id);
1364
				foreach($checked as $id)
1365
				{
1366
					if(!$this->bo->check_access($id, Acl::EDIT))
1367
					{
1368
						$failed++;
1369
						continue;
1370
					}
1371
					if($add_remove == 'add')
1372
					{
1373
						$action_msg = lang('linked to %1', $title);
0 ignored issues
show
Unused Code introduced by
The call to lang() has too many arguments starting with $title. ( Ignorable by Annotation )

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

1373
						$action_msg = /** @scrutinizer ignore-call */ lang('linked to %1', $title);

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...
1374
						if(Link::link('infolog', $id, $app, $link_id))
1375
						{
1376
							$success++;
1377
						}
1378
						else
1379
						{
1380
							$failed++;
1381
						}
1382
					}
1383
					else
1384
					{
1385
						$action_msg = lang('unlinked from %1', $title);
1386
						$count = Link::unlink(0, 'infolog', $id, '', $app, $link_id);
1387
						$success += $count;
1388
					}
1389
				}
1390
				return $failed == 0;
1391
1392
			case 'document':
1393
				if (!$settings) $settings = $this->prefs['default_document'];
1394
				$document_merge = new infolog_merge();
1395
				$msg = $document_merge->download($settings, $checked, '', $this->prefs['document_dir']);
1396
				$failed = count($checked);
1397
				return false;
1398
			case 'ical':
1399
				// infolog_ical lets horde be auto-loaded, so it must go first
1400
				$boical = new infolog_ical();
1401
				Api\Header\Content::type('todo.ics','text/calendar');
1402
				echo $boical->exportvCalendar($checked);
1403
				exit();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
1404
1405
		}
1406
1407
		// Actions that need to loop
1408
		foreach($checked as $id)
1409
		{
1410
			if(!$entry = $this->bo->read($id))
1411
			{
1412
				continue;
1413
			}
1414
			switch($action)
1415
			{
1416
				case 'close':
1417
					$action_msg = lang('closed');
1418
					$this->close($id, '', false, $skip_notifications);
1419
					$success++;
1420
					break;
1421
1422
				case 'delete':
1423
					$action_msg = $settings == 'sub' ? lang(' (and children) deleted') : lang('deleted');
1424
					$result = $this->bo->delete($id, $settings=='sub', false, $skip_notifications);
1425
					if($result == true)
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
1426
					{
1427
						$success++;
1428
					}
1429
					else
1430
					{
1431
						$failed++;
1432
					}
1433
					break;
1434
1435
				case 'type':
1436
					$action_msg = lang('changed type');
1437
					// Dont allow to change the type, if user has no delete rights from the group-owner
1438
					if ($id && !($this->bo->grants[$entry['info_owner']] & Acl::DELETE))
1439
					{
1440
						$failed++;
1441
						break;
1442
					}
1443
					$entry['info_type'] = $settings;
1444
					try {
1445
						$this->bo->write($entry, true,true,true,$skip_notifications,true); // Throw exceptions
1446
					}
1447
					catch (Api\Exception\WrongUserinput $e)
1448
					{
1449
						$msg .= "\n".$e->getMessage();
1450
						$failed++;
1451
						break;
1452
					}
1453
					$success++;
1454
					break;
1455
1456
				case 'completion':
1457
					$action_msg = lang('changed completion to %1%', $settings);
1458
					$entry['info_percent'] = $settings;
1459
					// Done and not-started entries will get changed right back if we don't change the status too
1460
					if(in_array($entry['info_status'],array('not-started','done','billed','cancelled','archive')))
1461
					{
1462
						$entry['info_status'] = 'ongoing';
1463
					}
1464
					if($entry['info_percent'] == 0)
1465
					{
1466
						$entry['info_status'] = 'not-started';
1467
					}
1468
					else if ($entry['info_percent'] == 100)
1469
					{
1470
						$entry['info_status'] = 'done';
1471
					}
1472
					if($this->bo->write($entry, true,true,true,$skip_notifications))
1473
					{
1474
						$success++;
1475
					}
1476
					else
1477
					{
1478
						$failed++;
1479
					}
1480
					break;
1481
1482
				case 'undelete':	// set it to valid status != 'deleted' for that type
1483
					$settings = isset($this->bo->status[$entry['info_type']]['done']) ?
1484
						$this->bo->status[$entry['info_type']]['done'] :
1485
						$this->bo->status['defaults'][$entry['info_type']];
1486
					// fall-through
1487
				case 'status':
1488
					if(isset($this->bo->status[$entry['info_type']][$settings]))
1489
					{
1490
						$action_msg = lang('changed status to %1', lang($this->bo->status[$entry['info_type']][$settings]));
1491
						if(!in_array($settings,array('done','billed','cancelled','archive')) && $entry['info_percent'] == 100)
1492
						{
1493
							// Done entries will get changed right back if we don't change the completion too
1494
							$entry['info_percent'] = 10;
1495
						}
1496
						if(in_array($settings, array('not-started')) && $entry['info_percent'] > 0)
1497
						{
1498
							$entry['info_percent'] = 0;
1499
						}
1500
						$entry['info_status'] = $settings;
1501
						if($this->bo->write($entry, true,true,true,$skip_notifications))
1502
						{
1503
							$success++;
1504
						}
1505
					}
1506
					else
1507
					{
1508
						$msg .= lang('Invalid status for entry type %1.', lang($this->bo->enums['type'][$entry['info_type']]));
1509
						$failed++;
1510
					}
1511
					break;
1512
1513
				case 'cat':
1514
					if($settings)
1515
					{
1516
						$cat_name = Api\Categories::id2name($settings);
1517
						$action_msg = lang('changed category to %1', $cat_name);
1518
					}
1519
					else
1520
					{
1521
						$action_msg = lang('removed category');
1522
					}
1523
					$entry['info_cat'] = $settings;
1524
					if($this->bo->write($entry, true,true,true,$skip_notifications))
1525
					{
1526
						$success++;
1527
					}
1528
					else
1529
					{
1530
						$failed++;
1531
					}
1532
					break;
1533
1534
				case 'responsible':
1535
					list($add_remove, $user_str) = explode('_', $settings, 2);
1536
					$action_msg = ($add_remove == 'ok' ? lang('changed') : ($add_remove == 'add' ? lang('added') : lang('removed'))) . ' ';
1537
					$names = array();
1538
					$users = explode(',', $user_str);
1539
					foreach($users as $account_id)
1540
					{
1541
						$names[] = Api\Accounts::username($account_id);
1542
					}
1543
					$action_msg .= implode(', ', $names);
1544
					if($add_remove == 'ok')
1545
					{
1546
						$entry['info_responsible'] = (array)$users;
1547
					}
1548
					else
1549
					{
1550
						$function = $add_remove == 'add' ? 'array_merge' : 'array_diff';
1551
						$entry['info_responsible'] = array_unique($function($entry['info_responsible'], (array)$users));
1552
					}
1553
					if($this->bo->write($entry, true,true,true,$skip_notifications))
1554
					{
1555
						$success++;
1556
					}
1557
					else
1558
					{
1559
						$failed++;
1560
					}
1561
					break;
1562
				case 'startdate':
1563
				case 'enddate':
1564
					$field = $action == 'startdate' ? 'info_startdate' : 'info_enddate';
1565
					list($ok, $date) = explode('_', $settings, 2);
1566
					$entry[$field] = $date ? Api\DateTime::to($date, 'ts') : null;
1567
					$action_msg = lang('changed');
1568
					if($this->bo->write($entry, true,true,true,$skip_notifications))
1569
					{
1570
						$success++;
1571
					}
1572
					else
1573
					{
1574
						$failed++;
1575
					}
1576
					break;
1577
			}
1578
		}
1579
		return $failed == 0;
1580
	}
1581
1582
	/**
1583
	 * Closes an infolog
1584
	 *
1585
	 * @param int|array $values=0 info_id (default _GET[info_id])
1586
	 * @param string $_referer=''
1587
	 * @param boolean $closesingle=false
1588
	 */
1589
	function close($values=0,$_referer='',$closesingle=false,$skip_notification = false)
1590
	{
1591
		//echo "<p>".__METHOD__."($values,$referer,$closeall)</p>\n";
1592
		$info_id = (int) (is_array($values) ? $values['info_id'] : ($values ? $values : $_GET['info_id']));
1593
		$referer = is_array($values) ? $values['referer'] : $_referer;
1594
1595
		if ($info_id)
1596
		{
1597
			$info = $this->bo->read($info_id);
1598
			#_debug_array($info);
1599
			$status = $info['info_status'];
1600
			// closed stati assumed array('done','billed','cancelled')
1601
			if (isset($this->bo->status[$info['info_type']]['done'])) {
1602
				$status ='done';
1603
			} elseif (isset($this->bo->status[$info['info_type']]['billed'])) {
1604
				$status ='billed';
1605
			} elseif (isset($this->bo->status[$info['info_type']]['cancelled'])) {
1606
				$status ='cancelled';
1607
			}
1608
			#_debug_array($status);
1609
			$values = array(
1610
				'info_id'     => $info_id,
1611
				'info_type'   => $info['info_type'],
1612
				'info_status' => $status,
1613
				'info_percent'=> 100,
1614
				'info_datecompleted' => $this->bo->now_su,
0 ignored issues
show
Bug introduced by
The property now_su does not seem to exist on infolog_bo.
Loading history...
1615
			);
1616
			$this->bo->write($values, true,true,true,$skip_notification);
1617
1618
			$query = array('action'=>'sp','action_id'=>$info_id);
1619
			if (!$closesingle) {
1620
				foreach((array)$this->bo->search($query) as $info)
1621
				{
1622
					if ($info['info_id_parent'] == $info_id)	// search also returns linked entries!
1623
					{
1624
						$this->close($info['info_id'],$referer,$closesingle,$skip_notification);	// we call ourselfs recursive to process subs from subs too
1625
					}
1626
				}
1627
			}
1628
		}
1629
		if ($referer) $this->tmpl->location($referer);
1630
	}
1631
1632
	/**
1633
	 * Deletes an InfoLog entry
1634
	 *
1635
	 * @param array|int $values info_id (default _GET[info_id])
1636
	 * @param string $_referer
1637
	 * @param string $called_by
1638
	 * @param boolean $skip_notification Do not send notification of deletion
1639
	 */
1640
	function delete($values=0,$_referer='',$called_by='',$skip_notification=False)
1641
	{
1642
		$info_id = (int) (is_array($values) ? $values['info_id'] : ($values ? $values : $_GET['info_id']));
1643
		$referer = is_array($values) ? $values['referer'] : $_referer;
1644
1645
		if (!is_array($values) && $info_id > 0 && !$this->bo->anzSubs($info_id))	// entries without subs get confirmed by javascript
1646
		{
1647
			$values = array('delete' => true);
1648
		}
1649
		//echo "<p>infolog_ui::delete(".print_r($values,true).",'$referer','$called_by') info_id=$info_id</p>\n";
1650
1651
		if (is_array($values) || $info_id <= 0)
1652
		{
1653
			if (($values['delete'] || $values['delete_subs']) && $info_id > 0 && $this->bo->check_access($info_id,Acl::DELETE))
1654
			{
1655
				$deleted = $this->bo->delete($info_id,$values['delete_subs'],$values['info_id_parent'], $skip_notification);
1656
			}
1657
			if ($called_by)		// direct call from the same request
1658
			{
1659
				return $deleted ? lang('InfoLog entry deleted') : '';
1660
			}
1661
			if ($values['called_by'] == 'edit')	// we run in the edit popup => give control back to edit
1662
			{
1663
				$this->edit(array(
1664
					'info_id' => $info_id,
1665
					'button'  => array('deleted' => true),	// not delete!
1666
					'referer' => $referer,
1667
					'msg'     => $deleted ? lang('Infolog entry deleted') : '',
1668
				));
1669
			}
1670
			return $referer ? $this->tmpl->location($referer) : $this->index();
1671
		}
1672
		$readonlys = $values = array();
1673
		$values['main'][1] = $this->get_info($info_id,$readonlys['main']);
1674
1675
		$this->tmpl->read('infolog.delete');
1676
1677
		$values['nm'] = array(
1678
			'action'         => 'sp',
1679
			'action_id'      => $info_id,
1680
			'options-filter' => $this->filters,
1681
			'get_rows'       => 'infolog.infolog_ui.get_rows',
1682
			'no_filter2'     => True
1683
		);
1684
		$values['main']['no_actions'] = $values['nm']['no_actions'] = True;
1685
1686
		$persist['info_id'] = $info_id;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$persist was never initialized. Although not strictly required by PHP, it is generally a good practice to add $persist = array(); before regardless.
Loading history...
1687
		$persist['referer'] = $referer;
1688
		$persist['info_id_parent'] = $values['main'][1]['info_id_parent'];
1689
		$persist['called_by'] = $called_by;
1690
1691
		$GLOBALS['egw_info']['flags']['app_header'] = lang('InfoLog').' - '.lang('Delete');
1692
		$GLOBALS['egw_info']['flags']['params']['manual'] = array('page' => 'ManualInfologDelete');
1693
1694
		$this->tmpl->exec('infolog.infolog_ui.delete',$values,array(),$readonlys,$persist,$called_by == 'edit' ? 2 : 0);
1695
	}
1696
1697
	/**
1698
	 * Edit/Create an InfoLog Entry
1699
	 *
1700
	 * @param array $content Content from the eTemplate Exec call or info_id on inital call
1701
	 * @param string $action Name of an app of 'sp' for a infolog-sub
1702
	 * @param int $action_id of app-entry to which a link is created
1703
	 * @param string $type Type of log-entry: note,todo,task
1704
	 * @param string $referer array with param/get-vars of the refering page
1705
	 */
1706
	function edit($content = null,$action = '',$action_id=0,$type='',$referer='')
1707
	{
1708
		if (($submit = is_array($content)))
1709
		{
1710
			//echo "infolog_ui::edit: content="; _debug_array($content);
1711
			$info_id   = $content['info_id'];
1712
			$action    = $content['action'];    unset($content['action']);
1713
			$action_id = $content['action_id']; unset($content['action_id']);
1714
			$referer   = $content['referer'];   unset($content['referer']);
1715
			$no_popup  = $content['no_popup'];  unset($content['no_popup']);
1716
1717
			$button = @key($content['button']);
1718
			if (!$button && $action) $button = $action;	// action selectbox
1719
			//info_cc expects an comma separated string
1720
			//error_log(__METHOD__.__LINE__.array2string($content));
1721
			if (empty($content['info_cc'])) $content['info_cc'] = "";
1722
			if (is_array($content['info_cc']))
1723
			{
1724
				foreach($content['info_cc'] as $i => $value)
1725
				{
1726
					//imap_rfc822 should not be used, but it works reliable here, until we have some regex solution or use horde stuff
1727
					$addresses = imap_rfc822_parse_adrlist($value, '');
1728
					//error_log(__METHOD__.__LINE__.$value.'->'.array2string($addresses[0]));
1729
					$content['info_cc'][$i]=$addresses[0]->host ? $addresses[0]->mailbox.'@'.$addresses[0]->host : $addresses[0]->mailbox;
1730
				}
1731
				if (!empty($content['info_cc'])) $content['info_cc'] = implode(',',$content['info_cc']);
1732
			}
1733
			unset($content['button']);
1734
			if ($button)
1735
			{
1736
				// Copy or schedule Infolog
1737
				if (in_array($button,array('copy','schedule','ical','tracker')))
1738
				{
1739
					$action = $button;
1740
					if (!$info_id || $this->bo->check_access($info_id,Acl::EDIT))
1741
					{
1742
						$button = 'apply';	// need to store infolog first
1743
					}
1744
				}
1745
				//Validate the enddate must be grather than startdate
1746
				if (!empty($content['info_enddate']) && !empty($content['info_startdate']))
1747
				{
1748
					$duration_date = $content['info_enddate']-$content['info_startdate'];
1749
					if ($duration_date < 0)
1750
					{
1751
						$this->tmpl->set_validation_error('info_startdate', lang('Startdate must be before Enddate!!!'));
1752
						$button = $action = '';	// stop save or apply
1753
					}
1754
				}
1755
				//echo "<p>infolog_ui::edit(info_id=$info_id) '$button' button pressed, content="; _debug_array($content);
1756
				if (($button == 'save' || $button == 'apply') && isset($content['info_subject']) && empty($content['info_subject']))
1757
				{
1758
					$this->tmpl->set_validation_error('info_subject',lang('Field must not be empty !!!'));
1759
					$button = $action = '';	// stop save or apply
1760
				}
1761
				if (($button == 'save' || $button == 'apply') && $info_id)
1762
				{
1763
					$old = $this->bo->read($info_id);
1764
					if (!($edit_acl = $this->bo->check_access($info_id,Acl::EDIT)))
1765
					{
1766
						$status_only = $this->bo->is_responsible($old);
1767
						$undelete = $this->bo->check_access($old,infolog_bo::ACL_UNDELETE);
1768
					}
1769
					// enddate in the past gives warning
1770
					if (isset($content['info_enddate'])
1771
							&& $content['info_enddate'] < $this->bo->user_time_now
1772
							&& !$this->bo->allow_past_due_date && !($content['info_status'] == 'done'
1773
							|| $content['info_status'] == 'archive'))
1774
					{
1775
						$this->tmpl->set_validation_error('info_enddate', lang('Due date must be in the future!!!'));
1776
					}
1777
				}
1778
				if (($button == 'save' || $button == 'apply') && (!$info_id || $edit_acl || $status_only || $undelete))
1779
				{
1780
					$operation = $info_id ?  'edit' : 'add';
1781
1782
					if (is_array($content['link_to']['to_id']) && count($content['link_to']['to_id']))
1783
					{
1784
						$content['info_link_id'] = 0;	// as field has to be int
1785
					}
1786
					$active_tab = $content['tabs'];
1787
					if (!($info_id = $this->bo->write($content, true, true, true, $content['no_notifications'])))
1788
					{
1789
						$content['msg'] = $info_id !== 0 || !$content['info_id'] ? lang('Error: saving the entry') :
1790
							lang('Error: the entry has been updated since you opened it for editing!').'<br />'.
1791
							lang('Copy your changes to the clipboard, %1reload the entry%2 and merge them.','<a href="'.
0 ignored issues
show
Unused Code introduced by
The call to lang() has too many arguments starting with '<a href="' . htmlspecia...' => $referer))) . '">'. ( Ignorable by Annotation )

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

1791
							/** @scrutinizer ignore-call */ 
1792
       lang('Copy your changes to the clipboard, %1reload the entry%2 and merge them.','<a href="'.

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...
1792
								htmlspecialchars(Egw::link('/index.php',array(
1793
									'menuaction' => 'infolog.infolog_ui.edit',
1794
									'info_id'    => $content['info_id'],
1795
									'no_popup'   => $no_popup,
1796
									'referer'    => $referer,
1797
								))).'">','</a>');
1798
						$button = $action = '';	// not exiting edit
1799
						$info_id = $content['info_id'];
1800
					}
1801
					else
1802
					{
1803
						$GLOBALS['egw']->preferences->add('infolog','preferred_type',$content['info_type']);
1804
						$GLOBALS['egw']->preferences->save_repository(false,'user',false);
1805
						$content['msg'] = lang('InfoLog entry saved');
1806
						Framework::refresh_opener($content['msg'],'infolog',$info_id,$operation);
1807
					}
1808
					$content['tabs'] = $active_tab;
1809
1810
					$pm_links = Link::get_links('infolog',$content['info_id'],'projectmanager');
1811
1812
					$content['link_to']['to_app'] = 'infolog';
1813
					$content['link_to']['to_id'] = $info_id;
1814
1815
					if ($info_link_id && strpos($info_link_id,':') !== false)	// updating info_link_id if necessary
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $info_link_id does not exist. Did you maybe mean $info_id?
Loading history...
1816
					{
1817
						list($app,$id) = explode(':',$info_link_id);
1818
						$link = Link::get_link('infolog',$info_id,$app,$id);
1819
						if ((int) $content['info_link_id'] != (int) $link['link_id'])
1820
						{
1821
							$content['info_link_id'] = $link['link_id'];
1822
1823
							$to_write = array(
1824
								'info_id'      => $content['info_id'],
1825
								'info_link_id' => $content['info_link_id'],
1826
								'info_from'    => $content['info_from'],
1827
								'info_type'    => $content['info_type'],
1828
								'info_owner'   => $content['info_owner'],
1829
							);
1830
							//echo "<p>updating info_link_id: ".print_r($to_write,true)."</p>\n";
1831
							$this->bo->write($to_write,False,true,true,true);	// last true = no notifications, as no real change
1832
1833
							// Do not override info_contact if is already filled with contact
1834
							if ($content['info_contact'])
1835
							{
1836
								unset($to_write['info_contact']);
1837
								unset($to_write['blur_title']);
1838
							}
1839
1840
							// we need eg. the new modification date, for further updates
1841
							$content = array_merge($content,$to_write);
1842
						}
1843
					}
1844
1845
					// Need to purge description history after encryption?
1846
					if($content['clean_history'])
1847
					{
1848
						$history = new Api\Storage\History('infolog');
1849
						$record_count = $history->delete_field($info_id, 'De');
0 ignored issues
show
Unused Code introduced by
The assignment to $record_count is dead and can be removed.
Loading history...
1850
					}
1851
				}
1852
				elseif ($button == 'delete' && $info_id > 0)
1853
				{
1854
					if (!$referer && $action) $referer = array(
1855
						'menuaction' => 'infolog.infolog_ui.index',
1856
						'action' => $action,
1857
						'action_id' => $action_id
1858
					);
1859
					if (!($content['msg'] = $this->delete($info_id,$referer,'edit'))) return;	// checks ACL first
1860
1861
					Framework::refresh_opener($content['msg'],'infolog',$info_id,'delete');
1862
				}
1863
				// called again after delete confirmation dialog
1864
				elseif ($button == 'deleted'  && $content['msg'])
1865
				{
1866
					Framework::refresh_opener($content['msg'],'infolog',$info_id,'delete');
1867
				}
1868
				if ($button == 'save' || $button == 'cancel' || $button == 'delete' || $button == 'deleted')
1869
				{
1870
					if ($no_popup)
1871
					{
1872
						Egw::redirect_link($referer,array('msg' => $content['msg']));
1873
					}
1874
					Framework::window_close();
1875
				}
1876
			}
1877
			// on a type-change, set the status to the default status of that type, if the actual status is not supported by the new type
1878
			if (!array_key_exists($content['info_status'],$this->bo->status[$content['info_type']]))
1879
			{
1880
				$content['info_status'] = $this->bo->status['defaults'][$content['info_type']];
1881
				// Make sure we don't end up with invalid status / percent combinations
1882
				if ($content['info_status'] != 'done')
1883
				{
1884
					$content['info_datecompleted'] = '';
1885
					if((int)$content['info_percent'] === 100)
1886
					{
1887
						$content['info_percent'] = 10;
1888
					}
1889
				}
1890
				else
1891
				{
1892
					$content['info_percent'] = 100;
1893
				}
1894
				if($content['info_status'] != 'not-started' && (int)$content['info_percent'] == 0)
1895
				{
1896
					$content['info_percent'] = 10;
1897
				}
1898
			}
1899
		}
1900
		else	// new call via GET
1901
		{
1902
			//echo "<p>infolog_ui::edit: info_id=$info_id,  action='$action', action_id='$action_id', type='$type', referer='$referer'</p>\n";
1903
			$action    = $action    ? $action    : $_REQUEST['action'];
1904
			$action_id = $action_id ? $action_id : $_REQUEST['action_id'];
1905
			$info_id   = $content   ? $content   : $_REQUEST['info_id'];
0 ignored issues
show
introduced by
$content is of type null, thus it always evaluated to false.
Loading history...
1906
			$type      = $type      ? $type      : $_REQUEST['type'];
1907
			$referer   = $referer !== '' ? $referer : ($_GET['referer'] ? $_GET['referer'] :
1908
				Api\Header\Referer::get('/index.php?menuaction=infolog.infolog_ui.index'));
1909
			if (strpos($referer, 'msg=') !== false) $referer = preg_replace('/([&?]{1})msg=[^&]+&?/','\\1',$referer);	// remove previou/old msg from referer
1910
			$no_popup  = $_GET['no_popup'];
1911
			$print = (int) $_REQUEST['print'];
1912
			//echo "<p>infolog_ui::edit: info_id=$info_id,  action='$action', action_id='$action_id', type='$type', referer='$referer'</p>\n";
1913
1914
			if (($content = $this->bo->read( $info_id || $action != 'sp' ? $info_id : $action_id )) === false)
1915
			{
1916
				Framework::window_close(lang('Permission denied!'));
1917
			}
1918
			if (is_numeric($_REQUEST['cat_id']))
1919
			{
1920
				$content['info_cat'] = (int)$_REQUEST['cat_id'];
1921
			}
1922
			if (!$content)
1923
			{
1924
				$content['info_cat'] = $this->prefs['cat_add_default'];
1925
			}
1926
			if ($_GET['msg']) $content['msg'] = strip_tags($_GET['msg']);	// dont allow HTML!
1927
1928
			switch($this->prefs['set_start'])
1929
			{
1930
				case 'date': default: $set_startdate = mktime(0,0,0,date('m',$this->bo->user_time_now),date('d',$this->bo->user_time_now),date('Y',$this->bo->user_time_now)); break;
1931
				case 'datetime':      $set_startdate = $this->bo->user_time_now; break;
1932
				case 'empty':         $set_startdate = 0; break;
1933
			}
1934
			if ((int)$content['info_link_id'] > 0 && !Link::get_link($content['info_link_id']))
1935
			{
1936
				$content['info_link_id'] = 0;	// link has been deleted
1937
				if (!$content['info_custom_link']) $content['info_from'] = '';
1938
			}
1939
			if (!$info_id && $action_id && $action == 'sp')    // new SubProject
1940
			{
1941
				if (!$this->bo->check_access($action_id,Acl::ADD))
1942
				{
1943
					return $referer ? $this->tmpl->location($referer) : $this->index(0,$action,$action_id);
1944
				}
1945
			}
1946
			else
1947
			{
1948
				$undelete = $this->bo->check_access($content,infolog_bo::ACL_UNDELETE);
1949
			}
1950
			$content['links'] = $content['link_to'] = array(
1951
				'to_id' => $info_id,
1952
				'to_app' => 'infolog',
1953
			);
1954
		}
1955
		// new call via GET or some actions handled here, as they can happen both ways ($_GET[action] or button/action in GUI)
1956
		if (!$submit || in_array($action,array('sp','copy','schedule','ical','to_tracker')))
1957
		{
1958
			switch ($action)
1959
			{
1960
				case 'schedule':
1961
					Egw::redirect_link('/index.php',array(
1962
						'menuaction' => 'calendar.calendar_uiforms.edit',
1963
						'link_app' => 'infolog',
1964
						'link_id' => $info_id,
1965
					));
1966
					break;
1967
				case 'ical':
1968
					$boical = new infolog_ical();
1969
					$result = $boical->exportVTODO($content,'2.0','PUBLISH',false);
1970
					Api\Header\Content::type('todo.ics', 'text/calendar');
1971
					echo $result;
1972
					exit();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
1973
				case 'sp':
1974
				case 'copy':
1975
					$info_id = 0;
1976
					$this->create_copy($content, $action == 'sp');
1977
					if ($action == 'sp')	// for sub-entries use type or category, like for new entries
1978
					{
1979
						if ($type) $content['info_type'] = $type;
1980
						if (is_numeric($_REQUEST['cat_id'])) $content['info_cat'] = (int) $_REQUEST['cat_id'];
1981
					}
1982
					unset($action);	// it get stored in $content and will cause an other copy after [apply]
1983
					break;
1984
				case 'to_tracker':
1985
					Egw::redirect_link('/index.php',array(
1986
						'menuaction' => 'tracker.tracker_ui.edit',
1987
						Link::get_registry('tracker', 'add_app').'[]' => 'infolog',
1988
						Link::get_registry('tracker','add_id').'[]' => $info_id,
1989
					));
1990
					break;
1991
				case 'projectmanager':
1992
					$content['pm_id'] = $action_id;
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment if this fall-through is intended.
Loading history...
1993
				default:	// to allow other apps to participate
1994
					$content['info_subject'] = Link::title($action, $id);
1995
					$action_ids = explode(',',$action_id);
1996
					if(count($action_ids) == 1)
1997
					{
1998
						$content['info_contact'] = array('app' => $action, 'id' => $action_id);
1999
					}
2000
					foreach ($action_ids as $n => $id)
2001
					{
2002
						Link::link('infolog', $content['link_to']['to_id'], $action, $id);
2003
2004
						// calling "infolog_set" hook for first, in case app wants to set some more values
2005
						if (!$n && ($set = Api\Hooks::single(array('location'=>'infolog_set','id'=>$action_id),$action)))
2006
						{
2007
							foreach((array)$set['link_app'] as $i => $l_app)
2008
							{
2009
								if (($l_id=$set['link_id'][$i])) Link::link('infolog',$content['link_to']['to_id'],$l_app,$l_id);
2010
							}
2011
							unset($set['link_app']);
2012
							unset($set['link_id']);
2013
2014
							$content = array_merge($content, $set);
2015
						}
2016
					}
2017
					// fall through
2018
				case '':
2019
					if ($info_id)
2020
					{
2021
						if (!isset($pm_links))
2022
						{
2023
							$pm_links = Link::get_links('infolog',$info_id,'projectmanager');
2024
						}
2025
						break;	// normal edit
2026
					}
2027
				case 'new':		// new entry, set some defaults, if not set by infolog_set hook
2028
					if (empty($content['info_startdate'])) $content['info_startdate'] = (int) $_GET['startdate'] ? (int) $_GET['startdate'] : $set_startdate;
2029
					if (empty($content['info_priority'])) $content['info_priority'] = 1; // normal
2030
					$content['info_owner'] = $this->user;
2031
					if ($type != '' && empty($content['info_type']))
2032
					{
2033
						$content['info_type'] = $type;
2034
					}
2035
					else if ($type == '' && empty($content['info_type']) && isset($GLOBALS['egw_info']['user']['preferences']['infolog']['preferred_type']))
2036
					{
2037
						$content['info_type'] = $GLOBALS['egw_info']['user']['preferences']['infolog']['preferred_type'];
2038
					}
2039
					if (empty($content['info_status'])) $content['info_status'] = $this->bo->status['defaults'][$content['info_type']];
2040
					if (empty($content['info_percent'])) $content['info_percent'] = $content['info_status'] == 'done' ? '100%' : '0%';
2041
					break;
2042
			}
2043
			if (!isset($this->bo->enums['type'][$content['info_type']]))
2044
			{
2045
				$content['info_type'] = 'note';
2046
			}
2047
		}
2048
		// group owners
2049
		$types = $this->bo->enums['type'];
2050
		if ($this->bo->group_owners)
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->bo->group_owners 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...
2051
		{
2052
			// remove types owned by groups the user has no edit grant (current type is made readonly)
2053
			foreach($this->bo->group_owners as $type => $group)
2054
			{
2055
				if (!($this->bo->grants[$group] & Acl::EDIT))
2056
				{
2057
					if ($type == $content['info_type'])
2058
					{
2059
						//echo "<p>setting type to r/o as user has no edit rights from group #$group</p>\n";
2060
						$readonlys['info_type'] = true;
2061
					}
2062
					else
2063
					{
2064
						unset($types[$type]);
2065
					}
2066
				}
2067
			}
2068
			// set group as owner if type has a group-owner set
2069
			if (isset($this->bo->group_owners[$content['info_type']]))
2070
			{
2071
				$content['info_owner'] = $this->bo->group_owners[$content['info_type']];
2072
				// Dont allow to change the type, if user has no delete rights from the group-owner
2073
				if ($info_id && !($this->bo->grants[$content['info_owner']] & Acl::DELETE))
2074
				{
2075
					//echo "<p>setting type to r/o as user has no delete rights from group #$group</p>\n";
2076
					$readonlys['info_type'] = true;
2077
				}
2078
				// disable info_access for group-owners
2079
				$readonlys['info_access'] = true;
2080
			}
2081
			elseif($GLOBALS['egw']->accounts->get_type($content['info_owner']) == 'g')
2082
			{
2083
				$content['info_owner'] = $this->user;
2084
			}
2085
		}
2086
		$preserv = $content;
2087
2088
		// Don't preserve message or links
2089
		unset($preserv['msg']);
2090
		unset($preserv['links']); unset($preserv['link_to']);
2091
2092
		// for no edit rights or implizit edit of responsible user make all fields readonly, but status and percent
2093
		if ($info_id && !$this->bo->check_access($content, Acl::EDIT) && !$undelete)
2094
		{
2095
			$readonlys['__ALL__'] = true;	// make all fields not explicitly set readonly
2096
			if ($this->bo->is_responsible($content))
2097
			{
2098
				foreach($this->bo->responsible_edit as $name)
2099
				{
2100
					$readonlys[$name] = false;
2101
				}
2102
				$readonlys['timestamp'] = $readonlys['info_des'];
2103
				$readonlys['button[edit]'] = $readonlys['button[save]'] = $readonlys['button[apply]'] = $readonlys['no_notifications'] = false;
2104
			}
2105
			$readonlys['action'] = $readonlys['button[cancel]'] = false;	// always allowed
2106
		}
2107
		elseif (!$info_id)
2108
		{
2109
			$readonlys['action'] = true;
2110
		}
2111
		// ToDo: use the old status before the delete
2112
		if ($info_id && $undelete)
2113
		{
2114
			$content['info_status'] = $this->bo->status['defaults'][$content['info_type']];
2115
			$this->tmpl->setElementAttribute('button[save]', 'label', 'Un-Delete');
2116
		}
2117
2118
		if (!($readonlys['button[delete]'] = !$info_id || !$this->bo->check_access($content, Acl::DELETE)))
2119
		{
2120
			$content['info_anz_subs'] = $this->bo->anzSubs($info_id);	// to determine js confirmation of delete or not
2121
		}
2122
		$GLOBALS['egw_info']['flags']['app_header'] = lang($this->messages[$info_id ? 'edit' : ($action == 'sp' ? 'add_sub' : 'add')]);
2123
2124
		// use a typ-specific template (infolog.edit.xyz), if one exists, otherwise fall back to the generic one
2125
		if (!$this->tmpl->read('infolog.edit.'.$content['info_type']))
2126
		{
2127
			$this->tmpl->read($print ? 'infolog.edit.print':'infolog.edit');
2128
		}
2129
		if ($this->bo->has_customfields($content['info_type']))
2130
		{
2131
			$content['customfields'] = $content['info_type'];
2132
		}
2133
		else
2134
		{
2135
			$readonlys['tabs']['customfields'] = true;
2136
		}
2137
		if (!isset($GLOBALS['egw_info']['user']['apps']['projectmanager']))
2138
		{
2139
			$readonlys['tabs']['project'] = true;	// disable the project tab
2140
		}
2141
2142
		$content['duration_format'] = $this->duration_format;
2143
		$content['hours_per_workday'] = $this->hours_per_workday;
2144
		if ($this->prefs['show_id']) $content['info_number'] = $info_id;
2145
2146
		// Check no notification preference, update if type changed
2147
		if($content['info_type'] != $content['old_type'])
2148
		{
2149
			$content['no_notifications'] = in_array($content['info_type'], !is_array($this->prefs['no_notification_types']) ?
2150
					explode(',',$this->prefs['no_notification_types']):
2151
					$this->prefs['no_notification_types']
2152
			);
2153
		}
2154
2155
		$content['info_anz_subs'] = (int)$content['info_anz_subs'];	// gives javascript error if empty!
2156
2157
		$old_pm_id = is_array($pm_links) ? array_shift($pm_links) : $content['old_pm_id'];
2158
		unset($content['old_pm_id']);
2159
2160
		if ($info_id && $this->bo->history)
2161
		{
2162
			$content['history'] = array(
2163
				'id'  => $info_id,
2164
				'app' => 'infolog',
2165
				'status-widgets' => array(
2166
					'Ty' => $types,
2167
					//'Li',	// info_link_id
2168
					'parent' => 'link-entry:infolog',
2169
					'Ca' => 'select-cat',
2170
					'Pr' => $this->bo->enums['priority'],
2171
					'Ow' => 'select-account',
2172
					//'Ac',	//	info_access: private||public
2173
					'St' => (array)$this->bo->status[$content['info_type']]+array('deleted' => 'deleted'),
2174
					'Pe' => 'select-percent',
2175
					'Co' => 'date-time',
2176
					'st' => 'date-time',
2177
					'Mo' => 'date-time',
2178
					'En' => 'date',
2179
					'Re' => 'select-account',
2180
					// PM fields, ToDo: access control!!!
2181
					'pT' => 'date-duration',
2182
					'uT' => 'date-duration',
2183
					'replanned' => 'date-duration',
2184
//					'pL' => 'projectmanager-pricelist',
2185
					'pr' => 'float',
2186
				),
2187
			);
2188
			$history_stati = array();
2189
			$tracking = new infolog_tracking($this);
2190
			foreach($tracking->field2history as $field => $history)
2191
			{
2192
				$history_stati[$history] = $tracking->field2label[$field];
2193
			}
2194
			// Modified date removed from field2history, we don't need that in the history
2195
			$history_stati['Mo'] = $tracking->field2label['info_datemodified'];
2196
			unset($tracking);
2197
		}
2198
		else
2199
		{
2200
			$readonlys['tabs']['history'] = true;
2201
		}
2202
		$sel_options = array(
2203
			'info_type'     => $types,
2204
			'info_priority' => $this->bo->enums['priority'],
2205
			'info_confirm'  => $this->bo->enums['confirm'],
2206
			'info_status'   => $this->bo->status[$content['info_type']],
2207
			'status'        => $history_stati,
2208
			'action'        => array(
2209
				'copy'  => array('label' => 'Copy', 'title' => 'Copy this Infolog'),
2210
				'sp'    => 'Sub-entry',
2211
				'print' => array('label' => 'Print', 'title' => 'Print this Infolog'),
2212
				'ical' => array('label' => 'Export iCal', 'title' => 'Export iCal'),
2213
				'to_tracker' => array('label' => 'Tracker', 'title' => 'Convert to a ticket'),
2214
			),
2215
		);
2216
		if ($GLOBALS['egw_info']['user']['apps']['calendar'])
2217
		{
2218
			$sel_options['action']['schedule'] = array('label' => 'Schedule', 'title' => 'Schedule appointment');
2219
		}
2220
		if ($GLOBALS['egw_info']['user']['apps']['stylite'] && !$GLOBALS['egw_info']['server']['disable_pgp_encryption'])
2221
		{
2222
			$content['encryption_ts'] = filemtime(EGW_SERVER_ROOT.'/stylite/js/infolog-encryption.js');
2223
		}
2224
		elseif ($GLOBALS['egw_info']['server']['disable_pgp_encryption'])
2225
		{
2226
			$readonlys['encrypt'] = true;
2227
		}
2228
		$GLOBALS['egw_info']['flags']['app_header'] = lang('InfoLog').' - '.
2229
			($content['status_only'] ? lang('Edit Status') : lang('Edit'));
2230
		$GLOBALS['egw_info']['flags']['params']['manual'] = array('page' => ($info_id ? 'ManualInfologEdit' : 'ManualInfologAdd'));
2231
		//error_log(substr($content['info_des'],1793,10));
2232
		//$content['info_des'] = substr($content['info_des'],0,1793);
2233
		//echo "<p>infolog_ui.edit(info_id='$info_id',action='$action',action_id='$action_id') readonlys="; print_r($readonlys); echo ", content = "; _debug_array($content);
2234
		//$content['info_cc'] is expected (by the widget) to be an array of emailaddresses, but is stored as comma separated string
2235
		if (!empty($content['info_cc'])&&!is_array($content['info_cc']))$content['info_cc'] = explode(',',$content['info_cc']);
2236
		$preserve = array_merge( $preserv, array(	// preserved values
2237
			'info_id'       => $info_id,
2238
			'action'        => $action,
2239
			'action_id'     => $action_id,
2240
			'referer'       => $referer,
2241
			'no_popup'      => $no_popup,
2242
			'old_pm_id'     => $old_pm_id,
2243
			'old_type'      => $content['info_type'],
2244
		));
2245
		$this->tmpl->exec('infolog.infolog_ui.edit',$content,$sel_options,$readonlys,$preserve,$no_popup ? 0 : 2);
2246
	}
2247
2248
	/**
2249
	 * Create copy or sub-entry from an entry currently read into $content
2250
	 *
2251
	 * Taking into account prefs and config about what to copy
2252
	 *
2253
	 * @param array &$content
2254
	 * @param boolean $create_sub true: create a sub-entry instead of a copy, default false to create a copy
2255
	 */
2256
	private function create_copy(array &$content, $create_sub=false)
2257
	{
2258
		$info_id = $content['info_id'];	// it will be unset by exclude-fields
2259
2260
		// empty fields configured to be excluded (also contains id, uid, ...)
2261
		$exclude_fields = $create_sub ? $this->bo->sub_excludefields : $this->bo->copy_excludefields;
2262
		foreach ($exclude_fields as $field)
2263
		{
2264
			unset($content[$field]);
2265
			if ($field == 'info_from') unset($content['info_link_id']);	// both together is called contact in UI
2266
		}
2267
		if ($create_sub)
2268
		{
2269
			$content['info_id_parent'] = $info_id;
2270
		}
2271
		// no startdate or startdate in the past --> set startdate from pref
2272
		if (!isset($content['info_startdate']) || $content['info_startdate'] < $this->bo->user_time_now)
2273
		{
2274
			switch($this->prefs['set_start'])
2275
			{
2276
				case 'date': default: $set_startdate = mktime(0,0,0,date('m',$this->bo->user_time_now),date('d',$this->bo->user_time_now),date('Y',$this->bo->user_time_now)); break;
2277
				case 'datetime':      $set_startdate = $this->bo->user_time_now; break;
2278
				case 'empty':         $set_startdate = 0; break;
2279
			}
2280
			$content['info_startdate'] = $set_startdate;
2281
		}
2282
		// enddate in the past --> uset it
2283
		if (isset($content['info_enddate']) || $content['info_enddate'] < $this->bo->user_time_now)
2284
		{
2285
			unset($content['info_enddate']);
2286
		}
2287
		if (!isset($content['info_type']))
2288
		{
2289
			$types = array_keys($this->get_validtypes());
2290
			$content['info_type'] = $types[0];
2291
		}
2292
		// get a consistent status, percent and date-completed
2293
		if (!isset($content['info_status'])) $content['info_status'] = $this->bo->status['defaults'][$content['info_type']];
2294
		if (!isset($content['info_percent'])) $content['info_percent'] = $content['info_status'] == 'done' ? '100%' : '0%';
2295
		$content['info_datecompleted'] =$content['info_status'] == 'done' ? $this->bo->user_time_now : 0;
2296
2297
		if (!isset($content['info_cat'])) $content['info_cat'] = $this->prefs['cat_add_default'];
2298
2299
		if(!is_array($content['link_to'])) $content['link_to'] = array();
2300
		$content['link_to']['to_app'] = 'infolog';
2301
		$content['link_to']['to_id'] = 0;
2302
		// Get links to be copied, if not excluded
2303
		if (!in_array('link_to',$exclude_fields) || !in_array('attachments',$exclude_fields))
2304
		{
2305
			foreach(Link::get_links($content['link_to']['to_app'], $info_id) as $link)
2306
			{
2307
				if ($link['app'] != Link::VFS_APPNAME && !in_array('link_to', $exclude_fields))
2308
				{
2309
					Link::link('infolog', $content['link_to']['to_id'], $link['app'], $link['id'], $link['remark']);
2310
				}
2311
				elseif ($link['app'] == Link::VFS_APPNAME && !in_array('attachments', $exclude_fields))
2312
				{
2313
					Link::link('infolog', $content['link_to']['to_id'], Link::VFS_APPNAME, array(
2314
						'tmp_name' => Link::vfs_path($link['app2'], $link['id2']).'/'.$link['id'],
2315
						'name' => $link['id'],
2316
					), $link['remark']);
2317
				}
2318
			}
2319
		}
2320
		$content['links'] = $content['link_to'];
2321
2322
		if ($content['info_link_id'])
2323
		{
2324
			$info_link_id = $content['info_link_id'];
2325
			// we need this if copy is triggered via context menu action
2326
			if (!isset($content['info_contact']) || empty($content['info_contact']) || $content['info_contact'] === 'copy:')
2327
			{
2328
				$linkinfos = Link::get_link($info_link_id);
2329
				$content['info_contact'] = $linkinfos['link_app1']=='infolog'?
2330
						array('app' => $linkinfos['link_app2'], 'id' => $linkinfos['link_id2']):
2331
						array('app' => $linkinfos['link_app1'], 'id' => $linkinfos['link_id1']);
2332
				if ($content['info_contact']['app'] == 'projectmanager')
2333
				{
2334
					$content['pm_id'] = $linkinfos['link_app1']=='projectmanager'? $linkinfos['link_id1']:$linkinfos['link_id2'];
2335
				}
2336
			}
2337
			unset($content['info_link_id']);
2338
		}
2339
		$content['info_owner'] = !(int)$this->owner || !$this->bo->check_perms(Acl::ADD,0,$this->owner) ? $this->user : $this->owner;
0 ignored issues
show
Bug introduced by
The method check_perms() does not exist on infolog_bo. ( Ignorable by Annotation )

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

2339
		$content['info_owner'] = !(int)$this->owner || !$this->bo->/** @scrutinizer ignore-call */ check_perms(Acl::ADD,0,$this->owner) ? $this->user : $this->owner;

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug Best Practice introduced by
The property owner does not exist on infolog_ui. Did you maybe forget to declare it?
Loading history...
2340
2341
		if (!empty($content['info_subject']))
2342
		{
2343
			if ($create_sub)
2344
			{
2345
				$config = Api\Config::read('infolog');
2346
				$prefix = lang(empty($config['sub_prefix']) ? 'Re:': $config['sub_prefix']);
2347
			}
2348
			else
2349
			{
2350
				$prefix = lang('Copy of:');
2351
			}
2352
			$content['info_subject'] = $prefix.' '.$content['info_subject'];
2353
		}
2354
		if (!$create_sub)
2355
		{
2356
			$content['msg'] .= ($content['msg']?"\n":'').lang('%1 copied - the copy can now be edited', lang(Link::get_registry('infolog','entry')));
0 ignored issues
show
Unused Code introduced by
The call to lang() has too many arguments starting with lang(EGroupware\Api\Link...ry('infolog', 'entry')). ( Ignorable by Annotation )

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

2356
			$content['msg'] .= ($content['msg']?"\n":'')./** @scrutinizer ignore-call */ lang('%1 copied - the copy can now be edited', lang(Link::get_registry('infolog','entry')));

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...
2357
		}
2358
	}
2359
2360
	function icon($cat,$id,$status='')
2361
	{
2362
		if (!$status || !($icon = $this->icons[$cat][$id.'_'.$status]))
2363
		{
2364
			$icon = $this->icons[$cat][$id];
2365
		}
2366
		if ($icon && !Api\Image::find('infolog', $icon))
2367
		{
2368
			$icon = False;
2369
		}
2370
		if (!$status || !($alt = $this->icons[$cat][$id.'_'.$status.'_alt']))
2371
		{
2372
			if (!($alt = $this->icons[$cat][$id.'_alt']))
2373
			{
2374
				$alt = $id;
2375
			}
2376
		}
2377
		return $icon ? Api\Html::image('infolog',$icon,lang($alt),'border=0') : lang($alt);
2378
	}
2379
2380
	/**
2381
	 * Infolog's site configuration
2382
	 *
2383
	 */
2384
	public function admin($content = array())
2385
	{
2386
		$fields = array(
2387
			'info_cat'      => 'Category',
2388
			'info_from'     => 'Contact',
2389
			'info_subject'  => 'Subject',
2390
			'info_des'      => 'Description',
2391
			'link_to'       => 'Links',
2392
			'info_priority' => 'Priority',
2393
			'info_location' => 'Location',
2394
			'info_planned_time' => 'Planned time',
2395
			'info_used_time'    => 'Used time',
2396
		);
2397
		$excludefields = array(
2398
			'info_cat'      => 'Category',
2399
			'info_from'     => 'Contact',
2400
			'info_subject'  => 'Subject',
2401
			'info_des'      => 'Description',
2402
			'link_to'       => 'Links',
2403
			'attachments'   => 'Attachments',
2404
			'info_priority' => 'Priority',
2405
			'info_location' => 'Location',
2406
			'info_planned_time' => 'Planned time',
2407
			'info_used_time'    => 'Used time',
2408
			'info_type' => 'Type',
2409
			'info_owner' => 'Owner',
2410
			'info_responsible' => 'Responsible',
2411
			'info_access' => 'Access',
2412
			'info_startdate' => 'Startdate',
2413
			'info_enddate' => 'Enddate',
2414
			'info_id_parent' => 'Parent',
2415
			'info_status' => 'Status',
2416
			'info_confirm' => 'Confirm',
2417
			'pl_id' => 'pricelist',
2418
			'info_price' => 'price',
2419
			'info_percent' => 'completed',
2420
			'info_datecompleted' => 'date completed',
2421
			'info_replanned_time' => 're-planned time',
2422
			'info_cc' => 'CC',
2423
		);
2424
		// add customfields to field list
2425
		foreach(Api\Storage\Customfields::get('infolog') as $name => $data)
2426
		{
2427
			$excludefields['#'.$name] = $data['label'];
2428
		}
2429
		$sub_excludefields = $excludefields;
2430
		unset($sub_excludefields['info_id_parent']);	// always set to parent!
2431
2432
		$config = Api\Config::read('infolog');
2433
		Api\Translation::add_app('infolog');
2434
2435
		if($content)
2436
		{
2437
			// Save
2438
			$button = key($content['button']);
2439
			if($button == 'save' || $button == 'apply')
2440
			{
2441
				$this->bo->responsible_edit = array('info_status','info_percent','info_datecompleted');
2442
2443
				if ($content['responsible_edit'])
2444
				{
2445
					$extra = array_intersect($content['responsible_edit'],array_keys($fields));
2446
					$this->bo->responsible_edit = array_unique(array_merge($this->bo->responsible_edit,$extra));
2447
				}
2448
				Api\Config::save_value('copy_excludefields', $content['copy_excludefields'] ? $content['copy_excludefields'] : null, 'infolog');
2449
				Api\Config::save_value('sub_excludefields', $content['sub_excludefields'] ? $content['sub_excludefields'] : array('*NONE*'), 'infolog');
2450
				Api\Config::save_value('responsible_edit', $this->bo->responsible_edit, 'infolog');
2451
				Api\Config::save_value('implicit_rights', $this->bo->implicit_rights = $content['implicit_rights'] == 'edit' ? 'edit' : 'read', 'infolog');
2452
				Api\Config::save_value('history', $this->bo->history = $content['history'], 'infolog');
2453
				Api\Config::save_value('index_load_cfs', implode(',', (array)$content['index_load_cfs']), 'infolog');
2454
				Api\Config::save_value('sub_prefix', $content['sub_prefix'], 'infolog');
2455
				Api\Config::save_value('allow_past_due_date', $content['allow_past_due_date'], 'infolog');
2456
				// Notifications
2457
				$notifications =& $config[infolog_tracking::CUSTOM_NOTIFICATION];
2458
				$notifications[$content['notification_type']] = $content['notification'];
2459
				Api\Config::save_value(infolog_tracking::CUSTOM_NOTIFICATION, $notifications,'infolog');
2460
			}
2461
2462
			if($button == 'save' || $button == 'cancel')
2463
			{
2464
				Egw::redirect_link('/index.php', array(
2465
					'menuaction' => 'admin.admin_ui.index',
2466
					'ajax' => 'true'
2467
				), 'admin');
2468
			}
2469
		}
2470
		else
2471
		{
2472
			// Load
2473
			$content = $config;
2474
2475
			$content['implicit_rights'] = $this->bo->implicit_rights;
2476
			$content['responsible_edit'] = $this->bo->responsible_edit;
2477
			$content['copy_excludefields'] = $this->bo->copy_excludefields;
2478
			$content['sub_excludefields'] = $this->bo->sub_excludefields;
2479
			$content['history'] = $this->bo->history;
2480
		}
2481
2482
		$GLOBALS['egw_info']['flags']['app_header'] = lang('InfoLog').' - '.lang('Site configuration');
2483
2484
		// Load selected custom notification
2485
		if(!$content['notification_type'])
2486
		{
2487
			$content['notification_type'] = '~global~';
2488
		}
2489
		$content['notification'] = $config[infolog_tracking::CUSTOM_NOTIFICATION][$content['notification_type']];
2490
		$sel_options = array(
2491
			'implicit_rights' => array(
2492
				'read' => 'read rights (default)',
2493
				'edit' => 'edit rights (full edit rights incl. making someone else responsible!)',
2494
			),
2495
			'responsible_edit' => $fields,
2496
			'copy_excludefields' => $excludefields,
2497
			'sub_excludefields' => $sub_excludefields,
2498
			'history'     => array(
2499
				'' => lang('No'),
2500
				'history' => lang('Yes, with purging of deleted items possible'),
2501
				'history_admin_delete' => lang('Yes, only admins can purge deleted items'),
2502
				'history_no_delete' => lang('Yes, noone can purge deleted items'),
2503
			),
2504
			'index_load_cfs' => $this->bo->enums['type'],
2505
			'notification_type' => array('~global~' => 'all') + $this->bo->enums['type']
2506
		);
2507
		$preserve['notification_old_type'] = $content['notification_type'];
0 ignored issues
show
Comprehensibility Best Practice introduced by
$preserve was never initialized. Although not strictly required by PHP, it is generally a good practice to add $preserve = array(); before regardless.
Loading history...
2508
		$this->tmpl->read('infolog.config');
2509
		$this->tmpl->exec('infolog.infolog_ui.admin',$content,$sel_options,array(),$preserve);
2510
	}
2511
2512
	/**
2513
	 * imports a mail as infolog
2514
	 *
2515
	 * @param array $mailContent = null content of mail
2516
	 * @return  array
2517
	 */
2518
	function mail_import(array $mailContent=null)
2519
	{
2520
		// It would get called from compose as a popup with egw_data
2521
		if (!is_array($mailContent) && ($_GET['egw_data']))
2522
		{
2523
			// get the mail raw data
2524
			Link::get_data ($_GET['egw_data']);
2525
			return false;
2526
		}
2527
2528
		return $this->edit($this->bo->import_mail($mailContent['addresses'],
2529
				$mailContent['subject'],
2530
				$mailContent['message'],
2531
				$mailContent['attachments'],
2532
				$mailContent['date']));
2533
	}
2534
2535
	/**
2536
	 * shows infolog in other applications
2537
	 *
2538
	 * @param $args['location'] location des hooks: {addressbook|projects|calendar}_view|infolog
2539
	 * @param $args['view']     menuaction to view, if location == 'infolog'
2540
	 * @param $args['app']      app-name, if location == 'infolog'
0 ignored issues
show
Documentation Bug introduced by
The doc comment app-name, if at position 0 could not be parsed: Unknown type name 'app-name' at position 0 in app-name, if.
Loading history...
2541
	 * @param $args['view_id']  name of the id-var for location == 'infolog'
2542
	 * @param $args[$args['view_id']] id of the entry
2543
	 * this function can be called for any app, which should include infolog: \
2544
	 * 	Api\Hooks::process(array( \
2545
	 * 		 * 'location' => 'infolog', \
2546
	 * 		 * 'app'      => <your app>, \
2547
	 * 		 * 'view_id'  => <id name>, \
2548
	 * 		 * <id name>  => <id value>, \
2549
	 * 		 * 'view'     => <menuaction to view an entry in your app> \
2550
	 * 	));
2551
	 */
2552
	function hook_view($args)
2553
	{
2554
		// Load JS for infolog actions
2555
		Framework::includeJS('.','app','infolog');
2556
2557
		switch ($args['location'])
2558
		{
2559
			case 'addressbook_view':
2560
				$app     = 'addressbook';
2561
				$view_id = 'ab_id';
2562
				$view_id2 = 'contact_id';
2563
				$view    = 'addressbook.addressbook_ui.view';
2564
				break;
2565
			case 'projects_view':
2566
				$app     = 'projects';
2567
				$view_id = 'project_id';
2568
				$view    = 'projects.uiprojects.view';
2569
				break;
2570
			default:
2571
				$app     = $args['app'];
2572
				$view_id = $args['view_id'];
2573
				$view    = $args['view'];
2574
		}
2575
		if (!is_array($args) || $args['debug'])
2576
		{
2577
			echo "<p>infolog_ui::hook_view("; print_r($args); echo "): app='$app', $view_id='$args[$view_id]', view='$view'</p>\n";
2578
		}
2579
		if (!isset($app) || !isset($args[$view_id]))
2580
		{
2581
			return False;
2582
		}
2583
		$this->called_by = $app;	// for read/save_sessiondata, to have different sessions for the hooks
0 ignored issues
show
Bug Best Practice introduced by
The property called_by does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
2584
2585
		// Set to calling app, so actions wind up in the correct place client side
2586
		$GLOBALS['egw_info']['flags']['currentapp'] = $app;
2587
2588
		Api\Translation::add_app('infolog');
2589
2590
		// Still want infolog Api\Categories though
2591
		$GLOBALS['egw']->categories = new Api\Categories('','infolog');
2592
		$this->index(null,$app,$args[$view_id],array(
2593
			'menuaction' => $view,
2594
			isset($view_id2) ? $view_id2 : $view_id => $args[$view_id]
2595
		),True);
2596
	}
2597
2598
	/**
2599
	 * Defines the fields for the csv export
2600
	 *
2601
	 * @param string $type infolog type to include only the matching custom fields if set
2602
	 * @return array
2603
	 */
2604
	function csv_export_fields($type=null)
2605
	{
2606
		$fields = array(
2607
			'info_type'          => lang('Type'),
2608
			'info_from'          => lang('Contact'),
2609
//			'info_link_id'       => lang('primary link'),
2610
			'info_cat'           => array('label' => lang('Category'),'type' => 'select-cat'),
2611
			'info_priority'      => lang('Priority'),
2612
			'info_owner'         => array('label' => lang('Owner'),'type' => 'select-account'),
2613
			'info_access'        => lang('Access'),
2614
			'info_status'        => lang('Status'),
2615
			'info_percent'       => lang('Completed'),
2616
			'info_datecompleted' => lang('Date completed'),
2617
			'info_datemodified'  => lang('Last modified'),
2618
			'info_modifier'      => array('label' => lang('Modifier'),'type' => 'select-account'),
2619
			'info_location'      => lang('Location'),
2620
			'info_startdate'     => lang('Startdate'),
2621
			'info_enddate'       => lang('Enddate'),
2622
			'info_responsible'   => array('label' => lang('Responsible'),'type' => 'select-account'),
2623
			'info_subject'       => lang('Subject'),
2624
			'info_des'           => lang('Description'),
2625
			'info_id'            => lang('Id'),
2626
			// PM fields
2627
			'info_planned_time'  => lang('planned time'),
2628
			'info_used_time'     => lang('used time'),
2629
			'pl_id'              => lang('pricelist'),
2630
			'info_price'         => lang('price'),
2631
		);
2632
		foreach($this->bo->timestamps as $name)
2633
		{
2634
			$fields[$name] = array('label' => $fields[$name],'type' => 'date-time');
2635
		}
2636
		foreach($this->bo->customfields as $name => $data)
2637
		{
2638
			if ($data['type2'] && $type && !in_array($type,explode(',',$data['type2']))) continue;
2639
2640
			$fields['#'.$name] = array(
2641
				'label' => $data['label'],
2642
				'type'  => $data['type'],
2643
			);
2644
		}
2645
		return $fields;
2646
	}
2647
}
2648