Skip to content
Snippets Groups Projects
  • Jared Hancock's avatar
    29b37144
    Remove requirement of ticket id in subject line · 29b37144
    Jared Hancock authored
    This patch affords an administrator the ability to remove the
    [#%{ticket.number}] from the email template subject line for the new ticket
    autoresponse and the new message autoresponse. Previously, the ticket number
    with a prefixed hash in brackets was used to identify which ticket thread an
    email was in reference to.
    
    With this patch, the email message-id (which was already kept on file) is
    sent in the MIME "References" header. When a user responds to and
    autoresponse email, the "References" will include this message-id in the
    return email. The ticket thread is then matched up with the email based on
    the message-id rather than the subject line.
    
    Ticket numbers are still supported in the subject line, in the event that
    non-compliant email clients do not properly include the References header.
    29b37144
    History
    Remove requirement of ticket id in subject line
    Jared Hancock authored
    This patch affords an administrator the ability to remove the
    [#%{ticket.number}] from the email template subject line for the new ticket
    autoresponse and the new message autoresponse. Previously, the ticket number
    with a prefixed hash in brackets was used to identify which ticket thread an
    email was in reference to.
    
    With this patch, the email message-id (which was already kept on file) is
    sent in the MIME "References" header. When a user responds to and
    autoresponse email, the "References" will include this message-id in the
    return email. The ticket thread is then matched up with the email based on
    the message-id rather than the subject line.
    
    Ticket numbers are still supported in the subject line, in the event that
    non-compliant email clients do not properly include the References header.
api.tickets.php 5.63 KiB
<?php

include_once INCLUDE_DIR.'class.api.php';
include_once INCLUDE_DIR.'class.ticket.php';

class TicketApiController extends ApiController {

    # Supported arguments -- anything else is an error. These items will be
    # inspected _after_ the fixup() method of the ApiXxxDataParser classes
    # so that all supported input formats should be supported
    function getRequestStructure($format) {
        $supported = array(
            "alert", "autorespond", "source", "topicId",
            "name", "email", "subject", "phone", "phone_ext",
            "attachments" => array("*" =>
                array("name", "type", "data", "encoding")
            ),
            "message", "ip", "priorityId"
        );

        if(!strcasecmp($format, 'email'))
            $supported = array_merge($supported, array('header', 'mid',
                'emailId', 'ticketId', 'reply-to', 'reply-to-name'));

        return $supported;
    }

    /*
     Validate data - overwrites parent's validator for additional validations.
    */
    function validate(&$data, $format) {
        global $ost;

        //Call parent to Validate the structure
        if(!parent::validate($data, $format))
            $this->exerr(400, 'Unexpected or invalid data received');

        //Nuke attachments IF API files are not allowed.
        if(!$ost->getConfig()->allowAPIAttachments())
            $data['attachments'] = array();

        //Validate attachments: Do error checking... soft fail - set the error and pass on the request.
        if($data['attachments'] && is_array($data['attachments'])) {
            foreach($data['attachments'] as &$attachment) {
                if(!$ost->isFileTypeAllowed($attachment))
                    $attachment['error'] = 'Invalid file type (ext) for '.Format::htmlchars($attachment['name']);
                elseif ($attachment['encoding'] && !strcasecmp($attachment['encoding'], 'base64')) {
                    if(!($attachment['data'] = base64_decode($attachment['data'], true)))
                        $attachment['error'] = sprintf('%s: Poorly encoded base64 data', Format::htmlchars($attachment['name']));
                }
            }
            unset($attachment);
        }

        return true;
    }


    function create($format) {

        if(!($key=$this->requireApiKey()) || !$key->canCreateTickets())
            return $this->exerr(401, 'API key not authorized');

        $ticket = null;
        if(!strcasecmp($format, 'email')) {
            # Handle remote piped emails - could be a reply...etc.
            $ticket = $this->processEmail();
        } else {
            # Parse request body
            $ticket = $this->createTicket($this->getRequest($format));
        }

        if(!$ticket)
            return $this->exerr(500, "Unable to create new ticket: unknown error");

        $this->response(201, $ticket->getExtId());
    }

    /* private helper functions */

    function createTicket($data) {

        # Pull off some meta-data
        $alert = $data['alert'] ? $data['alert'] : true;
        $autorespond = $data['autorespond'] ? $data['autorespond'] : true;
        $data['source'] = $data['source'] ? $data['source'] : 'API';

        # Create the ticket with the data (attempt to anyway)
        $errors = array();
        $ticket = Ticket::create($data, $errors, $data['source'], $autorespond, $alert);
        # Return errors (?)
        if (count($errors)) {
            if(isset($errors['errno']) && $errors['errno'] == 403)
                return $this->exerr(403, 'Ticket denied');
            else
                return $this->exerr(
                        400,
                        "Unable to create new ticket: validation errors:\n"
                        .Format::array_implode(": ", "\n", $errors)
                        );
        } elseif (!$ticket) {
            return $this->exerr(500, "Unable to create new ticket: unknown error");
        }

        return $ticket;
    }

    function processEmail() {

        $data = $this->getEmailRequest();
        if($data['ticketId'] && ($ticket=Ticket::lookup($data['ticketId']))) {
            if(($msgid=$ticket->postMessage($data, 'Email')))
                return $ticket;
        }

        if (($thread = ThreadEntry::lookupByEmailHeaders($data))
                && $thread->postEmail($data)) {
            return true;
        }
        return $this->createTicket($data);
    }

}

//Local email piping controller - no API key required!
class PipeApiController extends TicketApiController {

    //Overwrite grandparent's (ApiController) response method.
    function response($code, $resp) {

        //Use postfix exit codes - instead of HTTP
        switch($code) {
            case 201: //Success
                $exitcode = 0;
                break;
            case 400:
                $exitcode = 66;
                break;
            case 401: /* permission denied */
            case 403:
                $exitcode = 77;
                break;
            case 415:
            case 416:
            case 417:
            case 501:
                $exitcode = 65;
                break;
            case 503:
                $exitcode = 69;
                break;
            case 500: //Server error.
            default: //Temp (unknown) failure - retry
                $exitcode = 75;
        }

        //echo "$code ($exitcode):$resp";
        //We're simply exiting - MTA will take care of the rest based on exit code!
        exit($exitcode);
    }

    function  process() {
        $pipe = new PipeApiController();
        if(($ticket=$pipe->processEmail()))
           return $pipe->response(201, $ticket->getNumber());

        return $pipe->exerr(416, 'Request failed - retry again!');
    }
}

?>