diff --git a/api/.htaccess b/api/.htaccess index e73e2eb3b9c3f1204223b4426274817c0200e279..f460420d6d2f55f362fb420788b6751afe38bbba 100644 --- a/api/.htaccess +++ b/api/.htaccess @@ -1,8 +1,11 @@ -RewriteEngine On +<IfModule mod_rewrite.c> -RewriteBase /api/ +RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d +RewriteCond %{REQUEST_URI} (.*/api) + +RewriteRule ^(.*)$ %1/http.php/$1 [L] -RewriteRule ^(.*)$ http.php/$1 [L] +</IfModule> diff --git a/include/api.ticket.php b/include/api.ticket.php index 4fcae4b183925dfe32bb91c95275a986fc492ea6..51e129ad41e5cfe691517fbdb1a180326ecec4f7 100644 --- a/include/api.ticket.php +++ b/include/api.ticket.php @@ -15,7 +15,7 @@ class TicketController extends ApiController { "attachments" => array("*" => array("name", "type", "data", "encoding") ), - "message", "ip" + "message", "ip", "priorityId" ); if ($format == "xml") return array("ticket" => $supported); else return $supported; @@ -43,7 +43,7 @@ class TicketController extends ApiController { if (!($info["data"] = base64_decode($info["data"], true))) Http::response(400, sprintf( "%s: Poorly encoded base64 data", - $filename)); + $info['name'])); } $info['size'] = strlen($info['data']); } diff --git a/include/class.api.php b/include/class.api.php index 1bd25463484a4e2f01a1d72050dde4e92d89d967..f3d3094a5b695a1601cb0d91e5dafdfe6014918b 100644 --- a/include/class.api.php +++ b/include/class.api.php @@ -184,7 +184,7 @@ class ApiController { function validate($data, $structure, $prefix="") { foreach ($data as $key=>$info) { if (is_array($structure) and is_array($info)) { - $search = isset($structure[$key]) ? $key : "*"; + $search = (isset($structure[$key]) && !is_numeric($key)) ? $key : "*"; if (isset($structure[$search])) { $this->validate($info, $structure[$search], "$prefix$key/"); continue; @@ -219,16 +219,21 @@ class ApiXmlDataParser extends XmlDataParser { } else if ($key == "autorespond") { $value = (bool)$value; } else if ($key == "attachments") { - foreach ($value as &$info) { - $info["data"] = $info[":text"]; - unset($info[":text"]); + if(!isset($value['file'][':text'])) + $value = $value['file']; + + if($value && is_array($value)) { + foreach ($value as &$info) { + $info["data"] = $info[":text"]; + unset($info[":text"]); + } + unset($info); } - unset($info); - } - if (is_array($value)) { + } else if(is_array($value)) { $value = $this->fixup($value); } } + return $current; } } @@ -255,7 +260,7 @@ class ApiJsonDataParser extends JsonDataParser { # PHP5: fopen("data://$data[5:]"); if (substr($data, 0, 5) != "data:") { $info = array( - "data" => $data, + "data" => $data, "type" => "text/plain", "name" => key($info)); } else { @@ -264,11 +269,17 @@ class ApiJsonDataParser extends JsonDataParser { list($type, $extra) = explode(";", $meta); $info = array( "data" => $contents, - "type" => $type, + "type" => ($type) ? $type : "text/plain", "name" => key($info)); if (substr($extra, -6) == "base64") $info["encoding"] = "base64"; - # TODO: Handle 'charset' hint in $extra + # Handle 'charset' hint in $extra, such as + # data:text/plain;charset=iso-8859-1,Blah + # Convert to utf-8 since it's the encoding scheme + # for the database. Otherwise, assume utf-8 + list($param,$charset) = explode('=', $extra); + if ($param == 'charset' && function_exists('iconv')) + $contents = iconv($charset, "UTF-8", $contents); } } unset($value); diff --git a/include/class.xml.php b/include/class.xml.php index 854f182372c94f57338b8b45a94d155f69f5bd33..56baf4fbccaf65985921e081afd8e5f475c95c07 100644 --- a/include/class.xml.php +++ b/include/class.xml.php @@ -77,10 +77,14 @@ class XmlDataParser { $this->content = array_pop($this->stack); $i = 1; if (array_key_exists($name, $this->content)) { - while (array_key_exists("$name$i", $this->content)) $i++; - $name = "$name$i"; - } - $this->content[$name] = $prev; + if(!isset($this->content[$name][0])) { + $current = $this->content[$name]; + unset($this->content[$name]); + $this->content[$name][0] = $current; + } + $this->content[$name][] = $prev; + } else + $this->content[$name] = $prev; } function content($parser, $data) { diff --git a/scp/css/scp.css b/scp/css/scp.css index 59964d4e61561ba78bc704e3e6ddb2d1a73af964..67e6212512c0bf617d934fbf1feabd369784fd80 100644 --- a/scp/css/scp.css +++ b/scp/css/scp.css @@ -311,6 +311,7 @@ a.Icon:hover { .Icon.webTicket { background:url(../images/icons/ticket_source_web.gif) 0 0 no-repeat; } .Icon.emailTicket { background:url(../images/icons/ticket_source_email.gif) 0 0 no-repeat; } .Icon.phoneTicket { background:url(../images/icons/ticket_source_phone.gif) 0 0 no-repeat; } +.Icon.apiTicket { background:url(../images/icons/ticket_source_other.gif) 0 0 no-repeat; } .Icon.otherTicket { background:url(../images/icons/ticket_source_other.gif) 0 0 no-repeat; } .Icon.overdueTicket { background:url(../images/icons/overdue_ticket.gif) 0 0 no-repeat; } .Icon.assignedTicket { background:url(../images/icons/assigned_ticket.gif) 0 0 no-repeat; } diff --git a/setup/doc/api.md b/setup/doc/api.md new file mode 100644 index 0000000000000000000000000000000000000000..7509616aa2406b842f56cacd7a6c0693b23415ca --- /dev/null +++ b/setup/doc/api.md @@ -0,0 +1,29 @@ +osTicket API +============ + +The osTicket API is implemented as (somewhat) simple XML or JSON over HTTP. +For now, only ticket creation is supported, but eventually, all resources +inside osTicket will be accessible and modifiable via the API. + +Authentication +-------------- + +Authentication via the API is done via API keys configured inside the +osTicket admin panel. API keys are created and tied to a source IP address, +which will be checked against the source IP of requests to the HTTP API. + +API keys can be created and managed via the admin panel. Navigate to Manage +-> API keys. Use *Add New API Key* to create a new API key. Currently, no +special configuration is required to allow the API key to be used for the +HTTP API. All API keys are valid for the HTTP API. + +Wrappers +-------- + +Currently, there are no wrappers for the API. If you've written one and +would like it on the list, submit a pull request to add your wrapper. + +Resources +--------- + +- [Tickets](api/tickets.md) diff --git a/setup/doc/api/tickets.md b/setup/doc/api/tickets.md new file mode 100644 index 0000000000000000000000000000000000000000..fc79b1aead833a9f4cf9fd3a214635dcba9e52fc --- /dev/null +++ b/setup/doc/api/tickets.md @@ -0,0 +1,128 @@ +Tickets +======= +The API supports ticket creation via the HTTP API (as well as via email, +etc.). Currently, the API support creation of tickets only -- so no +modifications and deletions of existing tickets is possible via the API for +now. + +Create a Ticket +--------------- + +Tickets can be created in the osTicket system by sending an HTTP POST to +`api/tickets.xml` or `api/tickets.json` depending on the format of the +request content. + +### Fields ###### + +* __email__: *required* Email address of the submitter +* __name__: *required* Name of the submitter +* __subject__: *required* Subject of the ticket +* __message__: *required* Initial message for the ticket thread +* __alert__: If unset, disable alerts to staff. Default is `true` +* __autorespond__: If unset, disable autoresponses. Default is `true` +* __ip__: IP address of the submitter +* __phone__: Phone number of the submitter +* __phone_ext__: Phone number extension -- can also be embedded in *phone* +* __priorityId__: Priority *id* for the new ticket to assume +* __source__: Source of the ticket, default is `API` +* __topicId__: Help topic *id* associated with the ticket +* __attachments__: An array of files to attach to the initial message. + Each attachment must have some content and also the + following fields: + * __name__: *required* name of the file to be attached. Multiple files + with the same name are allowable + * __type__: Mime type of the file. Default is `text/plain` + * __encoding__: Set to `base64` if content is base64 encoded + +### XML Payload Example ###### + +* `POST /api/tickets.xml` + +The XML data format is extremely lax. Content can be either sent as an +attribute or a named element if it has no sub-content. + +In the example below, the simple element could also be replaced as +attributes on the root `<ticket>` element; however, if a `CDATA` element is +necessary to hold special content, or difficult content such as double +quotes is to be embedded, simple sub-elements are also supported. + +Notice that the phone extension can be sent as the `@ext` attribute of the +`phone` sub-element. + +``` xml +<?xml version="1.0" encoding="UTF-8"?> +<ticket alert="true" autorespond="true" source="API"> + <name>Angry User</name> + <email>api@osticket.com</email> + <subject>Testing API</subject> + <phone ext="123">318-555-8634</phone> + <message><![CDATA[Message content here]]></message> + <attachments> + <file name="file.txt" type="text/plain"><![CDATA[ + File content is here and is automatically trimmed + ]]></file> + <file name="image.gif" type="image/gif" encoding="base64"> + R0lGODdhMAAwAPAAAAAAAP///ywAAAAAMAAwAAAC8IyPqcvt3wCcDkiLc7C0qwy + GHhSWpjQu5yqmCYsapyuvUUlvONmOZtfzgFzByTB10QgxOR0TqBQejhRNzOfkVJ + +5YiUqrXF5Y5lKh/DeuNcP5yLWGsEbtLiOSpa/TPg7JpJHxyendzWTBfX0cxOnK + PjgBzi4diinWGdkF8kjdfnycQZXZeYGejmJlZeGl9i2icVqaNVailT6F5iJ90m6 + mvuTS4OK05M0vDk0Q4XUtwvKOzrcd3iq9uisF81M1OIcR7lEewwcLp7tuNNkM3u + Nna3F2JQFo97Vriy/Xl4/f1cf5VWzXyym7PHhhx4dbgYKAAA7 + </file> + </attachments> + <ip>123.211.233.122</ip> +</ticket> +``` + +### JSON Payload Example ### + +* `POST /api/tickets.json` + +Attachment data for the JSON content uses the [RFC 2397][] data URL format. +As described above, the content-type and base64 encoding hints are optional. +Furthermore, a character set can be optionally declared for each attachment +and will be automatically converted to UTF-8 for database storage. + +Notice that the phone number extension can be embedded in the `phone` value +denoted with a capital `X` + +Do also note that the JSON format forbids a comma after the last element in +an object or array definition, and newlines are not allowed inside strings. + +``` json +{ + "alert": true, + "autorespond": true, + "source": "API", + "name": "Angry User", + "email": "api@osticket.com", + "phone": "3185558634X123", + "subject": "Testing API", + "ip": "123.211.233.122", + "message": "MESSAGE HERE", + "attachments": [ + {"file.txt": "data:text/plain;charset=utf-8,content"}, + {"image.png": "data:image/png;base64,R0lGODdhMAA..."}, + ] +} +``` + +[rfc 2397]: http://www.ietf.org/rfc/rfc2397.txt "Data URLs" + +### Response ###### + +If successful, the server will send `HTTP/201 Created`. Otherwise, it will +send an appropriate HTTP status with the content being the error +description. Most likely offenders are + +* Required field not included +* Data type mismatch (text send for numeric field) +* Incorrectly encoded base64 data +* Unsupported field sent +* Incorrectly formatted content (bad JSON or XML) + +Upon success, the content of the response will be the external ticket id of +the newly-created ticket. + + Status: 201 Created + 123456