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