1<?php
2/**
3 * ProjectManager - Projects user interface
4 *
5 * @link http://www.egroupware.org
6 * @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
7 * @package projectmanager
8 * @copyright (c) 2005-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
13use EGroupware\Api;
14use EGroupware\Api\Link;
15use EGroupware\Api\Framework;
16use EGroupware\Api\Egw;
17use EGroupware\Api\Acl;
18use EGroupware\Api\Etemplate;
19
20/**
21 * ProjectManager UI: list and edit projects
22 */
23class projectmanager_ui extends projectmanager_bo
24{
25	/**
26	 * Functions to call via menuaction
27	 *
28	 * @var array
29	 */
30	var $public_functions = array(
31		'index' => true,
32		'list'	=> true,
33		'edit'  => true,
34		'view'  => true,
35	);
36	/**
37	 * Labels for pm_status, value - label pairs
38	 *
39	 * @var array
40	 */
41	static $status_labels;
42	/**
43	 * Labels for pm_access, value - label pairs
44	 *
45	 * @var array
46	 */
47	var $access_labels;
48	/**
49	 * Labels for mains- & sub-projects filter
50	 *
51	 * @var array
52	 */
53	var $filter_labels;
54
55	/**
56	 * Etemplate
57	 */
58	var $template;
59
60	/**
61	 * Constructor, calls the constructor of the extended class
62	 *
63	 * @return projectmanager_ui
64	 */
65	function __construct(Etemplate $etemplate = null)
66	{
67		parent::__construct();
68
69		if($etemplate === null)
70		{
71			$etemplate = new Etemplate();
72		}
73		$this->template = $etemplate;
74
75		static::$status_labels = array(
76			'active'    => lang('Active'),
77			'nonactive' => lang('Nonactive'),
78			'archive'   => lang('Archive'),
79			'template'  => lang('Template'),
80		);
81		if($this->history)
82		{
83			static::$status_labels[self::DELETED_STATUS] = lang('Deleted');
84		}
85		$this->access_labels = array(
86			'public'    => lang('Public'),
87			'anonym'    => lang('Anonymous public'),
88			'private'   => lang('Private'),
89		);
90		$this->filter_labels = array(
91			''			=> lang('All'),
92			'mains'		=> lang('Mainprojects'),
93			'subs'		=> lang('Subprojects'),
94		);
95	}
96
97
98	/**
99	 * Set up all project templates so the user can quickly switch between them,
100	 * with no reload needed
101	 */
102	public function index(array $content = null)
103	{
104		// Check ACL first, no read access will trigger redirects
105		if ((int) $_REQUEST['pm_id'])
106		{
107			$pm_id = (int) $_REQUEST['pm_id'];
108			// store the current project (only for index, as popups may be called by other parent-projects)
109		}
110		else if ($_GET['pm_id'])
111		{
112			// AJAX requests have pm_id only in GET, not REQUEST
113			$pm_id = (int)$_GET['pm_id'];
114		}
115		else
116		{
117			$pm_id = $GLOBALS['egw_info']['user']['preferences']['projectmanager']['current_project'];
118		}
119		if(!$this->check_acl(Acl::READ,$pm_id))
120		{
121			$pm_id = $_GET['pm_id'] = $GLOBALS['egw_info']['user']['preferences']['projectmanager']['current_project'] = 0;
122
123		}
124		if(!$this->check_acl(Acl::READ, $pm_id))
125		{
126			$this->data = array();
127		}
128		$this->pm_list();
129
130		$element_list = new projectmanager_elements_ui();
131		$element_list->index();
132
133		$gantt = new projectmanager_gantt();
134		$gantt->chart();
135
136		$prices = new projectmanager_pricelist_ui();
137		$prices->index();
138	}
139
140	/**
141	 * View a project
142	 */
143	function view()
144	{
145		$this->edit(null,true);
146	}
147
148	/**
149	 * Edit, add or view a project
150	 *
151	 * @var array $content content-array if called by process-exec
152	 * @var boolean $view only view project, default false, only used on first call !is_array($content)
153	 */
154	function edit($content=null,$view=false)
155	{
156		if ((int) $this->debug >= 1 || $this->debug == 'edit') $this->debug_message("projectmanager_ui::edit(,$view) content=".print_r($content,true));
157
158		$this->template->read('projectmanager.edit');
159
160		if (is_array($content))
161		{
162			$old_status = $content['old_status'];
163
164			if ($content['pm_id'])
165			{
166				$this->read($content['pm_id']);
167			}
168			else
169			{
170				$this->init();
171			}
172			$view = $content['view'] && !$content['edit'] || !$this->check_acl(Acl::EDIT);
173
174			if (!$content['view'])	// just changed to edit-mode, still counts as view
175			{
176				//echo "content="; _debug_array($content);
177				$this->data_merge($content);
178				//error_log("after data_merge data="); error_log(array2string($this->data));
179
180				// set the data of the pe_summary, if the project-value is unset
181				$pe_summary = $this->pe_summary();
182				$datasource = CreateObject('projectmanager.datasource');
183				foreach($datasource->name2id as $pe_name => $id)
184				{
185					$pm_name = str_replace('pe_','pm_',$pe_name);
186					// check if update is necessary, because a field has be set or changed
187					if (($content[$pm_name] || $pm_name == 'pm_completion' && $content[$pm_name] !== '') &&
188						($content[$pm_name] != $this->data[$pm_name] || !($this->data['pm_overwrite'] & $id)))
189					{
190						//error_log( "$pm_name set to '".$this->data[$pm_name]);
191						$this->data['pm_overwrite'] |= $id;
192					}
193					// or check if a field is no longer set, or the datasource changed => set it from the datasource
194					elseif (!$content[$pm_name] && ($pm_name != 'pm_completion' || $content[$pm_name] === '') &&
195						    ($this->data['pm_overwrite'] & $id) ||
196						    !($this->data['pm_overwrite'] & $id) && $this->data[$pm_name] != $pe_summary[$pe_name])
197					{
198						// if we have a change in the datasource, set pe_synced
199						if ($this->data[$pm_name] != $pe_summary[$pe_name])
200						{
201							$this->data['pm_synced'] = $this->now_su;
202						}
203						$this->data[$pm_name] = $pe_summary[$pe_name];
204						//echo "$pm_name re-set to default '".$this->data[$pm_name]."'<br>\n";
205						$this->data['pm_overwrite'] &= ~$id;
206					}
207				}
208				//echo "after foreach(datasource->name2id...) data="; _debug_array($this->data);
209				// process new and changed project-members
210				foreach((array)$content['member'] as $n => $uid)
211				{
212					if (!(int) $content['role'][$n])
213					{
214						unset($this->data['pm_members'][$uid]);
215					}
216					elseif ((int) $uid)
217					{
218						$this->data['pm_members'][(int)$uid] = array(
219							'member_uid' => (int) $uid,
220							'member_availibility' => empty($content['availibility'][$n]) ? 100.0 : $content['availibility'][$n],
221							'role_id'    => (int) $content['role'][$n],
222						);
223						if ($GLOBALS['egw_info']['user']['apps']['admin'] && $content['general_avail'][$n])
224						{
225							$this->set_availibility($uid,$content['general_avail'][$n]);
226						}
227					}
228				}
229			}
230			//echo "projectmanager_ui::edit(): data="; _debug_array($this->data);
231
232			if (($content['save'] || $content['apply']) && $this->check_acl(Acl::EDIT))
233			{
234				// generate project-number, taking into account a given parent-project
235				if (empty($this->data['pm_number']))
236				{
237					$parent_number = '';
238					if ($content['add_link'])
239					{
240						list($app,$app_id) = explode(':',$content['add_link'],2);
241						if ($app == 'projectmanager' && ($parent = $this->search(array('pm_id'=>$app_id),'pm_number')))
242						{
243							$parent_number = $parent[0]['pm_number'];
244						}
245					}
246					$this->generate_pm_number(true,$parent_number);
247				}
248				if ($this->not_unique())
249				{
250					$msg = lang('Error: project-ID already exist, choose an other one or have one generated by leaving it emtpy !!!');
251					unset($content['save']);	// dont exit
252				}
253				elseif ($ret = $this->save() != 0)
254				{
255					$msg = $ret === TRUE ?
256							lang('Error: the entry has been updated since you opened it for editing!').'<br />'.
257							lang('Copy your changes to the clipboard, %1reload the entry%2 and merge them.','<a href="'.
258								htmlspecialchars(Egw::link('/index.php',array(
259									'menuaction' => 'projectmanager.projectmanager_ui.edit',
260									'pm_id'    => $content['pm_id']
261								))).'">','</a>') :
262							lang('Error: saving the project (%1) !!!',$this->db->Error);
263					unset($content['save']);	// dont exit
264				}
265				else
266				{
267					$msg = lang('Project saved');
268
269					// create project already linked to a parent, in that case param of link-call need to be swaped
270					// as we want the new project to be the sub of the given project
271					if ($content['add_link'])
272					{
273						list($app,$app_id) = explode(':',$content['add_link'],2);
274						Link::link($app,$app_id,'projectmanager',$this->data['pm_id']);
275					}
276					// writing links for new entry, existing ones are handled by the widget itself
277					if (!$content['pm_id'] && is_array($content['link_to']['to_id']))
278					{
279						Link::link('projectmanager',$this->data['pm_id'],$content['link_to']['to_id']);
280
281						// check if we have dragged in images and fix their image urls
282						if (Etemplate\Widget\Vfs::fix_html_dragins('projectmanager', $this->data['pm_id'],
283							$content['link_to']['to_id'], $content['pm_description']))
284						{
285							Api\Storage\Base::update(array(
286								'pm_description' => $content['pm_description'],
287							));
288						}
289					}
290					if ($content['template'] && $new_id = $this->copy($content['template'],2))
291					{
292						$msg = lang('Template including elment-tree saved as new project');
293						$content['pm_id'] = $new_id;
294						unset($content['template']);
295					}
296					if ($content['status_sources'] && $old_status != $this->data['pm_status'])
297					{
298						ExecMethod2('projectmanager.projectmanager_elements_bo.run_on_sources','change_status',
299							array('pm_id'=>$this->data['pm_id']),$this->data['pm_status']);
300					}
301				}
302				if ($content['apply']) Framework::refresh_opener($msg, 'projectmanager', $this->data['pm_id'], 'edit');
303			}
304			if ($content['delete'] && $this->check_acl(Acl::DELETE))
305			{
306				$msg = $this->delete($pm_id,$content['delete_sources']) ? lang('Project deleted') : lang('Error: deleting project !!!');
307			}
308			if ($content['save'] || $content['delete'])	// refresh opener and output message
309			{
310				Framework::refresh_opener($msg,'projectmanager', $this->data['pm_id'], $content['save']?'edit':'delete');
311				Framework::window_close();
312				exit();
313			}
314			$template = $content['template'];
315		}
316		else
317		{
318			if ($_GET['msg']) $msg = strip_tags($_GET['msg']);
319
320			if ((int) $_GET['pm_id'])
321			{
322				$this->read((int) $_GET['pm_id']);
323			}
324			// for a new sub-project set some data from the parent
325			elseif ($_GET['link_app'] == 'projectmanager' && (int) $_GET['link_id'] && $this->read((int) $_GET['link_id']))
326			{
327				if (!$this->check_acl(Acl::READ))	// no read-rights for the parent, eg. someone edited the url
328				{
329					$GLOBALS['egw']->framework->render(lang('Permission denied !!!'));
330					exit();
331				}
332				$this->generate_pm_number(true,$parent_number=$this->data['pm_number']);
333				foreach(array('pm_id','pm_title','pm_description','pm_creator','pm_created','pm_modified','pm_modifier','pm_real_start','pm_real_end','pm_completion','pm_status','pm_used_time','pm_planned_time','pm_replanned_time','pm_used_budget','pm_planned_budget') as $key)
334				{
335					unset($this->data[$key]);
336				}
337				include_once(EGW_INCLUDE_ROOT.'/projectmanager/inc/class.datasource.inc.php');
338				$this->data['pm_overwrite'] &= PM_PLANNED_START | PM_PLANNED_END;
339			}
340			if((int)$_GET['template'] && $this->read((int) $_GET['template']))
341			{
342				if (!$this->check_acl(Acl::READ))	// no read-rights for the template, eg. someone edited the url
343				{
344					$GLOBALS['egw']->framework->render(lang('Permission denied !!!'));
345					exit();
346				}
347				// we do only stage 1 of the copy, so if the user hits cancel everythings Ok
348				$this->copy($template = (int) $_GET['template'],1,$parent_number);
349			}
350			if ($this->data['pm_id'])
351			{
352				if (!$this->check_acl(Acl::READ))
353				{
354					$GLOBALS['egw']->framework->render(lang('Permission denied !!!'));
355					exit();
356				}
357				if (!$this->check_acl(Acl::EDIT)) $view = true;
358			}
359			// no pm-number set, generate one
360			if (!$this->data['pm_number']) $this->generate_pm_number(true);
361
362			$old_status = $this->data['pm_status'];
363		}
364		if (!$pe_summary) $pe_summary = $this->pe_summary();
365
366		if (!isset($content['add_link']) && !$this->data->pm_id && isset($_GET['link_app']) && isset($_GET['link_id']) &&
367			preg_match('/^[a-z_0-9-]+:[:a-z_0-9-]+$/i',$_GET['link_app'].':'.$_GET['link_id']))	// gard against XSS
368		{
369			$add_link = $_GET['link_app'].':'.$_GET['link_id'];
370		}
371		else
372		{
373			$add_link = $content['add_link'];
374		}
375		$content = $this->data + array(
376			'msg'  => $msg,
377			'tabs' => $content['tabs'],
378			'view' => $view,
379			'ds'   => $pe_summary,
380			'link_to' => array(
381				'to_id' => $content['link_to']['to_id'] ? $content['link_to']['to_id'] : $this->data['pm_id'],
382				'to_app' => 'projectmanager',
383			),
384			'duration_format' => ','.$this->config['duration_format'],
385			'no_budget' => !$this->check_acl(EGW_ACL_BUDGET,0,true) || !$this->data['pm_accounting_type'] || in_array($this->data['pm_accounting_type'],array('status','times')) ||
386				$this->config['accounting_types'] && !array_intersect(!is_array($this->config['accounting_types']) ? explode(',',$this->config['accounting_types']) : $this->config['accounting_types'],array('budget','pricelist')),
387			'status_sources' => $content['status_sources'],
388		);
389		if ($add_link && !is_array($content['link_to']['to_id']))
390		{
391			list($app,$app_id) = explode(':',$add_link,2);
392			Link::link('projectmanager',$content['link_to']['to_id'],$app,$app_id);
393		}
394		$content['links'] = $content['link_to'];
395
396		$preserv = $this->data;
397		// empty not explicitly in the project set values
398		if (!is_object($datasource)) $datasource =& CreateObject('projectmanager.datasource');
399		foreach($datasource->name2id as $pe_name => $id)
400		{
401			$pm_name = str_replace('pe_','pm_',$pe_name);
402			if (!($this->data['pm_overwrite'] & $id) && !in_array($pm_name, array('cat_id', 'pm_title')))
403			{
404				$content[$pm_name] = $preserv[$pm_name] = '';
405			}
406		}
407		// check if user should inherit coordinator role from being part of a group set as coordinator member
408		$memberships = $GLOBALS['egw']->accounts->memberships($this->user);
409		$member_from_groups = array_intersect_key((array)$this->data['pm_members'], $memberships);
410		$coord_from_groups_roles = false;
411		foreach ($member_from_groups as $member_from_group => $member_acl)
412		{
413			if ($this->data['pm_members'][$member_from_group]['role_id'] == 1)
414			{
415				$coord_from_groups_roles = true;
416				break;
417			}
418		}
419
420		if(!is_array($this->config['accounting_types']))
421		{
422			$this->config['accounting_types'] = explode(',',$this->config['accounting_types']);
423		}
424		$readonlys = array(
425			'delete' => !$this->data['pm_id'] || !$this->check_acl(Acl::DELETE),
426			'edit' => !$view || !$this->check_acl(Acl::EDIT),
427			'tabs' => array(
428				'accounting' => !$this->check_acl(EGW_ACL_BUDGET) &&	// disable the tab, if no budget rights and no owner or coordinator
429					($this->config['accounting_types'] && count($this->config['accounting_types']) == 1 ||
430					!($this->data['pm_creator'] == $this->user || $this->data['pm_members'][$this->user]['role_id'] == 1 ||
431					$coord_from_groups_roles)) ||
432					$this->config['accounting_types'] == array('status') || $this->config['accounting_types'] == array('times'),
433				'custom' => !count($this->customfields),	// only show customfields tab, if there are some
434				'history' => !$this->data['pm_id'],        //suppress history for the first loading without ID
435			),
436			'customfields' => $view,
437			'general_avail[1]' => !$GLOBALS['egw_info']['user']['apps']['admin'],
438		);
439		if ($readonlys['delete']) $this->template->disable_cells('delete_sources');
440
441		if (!$this->check_acl(EGW_ACL_EDIT_BUDGET))
442		{
443			$readonlys['pm_planned_budget'] = $readonlys['pm_used_budget'] = true;
444			unset($content['pm_planned_budget']);
445			unset($content['pm_used_budget']);
446			unset($content['ds']['pe_planned_budget']);
447			unset($content['ds']['pe_used_budget']);
448		}
449		$n = 2;
450		foreach((array)$this->data['pm_members'] as $uid => $data)
451		{
452			$content['role'][$n] = $data['role_id'];
453			$content['member'][$n] = $data['member_uid'];
454			$content['availibility'][$n] = empty($data['member_availibility']) ? 100.0 : $data['member_availibility'];
455			if (!is_array($general_avail)) $general_avail = $this->get_availibility();
456			$content['general_avail'][$n] = empty($general_avail[$uid]) ? 100.0 : $general_avail[$uid];
457			$readonlys["general_avail[$n]"] = $view || !$GLOBALS['egw_info']['user']['apps']['admin'];
458			$readonlys["role[$n]"] = $readonlys["availibility[$n]"] = $view;
459			++$n;
460		}
461		//_debug_array($content);
462		$preserv += array(
463			'view'     => $view,
464			'add_link' => $add_link,
465			'member'   => $content['member'],
466			'template' => $template,
467			'old_status' => $old_status,
468		);
469		$this->instanciate('roles');
470
471		$sel_options = array(
472			'pm_status' => &self::$status_labels,
473			'pm_access' => &$this->access_labels,
474			'role'      => $this->roles->query_list(array(
475				'label' => 'role_title',
476				'title' => 'role_description',
477			),'role_id',array(
478				'pm_id' => array(0,(int)$this->data['pm_id'])
479			)),
480			'pm_accounting_type' => array(
481				'status' => 'No accounting, only status',
482				'times'  => 'No accounting, only times and status',
483				'budget' => 'Budget (no pricelist)',
484				'pricelist' => 'Budget and pricelist',
485			),
486		);
487		// Can't set deleted as a status
488		if($content['pm_status'] != self::DELETED_STATUS)
489		{
490			unset($sel_options['pm_status'][self::DELETED_STATUS]);
491		}
492		$content['history'] = array(
493				'id'  => $this->data['pm_id'],
494				'app' => 'projectmanager',
495				'status-widgets' => array(
496					'pm_modifier' => 'select-account',
497					'cat_id' => 'select-cat',
498					'pm_modified' => 'date-time',
499					'pm_planned_start' => 'date-time',
500					'pm_planned_end' => 'date-time',
501					'pm_real_start' => 'date-time',
502					'pm_real_end' => 'date-time',
503				),
504		);
505		$sel_options['status'] = $this->field2label;
506
507		if ($this->config['accounting_types'])	// only allow the configured types
508		{
509			$allowed = $this->config['accounting_types'];
510			if(!is_array($allowed))
511			{
512				$allowed = explode(',',$allowed);
513			}
514			foreach($sel_options['pm_accounting_type'] as $key => $label)
515			{
516				if (!in_array($key,$allowed)) unset($sel_options['pm_accounting_type'][$key]);
517			}
518			if (count($sel_options['pm_accounting_type']) == 1)
519			{
520				if(!$content['pm_accounting_type'])
521				{
522					reset($sel_options['pm_accounting_type']);
523					$content['pm_accounting_type'] = $preserv['pm_accounting_type'] =
524						key($sel_options['pm_accounting_type']);
525				}
526				$readonlys['pm_accounting_type'] = true;
527			}
528		}
529		if ($view)
530		{
531			foreach($this->db_cols as $name)
532			{
533				$readonlys[$name] = true;
534			}
535			$readonlys['save'] = $readonlys['apply'] = true;
536
537			// add fields not stored in the main-table
538			$readonlys['pm_members'] = $readonlys['edit_roles'] = true;
539
540			$readonlys['links'] = $readonlys['link_to'] = true;
541		}
542
543		$GLOBALS['egw_info']['flags']['app_header'] = lang('projectmanager') . ' - ' .
544			($this->data['pm_id'] ? ($view ? lang('View project') : lang('Edit project')) : lang('Add project'));
545		$this->template->exec('projectmanager.projectmanager_ui.edit',$content,$sel_options,$readonlys,$preserv,2);
546	}
547
548	/**
549	 * query projects for nextmatch in the projects-list
550	 *
551	 * reimplemented from Api\Storage\Base to disable action-buttons based on the Acl and make some modification on the data
552	 *
553	 * @param array $query
554	 * @param array &$rows returned rows/cups
555	 * @param array &$readonlys eg. to disable buttons based on Acl
556	 */
557	function get_rows(&$query_in,&$rows,&$readonlys)
558	{
559		// for unknown reason, order is sometimes set to an element column, eg. pe_modified
560		// need to fix that, as it gives a sql error otherwise
561		if (substr($query_in['order'], 0, 3) === 'pe_')
562		{
563			$query_in['order'] = 'pm_'.substr($query_in['order'], 3);
564		}
565		if (!$this->db->get_column_attribute($query_in['order'], $this->table_name,'projectmanager'))
566		{
567			$query_in['order'] = 'pm_modified';
568		}
569
570		$query = $query_in;
571		// Don't keep pm_id filter in seesion
572		unset($query_in['col_filter']['pm_id']);
573		Api\Cache::setSession('projectmanager', 'project_list',
574			array_diff_key ($query_in, array_flip(array('rows','actions','action_links','placeholder_actions'))));
575
576		//echo "<p>projectmanager_ui::get_rows(".print_r($query,true).")</p>\n";
577		// save the state of the index in the user prefs
578		$state = serialize(array(
579			'filter'     => $query['filter'],
580			'filter2'    => $query['filter2'],
581			'cat_id'     => $query['cat_id'],
582			'order'      => $query['order'],
583			'sort'       => $query['sort'],
584		));
585		if ($state != $this->prefs['pm_index_state'])
586		{
587			$GLOBALS['egw']->preferences->add('projectmanager','pm_index_state',$state);
588			// save prefs, but do NOT invalid the cache (unnecessary)
589			$GLOBALS['egw']->preferences->save_repository(false,'user',false);
590		}
591		$GLOBALS['egw']->session->commit_session();
592		// handle nextmatch filters like col_filters
593		foreach(array('cat_id' => 'cat_id','filter2' => 'pm_status') as $nm_name => $pm_name)
594		{
595			unset($query['col_filter'][$pm_name]);
596			if ($query[$nm_name]) $query['col_filter'][$pm_name] = $query[$nm_name];
597		}
598		$query['col_filter']['subs_or_mains'] = $query['filter'];
599		// Sub-projects
600		if($query_in['csv_export'] !== 'refresh')
601		{
602			if ($query['col_filter']['pm_id'])
603			{
604				$query['col_filter']['subs_or_mains'] = $query['col_filter']['pm_id'];
605			}
606			unset($query['col_filter']['pm_id']);
607		}
608
609		$total = parent::get_rows($query,$rows,$readonlys,'',true, false, array('children'));
610
611		$readonlys = array();
612		foreach($rows as &$row)
613		{
614			// Hide as much as possible for users without read access, but still have other permissions
615			if(!$this->check_acl(Acl::READ,$row['pm_id']))
616			{
617				foreach($row as $key => &$value)
618				{
619					if(!in_array($key, array('pm_id','pm_number','pm_title'))) $value = '';
620				}
621			}
622			if (!$this->check_acl(Acl::EDIT,$row))
623			{
624				$row['class'] .= ' rowNoEdit';
625			}
626			if (!$this->check_acl(Acl::DELETE,$row))
627			{
628				$row['class'] .= ' rowNoDelete';
629			}
630			if($row['pm_status'] != self::DELETED_STATUS)
631			{
632				$row['class'] .= ' rowNoUndelete ';
633			}
634			$pm_ids[] = $row['pm_id'];
635
636			if (!$this->check_acl(EGW_ACL_BUDGET,$row))
637			{
638				unset($row['pm_used_budget']);
639				unset($row['pm_planned_budget']);
640			}
641		}
642
643		//Roles
644		// query the project-members only, if user choose to display them
645		if ($pm_ids && (@strstr($GLOBALS['egw_info']['user']['preferences']['projectmanager']['nextmatch-projectmanager.list.rows'],',role') !== false ||
646			// Current value, if user just changed column selection
647			@strstr(implode(',', $query['selectcols']),',role') !== false ))
648		{
649			$this->instanciate('roles');
650			$roles = $this->roles->query_list();
651
652			$all_members = $this->read_members($pm_ids);
653			foreach($rows as &$row)
654			{
655				$members = $row['pm_members'] = $all_members[$row['pm_id']];
656				// Set a value even if empty, or previous row won't be cleared.
657				for($i = 0; $i < 5; $i++)
658				{
659					$row['role'.$i] = array();
660				}
661				if (!$members) continue;
662
663				foreach($members as $uid => $data)
664				{
665					if (($pos = array_search($data['role_id'],array_keys($roles))) !== false)
666					{
667						$row['role'.$pos][] = $uid;
668					}
669				}
670			}
671		}
672		//_debug_array($rows);
673		if ((int) $this->debug >= 2 || $this->debug == 'get_rows')
674		{
675			$this->debug_message("projectmanager_ui::get_rows(".print_r($query,true).") total=$total, rows =".print_r($rows,true)."\nreadonlys=".print_r($readonlys,true));
676		}
677		// disable time & budget columns if pm is configures for status or status and time only
678		if ($this->config['accounting_types'] == 'status')
679		{
680			$rows['no_pm_used_time_pm_planned_time'] = $rows['no_pm_used_time_pm_planned_time_pm_replanned_time'] = true;
681			$rows['no_pm_used_budget_pm_planned_budget'] = true;
682			$query_in['options-selectcols']['pm_used_time'] = $query_in['options-selectcols']['pm_planned_time'] = $query_in['options-selectcols']['pm_replanned_time'] = false;
683			$query_in['options-selectcols']['pm_used_budget'] = $query_in['options-selectcols']['pm_planned_budget'] = false;
684		}
685		if ($this->config['accounting_types'] == 'status,times')
686		{
687			$rows['no_pm_used_budget_pm_planned_budget'] = true;
688			$query_in['options-selectcols']['pm_used_budget'] = $query_in['options-selectcols']['pm_planned_budget'] = false;
689		}
690
691		return $total;
692	}
693
694	/**
695	 * List existing projects
696	 *
697	 * @param array $content=null
698	 * @param string $msg=''
699	 */
700	function pm_list($content=null,$msg='')
701	{
702		if ((int) $this->debug >= 1 || $this->debug == 'index') $this->debug_message("projectmanager_ui::index(,$msg) content=".print_r($content,true));
703
704		$this->template->read('projectmanager.list');
705
706		if ($_GET['msg']) $msg = $_GET['msg'];
707
708		if ($content['nm']['action'])
709		{
710			if (!count($content['nm']['selected']) && !$content['nm']['select_all'])
711			{
712				$msg = lang('You need to select some entries first!');
713			}
714			else
715			{
716				if ($this->action($content['nm']['action'],$content['nm']['selected'],$content['nm']['select_all'],
717					$success,$failed,$action_msg,'project_list',$msg,$content['nm']['checkboxes']['sources_too'],
718					$content['nm']['checkboxes']['no_notifications']))
719				{
720					$msg .= lang('%1 project(s) %2',$success,$action_msg);
721				}
722				elseif(is_null($msg))
723				{
724					$msg .= lang('%1 project(s) %2, %3 failed because of insufficent rights !!!',$success,$action_msg,$failed);
725				}
726			}
727		}
728		$delete_sources = $content['delete_sources'];
729		$content = $content['nm']['rows'];
730
731		if ($content['delete'] || $content['ganttchart'])
732		{
733			foreach(array('delete','ganttchart') as $action)
734			{
735				if ($content[$action])
736				{
737					$pm_id = key($content[$action]);
738					break;
739				}
740			}
741			//echo "<p>uiprojectmanger::index() action='$action', pm_id='$pm_id'</p>\n";
742			switch($action)
743			{
744				case 'ganttchart':
745					$this->template->location(array(
746						'menuaction' => 'projectmanager.projectmanager_ganttchart.show',
747						'pm_id'      => $pm_id,
748					));
749					break;
750
751				case 'delete':
752					if (!$this->read($pm_id) || !$this->check_acl(Acl::DELETE))
753					{
754						$msg = lang('Permission denied !!!');
755					}
756					else
757					{
758						$msg = $this->delete($pm_id,$delete_sources) ? lang('Project deleted') :
759							lang('Error: deleting project !!!');
760					}
761					break;
762			}
763		}
764		$content = array(
765			'nm' => Api\Cache::getSession('projectmanager', 'project_list'),
766			'duration_format' => ','.$this->config['duration_format'],
767		);
768		if($msg)
769		{
770			Framework::message($msg);
771		}
772		if (!is_array($content['nm']))
773		{
774			$content['nm'] = array(
775				'get_rows'       =>	'projectmanager.projectmanager_ui.get_rows',
776				'filter2'        => 'active',// I initial value for the filter
777				'options-filter2'=> self::$status_labels,
778				'filter2_no_lang'=> True,// I  set no_lang for filter (=dont translate the options)
779				'filter'         => 'mains',
780				'options-filter' => array('' => lang('All projects'))+$this->filter_labels,
781				'filter_no_lang' => True,// I  set no_lang for filter (=dont translate the options)
782				'order'          =>	'pm_modified',// IO name of the column to sort after (optional for the sortheaders)
783				'sort'           =>	'DESC',// IO direction of the sort: 'ASC' or 'DESC'
784				'default_cols'   => '!role0,role1,role2,role3,role4,pm_used_time_pm_planned_time_pm_replanned_time,legacy_actions,cat_id',
785				'row_id'         => 'pm_id',
786				'favorites'		=> true,
787				'row_modified'	=> 'pm_modified',
788				'is_parent'		=> 'children',
789				'parent_id'		=> 'pm_id'
790			);
791			// use the state of the last session stored in the user prefs
792			if (($state = @unserialize($this->prefs['pm_index_state'])))
793			{
794				$content['nm'] = array_merge($content['nm'],$state);
795			}
796		}
797		$content['nm']['actions'] = $this->get_actions();
798		if($_GET['search'])
799		{
800			$content['nm']['search'] = $_GET['search'];
801		}
802
803		// Set up role columns
804		$this->instanciate('roles');
805		$roles = $this->roles->query_list();
806		$role_count = 0;
807		foreach($roles as $role_name)
808		{
809			if($role_count > 5)
810			{
811				break;
812			}
813			$content['nm']['roles'][$role_count] = $role_name;
814			$role_count++;
815		}
816		// Clear extras
817		for(; $role_count < 5; $role_count++)
818		{
819			$content['nm']['no_role'.$role_count] = true;
820		}
821
822		$sel_options = array(
823			'project_tree' => $this->ajax_tree(0, true,$this->prefs['current_project'])
824		);
825		$this->template->setElementAttribute('project_tree','actions', projectmanager_ui::project_tree_actions());
826		if($this->prefs['current_project'])
827		{
828			$content['project_tree'] = 'projectmanager::'.$this->prefs['current_project'];
829		}
830		$GLOBALS['egw_info']['flags']['app_header'] = lang('projectmanager').' - '.lang('Projectlist');
831		$this->template->exec('projectmanager.projectmanager_ui.pm_list',$content,$sel_options);
832	}
833
834	/**
835	 * Get list of templates
836	 *
837	 * @param string $label='label' key for label
838	 * @param string $title='title' key for title
839	 * @return array of array with keys $label and $title
840	 */
841	private function get_templates($label='label', $title='title')
842	{
843		static $templates;	// cache result within request
844		if (!isset($templates))
845		{
846			$templates = array();
847			list(,,$show) = explode('_',$this->prefs['show_projectselection']);
848			foreach((array)$this->search(array(
849				'pm_status' => 'template',
850			),$this->table_name.'.pm_id AS pm_id,pm_number,pm_title','pm_number','','',False,'OR') as $template)
851			{
852				$templates[$template['pm_id']] = array(
853					$label => $show == 'number' ? $template['pm_number'] : $template['pm_title'],
854					$title => $show == 'number' ? $template['pm_title'] : $template['pm_number'],
855				);
856			}
857		}
858		return $templates;
859	}
860
861	/**
862	 * Get actions / context menu for index
863	 *
864	 * Changes here, require to log out, as $content['nm'] get stored in session!
865	 *
866	 * @return array see nextmatch_widget::egw_actions()
867	 */
868	public function get_actions()
869	{
870		$actions = array(
871			'view' => array(
872				'caption' => 'Elementlist',
873				'default' => true,
874				'allowOnMultiple' => false,
875				'onExecute' => 'javaScript:app.projectmanager.set_project',
876				'target' => '_self',
877				'group' => $group=1,
878				'default' => $GLOBALS['egw_info']['user']['preferences']['projectmanager']['pm_list'] != '~edit~',
879			),
880			'open' => array(	// does edit if allowed, otherwise view
881				'caption' => 'Open',
882				'allowOnMultiple' => false,
883				'egw_open' => 'edit-projectmanager',
884				'group' => $group,
885				'default' => $GLOBALS['egw_info']['user']['preferences']['projectmanager']['pm_list'] == '~edit~',
886			),
887			'add' => array(
888				'caption' => 'Add',
889				'group' => $group,
890				'children' => array(
891					'new' => array(
892						'caption' => 'Empty',
893						'egw_open' => 'add-projectmanager',
894					),
895					'copy' => array(
896						'caption' => 'Copy',
897						'url' => 'menuaction=projectmanager.projectmanager_ui.edit&template=$id',
898						'popup' => Link::get_registry('projectmanager', 'add_popup'),
899					),
900					'template' => array(
901						'caption' => 'Template',
902						'icon' => 'move',
903						'children' => $this->get_templates('caption','hint'),
904						// get inherited by children
905						'prefix' => 'template_',
906						'url' => 'menuaction=projectmanager.projectmanager_ui.edit&template=$action',
907						'popup' => Link::get_registry('projectmanager', 'add_popup'),
908					),
909					'sub' => array(
910						'caption' => 'Subproject',
911						'url' => 'menuaction=projectmanager.projectmanager_ui.edit&link_app=projectmanager&link_id=$id',
912						'popup' => Link::get_registry('projectmanager', 'add_popup'),
913						'icon' => 'navbar',
914					),
915					'templatesub' => array(
916						'caption' => 'Template as subproject',
917						'icon' => 'move',
918						'children' => $this->get_templates('caption','hint'),
919						// get inherited by children
920						'prefix' => 'templatesub_',
921						'url' => 'menuaction=projectmanager.projectmanager_ui.edit&template=$action&link_app=projectmanager&link_id=$id',
922						'popup' => Link::get_registry('projectmanager', 'add_popup'),
923					),
924				),
925			),
926			'no_notifications' => array(
927				'caption' => 'Do not notify',
928				'checkbox' => true,
929				'hint' => 'Do not notify of these changes',
930				'confirm_mass_selection' => "You are going to change %1 entries: Are you sure you want to send notifications about this change?",
931				'group' => $group,
932			),
933			'ganttchart' => array(
934				'icon' => 'projectmanager/navbar',
935				'caption' => 'Ganttchart',
936				'onExecute' => 'javaScript:app.projectmanager.show_gantt',
937				'group' => ++$group,
938				'hideOnMobile' => true
939			),
940			'pricelist' => array(
941				'icon' => 'pricelist',
942				'caption' => 'Pricelist',
943				'onExecute' => 'javaScript:app.projectmanager.show_pricelist',
944				'allowOnMultiple' => false,
945				'group' => $group,
946				'hideOnMobile' => true
947			),
948			'filemanager' => array(
949				'icon' => 'filemanager/navbar',
950				'caption' => 'Filemanager',
951				'onExecute' => 'javaScript:app.projectmanager.show_filemanager',
952				'allowOnMultiple' => false,
953				'group' => $group,
954			),
955			'documents' => projectmanager_merge::document_action(
956				$GLOBALS['egw_info']['user']['preferences']['projectmanager']['document_dir'],
957				$group, 'Insert in document', 'document_'
958			),
959			'cat' => Etemplate\Widget\Nextmatch::category_action(
960				'projectmanager',$group,'Change category','cat_'
961			)+array(
962				'disableClass' => 'rowNoEdit',
963				'confirm_mass_selection' => true,
964			),
965			'export' => array(
966				'caption' => 'Export',
967				'icon' => 'filesave',
968				'group' => $group,
969				'allowOnMultiple' => true,
970				'url' => 'menuaction=importexport.importexport_export_ui.export_dialog&appname=projectmanager&plugin=projectmanager_export_projects_csv&selection=$id',
971				'popup' => '850x440',
972				'hideOnMobile' => true
973			),
974			'sources_too' => array(
975				'caption' => 'Datasources too',
976				'checkbox' => true,
977				'hint' => 'If checked the datasources of the elements (eg. InfoLog entries) will change their status too.',
978				'group' => ++$group,
979				'hideOnMobile' => true
980			),
981			'status' => array(
982				'icon' => 'apply',
983				'caption' => 'Modify status',
984				'group' => $group,
985				'children' => self::$status_labels,
986				'prefix' => 'status_',
987				'disableClass' => 'rowNoEdit',
988				'hideOnMobile' => true,
989				'confirm_mass_selection' => true,
990			),
991			'delete' => array(
992				'caption' => 'Delete',
993				'confirm' => 'Delete this project',
994				'confirm_multiple' => 'Delete these entries',
995				'group' => $group,
996				'disableClass' => 'rowNoDelete',
997				'hideOnMobile' => true,
998				'confirm_mass_selection' => true,
999			),
1000			'undelete' => array(
1001				'caption' => 'Un-Delete',
1002				'confirm' => 'Recover this entry',
1003				'confirm_multiple' => 'Recover these entries',
1004				'group' => $group,
1005				'icon' => 'revert',
1006				'disableClass' => 'rowNoUndelete',
1007				'hideOnDisabled' => true,
1008				'hideOnMobile' => true,
1009				'confirm_mass_selection' => true,
1010			)
1011		);
1012
1013		// Can't set deleted as a status
1014		unset($actions['status']['children'][self::DELETED_STATUS]);
1015
1016		if (!$GLOBALS['egw_info']['user']['apps']['filemanager'])
1017		{
1018			unset($actions['filemanager']);
1019		}
1020		// show pricelist only if we use pricelists
1021		if ($this->config['accounting_types'] && !in_array('pricelist',(array)$this->config['accounting_types']))
1022		{
1023			unset($actions['pricelist']);
1024		}
1025		//_debug_array($actions);
1026		return $actions;
1027	}
1028
1029	/**
1030	 * apply an action to multiple projects
1031	 *
1032	 * @param string/int $action Action to take
1033	 * @param array $checked project id's to use if !$use_all
1034	 * @param boolean $use_all if true use all entries of the current selection (in the session)
1035	 * @param int &$success number of succeded actions
1036	 * @param int &$failed number of failed actions (not enought permissions)
1037	 * @param string &$action_msg translated verb for the actions, to be used in a message like %1 entries 'deleted'
1038	 * @param string/array $session_name 'index' or 'email', or array with session-data depending if we are in the main list or the popup
1039	 * @param string &$msg
1040	 * @param booelan $sources_too=false should delete or status be changed in resources too
1041	 * @return boolean true if all actions succeded, false otherwise
1042	 */
1043	function action($action,$checked,$use_all,&$success,&$failed,&$action_msg,$session_name,&$msg,$sources_too=false, $no_notification = false)
1044	{
1045		//echo "<p>projects_ui::action('$action',".print_r($checked,true).','.(int)$use_all.",...)</p>\n";
1046		$success = $failed = 0;
1047		if ($use_all)
1048		{
1049			// get the whole selection
1050			$old_query = Api\Cache::getSession('projectmanager', 'project_list');
1051			$query = is_array($session_name) ? $session_name : Api\Cache::getSession('projectmanager', $session_name);
1052
1053			@set_time_limit(0);			// switch off the execution time limit, as it's for big selections to small
1054			$query['num_rows'] = -1;	// all
1055			$this->get_rows($query,$projects,$readonlys);
1056			// only use the ids
1057			foreach($projects as $project)
1058			{
1059				if(is_array($project) && $project['pm_id'] && is_numeric($project['pm_id'])) $checked[] = $project['pm_id'];
1060			}
1061			// Reset query
1062			Api\Cache::setSession('projectmanager', 'project_list', $old_query);
1063		}
1064
1065		// Dialogs to get options
1066		list($action, $settings) = explode('_', $action, 2);
1067
1068		switch($action)
1069		{
1070			case 'gantt':
1071				Egw::redirect_link('/index.php', array(
1072					'menuaction' => 'projectmanager.projectmanager_ganttchart.show',
1073					'pm_id'      => implode(',',$checked),
1074				));
1075				break;
1076			case 'delete':
1077				$action_msg = lang('deleted');
1078				foreach($checked as $pm_id)
1079				{
1080					if (!$this->read($pm_id) || !$this->check_acl(Acl::DELETE))
1081					{
1082						$failed++;
1083					}
1084					elseif ($this->delete($pm_id,$settings||$sources_too, $no_notification))
1085					{
1086						$success++;
1087					}
1088				}
1089				break;
1090			case 'undelete':
1091				$action_msg =lang('recovered');
1092				$elements_bo = new \projectmanager_elements_bo();
1093				foreach($checked as $pm_id)
1094				{
1095					if(!$this->read($pm_id))
1096					{
1097						$failed++;
1098						continue;
1099					}
1100					$this->save(array('pm_status' => 'active'),true, true, $no_notification);
1101					if($sources_too)
1102					{
1103						$elements_bo->run_on_sources('change_status', array('pm_id'=>$pm_id),'active');
1104					}
1105					$success++;
1106				}
1107				break;
1108			case 'cat':
1109			case 'status':
1110				$action_msg = $action == 'cat' ? lang('category set') : lang('status set');
1111				foreach($checked as $pm_id)
1112				{
1113					if (!$this->read($pm_id) || !$this->check_acl(Acl::EDIT))
1114					{
1115						$failed++;
1116					}
1117					else
1118					{
1119						$old_status = $this->data['pm_status'];
1120						$this->data[$action == 'cat' ? 'cat_id' : 'pm_status'] = $settings;
1121						if (!$this->save(null, true, true, $no_notification))
1122						{
1123							if ($action == 'status' && $sources_too && $old_status == $this->data['pm_status'])
1124							{
1125								ExecMethod2('projectmanager.projectmanager_elements_bo.run_on_sources','change_status',
1126									array('pm_id'=>$this->data['pm_id']),$this->data['pm_status']);
1127							}
1128							$success++;
1129						}
1130					}
1131				}
1132				break;
1133			case 'document':
1134				if (!$settings) $settings = $GLOBALS['egw_info']['user']['preferences']['projectmanager']['default_document'];
1135				$document_merge = new projectmanager_merge();
1136				$msg = $document_merge->download($settings, $checked, '', $GLOBALS['egw_info']['user']['preferences']['projectmanager']['document_dir']);
1137				$failed = count($checked);
1138				return false;
1139		}
1140
1141		return !$failed;
1142	}
1143
1144	/**
1145	 * Generate the project tree nodes
1146	 *
1147	 * @param int $parent_pm_id= just return children of this project
1148	 * @param boolean $return Return the information (true), or send it back as JSON
1149	 * @param int $_pm_id=null current project allways to include
1150	 */
1151	public static function ajax_tree($parent_pm_id=null, $return=false, $_pm_id=null)
1152	{
1153		if (!$return && !isset($parent_pm_id) && !empty($_GET['id']))
1154		{
1155			list($filter,$parent_pm_id) = explode('::', $_GET['id']);
1156		}
1157
1158		if($return || !($parent_pm_id || $filter))
1159		{
1160			$projects = array();
1161			foreach(self::$status_labels as $status => $label)
1162			{
1163				$projects[] = array(
1164					'id'	=> $status,
1165					'open'	=> $status == 'active',
1166					'text'	=> $label,
1167					'item'	=> array(),
1168					'child'	=> 1
1169				);
1170			}
1171			$nodes = array(
1172				'id' => empty($_GET['id']) ? 0 : $_GET['id'],
1173				'item' => $projects,
1174			);
1175		}
1176		else
1177		{
1178			$nodes = array(
1179				'id' => $_GET['id'],
1180				'item' => array()
1181			);
1182			//error_log(array2string(self::$status_labels));
1183			if(in_array($filter, array_keys(self::$status_labels)))
1184			{
1185				$nodes = array(
1186					'id'	=> $filter,
1187					'text'	=> self::$status_labels[$filter],
1188					'item'	=> array()
1189				);
1190				$filter = array('pm_status' => $filter);
1191			}
1192			else
1193			{
1194				$filter = array();
1195				if($parent_pm_id)
1196				{
1197					$project = $GLOBALS['projectmanager_bo']->read($parent_pm_id);
1198					$filter['pm_status'] = $project['pm_status'];
1199				}
1200			}
1201			self::_project_tree_leaves($filter,$parent_pm_id?$parent_pm_id : 'mains',$_pm_id ? $_pm_id : $parent_pm_id,$nodes);
1202		}
1203
1204		// Remove keys for tree widget
1205		$f = function(&$project) use (&$f)
1206		{
1207			if(!$project['item']) return;
1208			$project['item'] = array_values($project['item']);
1209			foreach($project['item'] as &$item)
1210			{
1211				$f($item);
1212			}
1213		};
1214		$f($nodes);
1215
1216		//error_log(__METHOD__."($parent_pm_id, $return, $_pm_id) \$_GET['id']=".array2string($_GET['id']).", projects=".array2string($nodes));
1217		if ($return)
1218		{
1219			return $nodes;
1220		}
1221		Etemplate\Widget\Tree::send_quote_json($nodes);
1222	}
1223
1224	protected static function _project_tree_leaves($filter, $parent_pm_id = 'mains', $_pm_id, &$projects = array())
1225	{
1226		//error_log(__METHOD__ . "(".array2string($filter).", $parent_pm_id, $_pm_id)");
1227
1228		$type = $GLOBALS['egw_info']['user']['preferences']['projectmanager']['show_projectselection'];
1229		if (substr($type,-5) == 'title')
1230		{
1231			$label = 'pm_title';
1232			$title = 'pm_number';
1233		}
1234		else
1235		{
1236			$label = 'pm_number';
1237			$title = 'pm_title';
1238		}
1239		foreach($GLOBALS['projectmanager_bo']->get_project_tree($filter,'AND',$parent_pm_id, $_pm_id) as $project)
1240		{
1241			if ($GLOBALS['egw_info']['user']['preferences']['projectmanager']['show_projectselection']=='tree_with_number_title')
1242			{
1243				$text = $project[$title].': '.$project[$label];
1244			}
1245			else
1246			{
1247				$text = $project[$label];
1248			}
1249			$p = array(
1250				// Using UID for consistency with nextmatch
1251				'id' => 'projectmanager::'.$project['pm_id'],
1252				'text'	=>	$text,
1253				'path'	=>	$project['path'],
1254				/*
1255				These ones to play nice when a user puts a tree & a selectbox with the same
1256				ID on the form (addressbook edit):
1257				if tree overwrites selectbox options, selectbox will still work
1258				*/
1259				'label'	=>	$text,
1260				'title'	=>	$project[$title],
1261				'child' => (int)($project['children'] > 0),
1262			);
1263			if($project['pm_parent'] == null && !$filter)
1264			{
1265				$projects[$project['pm_id']] = $p;
1266			}
1267			else
1268			{
1269				$path = explode('/',$project['path']);
1270				array_shift($path);
1271				array_pop($path);
1272				unset($p['path']);
1273				$parent =& $projects['item'];
1274				foreach($path as $part)
1275				{
1276					$parent =& $parent[$part]['item'];
1277				}
1278				$parent[$project['pm_id']] = $p;
1279			}
1280		}
1281	}
1282
1283	/**
1284	 * Generate the project tree actions
1285	 */
1286	public static function project_tree_actions()
1287	{
1288		$actions = array(
1289			array(
1290				'caption' => 'Elementlist',
1291				'allowOnMultiple' => false,
1292				'onExecute' => 'javaScript:app.projectmanager.set_project',
1293				'default' => true,
1294				'allowOnMultiple' => false,
1295			),
1296			array(
1297				'caption' => 'Ganttchart',
1298				'icon' => 'navbar',
1299				'app'  => 'projectmanager',
1300				'onExecute' => 'javaScript:app.projectmanager.show_gantt'
1301			),
1302
1303		);
1304		// show pricelist only if we use pricelists
1305		$config = Api\Config::read('projectmanager');
1306		if (!$config['accounting_types'] || in_array('pricelist',(is_array($config['accounting_types'])?$config['accounting_types']:explode(',',$config['accounting_types']))))
1307		{
1308			// menuitem links to project-spezific priclist only if user has rights and it is used
1309			// to not always instanciate the priclist class, this code dublicats bopricelist::check_acl(Acl::READ),
1310			// specialy the always existing READ right for the general pricelist!!!
1311			$actions[] = array(
1312				'caption' => 'Pricelist',
1313				'icon' => 'pricelist',
1314				'app'  => 'projectmanager',
1315				'onExecute' => 'javaScript:app.projectmanager.show_pricelist',
1316				'allowOnMultiple' => false,
1317			);
1318		}
1319		if (isset($GLOBALS['egw_info']['user']['apps']['filemanager']))
1320		{
1321			$actions[] = array(
1322				'caption' => 'Filemanager',
1323				'icon' => 'filemanager/navbar',
1324				'onExecute' => 'javaScript:app.projectmanager.show_filemanager',
1325				'allowOnMultiple' => false,
1326			);
1327		}
1328		return $actions;
1329	}
1330}
1331