1<?php 2/********************************************************************* 3 class.dept.php 4 5 Department class 6 7 Peter Rotich <peter@osticket.com> 8 Copyright (c) 2006-2013 osTicket 9 http://www.osticket.com 10 11 Released under the GNU General Public License WITHOUT ANY WARRANTY. 12 See LICENSE.TXT for details. 13 14 vim: expandtab sw=4 ts=4 sts=4: 15**********************************************************************/ 16require_once INCLUDE_DIR . 'class.search.php'; 17require_once INCLUDE_DIR.'class.role.php'; 18 19class Dept extends VerySimpleModel 20implements TemplateVariable, Searchable { 21 22 static $meta = array( 23 'table' => DEPT_TABLE, 24 'pk' => array('id'), 25 'joins' => array( 26 'parent' => array( 27 'constraint' => array('pid' => 'Dept.id'), 28 'null' => true, 29 ), 30 'email' => array( 31 'constraint' => array('email_id' => 'Email.email_id'), 32 'null' => true, 33 ), 34 'sla' => array( 35 'constraint' => array('sla_id' => 'SLA.id'), 36 'null' => true, 37 ), 38 'manager' => array( 39 'null' => true, 40 'constraint' => array('manager_id' => 'Staff.staff_id'), 41 ), 42 'members' => array( 43 'null' => true, 44 'list' => true, 45 'reverse' => 'Staff.dept', 46 ), 47 'extended' => array( 48 'null' => true, 49 'list' => true, 50 'reverse' => 'StaffDeptAccess.dept' 51 ), 52 ), 53 ); 54 55 var $_members; 56 var $_primary_members; 57 var $_extended_members; 58 59 var $_groupids; 60 var $config; 61 62 var $schedule; 63 64 var $template; 65 var $autorespEmail; 66 67 const ALERTS_DISABLED = 2; 68 const DISPLAY_DISABLED = 2; 69 const ALERTS_DEPT_AND_EXTENDED = 1; 70 const ALERTS_DEPT_ONLY = 0; 71 const ALERTS_ADMIN_ONLY = 3; 72 73 const FLAG_ASSIGN_MEMBERS_ONLY = 0x0001; 74 const FLAG_DISABLE_AUTO_CLAIM = 0x0002; 75 const FLAG_ACTIVE = 0x0004; 76 const FLAG_ARCHIVED = 0x0008; 77 const FLAG_ASSIGN_PRIMARY_ONLY = 0x0010; 78 const FLAG_DISABLE_REOPEN_AUTO_ASSIGN = 0x0020; 79 80 const PERM_DEPT = 'visibility.departments'; 81 82 static protected $perms = array( 83 self::PERM_DEPT => array( 84 'title' => /* @trans */ 'Department', 85 'desc' => /* @trans */ 'Ability to see all Departments', 86 'primary' => true, 87 ), 88 ); 89 90 function asVar() { 91 return $this->getName(); 92 } 93 94 static function getVarScope() { 95 return array( 96 'name' => 'Department name', 97 'manager' => array( 98 'class' => 'Staff', 'desc' => 'Department manager', 99 'exclude' => 'dept', 100 ), 101 'members' => array( 102 'class' => 'UserList', 'desc' => 'Department members', 103 ), 104 'parent' => array( 105 'class' => 'Dept', 'desc' => 'Parent department', 106 ), 107 'sla' => array( 108 'class' => 'SLA', 'desc' => 'Service Level Agreement', 109 ), 110 'signature' => 'Department signature', 111 ); 112 } 113 114 function getVar($tag) { 115 switch ($tag) { 116 case 'members': 117 return new UserList($this->getMembers()->all()); 118 } 119 } 120 121 static function getSearchableFields() { 122 return array( 123 'name' => new TextboxField(array( 124 'label' => __('Name'), 125 )), 126 'manager' => new DepartmentManagerSelectionField(array( 127 'label' => __('Manager'), 128 )), 129 ); 130 } 131 132 static function supportsCustomData() { 133 return false; 134 } 135 136 function getId() { 137 return $this->id; 138 } 139 140 function getName() { 141 return $this->name; 142 } 143 144 function getLocalName($locale=false) { 145 $tag = $this->getTranslateTag(); 146 $T = CustomDataTranslation::translate($tag); 147 return $T != $tag ? $T : $this->name; 148 } 149 static function getLocalById($id, $subtag, $default) { 150 $tag = _H(sprintf('dept.%s.%s', $subtag, $id)); 151 $T = CustomDataTranslation::translate($tag); 152 return $T != $tag ? $T : $default; 153 } 154 static function getLocalNameById($id, $default) { 155 return static::getLocalById($id, 'name', $default); 156 } 157 158 function getTranslateTag($subtag='name') { 159 return _H(sprintf('dept.%s.%s', $subtag, $this->getId())); 160 } 161 162 function getFullName() { 163 return self::getNameById($this->getId()); 164 } 165 166 function getStatus() { 167 if($this->flags & self::FLAG_ACTIVE) 168 return __('Active'); 169 elseif($this->flags & self::FLAG_ARCHIVED) 170 return __('Archived'); 171 else 172 return __('Disabled'); 173 } 174 175 function allowsReopen() { 176 return !($this->flags & self::FLAG_ARCHIVED); 177 } 178 179 function isActive() { 180 return !!($this->flags & self::FLAG_ACTIVE); 181 } 182 183 function getEmailId() { 184 return $this->email_id; 185 } 186 187 /** 188 * getAlertEmail 189 * 190 * Fetches either the department email (for replies) if configured. 191 * Otherwise, the system alert email address is used. 192 */ 193 function getAlertEmail() { 194 global $cfg; 195 196 if ($this->email) 197 return $this->email; 198 199 return $cfg ? $cfg->getDefaultEmail() : null; 200 } 201 202 function getEmail() { 203 global $cfg; 204 205 if ($this->email) 206 return $this->email; 207 208 return $cfg? $cfg->getDefaultEmail() : null; 209 } 210 211 function getNumMembers() { 212 return count($this->getMembers()); 213 } 214 215 function getMembers() { 216 if (!isset($this->_members)) { 217 $members = Staff::objects() 218 ->distinct('staff_id') 219 ->constrain(array( 220 // Ensure that joining through dept_access is only relevant 221 // for this department, so that the `alerts` annotation 222 // can work properly 223 'dept_access' => new Q(array('dept_access__dept_id' => $this->getId())) 224 )) 225 ->filter(Q::any(array( 226 'dept_id' => $this->getId(), 227 'staff_id' => $this->manager_id, 228 'dept_access__dept_id' => $this->getId(), 229 ))); 230 231 $this->_members = Staff::nsort($members); 232 } 233 return $this->_members; 234 } 235 236 function getAvailableMembers() { 237 $members = clone $this->getMembers(); 238 return $members->filter(array( 239 'isactive' => 1, 240 'onvacation' => 0, 241 )); 242 } 243 244 function getPrimaryMembers() { 245 if (!isset($this->_primary_members)) { 246 $members = clone $this->getMembers(); 247 $members->filter(array('dept_id' =>$this->getId())); 248 $this->_primary_members = $members->all(); 249 } 250 251 return $this->_primary_members; 252 } 253 254 function getExtendedMembers() { 255 if (!isset($this->_exended_members)) { 256 // We need a query set so we can sort the names 257 $members = StaffDeptAccess::objects(); 258 $members->filter(array('dept_id' => $this->getId())); 259 $members = Staff::nsort($members, 'staff__'); 260 $extended = array(); 261 foreach($members as $member) { 262 if (!$member->staff) 263 continue; 264 // Annoted the staff model with alerts and role 265 $extended[] = AnnotatedModel::wrap($member->staff, array( 266 'alerts' => $member->isAlertsEnabled(), 267 'role_id' => $member->role_id, 268 )); 269 } 270 271 $this->_extended_members = $extended; 272 } 273 274 return $this->_extended_members; 275 } 276 277 // Get eligible members only 278 function getAssignees($criteria=array()) { 279 if (!$this->assignPrimaryOnly() && !$this->assignMembersOnly()) { 280 // this is for if all agents is set - assignment is not restricted 281 // based on department membership. 282 $members = Staff::objects()->filter(array( 283 'onvacation' => 0, 284 'isactive' => 1, 285 )); 286 } else { 287 //this gets just the members of the dept including extended access 288 $members = clone $this->getAvailableMembers(); 289 290 //Restrict to the primary members only 291 if ($this->assignPrimaryOnly()) 292 $members->filter(array('dept_id' => $this->getId())); 293 } 294 295 // Restrict agents based on visibility of the assigner 296 if (($staff=$criteria['staff'])) 297 $members = $staff->applyDeptVisibility($members); 298 299 // Sort based on set name format 300 return Staff::nsort($members); 301 } 302 303 function getMembersForAlerts() { 304 if ($this->isGroupMembershipEnabled() == self::ALERTS_DISABLED) { 305 // Disabled for this department 306 $rv = array(); 307 } 308 else { 309 $rv = clone $this->getAvailableMembers(); 310 $rv->filter(Q::any(array( 311 // Ensure "Alerts" is enabled — must be a primary member or 312 // have alerts enabled on your membership and have alerts 313 // configured to extended to extended access members 314 'dept_id' => $this->getId(), 315 // NOTE: Manager is excluded here if not a member 316 Q::all(array( 317 'dept_access__dept__group_membership' => self::ALERTS_DEPT_AND_EXTENDED, 318 'dept_access__flags__hasbit' => StaffDeptAccess::FLAG_ALERTS, 319 )), 320 ))); 321 } 322 return $rv; 323 } 324 325 function getNumMembersForAlerts() { 326 return count($this->getMembersForAlerts()); 327 } 328 329 function getSLAId() { 330 return $this->sla_id; 331 } 332 333 function getSLA() { 334 return $this->sla; 335 } 336 337 function getScheduleId() { 338 return $this->schedule_id; 339 } 340 341 function getSchedule() { 342 if (!isset($this->schedule) && $this->getScheduleId()) 343 $this->schedule = BusinessHoursSchedule::lookup( 344 $this->getScheduleId()); 345 346 return $this->schedule; 347 } 348 349 function getTemplateId() { 350 return $this->tpl_id; 351 } 352 353 function getTemplate() { 354 global $cfg; 355 356 if (!$this->template) { 357 if (!($this->template = EmailTemplateGroup::lookup($this->getTemplateId()))) 358 $this->template = $cfg->getDefaultTemplate(); 359 } 360 361 return $this->template; 362 } 363 364 function getAutoRespEmail() { 365 366 if (!$this->autorespEmail) { 367 if (!$this->autoresp_email_id 368 || !($this->autorespEmail = Email::lookup($this->autoresp_email_id))) 369 $this->autorespEmail = $this->getEmail(); 370 } 371 372 return $this->autorespEmail; 373 } 374 375 function getEmailAddress() { 376 if(($email=$this->getEmail())) 377 return $email->getAddress(); 378 } 379 380 function getSignature() { 381 return $this->signature; 382 } 383 384 function canAppendSignature() { 385 return ($this->getSignature() && $this->isPublic()); 386 } 387 388 // Check if an agent or team is eligible for assignment 389 function canAssign($assignee) { 390 391 392 if ($assignee instanceof Staff) { 393 // Primary members only 394 if ($this->assignPrimaryOnly() && !$this->isPrimaryMember($assignee)) 395 return false; 396 397 // Extended members only 398 if ($this->assignMembersOnly() && !$this->isMember($assignee)) 399 return false; 400 } elseif (!$assignee instanceof Team) { 401 // Assignee can only be an Agent or a Team 402 return false; 403 } 404 405 // Make sure agent / team is availabe for assignment 406 if (!$assignee->isAvailable()) 407 return false; 408 409 return true; 410 } 411 412 function getManagerId() { 413 return $this->manager_id; 414 } 415 416 function getManager() { 417 return $this->manager; 418 } 419 420 function isManager(Staff $staff) { 421 $staff = $staff->getId(); 422 423 return ($this->getManagerId() && $this->getManagerId()==$staff); 424 } 425 426 function isMember(Staff $staff) { 427 $staff = $staff->getId(); 428 429 return $this->getMembers()->findFirst(array( 430 'staff_id' => $staff 431 )); 432 } 433 434 function isPrimaryMember(Staff $staff) { 435 return ($staff->getDeptId() == $this->getId()); 436 } 437 438 function isPublic() { 439 return $this->ispublic; 440 } 441 442 function autoRespONNewTicket() { 443 return $this->ticket_auto_response; 444 } 445 446 function autoRespONNewMessage() { 447 return $this->message_auto_response; 448 } 449 450 function noreplyAutoResp() { 451 return $this->noreply_autoresp; 452 } 453 454 function assignMembersOnly() { 455 return $this->flags & self::FLAG_ASSIGN_MEMBERS_ONLY; 456 } 457 458 function assignPrimaryOnly() { 459 return $this->flags & self::FLAG_ASSIGN_PRIMARY_ONLY; 460 } 461 462 function getAssignmentFlag() { 463 if($this->flags & self::FLAG_ASSIGN_MEMBERS_ONLY) 464 return 'members'; 465 elseif($this->flags & self::FLAG_ASSIGN_PRIMARY_ONLY) 466 return 'primary'; 467 else 468 return 'all'; 469 } 470 471 function disableAutoClaim() { 472 return $this->flags & self::FLAG_DISABLE_AUTO_CLAIM; 473 } 474 475 function disableReopenAutoAssign() { 476 return $this->flags & self::FLAG_DISABLE_REOPEN_AUTO_ASSIGN; 477 } 478 479 function isGroupMembershipEnabled() { 480 return $this->group_membership; 481 } 482 483 function getHashtable() { 484 $ht = $this->ht; 485 if (static::$meta['joins']) 486 foreach (static::$meta['joins'] as $k => $v) 487 unset($ht[$k]); 488 489 $ht['disable_auto_claim'] = $this->disableAutoClaim(); 490 $ht['status'] = $this->getStatus(); 491 $ht['assignment_flag'] = $this->getAssignmentFlag(); 492 $ht['disable_reopen_auto_assign'] = $this->disableReopenAutoAssign(); 493 return $ht; 494 } 495 496 function getInfo() { 497 return $this->getHashtable(); 498 } 499 500 function delete() { 501 global $cfg; 502 503 if (!$cfg 504 // Default department cannot be deleted 505 || $this->getId()==$cfg->getDefaultDeptId() 506 // Department with users cannot be deleted 507 || $this->members->count() 508 ) { 509 return 0; 510 } 511 512 $id = $this->getId(); 513 if (parent::delete()) { 514 $type = array('type' => 'deleted'); 515 Signal::send('object.deleted', $this, $type); 516 517 // DO SOME HOUSE CLEANING 518 //Move tickets to default Dept. TODO: Move one ticket at a time and send alerts + log notes. 519 Ticket::objects() 520 ->filter(array('dept_id' => $id)) 521 ->update(array('dept_id' => $cfg->getDefaultDeptId())); 522 523 // Move tasks 524 Task::objects() 525 ->filter(array('dept_id' => $id)) 526 ->update(array('dept_id' => $cfg->getDefaultDeptId())); 527 528 //Move Dept members: This should never happen..since delete should be issued only to empty Depts...but check it anyways 529 Staff::objects() 530 ->filter(array('dept_id' => $id)) 531 ->update(array('dept_id' => $cfg->getDefaultDeptId())); 532 533 // Clear any settings using dept to default back to system default 534 Topic::objects() 535 ->filter(array('dept_id' => $id)) 536 ->update(array('dept_id' => 0)); 537 538 Email::objects() 539 ->filter(array('dept_id' => $id)) 540 ->update(array('dept_id' => 0)); 541 542 // Delete extended access entries 543 StaffDeptAccess::objects() 544 ->filter(array('dept_id' => $id)) 545 ->delete(); 546 } 547 return true; 548 } 549 550 function __toString() { 551 return $this->getName(); 552 } 553 554 function getParent() { 555 return static::lookup($this->ht['pid']); 556 } 557 558 /** 559 * getFullPath 560 * 561 * Utility function to retrieve a '/' separated list of department IDs 562 * in the ancestry of this department. This is used to populate the 563 * `path` field in the database and is used for access control rather 564 * than the ID field since nesting of departments is necessary and 565 * department access can be cascaded. 566 * 567 * Returns: 568 * Slash-separated string of ID ancestry of this department. The string 569 * always starts and ends with a slash, and will always contain the ID 570 * of this department last. 571 */ 572 function getFullPath() { 573 $path = ''; 574 if ($p = $this->getParent()) 575 $path .= $p->getFullPath(); 576 else 577 $path .= '/'; 578 $path .= $this->getId() . '/'; 579 return $path; 580 } 581 582 /** 583 * setFlag 584 * 585 * Utility method to set/unset flag bits 586 * 587 */ 588 589 public function setFlag($flag, $val) { 590 591 if ($val) 592 $this->flags |= $flag; 593 else 594 $this->flags &= ~$flag; 595 } 596 597 function hasFlag($flag) { 598 return ($this->get('flags', 0) & $flag) != 0; 599 } 600 601 function flagChanged($flag, $var) { 602 if (($this->hasFlag($flag) && !$var) || 603 (!$this->hasFlag($flag) && $var)) 604 return true; 605 } 606 607 function export($dept, $criteria=null, $filename='') { 608 include_once(INCLUDE_DIR.'class.error.php'); 609 $members = $dept->getMembers(); 610 611 //Sort based on name formating 612 $members = Staff::nsort($members); 613 Export::departmentMembers($dept, $members, $filename); 614 } 615 616 /*----Static functions-------*/ 617 static function getIdByName($name, $pid=null) { 618 $row = static::objects() 619 ->filter(array( 620 'name' => $name, 621 'pid' => $pid ?: null)) 622 ->values_flat('id') 623 ->first(); 624 625 return $row ? $row[0] : 0; 626 } 627 628 static function getEmailIdById($id) { 629 $row = static::objects() 630 ->filter(array('id' => $id)) 631 ->values_flat('email_id') 632 ->first(); 633 634 return $row ? $row[0] : 0; 635 } 636 637 function getNameById($id) { 638 $names = Dept::getDepartments(); 639 return $names[$id]; 640 } 641 642 function getDefaultDeptName() { 643 global $cfg; 644 645 return ($cfg 646 && ($did = $cfg->getDefaultDeptId()) 647 && ($names = self::getDepartments())) 648 ? $names[$did] 649 : null; 650 } 651 652 static function getDepartments($criteria=null, $localize=true, $disabled=true) { 653 $depts = null; 654 655 if (!isset($depts) || $criteria) { 656 // XXX: This will upset the static $depts array 657 $depts = array(); 658 $query = self::objects(); 659 if (isset($criteria['publiconly']) && $criteria['publiconly']) 660 $query->filter(array( 661 'ispublic' => ($criteria['publiconly'] ? 1 : 0))); 662 663 if (isset($criteria['activeonly']) && $criteria['activeonly']) 664 $query->filter(array( 665 'flags__hasbit' => Dept::FLAG_ACTIVE)); 666 667 if ($manager=$criteria['manager']) 668 $query->filter(array( 669 'manager_id' => is_object($manager)?$manager->getId():$manager)); 670 671 if (isset($criteria['nonempty'])) { 672 $query->annotate(array( 673 'user_count' => SqlAggregate::COUNT('members') 674 ))->filter(array( 675 'user_count__gt' => 0 676 )); 677 } 678 679 $query->order_by('name') 680 ->values('id', 'pid', 'flags', 'name', 'parent'); 681 682 foreach ($query as $row) { 683 $display = ($row['flags'] & self::FLAG_ACTIVE); 684 685 $depts[$row['id']] = array('id' => $row['id'], 'pid'=>$row['pid'], 'name'=>$row['name'], 686 'parent'=>$row['parent'], 'disabled' => !$display); 687 } 688 689 $localize_this = function($id, $default) use ($localize) { 690 if (!$localize) 691 return $default; 692 693 $tag = _H("dept.name.{$id}"); 694 $T = CustomDataTranslation::translate($tag); 695 return $T != $tag ? $T : $default; 696 }; 697 698 // Resolve parent names 699 $names = array(); 700 foreach ($depts as $id=>$info) { 701 $name = $info['name']; 702 $loop = array($id=>true); 703 $parent = false; 704 while ($info['pid'] && ($info = $depts[$info['pid']])) { 705 $name = sprintf('%s / %s', $info['name'], $name); 706 if (isset($loop[$info['pid']])) 707 break; 708 $loop[$info['pid']] = true; 709 $parent = $info; 710 } 711 // Fetch local names 712 $names[$id] = $localize_this($id, $name); 713 } 714 715 // Apply requested filters 716 $requested_names = array(); 717 foreach ($names as $id=>$n) { 718 $info = $depts[$id]; 719 if (!$disabled && $info['disabled']) 720 continue; 721 if ($disabled === self::DISPLAY_DISABLED && $info['disabled']) 722 $n .= " - ".__("(disabled)"); 723 724 $requested_names[$id] = $n; 725 } 726 asort($requested_names); 727 728 // TODO: Use locale-aware sorting mechanism 729 if ($criteria) 730 return $requested_names; 731 732 $depts = $requested_names; 733 } 734 735 return $requested_names; 736 } 737 738 static function getPublicDepartments() { 739 $depts =null; 740 741 if (!$depts) 742 $depts = self::getDepartments(array('publiconly'=>true)); 743 744 return $depts; 745 } 746 747 static function getActiveDepartments() { 748 $depts =null; 749 750 if (!$depts) 751 $depts = self::getDepartments(array('activeonly'=>true)); 752 753 return $depts; 754 } 755 756 static function create($vars=false, &$errors=array()) { 757 $dept = new static($vars); 758 $dept->created = SqlFunction::NOW(); 759 return $dept; 760 } 761 762 static function __create($vars, &$errors) { 763 $dept = self::create($vars); 764 if (!$dept->update($vars, $errors)) 765 return false; 766 767 $dept->save(); 768 769 return $dept; 770 } 771 772 function save($refetch=false) { 773 if ($this->dirty) 774 $this->updated = SqlFunction::NOW(); 775 776 return parent::save($refetch || $this->dirty); 777 } 778 779 function update($vars, &$errors) { 780 global $cfg; 781 782 $id = $this->id; 783 if ($id && $id != $vars['id']) 784 $errors['err']=__('Missing or invalid Dept ID.') 785 .' '.__('Internal error occurred'); 786 787 if (!$vars['name']) { 788 $errors['name']=__('Name required'); 789 } elseif (($did = static::getIdByName($vars['name'], $vars['pid'])) 790 && $did != $id) { 791 $errors['name']=__('Department already exists'); 792 } 793 794 if (!$vars['ispublic'] && $cfg && ($vars['id']==$cfg->getDefaultDeptId())) 795 $errors['ispublic']=__('System default department cannot be private'); 796 797 if ($vars['pid'] && !($p = static::lookup($vars['pid']))) 798 $errors['pid'] = __('Department selection is required'); 799 800 $dept = Dept::lookup($vars['pid']); 801 if ($dept) { 802 if (!$dept->isActive()) 803 $errors['dept_id'] = sprintf(__('%s selected must be active'), __('Parent Department')); 804 elseif (strpos($dept->getFullPath(), '/'.$this->getId().'/') !== false) 805 $errors['pid'] = sprintf(__('%s cannot contain the current %s'), __('Parent Department'), __('Department')); 806 } 807 808 if ($vars['sla_id'] && !SLA::lookup($vars['sla_id'])) 809 $errors['sla_id'] = __('Invalid SLA'); 810 811 if ($vars['manager_id'] && !Staff::lookup($vars['manager_id'])) 812 $errors['manager_id'] = __('Unknown Staff'); 813 814 if ($vars['email_id'] && !Email::lookup($vars['email_id'])) 815 $errors['email_id'] = __('Unknown System Email'); 816 817 if ($vars['tpl_id'] && !EmailTemplateGroup::lookup($vars['tpl_id'])) 818 $errors['tpl_id'] = __('Unknown Template Set'); 819 820 if ($vars['autoresp_email_id'] && !Email::lookup($vars['autoresp_email_id'])) 821 $errors['autoresp_email_id'] = __('Unkown System Email'); 822 823 // Format access update as [array(dept_id, role_id, alerts?)] 824 $access = array(); 825 if (isset($vars['members'])) { 826 foreach (@$vars['members'] as $staff_id) { 827 $access[] = array($staff_id, $vars['member_role'][$staff_id], 828 @$vars['member_alerts'][$staff_id]); 829 } 830 } 831 $this->updateAccess($access, $errors); 832 833 if ($errors) 834 return false; 835 836 $vars['disable_auto_claim'] = isset($vars['disable_auto_claim']) ? 1 : 0; 837 if ($this->getId()) { 838 //flags 839 $disableAutoClaim = $this->flagChanged(self::FLAG_DISABLE_AUTO_CLAIM, $vars['disable_auto_claim']); 840 $disableAutoAssign = $this->flagChanged(self::FLAG_DISABLE_REOPEN_AUTO_ASSIGN, $vars['disable_reopen_auto_assign']); 841 $ticketAssignment = ($this->getAssignmentFlag() != $vars['assignment_flag']); 842 foreach ($vars as $key => $value) { 843 if ($key == 'status' && $this->getStatus() && strtolower($this->getStatus()) != $value) { 844 $type = array('type' => 'edited', 'status' => ucfirst($value)); 845 Signal::send('object.edited', $this, $type); 846 } elseif ((isset($this->$key) && ($this->$key != $value) && $key != 'members') || 847 ($disableAutoClaim && $key == 'disable_auto_claim') || 848 $ticketAssignment && $key == 'assignment_flag' || 849 $disableAutoAssign && $key == 'disable_reopen_auto_assign') { 850 $type = array('type' => 'edited', 'key' => $key); 851 Signal::send('object.edited', $this, $type); 852 } 853 } 854 } 855 if ($vars['disable_auto_claim'] !== 1) 856 unset($vars['disable_auto_claim']); 857 858 $this->pid = $vars['pid'] ?: null; 859 $this->ispublic = isset($vars['ispublic']) ? (int) $vars['ispublic'] : 0; 860 $this->email_id = isset($vars['email_id']) ? (int) $vars['email_id'] : 0; 861 $this->tpl_id = isset($vars['tpl_id']) ? (int) $vars['tpl_id'] : 0; 862 $this->sla_id = isset($vars['sla_id']) ? (int) $vars['sla_id'] : 0; 863 $this->schedule_id = isset($vars['schedule_id']) ? (int) $vars['schedule_id'] : 0; 864 $this->autoresp_email_id = isset($vars['autoresp_email_id']) ? (int) $vars['autoresp_email_id'] : 0; 865 $this->manager_id = $vars['manager_id'] ?: 0; 866 $this->name = Format::striptags($vars['name']); 867 $this->signature = Format::sanitize($vars['signature']); 868 $this->group_membership = $vars['group_membership']; 869 $this->ticket_auto_response = isset($vars['ticket_auto_response'])?$vars['ticket_auto_response']:1; 870 $this->message_auto_response = isset($vars['message_auto_response'])?$vars['message_auto_response']:1; 871 $this->flags = $vars['flags'] ?: 0; 872 873 $this->setFlag(self::FLAG_ASSIGN_MEMBERS_ONLY, isset($vars['assign_members_only'])); 874 $this->setFlag(self::FLAG_DISABLE_AUTO_CLAIM, isset($vars['disable_auto_claim'])); 875 $this->setFlag(self::FLAG_DISABLE_REOPEN_AUTO_ASSIGN, isset($vars['disable_reopen_auto_assign'])); 876 877 $filter_actions = FilterAction::objects()->filter(array('type' => 'dept', 'configuration' => '{"dept_id":'. $this->getId().'}')); 878 if ($filter_actions && $vars['status'] == 'active') 879 FilterAction::setFilterFlags($filter_actions, 'Filter::FLAG_INACTIVE_DEPT', false); 880 else 881 FilterAction::setFilterFlags($filter_actions, 'Filter::FLAG_INACTIVE_DEPT', true); 882 883 if ($cfg && ($this->getId() == $cfg->getDefaultDeptId())) 884 $vars['status'] = 'active'; 885 886 switch ($vars['status']) { 887 case 'active': 888 $this->setFlag(self::FLAG_ACTIVE, true); 889 $this->setFlag(self::FLAG_ARCHIVED, false); 890 break; 891 892 case 'disabled': 893 $this->setFlag(self::FLAG_ACTIVE, false); 894 $this->setFlag(self::FLAG_ARCHIVED, false); 895 break; 896 897 case 'archived': 898 $this->setFlag(self::FLAG_ACTIVE, false); 899 $this->setFlag(self::FLAG_ARCHIVED, true); 900 } 901 902 switch ($vars['assignment_flag']) { 903 case 'all': 904 $this->setFlag(self::FLAG_ASSIGN_MEMBERS_ONLY, false); 905 $this->setFlag(self::FLAG_ASSIGN_PRIMARY_ONLY, false); 906 break; 907 case 'members': 908 $this->setFlag(self::FLAG_ASSIGN_MEMBERS_ONLY, true); 909 $this->setFlag(self::FLAG_ASSIGN_PRIMARY_ONLY, false); 910 break; 911 case 'primary': 912 $this->setFlag(self::FLAG_ASSIGN_MEMBERS_ONLY, false); 913 $this->setFlag(self::FLAG_ASSIGN_PRIMARY_ONLY, true); 914 break; 915 } 916 917 $this->path = $this->getFullPath(); 918 919 $wasnew = $this->__new__; 920 if ($this->save() && $this->extended->saveAll()) { 921 if ($wasnew) { 922 // The ID wasn't available until after the commit 923 $this->path = $this->getFullPath(); 924 $this->save(); 925 } 926 return true; 927 } 928 929 if (isset($this->id)) 930 $errors['err']=sprintf(__('Unable to update %s.'), __('this department')) 931 .' '.__('Internal error occurred'); 932 else 933 $errors['err']=sprintf(__('Unable to create %s.'), __('this department')) 934 .' '.__('Internal error occurred'); 935 936 return false; 937 } 938 939 function updateAccess($access, &$errors) { 940 reset($access); 941 $dropped = array(); 942 foreach ($this->extended as $DA) 943 $dropped[$DA->staff_id] = 1; 944 while (list(, list($staff_id, $role_id, $alerts)) = each($access)) { 945 unset($dropped[$staff_id]); 946 if (!$role_id || !Role::lookup($role_id)) 947 $errors['members'][$staff_id] = __('Select a valid role'); 948 if (!$staff_id || !($staff=Staff::lookup($staff_id))) 949 $errors['members'][$staff_id] = __('No such agent'); 950 951 if ($staff->dept_id == $this->id) { 952 953 // If primary member then simply update the role. 954 if (($m = $this->members->findFirst(array( 955 'staff_id' => $staff_id)))) 956 $m->role_id = $role_id; 957 continue; 958 } 959 960 $da = $this->extended->findFirst(array('staff_id' => $staff_id)); 961 if (!isset($da)) { 962 $da = new StaffDeptAccess(array( 963 'staff_id' => $staff_id, 'role_id' => $role_id 964 )); 965 $this->extended->add($da); 966 $type = array('type' => 'edited', 'key' => 'Staff Added'); 967 Signal::send('object.edited', $this, $type); 968 } 969 else { 970 $da->role_id = $role_id; 971 } 972 $da->setAlerts($alerts); 973 974 } 975 976 if ($errors) 977 return false; 978 979 if ($dropped) { 980 $type = array('type' => 'edited', 'key' => 'Staff Removed'); 981 Signal::send('object.edited', $this, $type); 982 $this->extended->saveAll(); 983 $this->extended 984 ->filter(array('staff_id__in' => array_keys($dropped))) 985 ->delete(); 986 $this->extended->reset(); 987 } 988 989 // Save any role change. 990 $this->members->saveAll(); 991 992 return true; 993 } 994 995 static function getPermissions() { 996 return self::$perms; 997 } 998} 999RolePermission::register(/* @trans */ 'Miscellaneous', Dept::getPermissions()); 1000 1001class DepartmentQuickAddForm 1002extends Form { 1003 function getFields() { 1004 if ($this->fields) 1005 return $this->fields; 1006 1007 return $this->fields = array( 1008 'pid' => new ChoiceField(array( 1009 'label' => '', 1010 'default' => 0, 1011 'choices' => 1012 array(0 => '— '.__('Top-Level Department').' —') 1013 + Dept::getPublicDepartments() 1014 )), 1015 'name' => new TextboxField(array( 1016 'required' => true, 1017 'configuration' => array( 1018 'placeholder' => __('Name'), 1019 'classes' => 'span12', 1020 'autofocus' => true, 1021 'length' => 128, 1022 ), 1023 )), 1024 'email_id' => new ChoiceField(array( 1025 'label' => __('Email Mailbox'), 1026 'default' => 0, 1027 'choices' => 1028 array(0 => '— '.__('System Default').' —') 1029 + Email::getAddresses(), 1030 'configuration' => array( 1031 'classes' => 'span12', 1032 ), 1033 )), 1034 'private' => new BooleanField(array( 1035 'configuration' => array( 1036 'classes' => 'form footer', 1037 'desc' => __('This department is for internal use'), 1038 ), 1039 )), 1040 ); 1041 } 1042 1043 function getClean($validate = true) { 1044 $clean = parent::getClean(); 1045 1046 $clean['ispublic'] = !$clean['private']; 1047 unset($clean['private']); 1048 1049 return $clean; 1050 } 1051 1052 function render($staff=true, $title=false, $options=array()) { 1053 return parent::render($staff, $title, $options + array('template' => 'dynamic-form-simple.tmpl.php')); 1054 } 1055} 1056