Newer
Older
<?php
/*********************************************************************
class.dept.php
Department class
Peter Rotich <peter@osticket.com>
http://www.osticket.com
Released under the GNU General Public License WITHOUT ANY WARRANTY.
See LICENSE.TXT for details.
vim: expandtab sw=4 ts=4 sts=4:
**********************************************************************/
class Dept extends VerySimpleModel
implements TemplateVariable {
static $meta = array(
'table' => DEPT_TABLE,
'parent' => array(
'constraint' => array('pid' => 'Dept.id'),
'null' => true,
),
'constraint' => array('email_id' => 'Email.email_id'),
'sla' => array(
'constraint' => array('sla_id' => 'SLA.sla_id'),
'null' => true,
),
'manager' => array(
'constraint' => array('manager_id' => 'Staff.staff_id'),
'members' => array(
'null' => true,
'list' => true,
'reverse' => 'Staff.dept',
),
const ALERTS_DISABLED = 2;
const ALERTS_DEPT_AND_GROUPS = 1;
const ALERTS_DEPT_ONLY = 0;
const FLAG_ASSIGN_MEMBERS_ONLY = 0x0001;
function asVar() {
return $this->getName();
}
static function getVarScope() {
return array(
'name' => 'Department name',
'manager' => array(
'class' => 'Staff', 'desc' => 'Department manager',
'exclude' => 'dept',
),
'members' => array(
'class' => 'UserList', 'desc' => 'Department members',
),
'parent' => array(
'class' => 'Dept', 'desc' => 'Parent department',
),
'sla' => array(
'class' => 'SLA', 'desc' => 'Service Level Agreement',
),
'signature' => 'Department signature',
);
}
function getVar($tag) {
switch ($tag) {
case 'members':
return new UserList($this->getMembers()->all());
}
}
function getName() {
function getLocalName($locale=false) {
$tag = $this->getTranslateTag();
$T = CustomDataTranslation::translate($tag);
static function getLocalById($id, $subtag, $default) {
$tag = _H(sprintf('dept.%s.%s', $subtag, $id));
$T = CustomDataTranslation::translate($tag);
return $T != $tag ? $T : $default;
}
function getTranslateTag($subtag='name') {
return _H(sprintf('dept.%s.%s', $subtag, $this->getId()));
function getFullName() {
return self::getNameById($this->getId());
}
function getEmailId() {
/**
* getAlertEmail
*
* Fetches either the department email (for replies) if configured.
* Otherwise, the system alert email address is used.
*/
function getAlertEmail() {
global $cfg;
if ($this->email)
return $this->email;
return $cfg ? $cfg->getDefaultEmail() : null;
function getEmail() {
function getNumMembers() {
return count($this->getMembers());
function getMembers($criteria=null) {
->constrain(array(
// Ensure that joining through dept_access is only relevant
// for this department, so that the `alerts` annotation
// can work properly
'dept_access' => new Q(array('dept_access__dept_id' => $this->getId()))
))
->filter(Q::any(array(
'dept_id' => $this->getId(),
'staff_id' => $this->manager_id,
'dept_access__dept_id' => $this->getId(),
// TODO: Consider moving this into ::getAvailableMembers
if ($criteria && $criteria['available']) {
$members->filter(array(
'isactive' => 1,
'onvacation' => 0,
));
switch ($cfg->getAgentNameFormat()) {
case 'last':
case 'lastfirst':
case 'legal':
$members->order_by('lastname', 'firstname');
break;
default:
$members->order_by('firstname', 'lastname');
$this->_members = $members;
return $this->_members;
function getAvailableMembers() {
function getMembersForAlerts() {
if ($this->isGroupMembershipEnabled() == self::ALERTS_DISABLED) {
// Disabled for this department
$rv = array();
}
else {
$rv = clone $this->getAvailableMembers();
$rv->filter(Q::any(array(
// Ensure "Alerts" is enabled — must be a primary member or
// have alerts enabled on your membership.
'dept_id' => $this->getId(),
'dept_access__flags__hasbit' => StaffDeptAccess::FLAG_ALERTS,
)));
}
return $rv;
}
function getSLAId() {
function getSLA() {
return $this->sla;
}
function getTemplateId() {
if (!$this->template) {
if (!($this->template = EmailTemplateGroup::lookup($this->getTemplateId())))
$this->template = $cfg->getDefaultTemplate();
}
Peter Rotich
committed
if (!$this->autorespEmail) {
if (!$this->autoresp_email_id
|| !($this->autorespEmail = Email::lookup($this->autoresp_email_id)))
$this->autorespEmail = $this->getEmail();
Peter Rotich
committed
}
function getEmailAddress() {
if(($email=$this->getEmail()))
return $email->getAddress();
}
}
function canAppendSignature() {
return ($this->getSignature() && $this->isPublic());
}
function getManagerId() {
function getManager() {
function isManager($staff) {
if(is_object($staff)) $staff=$staff->getId();
return ($this->getManagerId() && $this->getManagerId()==$staff);
}
function isMember($staff) {
if (is_object($staff))
$staff = $staff->getId();
// Members are indexed by ID
$members = $this->getMembers();
return ($members && isset($members[$staff]));
}
function isPublic() {
function autoRespONNewTicket() {
function autoRespONNewMessage() {
function noreplyAutoResp() {
function assignMembersOnly() {
return $this->flags & self::FLAG_ASSIGN_MEMBERS_ONLY;
function isGroupMembershipEnabled() {
$ht = $this->ht;
if (static::$meta['joins'])
foreach (static::$meta['joins'] as $k => $v)
unset($ht[$k]);
$ht['assign_members_only'] = $this->flags & self::FLAG_ASSIGN_MEMBERS_ONLY;
function getInfo() {
return $this->getHashtable();
}
function delete() {
global $cfg;
if (!$cfg
// Default department cannot be deleted
|| $this->getId()==$cfg->getDefaultDeptId()
// Department with users cannot be deleted
if (parent::delete()) {
// DO SOME HOUSE CLEANING
//Move tickets to default Dept. TODO: Move one ticket at a time and send alerts + log notes.
Ticket::objects()
->filter(array('dept_id' => $id))
->update(array('dept_id' => $cfg->getDefaultDeptId()));
//Move Dept members: This should never happen..since delete should be issued only to empty Depts...but check it anyways
Staff::objects()
->filter(array('dept_id' => $id))
->update(array('dept_id' => $cfg->getDefaultDeptId()));
// Clear any settings using dept to default back to system default
Topic::objects()
->filter(array('dept_id' => $id))
->delete();
Email::objects()
->filter(array('dept_id' => $id))
->delete();
db_query('UPDATE '.FILTER_TABLE.' SET dept_id=0 WHERE dept_id='.db_input($id));
// Delete extended access entries
StaffDeptAccess::objects()
->filter(array('dept_id' => $id))
->delete();
function __toString() {
return $this->getName();
}
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
function getParent() {
return static::lookup($this->ht['pid']);
}
/**
* getFullPath
*
* Utility function to retrieve a '/' separated list of department IDs
* in the ancestry of this department. This is used to populate the
* `path` field in the database and is used for access control rather
* than the ID field since nesting of departments is necessary and
* department access can be cascaded.
*
* Returns:
* Slash-separated string of ID ancestry of this department. The string
* always starts and ends with a slash, and will always contain the ID
* of this department last.
*/
function getFullPath() {
$path = '';
if ($p = $this->getParent())
$path .= $p->getFullPath();
else
$path .= '/';
$path .= $this->getId() . '/';
return $path;
}
->filter(array(
'name' => $name,
'pid' => $pid ?: 0))
$names = static::getDepartments();
return $names[$id];
}
function getDefaultDeptName() {
global $cfg;
return ($cfg
&& ($did = $cfg->getDefaultDeptId())
&& ($names = self::getDepartments()))
? $names[$did]
: null;
static function getDepartments( $criteria=null, $localize=true) {
static $depts = null;
if (!isset($depts) || $criteria) {
// XXX: This will upset the static $depts array
$depts = array();
$query = self::objects();
if (isset($criteria['publiconly']))
$query->filter(array(
'ispublic' => ($criteria['publiconly'] ? 1 : 0)));
if ($manager=$criteria['manager'])
$query->filter(array(
'manager_id' => is_object($manager)?$manager->getId():$manager));
if (isset($criteria['nonempty'])) {
$query->annotate(array(
'user_count' => SqlAggregate::COUNT('members')
))->filter(array(
'user_count__gt' => 0
));
}
->values('id', 'pid', 'name', 'parent');
$depts[$row['id']] = $row;
$localize_this = function($id, $default) use ($localize) {
if (!$localize)
return $default;
$tag = _H("dept.name.{$id}");
$T = CustomDataTranslation::translate($tag);
return $T != $tag ? $T : $default;
};
// Resolve parent names
$names = array();
foreach ($depts as $id=>$info) {
$name = $info['name'];
$loop = array($id=>true);
$parent = false;
while ($info['pid'] && ($info = $depts[$info['pid']])) {
$name = sprintf('%s / %s', $info['name'], $name);
if (isset($loop[$info['pid']]))
break;
$loop[$info['pid']] = true;
$parent = $info;
}
// Fetch local names
$names[$id] = $localize_this($id, $name);
asort($names);
// TODO: Use locale-aware sorting mechanism
static function getPublicDepartments() {
static $depts =null;
if (!$depts)
$depts = self::getDepartments(array('publiconly'=>true));
return $depts;
static function create($vars=false, &$errors=array()) {
$dept->created = SqlFunction::NOW();
return $dept;
}
static function __create($vars, &$errors) {
$dept = self::create();
$dept->update($vars, $errors);
return isset($dept->id) ? $dept : null;
function save($refetch=false) {
if ($this->dirty)
$this->updated = SqlFunction::NOW();
return parent::save($refetch || $this->dirty);
$errors['err']=__('Missing or invalid Dept ID (internal error).');
$errors['name']=__('Name required');
} elseif (($did = static::getIdByName($vars['name'], $vars['pid']))
&& $did != $this->id) {
$errors['name']=__('Department already exists');
if (!$vars['ispublic'] && $cfg && ($vars['id']==$cfg->getDefaultDeptId()))
$errors['ispublic']=__('System default department cannot be private');
if ($vars['pid'] && !($p = static::lookup($vars['pid'])))
$errors['pid'] = __('Department selection is required');
// Format access update as [array(dept_id, role_id, alerts?)]
$access = array();
if (isset($vars['members'])) {
foreach (@$vars['members'] as $staff_id) {
$access[] = array($staff_id, $vars['member_role'][$staff_id],
@$vars['member_alerts'][$staff_id]);
}
}
$this->updateAccess($access, $errors);
$this->ispublic = isset($vars['ispublic'])?$vars['ispublic']:0;
$this->email_id = isset($vars['email_id'])?$vars['email_id']:0;
$this->tpl_id = isset($vars['tpl_id'])?$vars['tpl_id']:0;
$this->sla_id = isset($vars['sla_id'])?$vars['sla_id']:0;
$this->autoresp_email_id = isset($vars['autoresp_email_id'])?$vars['autoresp_email_id']:0;
$this->name = Format::striptags($vars['name']);
$this->signature = Format::sanitize($vars['signature']);
$this->group_membership = $vars['group_membership'];
$this->ticket_auto_response = isset($vars['ticket_auto_response'])?$vars['ticket_auto_response']:1;
$this->message_auto_response = isset($vars['message_auto_response'])?$vars['message_auto_response']:1;
$this->flags = isset($vars['assign_members_only']) ? self::FLAG_ASSIGN_MEMBERS_ONLY : 0;
$this->path = $this->getFullPath();
if ($this->save())
return $this->extended->saveAll();
$errors['err']=sprintf(__('Unable to update %s.'), __('this department'))
.' '.__('Internal error occurred');
$errors['err']=sprintf(__('Unable to create %s.'), __('this department'))
.' '.__('Internal error occurred');
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
function updateAccess($access, &$errors) {
reset($access);
$dropped = array();
foreach ($this->extended as $DA)
$dropped[$DA->staff_id] = 1;
while (list(, list($staff_id, $role_id, $alerts)) = each($access)) {
unset($dropped[$staff_id]);
if (!$role_id || !Role::lookup($role_id))
$errors['members'][$staff_id] = __('Select a valid role');
if (!$staff_id || !Staff::lookup($staff_id))
$errors['members'][$staff_id] = __('No such agent');
$da = $this->extended->findFirst(array('staff_id' => $staff_id));
if (!isset($da)) {
$da = StaffDeptAccess::create(array(
'staff_id' => $staff_id, 'role_id' => $role_id
));
$this->extended->add($da);
}
else {
$da->role_id = $role_id;
}
$da->setAlerts($alerts);
}
if (!$errors && $dropped) {
$this->extended
->filter(array('staff_id__in' => array_keys($dropped)))
->delete();
$this->extended->reset();
}
return !$errors;
}
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
class DepartmentQuickAddForm
extends Form {
function getFields() {
if ($this->fields)
return $this->fields;
return $this->fields = array(
'pid' => new ChoiceField(array(
'label' => '',
'default' => 0,
'choices' => array_merge(
array(0 => __('Top-Level Department')),
Dept::getDepartments()
)
)),
'name' => new TextboxField(array(
'required' => true,
'configuration' => array(
'placeholder' => __('Name'),
'classes' => 'span12',
'autofocus' => true,
'length' => 128,
),
)),
'email_id' => new ChoiceField(array(
'label' => __('Email Mailbox'),
'default' => 0,
'choices' => array_merge(
array(0 => '— '.__('System Default').' —'),
Email::getAddresses()
),
'configuration' => array(
'classes' => 'span12',
),
)),
'private' => new BooleanField(array(
'configuration' => array(
'classes' => 'form footer',
'desc' => __('This department is for internal use'),
),
)),
);
}
function getClean() {
$clean = parent::getClean();
$clean['ispublic'] = !$clean['private'];
unset($clean['private']);
return $clean;
}
function render($staff=true) {
return parent::render($staff, false, array('template' => 'dynamic-form-simple.tmpl.php'));
}
}