Newer
Older
return new FormattedDate($due);
break;
case 'close_date':
if ($this->isClosed())
return new FormattedDate($this->getCloseDate());
break;
case 'last_update':
return new FormattedDate($this->lastupdate);
case 'user':
return $this->getOwner();
default:
if ($a = $this->getAnswer($tag))
// The answer object is retrieved here which will
// automatically invoke the toString() method when the
// answer is coerced into text
static function getVarScope() {
$base = array(
'assigned' => __('Assigned Agent / Team'),
'class' => 'FormattedDate', 'desc' => __('Date Closed'),
),
'create_date' => array(
'class' => 'FormattedDate', 'desc' => __('Date Created'),
'class' => 'Dept', 'desc' => __('Department'),
'class' => 'FormattedDate', 'desc' => __('Due Date'),
'email' => __('Default email address of ticket owner'),
'name' => array(
'class' => 'PersonsName', 'desc' => __('Name of ticket owner'),
),
'number' => __('Ticket Number'),
'phone' => __('Phone number of ticket owner'),
'class' => 'Priority', 'desc' => __('Priority'),
),
'recipients' => array(
'class' => 'UserList', 'desc' => __('List of all recipient names'),
'source' => __('Source'),
'class' => 'TicketStatus', 'desc' => __('Status'),
),
'staff' => array(
'class' => 'Staff', 'desc' => __('Assigned/closing agent'),
),
'subject' => 'Subject',
'team' => array(
'class' => 'Team', 'desc' => __('Assigned/closing team'),
),
'thread' => array(
'class' => 'TicketThread', 'desc' => __('Ticket Thread'),
'class' => 'Topic', 'desc' => __('Help Topic'),
),
// XXX: Isn't lastreponse and lastmessage more useful
'last_update' => array(
'class' => 'FormattedDate', 'desc' => __('Time of last update'),
'class' => 'User', 'desc' => __('Ticket Owner'),
$extra = VariableReplacer::compileFormScope(TicketForm::getInstance());
return $base + $extra;
// Searchable interface
static function getSearchableFields() {
$base = array(
'number' => new TextboxField(array(
'label' => __('Ticket Number')
)),
'created' => new DatetimeField(array(
'label' => __('Create Date'),
'configuration' => array('fromdb' => true),
'duedate' => new DatetimeField(array(
'configuration' => array('fromdb' => true),
'est_duedate' => new DatetimeField(array(
'label' => __('SLA Due Date'),
'configuration' => array('fromdb' => true),
)),
'reopened' => new DatetimeField(array(
'label' => __('Reopen Date'),
'configuration' => array('fromdb' => true),
)),
'closed' => new DatetimeField(array(
'label' => __('Close Date'),
'configuration' => array('fromdb' => true),
)),
'lastupdate' => new DatetimeField(array(
'label' => __('Last Update'),
'configuration' => array('fromdb' => true),
)),
'assignee' => new AssigneeChoiceField(array(
'label' => __('Assignee'),
)),
'staff_id' => new AgentSelectionField(array(
'label' => __('Assigned Staff'),
)),
'team_id' => new TeamSelectionField(array(
'label' => __('Assigned Team'),
)),
'dept_id' => new DepartmentChoiceField(array(
'label' => __('Department'),
)),
'topic_id' => new HelpTopicChoiceField(array(
'label' => __('Help Topic'),
)),
'source' => new TicketSourceChoiceField(array(
'label' => __('Ticket Source'),
)),
'isoverdue' => new BooleanField(array(
'label' => __('Overdue'),
'descsearchmethods' => array(
'set' => '%s',
'nset' => 'Not %s'
),
)),
'isanswered' => new BooleanField(array(
'label' => __('Answered'),
'descsearchmethods' => array(
'set' => '%s',
'nset' => 'Not %s'
),
)),
'isassigned' => new AssignedField(array(
'label' => __('Assigned'),
)),
'ip_address' => new TextboxField(array(
'label' => __('IP Address'),
'configuration' => array('validator' => 'ip'),
)),
);
$tform = TicketForm::getInstance();
foreach ($tform->getFields() as $F) {
$fname = $F->get('name') ?: ('field_'.$F->get('id'));
if (!$F->hasData() || $F->isPresentationOnly())
continue;
if (!$F->isStorable())
$base[$fname] = $F;
else
$base["cdata__{$fname}"] = $F;
}
return $base;
}
static function supportsCustomData() {
return true;
}
//Replace base variables.
function replaceVars($input, $vars = array()) {
global $ost;
$recipients = $this->getRecipients(true);
$vars = array_merge($vars, array('ticket' => $this));
return $ost->replaceTemplateVariables($input, $vars);
}
function markUnAnswered() {
return (!$this->isAnswered() || $this->setAnsweredState(0));
}
function markAnswered() {
return ($this->isAnswered() || $this->setAnsweredState(1));
}
function markOverdue($whine=true) {
global $cfg;
Peter Rotich
committed
$this->isoverdue = 1;
if (!$this->save())
$this->logEvent('overdue');
Peter Rotich
committed
//NOTE: Previously logged overdue event is NOT annuled.
// clear due date if it's in the past
if ($this->getDueDate() && Misc::db2gmtime($this->getDueDate()) <= Misc::gmtime())
$this->duedate = null;
Peter Rotich
committed
// Clear SLA if est. due date is in the past
if ($this->getSLADueDate() && Misc::db2gmtime($this->getSLADueDate()) <= Misc::gmtime())
$this->sla = null;
//Dept Transfer...with alert.. done by staff
function transfer(TransferForm $form, &$errors, $alert=true) {
global $thisstaff, $cfg;
Peter Rotich
committed
// Check if staff can do the transfer
if (!$this->checkStaffPerm($thisstaff, Ticket::PERM_TRANSFER))
$cdept = $this->getDept(); // Current department
$dept = $form->getDept(); // Target department
if (!$dept || !($dept instanceof Dept))
$errors['dept'] = __('Department selection required');
elseif ($dept->getid() == $this->getDeptId())
$errors['dept'] = sprintf(
__('%s already in the department'), __('Ticket'));
else {
$this->dept_id = $dept->getId();
// Make sure the new department allows assignment to the
// currently assigned agent (if any)
if ($this->isAssigned()
&& ($staff=$this->getStaff())
&& $dept->assignMembersOnly()
&& !$dept->isMember($staff)
) {
$this->staff_id = 0;
}
}
Peter Rotich
committed
// Reopen ticket if closed
if ($this->isClosed())
$this->reopen();
// Set SLA of the new department
if (!$this->getSLAId() || $this->getSLA()->isTransient())
if (($slaId=$this->getDept()->getSLAId()))
$this->selectSLAId($slaId);
Peter Rotich
committed
// Log transfer event
$this->logEvent('transferred');
$note = null;
$comments = $form->getField('comments')->getClean();
if ($comments) {
$title = sprintf(__('%1$s transferred from %2$s to %3$s'),
__('Ticket'),
$cdept->getName(),
$dept->getName());
Peter Rotich
committed
if ($form->refer() && $cdept)
$this->thread->refer($cdept);
return true; //no alerts!!
if (($email = $dept->getAlertEmail())
&& ($tpl = $dept->getTemplate())
&& ($msg=$tpl->getTransferAlertMsgTemplate())
) {
$msg = $this->replaceVars($msg->asArray(),
// Recipients
$recipients = array();
// Assigned staff or team... if any
if($this->isAssigned() && $cfg->alertAssignedONTransfer()) {
if($this->getStaffId())
$recipients[] = $this->getStaff();
elseif ($this->getTeamId()
&& ($team=$this->getTeam())
&& ($members=$team->getMembers())
) {
$recipients = array_merge($recipients, $members);
}
}
elseif ($cfg->alertDeptMembersONTransfer() && !$this->isAssigned()) {
// Only alerts dept members if the ticket is NOT assigned.
foreach ($dept->getMembersForAlerts() as $M)
$recipients[] = $M;
// Always alert dept manager??
if ($cfg->alertDeptManagerONTransfer()
&& $dept
&& ($manager=$dept->getManager())
) {
$recipients[] = $manager;
}
$options += array('thread'=>$note);
foreach ($recipients as $k=>$staff) {
if (!is_object($staff)
|| !$staff->isAvailable()
|| in_array($staff->getEmail(), $sentlist)
) {
continue;
}
$alert = $this->replaceVars($msg, array('recipient' => $staff));
$email->sendAlert($staff, $alert['subj'], $alert['body'], null, $options);
global $thisstaff;
$dept = $this->getDept();
$assignee = $form->getAssignee();
if (!($assignee instanceof Staff)
|| !$thisstaff
|| $thisstaff->getId() != $assignee->getId()) {
$errors['err'] = __('Unknown assignee');
} elseif (!$assignee->isAvailable()) {
$errors['err'] = __('Agent is unavailable for assignment');
} elseif ($dept->assignMembersOnly() && !$dept->isMember($assignee)) {
$errors['err'] = __('Permission denied');
}
return $this->assignToStaff($assignee, $form->getComments(), false);
function assignToStaff($staff, $note, $alert=true) {
if(!is_object($staff) && !($staff = Staff::lookup($staff)))
Peter Rotich
committed
if (!$staff->isAvailable() || !$this->setStaffId($staff->getId()))
global $thisstaff;
$data = array();
if ($thisstaff && $staff->getId() == $thisstaff->getId())
$data['claim'] = true;
else
$data['staff'] = $staff->getId();
return true;
}
function assignToTeam($team, $note, $alert=true) {
if(!is_object($team) && !($team = Team::lookup($team)))
if (!$team->isActive() || !$this->setTeamId($team->getId()))
return false;
//Clear - staff if it's a closed ticket
// staff_id is overloaded -> assigned to & closed by.
$this->logEvent('assigned', array('team' => $team->getId()));
function assign(AssignmentForm $form, &$errors, $alert=true) {
$assignee = $form->getAssignee();
if ($assignee instanceof Staff) {
if ($this->getStaffId() == $assignee->getId()) {
$errors['assignee'] = sprintf(__('%s already assigned to %s'),
__('Ticket'),
__('the agent')
);
} elseif(!$assignee->isAvailable()) {
$errors['assignee'] = __('Agent is unavailable for assignment');
} elseif ($dept->assignMembersOnly() && !$dept->isMember($assignee)) {
$errors['err'] = __('Permission denied');
$refer = $this->staff ?: null;
if ($thisstaff && $thisstaff->getId() == $assignee->getId()) {
$alert = false;
$evd['staff'] = array($assignee->getId(), (string) $assignee->getName()->getOriginal());
}
} elseif ($assignee instanceof Team) {
if ($this->getTeamId() == $assignee->getId()) {
$errors['assignee'] = sprintf(__('%s already assigned to %s'),
__('Ticket'),
__('the team')
);
} else {
$refer = $this->team ?: null;
$this->team_id = $assignee->getId();
$evd = array('team' => $assignee->getId());
}
} else {
$errors['assignee'] = __('Unknown assignee');
if ($errors || !$this->save(true))
return false;
$this->logEvent('assigned', $evd);
$this->onAssign($assignee, $form->getComments(), $alert);
if ($refer && $form->refer())
$this->thread->refer($refer);
Peter Rotich
committed
// We can't release what is not assigned buddy!
if (!$this->isAssigned())
// We can only unassigned OPEN tickets.
if ($this->isClosed())
// Unassign staff (if any)
if ($this->getStaffId() && !$this->setStaffId(0))
// Unassign team (if any)
if ($this->getTeamId() && !$this->setTeamId(0))
Peter Rotich
committed
function release() {
return $this->unassign();
}
function refer(ReferralForm $form, &$errors, $alert=true) {
global $thisstaff;
$evd = array();
$referee = $form->getReferee();
case $referee instanceof Staff:
if ($this->getStaffId() == $referee->getId()) {
$errors['agent'] = sprintf(__('%s is assigned to %s'),
} elseif(!$referee->isAvailable()) {
$errors['agent'] = sprintf(__('Agent is unavailable for %s'),
$evd['staff'] = array($referee->getId(), (string) $referee->getName()->getOriginal());
case $referee instanceof Team:
if ($this->getTeamId() == $referee->getId()) {
$errors['team'] = sprintf(__('%s is assigned to %s'),
__('Ticket'),
__('the team')
);
} else {
//TODO::
$evd = array('team' => $referee->getId());
case $referee instanceof Dept:
if ($this->getDeptId() == $referee->getId()) {
$errors['dept'] = sprintf(__('%s is already in %s'),
__('Ticket'),
__('the department')
);
} else {
//TODO::
$evd = array('dept' => $referee->getId());
}
break;
default:
$errors['target'] = __('Unknown referral');
}
if (!$errors && !$this->thread->refer($referee))
$errors['err'] = __('Unable to refer ticket');
if ($errors)
return false;
$this->logEvent('referred', $evd);
return true;
}
function systemReferral($emails) {
if (!$this->thread)
return;
foreach ($emails as $id) {
if ($id != $this->email_id
&& ($email=Email::lookup($id))
&& $this->getDeptId() != $email->getDeptId()
&& ($dept=Dept::lookup($email->getDeptId()))
&& $this->thread->refer($dept)
)
$this->logEvent('referred',
array('dept' => $dept->getId()));
}
}
//Change ownership
function changeOwner($user) {
global $thisstaff;
if (!$user
|| ($user->getId() == $this->getOwnerId())
|| !($this->checkStaffPerm($thisstaff,
$this->user_id = $user->getId();
if (!$this->save())
$this->collaborators = null;
$this->recipients = null;
// Remove the new owner from list of collaborators
'user_id' => $user->getId(),
'thread_id' => $this->getThreadId()
));
if ($c)
$c->delete();
$this->logEvent('edited', array('owner' => $user->getId()));
return true;
}
function postMessage($vars, $origin='', $alerts=true) {
if ($origin)
$vars['origin'] = $origin;
$vars['ip_address'] = $vars['ip'];
elseif (!$vars['ip_address'] && $_SERVER['REMOTE_ADDR'])
$vars['ip_address'] = $_SERVER['REMOTE_ADDR'];
$errors = array();
$hdr = Mail_parse::splitHeaders($vars['header'], true);
$existingCollab = Collaborator::getIdByUserId($vars['userId'], $this->getThreadId());
if (($vars['userId'] != $this->user_id) && (!$existingCollab)) {
if ($vars['userId'] == 0) {
$emailStream = '<<<EOF' . $vars['header'] . 'EOF';
$parsed = EmailDataParser::parse($emailStream);
$email = $parsed['email'];
if (!$existinguser = User::lookupByEmail($email)) {
$name = $parsed['name'];
$user = User::fromVars(array('name' => $name, 'email' => $email));
$vars['userId'] = $user->getId();
}
}
else
$user = User::lookup($vars['userId']);
$c = $this->getThread()->addCollaborator($user,array(), $errors);
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
foreach (array('To', 'TO', 'Cc', 'CC') as $k) {
if ($user && isset($hdr[$k]) && $hdr[$k])
$addresses[] = Mail_Parse::parseAddressList($hdr[$k]);
}
if (count($addresses) > 1) {
$isMsg = true;
$c->setCc();
}
else
$c->setBcc();
}
else {
$c = Collaborator::lookup($existingCollab);
if ($c && !$c->isCc()) {
foreach (array('To', 'TO', 'Cc', 'CC') as $k) {
if (isset($hdr[$k]) && $hdr[$k])
$addresses[] = Mail_Parse::parseAddressList($hdr[$k]);
}
if (count($addresses) > 1) {
$isMsg = true;
$c->setCc();
}
}
}
if ($vars['userId'] == $this->user_id)
$isMsg = true;
//lookup user by userId. if they are bcc in thread, post internal note
if($collabs = $this->getRecipients()) {
foreach ($collabs as $collab) {
if(get_class($collab) == 'Collaborator' && $collab->user_id == $vars['userId'] && !$collab->isCc()) {
$user = User::lookup($vars['userId']);
$vars['note'] = $vars['message'];
//post internal note
if (!$isMsg) {
return $this->postNote($vars,$errors, $user, true);
}
}
}
}
if (!($message = $this->getThread()->addMessage($vars, $errors)))
$this->setLastMessage($message);
// Add email recipients as collaborators...
if ($vars['recipients']
&& (strtolower($origin) != 'email' || ($cfg && $cfg->addCollabsViaEmail()))
//Only add if we have a matched local address
&& $vars['to-email-id']
) {
//New collaborators added by other collaborators are disable --
// requires staff approval.
$info = array(
'isactive' => ($message->getUserId() == $this->getUserId())? 1: 0);
$collabs = array();
foreach ($vars['recipients'] as $recipient) {
// Skip virtual delivered-to addresses
if (strcasecmp($recipient['source'], 'delivered-to') === 0)
continue;
if (($cuser=User::fromVars($recipient))) {
if (!$existing = Collaborator::getIdByUserId($cuser->getId(), $this->getThreadId())) {
if ($c=$this->addCollaborator($cuser, $info, $errors, false)) {
$c->setCc();
// FIXME: This feels very unwise — should be a
// string indexed array for future
$collabs[$c->user_id] = array(
'name' => $c->getName()->getOriginal(),
'src' => $recipient['source'],
);
}
}
}
if ($collabs) {
$this->logEvent('collab', array('add' => $collabs), $message->user);
// Do not auto-respond to bounces and other auto-replies
$autorespond = isset($vars['mailflags'])
? !$vars['mailflags']['bounce'] && !$vars['mailflags']['auto-reply']
$reopen = $autorespond; // Do not reopen bounces
if ($autorespond && $message->isBounceOrAutoReply())
elseif ($autorespond && isset($vars['autorespond']))
$autorespond = $vars['autorespond'];
$this->onMessage($message, ($autorespond && $alerts), $reopen); //must be called b4 sending alerts to staff.
if ($autorespond && $alerts && $cfg && $cfg->notifyCollabsONNewMessage()) {
//when user replies, this is where collabs notified
$this->notifyCollaborators($message, array('signature' => ''));
}
return $message; //Our work is done...
'message' => $message,
'poster' => ($vars['poster'] ? $vars['poster'] : $this->getName())
);
$options = array('thread'=>$message);
// If enabled...send alert to staff (New Message Alert)
if ($cfg->alertONNewMessage()
&& ($email = $dept->getAlertEmail())
&& ($tpl = $dept->getTemplate())
&& ($msg = $tpl->getNewMessageAlertMsgTemplate())
) {
$msg = $this->replaceVars($msg->asArray(), $variables);
// Build list of recipients and fire the alerts.
$recipients = array();
if ($cfg->alertLastRespondentONNewMessage() && ($lr = $this->getLastRespondent()))
$recipients[] = $lr;
Peter Rotich
committed
//Assigned staff if any...could be the last respondent
if ($cfg->alertAssignedONNewMessage() && $this->isAssigned()) {
if ($staff = $this->getStaff())
$recipients[] = $staff;
elseif ($team = $this->getTeam())
$recipients = array_merge($recipients, $team->getMembers());
}
Peter Rotich
committed
// Dept manager
if ($cfg->alertDeptManagerONNewMessage()
&& $dept
&& ($manager = $dept->getManager())
) {
Peter Rotich
committed
// Account manager
if ($cfg->alertAcctManagerONNewMessage()
&& ($org = $this->getOwner()->getOrganization())
&& ($acct_manager = $org->getAccountManager())) {
if ($acct_manager instanceof Team)
$recipients = array_merge($recipients, $acct_manager->getMembers());
else
$recipients[] = $acct_manager;
}
$sentlist = array(); //I know it sucks...but..it works.
foreach ($recipients as $k=>$staff) {
if (!$staff || !$staff->getEmail()
|| !$staff->isAvailable()
|| in_array($staff->getEmail(), $sentlist)
) {
continue;
}
$alert = $this->replaceVars($msg, array('recipient' => $staff));
$email->sendAlert($staff, $alert['subj'], $alert['body'], null, $options);
return $message;
function postCannedReply($canned, $message, $alert=true) {
if ((!is_object($canned) && !($canned=Canned::lookup($canned)))
|| !$canned->isEnabled()
) {
foreach ($canned->attachments->getAll() as $file) {
$files[] = $file->file_id;
$_SESSION[':cannedFiles'][$file->file_id] = 1;
}
if ($cfg->isRichTextEnabled())
$this->replaceVars($canned->getPlainText()));
$info = array('msgId' => $message instanceof ThreadEntry ? $message->getId() : 0,
'poster' => __('SYSTEM (Canned Reply)'),
if (!($response=$this->postReply($info, $errors, false, false)))
if (($email=$dept->getEmail())
&& ($tpl = $dept->getTemplate())
&& ($msg=$tpl->getAutoReplyMsgTemplate())
) {
if ($dept && $dept->isPublic())
$signature=$dept->getSignature();
else
$signature='';
$msg = $this->replaceVars($msg->asArray(),
array(
'response' => $response,
'signature' => $signature,
'recipient' => $this->getOwner(),
)
);
$attachments = ($cfg->emailAttachments() && $files)
? $response->getAttachments() : array();
$options = array('thread' => $response);
if (($message instanceof ThreadEntry)
&& $message->getUserId() == $this->getUserId()
&& ($mid=$message->getEmailMessageId())) {
$options += array(
'inreplyto' => $mid,
'references' => $message->getEmailReferences()
);
}
$email->sendAutoReply($this->getOwner(), $msg['subj'], $msg['body'], $attachments,
return $response;
function postReply($vars, &$errors, $alert=true, $claim=true) {
if ($collabs = $this->getRecipients()) {
$collabIds = array();
foreach ($collabs as $collab)
$collabIds[] = $collab->user_id;
}
$ticket = Ticket::lookup($vars['id']);
if (isset($vars['ccs'])) {
foreach ($vars['ccs'] as $uid) {
$user = User::lookup($uid);
if (!in_array($uid, $collabIds))
if (($c2=$ticket->getThread()->addCollaborator($user,array(), $errors)))
$c2->setCc();
}
}
if (isset($vars['bccs'])) {
foreach ($vars['bccs'] as $uid) {
$user = User::lookup($uid);
if (!in_array($uid, $collabIds))
if (($c2=$ticket->getThread()->addCollaborator($user,array(), $errors)))
$c2->setBcc();
}
}
if (!$vars['poster'] && $thisstaff)
$vars['poster'] = $thisstaff;
$vars['staffId'] = $thisstaff->getId();
if (!$vars['ip_address'] && $_SERVER['REMOTE_ADDR'])
$vars['ip_address'] = $_SERVER['REMOTE_ADDR'];
if (!($response = $this->getThread()->addResponse($vars, $errors)))
$dept = $this->getDept();
// Set status - if checked.
if ($vars['reply_status_id']
&& $vars['reply_status_id'] != $this->getStatusId()
) {
$this->setStatus($vars['reply_status_id']);
// Claim on response bypasses the department assignment restrictions
$claim = ($claim
&& $cfg->autoClaimTickets()
&& !$dept->disableAutoClaim());
if ($claim && $thisstaff && $this->isOpen() && !$this->getStaffId()) {
$this->setStaffId($thisstaff->getId()); //direct assignment;
$this->lastrespondent = $response->staff;
$this->onResponse($response, array('assignee' => $assignee)); //do house cleaning..
/* email the user?? - if disabled - then bail out */
//allow agent to send from different dept email
$vars['from_name'] ? $email = Email::lookup($vars['from_name']) : $email = $dept->getEmail();
$options = array('thread'=>$response);
$signature = $from_name = '';
if ($thisstaff && $vars['signature']=='mine')
$signature=$thisstaff->getSignature();
elseif ($vars['signature']=='dept' && $dept->isPublic())
$signature=$dept->getSignature();
if ($thisstaff && ($type=$thisstaff->getReplyFromNameType())) {
switch ($type) {
case 'mine':
if (!$cfg->hideStaffName())
$from_name = (string) $thisstaff->getName();
break;
case 'dept':
if ($dept->isPublic())
$from_name = $dept->getName();
break;
case 'email':
default:
$from_name = $email->getName();
}
if ($from_name)
$options += array('from_name' => $from_name);
}
'response' => $response,
'signature' => $signature,
'staff' => $thisstaff,
'poster' => $thisstaff
);
$msg = $this->replaceVars($msg->asArray(),
$variables + array('recipient' => $user)
$attachments = $cfg->emailAttachments() ? $response->getAttachments() : array();
$collabsCc = array();
if ($vars['ccs'] && $vars['emailcollab']) {
$collabsCc[] = Collaborator::getCollabList($vars['ccs']);
$collabsCc['cc'] = $collabsCc;
$email->send($user, $msg['subj'], $msg['body'], $attachments,
$options, $collabsCc);
if ($vars['bccs']
&& $vars['emailcollab']
&& ($bcctpl = $dept->getTemplate())
&& ($bccmsg=$bcctpl->getReplyMsgTemplate())) {
foreach ($vars['bccs'] as $uid) {
if (!($recipient = User::lookup($uid)))
continue;
$msg = $this->replaceVars($bccmsg->asArray(), $variables + array(
'recipient' => $user,
'recipient.name.first' =>
$recipient->getName()->getFirst()));
$email->send($recipient, $msg['subj'], $msg['body'], $attachments, $options);
return $response;