diff --git a/include/ajax.tickets.php b/include/ajax.tickets.php index 05cf5354c426092df2d32adc814e83b31aac228a..68ff2fbacfb26e92f2e9a27c8958cf9b7e08adbd 100644 --- a/include/ajax.tickets.php +++ b/include/ajax.tickets.php @@ -661,11 +661,10 @@ function refer($tid, $target=null) { if (!$ticket->checkStaffPerm($thisstaff, Ticket::PERM_RELEASE) && !$thisstaff->isManager()) Http::response(403, __('Permission denied')); + $errors = array(); if (!$ticket->isAssigned()) $errors['err'] = __('Ticket is not assigned!'); - - $errors = array(); $info = array(':title' => sprintf(__('Ticket #%s: %s'), $ticket->getNumber(), __('Release Confirmation'))); diff --git a/include/class.mailer.php b/include/class.mailer.php index 017d7fbb0ec99267cf1e51920a4e2dd62a88e13d..5d7a135a354330624c1122f342a57c1ab3ab5c3f 100644 --- a/include/class.mailer.php +++ b/include/class.mailer.php @@ -399,28 +399,41 @@ class Mailer { // Use general failsafe default initially $eol = "\n"; // MAIL_EOL setting can be defined in `ost-config.php` - if (defined('MAIL_EOL') && is_string(MAIL_EOL)) { + if (defined('MAIL_EOL') && is_string(MAIL_EOL)) $eol = MAIL_EOL; - } - $mime = new Mail_mime($eol); - // Add recipients if (!is_array($recipients) && (!$recipients instanceof MailingList)) $recipients = array($recipients); foreach ($recipients as $recipient) { switch (true) { + case $recipient instanceof EmailRecipient: + $addr = sprintf('%s <%s>', + $recipient->getName(), + $recipient->getEmail()); + switch ($recipient->getType()) { + case 'to': + $mime->addTo($addr); + break; + case 'cc': + $mime->addCc($addr); + break; + case 'bcc': + $mime->addBcc($addr); + break; + } + break; case $recipient instanceof TicketOwner: case $recipient instanceof Staff: $mime->addTo(sprintf('%s <%s>', $recipient->getName(), $recipient->getEmail())); - break; + break; case $recipient instanceof Collaborator: $mime->addCc(sprintf('%s <%s>', $recipient->getName(), $recipient->getEmail())); - break; + break; default: // Assuming email address. $mime->addTo($recipient); @@ -572,6 +585,7 @@ class Mailer { if ($this->getEmail()) $args = array('-f '.$this->getEmail()->getEmail()); $mail = mail::factory('mail', $args); + $to = $headers['To']; $result = $mail->send($to, $headers, $body); if(!PEAR::isError($result)) return $messageId; diff --git a/include/class.thread.php b/include/class.thread.php index 177c608a0f66f6a9461ee00e3a01ce8f4815ffa3..66b339d0ac6837b42091eb177e606f1aedf148bc 100644 --- a/include/class.thread.php +++ b/include/class.thread.php @@ -1466,18 +1466,6 @@ implements TemplateVariable { return $entry; } - function setReplyFlag($entry, $replyType) { - switch ($replyType) { - case 'all': - return $entry->flags |= ThreadEntry::FLAG_REPLY_ALL; - break; - - case 'user': - return $entry->flags |= ThreadEntry::FLAG_REPLY_USER; - break; - } - } - //new entry ... we're trusting the caller to check validity of the data. static function create($vars=false) { global $cfg; @@ -1513,37 +1501,15 @@ implements TemplateVariable { 'poster' => $poster, 'source' => $vars['source'], 'flags' => $vars['flags'] ?: 0, - 'recipients' => $vars['recipients'], )); //add recipients to thread entry - $recipients = array(); - $ticket = Thread::objects()->filter(array('id'=>$vars['threadId']))->values_flat('object_id')->first(); - $ticketUser = Ticket::objects()->filter(array('ticket_id'=>$ticket[0]))->values_flat('user_id')->first(); - - //User - if ($ticketUser && strcasecmp('none', $vars['reply-to'])) { - $uEmail = UserEmailModel::objects()->filter(array('user_id'=>$ticketUser[0]))->values_flat('address')->first(); - $u = array(); - $u[$ticketUser[0]] = $uEmail[0]; - $recipients['to'] = $u; - } + if ($vars['recipients']) + $entry->recipients = json_encode($vars['recipients']); if (Collaborator::getIdByUserId($vars['userId'], $vars['threadId'])) $entry->flags |= ThreadEntry::FLAG_COLLABORATOR; - //add reply type flag - self::setReplyFlag($entry, $vars['reply-to']); - - //Cc collaborators - if ($vars['ccs'] && !strcasecmp('all', $vars['reply-to'])) { - $cc = Collaborator::getCollabList($vars['ccs']); - $recipients['cc'] = $cc; - } - - if ($vars['reply-to'] != 'none' && $recipients) - $entry->recipients = json_encode($recipients); - if ($entry->format == 'html') // The current codebase properly balances html $entry->flags |= self::FLAG_BALANCED; @@ -2893,10 +2859,19 @@ implements TemplateVariable { } function addResponse($vars, &$errors) { - $vars['threadId'] = $this->getId(); $vars['userId'] = 0; - $vars['pid'] = $this->getLastMessage()->id; + $vars['pid'] = $this->getLastMessage()->getId(); + + $vars['flags'] = 0; + switch ($vars['reply-to']) { + case 'all': + $vars['flags'] |= ThreadEntry::FLAG_REPLY_ALL; + break; + case 'user': + $vars['flags'] |= ThreadEntry::FLAG_REPLY_USER; + break; + } if (!($resp = ResponseThreadEntry::add($vars, $errors))) return $resp; diff --git a/include/class.ticket.php b/include/class.ticket.php index f9646b39df8096adb8c07a48c1b8de681c143cb5..bec6136bfee9b6ce72375cc15cca22f2db1b0400 100644 --- a/include/class.ticket.php +++ b/include/class.ticket.php @@ -841,18 +841,29 @@ implements RestrictedAccess, Threadable, Searchable { return $entries; } - // MailingList of recipients (owner + active collaborators) - function getRecipients() { - if (!isset($this->recipients)) { - $list = new MailingList(); - $list->add($this->getOwner()); - if ($collabs = $this->getActiveCollaborators()) { - foreach ($collabs as $c) - $list->add($c); - } - $this->recipients = $list; + // MailingList of participants (owner + collaborators) + function getRecipients($who='all', $whitelist=array(), $active=true) { + $list = new MailingList(); + switch (strtolower($who)) { + case 'user': + $list->addTo($this->getOwner()); + break; + case 'all': + $list->addTo($this->getOwner()); + // Fall-trough + case 'collabs': + if (($collabs = $active ? $this->getActiveCollaborators() : + $this->getCollaborators())) { + foreach ($collabs as $c) + if (!$whitelist || in_array($c->getUserId(), + $whitelist)) + $list->addCc($c); + } + break; + default: + return null; } - return $this->recipients; + return $list; } function getCollaborators() { @@ -1295,18 +1306,15 @@ implements RestrictedAccess, Threadable, Searchable { return $this->save(); } - //Status helper. - + // Ticket Status helper. function setStatus($status, $comments='', &$errors=array(), $set_closing_agent=true) { global $thisstaff; if ($thisstaff && !($role=$this->getRole($thisstaff))) return false; - if ($status && is_numeric($status)) - $status = TicketStatus::lookup($status); - - if (!$status || !$status instanceof TicketStatus) + if ((!$status instanceof TicketStatus) + && !($status = TicketStatus::lookup($status))) return false; // Double check permissions (when changing status) @@ -1317,7 +1325,7 @@ implements RestrictedAccess, Threadable, Searchable { return false; break; case 'deleted': - // XXX: intercept deleted status and do hard delete + // XXX: intercept deleted status and do hard delete TODO: soft deletes if ($role->hasPerm(Ticket::PERM_DELETE)) return $this->delete($comments); // Agent doesn't have permission to delete tickets @@ -2885,18 +2893,6 @@ implements RestrictedAccess, Threadable, Searchable { function postReply($vars, &$errors, $alert=true, $claim=true) { global $thisstaff, $cfg; - // Add new collabs if any. - $collabs = $this->getCollaborators(); - if (isset($vars['ccs'])) { - foreach ($vars['ccs'] as $uid) { - if ($collabs->findFirst(array('user_id' => $uid))) - continue; - - if ($user=User::lookup($uid)) - $this->addCollaborator($user, array(), $errors); - } - } - if (!$vars['poster'] && $thisstaff) $vars['poster'] = $thisstaff; @@ -2906,18 +2902,25 @@ implements RestrictedAccess, Threadable, Searchable { if (!$vars['ip_address'] && $_SERVER['REMOTE_ADDR']) $vars['ip_address'] = $_SERVER['REMOTE_ADDR']; + // Add new collaboratorss (if any). + if (isset($vars['ccs']) && count($vars['ccs'])) + $this->addCollaborators($vars['ccs']); + + // Get active recipients of the response + $recipients = $this->getRecipients($vars['reply-to'], $vars['ccs']); + if ($recipients instanceof MailingList) + $vars['recipients'] = $recipients->getEmailAddresses(); + if (!($response = $this->getThread()->addResponse($vars, $errors))) return null; $dept = $this->getDept(); $assignee = $this->getStaff(); - // Set status - if checked. + // Set status if new is selected if ($vars['reply_status_id'] - && $vars['reply_status_id'] != $this->getStatusId() - ) { - $this->setStatus($vars['reply_status_id']); - } - + && ($status = TicketStatus::lookup($vars['reply_status_id'])) + && $status->getId() != $this->getStatusId()) + $this->setStatus($status); // Claim on response bypasses the department assignment restrictions $claim = ($claim @@ -2973,8 +2976,9 @@ implements RestrictedAccess, Threadable, Searchable { ); if ($email - && ($tpl = $dept->getTemplate()) - && ($msg=$tpl->getReplyMsgTemplate())) { + && $recipients + && ($tpl = $dept->getTemplate()) + && ($msg=$tpl->getReplyMsgTemplate())) { $msg = $this->replaceVars($msg->asArray(), $variables + array('recipient' => $this->getOwner()) @@ -2984,17 +2988,6 @@ implements RestrictedAccess, Threadable, Searchable { $attachments = $cfg->emailAttachments() ? $response->getAttachments() : array(); - // Get active recipients - $recipients = new MailingList(); - $recipients->add($this->getOwner()); - if (!strcasecmp('all', $vars['reply-to']) - && ($collabs = $this->getActiveCollaborators())){ - foreach ($collabs as $c) - if ($vars['ccs'] && in_array($c->getUserId(), - $vars['ccs'])) - $recipients->add($c); - } - //Send email to recepients $email->send($recipients, $msg['subj'], $msg['body'], $attachments, $options); @@ -4070,35 +4063,30 @@ implements RestrictedAccess, Threadable, Searchable { if (!($ticket=self::create($create_vars, $errors, 'staff', false))) return false; - if (isset($vars['ccs'])) - $ticket->addCollaborators($vars['ccs'], array(), $errors); - - if (strcasecmp('user', $vars['reply-to'])) - $recipients = $ticket->getRecipients(); - else - $recipients = $ticket->getOwner(); - $vars['msgId']=$ticket->getLastMsgId(); // Effective role for the department $role = $ticket->getRole($thisstaff); - $alert = strcasecmp('none', $vars['reply-to']); + // Add collaborators (if any) + if (isset($vars['ccs']) && count($vars['ccs'])) + $ticket->addCollaborators($vars['ccs'], array(), $errors); + $alert = strcasecmp('none', $vars['reply-to']); // post response - if any $response = null; - if($vars['response'] && $role->hasPerm(Ticket::PERM_REPLY)) { + if ($vars['response'] && $role->hasPerm(Ticket::PERM_REPLY)) { $vars['response'] = $ticket->replaceVars($vars['response']); // $vars['cannedatachments'] contains the attachments placed on // the response form. - $response = $ticket->postReply($vars, $errors, $alert && !$cfg->notifyONNewStaffTicket()); + $response = $ticket->postReply($vars, $errors, ($alert && + !$cfg->notifyONNewStaffTicket())); } // Not assigned...save optional note if any if (!$ticket->isAssigned() && $vars['note']) { - if (!$cfg->isRichTextEnabled()) { + if (!$cfg->isRichTextEnabled()) $vars['note'] = new TextThreadEntryBody($vars['note']); - } $ticket->logNote(_S('New Ticket'), $vars['note'], $thisstaff, false); } @@ -4108,6 +4096,10 @@ implements RestrictedAccess, Threadable, Searchable { ) { return $ticket; //No alerts. } + + // Notice Recipients + $recipients = $ticket->getRecipients($vars['reply-to']); + // Send Notice to user --- if requested AND enabled!! if (($tpl=$dept->getTemplate()) && ($msg=$tpl->getNewTicketNoticeMsgTemplate()) diff --git a/include/class.util.php b/include/class.util.php index 52035bcb2287a0e204745c504bb8f5c62e2b7f28..962ef6f7a33efd342c3365b7c723d520ef9bc4fc 100644 --- a/include/class.util.php +++ b/include/class.util.php @@ -9,6 +9,34 @@ interface EmailContact { function getEmail(); } + +class EmailRecipient +implements EmailContact { + protected $contact; + protected $type; + + function __construct(EmailContact $contact, $type='to') { + $this->contact = $contact; + $this->type = $type; + } + + function getId() { + return $this->contact->getId(); + } + + function getEmail() { + return $this->contact->getEmail(); + } + + function getName() { + return $this->contact->getName(); + } + + function getType() { + return $this->type; + } +} + abstract class BaseList implements IteratorAggregate, Countable { protected $storage = array(); @@ -190,17 +218,43 @@ implements ArrayAccess, Serializable { class MailingList extends ListObject implements TemplateVariable { - function add($contact) { - if (!$contact instanceof EmailContact) - throw new InvalidArgumentException('Email Contact expected'); + function add($recipient) { + if (!$recipient instanceof EmailRecipient) + throw new InvalidArgumentException('Email Recipient expected'); - return parent::add($contact); + return parent::add($recipient); + } + + function addRecipient($contact, $to='to') { + return $this->add(new EmailRecipient($contact, $to)); + } + + function addTo(EmailContact $contact) { + return $this->addRecipient($contact, 'to'); + } + + function addCc(EmailContact $contact) { + return $this->addRecipient($contact, 'cc'); + } + + function addBcc(EmailContact $contact) { + return $this->addRecipient($contact, 'bcc'); } function __toString() { return $this->getNames(); } + // Recipients' email addresses + function getEmailAddresses() { + $list = array(); + foreach ($this->storage as $u) { + $list[$u->getType()][$u->getId()] = sprintf("%s <%s>", + $u->getName(), $u->getEmail()); + } + return $list; + } + function getNames() { $list = array(); foreach($this->storage as $user) { diff --git a/scp/tickets.php b/scp/tickets.php index 444275482087101137b03c913c6a72957efe1d64..e6f668aeb2b551df1ce9ce2e431c4164c9283de4 100644 --- a/scp/tickets.php +++ b/scp/tickets.php @@ -147,8 +147,7 @@ if($_POST && !$errors): case 'reply': if (!$role || !$role->hasPerm(Ticket::PERM_REPLY)) { $errors['err'] = __('Action denied. Contact admin for access'); - } - else { + } else { $vars = $_POST; $vars['cannedattachments'] = $response_form->getField('attachments')->getClean(); $vars['response'] = ThreadEntryBody::clean($vars['response']); @@ -175,8 +174,9 @@ if($_POST && !$errors): if(!$errors['err'] && Banlist::isBanned($ticket->getEmail())) $errors['err']=__('Email is in banlist. Must be removed to reply.'); } + $alert = strcasecmp('none', $_POST['reply-to']); - if(!$errors && ($response=$ticket->postReply($vars, $errors, + if (!$errors && ($response=$ticket->postReply($vars, $errors, $alert))) { $msg = sprintf(__('%s: Reply posted successfully'), sprintf(__('Ticket #%s'), @@ -203,7 +203,7 @@ if($_POST && !$errors): if ($ticket) $redirect = 'tickets.php?id='.$ticket->getId(); - } elseif(!$errors['err']) { + } elseif (!$errors['err']) { $errors['err']=sprintf('%s %s', __('Unable to post the reply.'), __('Correct any errors below and try again.'));