diff --git a/lib/api/events.php b/lib/api/events.php index cec2ca5..6e6c637 100644 --- a/lib/api/events.php +++ b/lib/api/events.php @@ -1,55 +1,55 @@ | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | +--------------------------------------------------------------------------+ */ class kolab_api_events extends kolab_api { protected $model = 'event'; public function run() { $this->initialize_handler(); $path = $this->input->path; $method = $this->input->method; if ($path[0] === 'inbox' && $method == 'GET') { $this->api_event_inbox(); } parent::run(); } /** * Returns list of event invitations in the INBOX folder */ protected function api_event_inbox() { $cal = new kolab_api_calendaring($this->backend); $list = $cal->get_scheduling_objects($this->model); - $props = $this->input->args['properties'] ? explode(',', $this->input->args['properties']) : null; + $props = $this->input->get_list_arg('properties'); $this->output->send($list, $this->model . '-list', null, $props); } } diff --git a/lib/api/folders.php b/lib/api/folders.php index d149a71..7426238 100644 --- a/lib/api/folders.php +++ b/lib/api/folders.php @@ -1,319 +1,319 @@ | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | +--------------------------------------------------------------------------+ */ class kolab_api_folders extends kolab_api { protected $model = 'folder'; public function run() { $this->initialize_handler(); $path = $this->input->path; $method = $this->input->method; $request_length = count($path); if (!$request_length && $method == 'POST') { $this->api_folder_create(); } else if (!$request_length && $method == 'GET') { $this->api_folder_list_folders(); } else if ($request_length >= 1) { switch (strtolower((string) $path[1])) { case 'objects': if ($method == 'HEAD') { $this->api_folder_count_objects(); } else if ($method == 'GET') { $this->api_folder_list_objects(); } break; case 'folders': if ($method == 'HEAD') { $this->api_folder_count_folders(); } else if ($method == 'GET') { $this->api_folder_list_folders(); } break; case 'search': if ($method == 'POST') { $this->api_folder_search(); } break; case 'empty': if ($method == 'POST') { $this->api_folder_empty(); } break; case 'deleteobjects': if ($method == 'POST') { $this->api_folder_delete_objects(); } break; case 'move': if ($method == 'POST') { $this->api_folder_move_objects(); } break; case '': if ($request_length == 1) { if ($method == 'GET') { $this->api_folder_info(); } else if ($method == 'PUT') { $this->api_folder_update(); } else if ($method == 'HEAD') { $this->api_folder_exists(); } else if ($method == 'DELETE') { $this->api_folder_delete(); } } } } throw new kolab_api_exception(kolab_api_exception::NOT_FOUND); } /** * Returns list of folders (all or subfolders of specified folder) */ protected function api_folder_list_folders() { $root = $this->input->path[0]; $list = $this->backend->folders_list(); - $props = $this->input->args['properties'] ? explode(',', $this->input->args['properties']) : null; + $props = $this->input->get_list_arg('properties'); // filter by parent if ($root) { $this->filter_folders($list, $root); } $this->output->send($list, $this->model . '-list', null, $props); } /** * Returns count of folders (all or subfolders of specified folder) */ protected function api_folder_count_folders() { $root = $this->input->path[0]; $list = $this->backend->folders_list(); // filter by parent if ($root) { $this->filter_folders($list, $root); } $this->output->headers(array('X-Count' => count($list))); $this->output->send_status(kolab_api_output::STATUS_OK); } /** * Returns folders info */ protected function api_folder_info() { $folder = $this->input->path[0]; $info = $this->backend->folder_info($folder); $context = array('object' => $info); - $props = $this->input->args['properties'] ? explode(',', $this->input->args['properties']) : null; + $props = $this->input->get_list_arg('properties'); $this->output->send($info, $this->model, $context, $props); } /** * Checks if folder exists */ protected function api_folder_exists() { $folder = $this->input->path[0]; $folder = $this->backend->folder_info($folder); $this->output->send_status(kolab_api_output::STATUS_OK); } /** * Delete a folder */ protected function api_folder_delete() { $folder = $this->input->path[0]; $folder = $this->backend->folder_info($folder); $folder->delete(); $this->output->send_status(kolab_api_output::STATUS_EMPTY); } /** * Update specified folder (rename or set folder type, or state) */ protected function api_folder_update() { $folder = $this->input->path[0]; $info = $this->backend->folder_info($folder); $input = $this->input->input($this->model, false, $info); $input->save(); $this->output->send(array('uid' => $folder), $this->model, null, array('uid')); } /** * Create a folder */ protected function api_folder_create() { $input = $this->input->input($this->model); $uid = $input->save(); $this->output->send(array('uid' => $uid), $this->model, null, array('uid')); } /** * Returns list of objects (not folders) in specified folder */ protected function api_folder_list_objects() { $folder = $this->input->path[0]; $type = $this->backend->folder_type($folder); $list = $this->backend->objects_list($folder); - $props = $this->input->args['properties'] ? explode(',', $this->input->args['properties']) : null; + $props = $this->input->get_list_arg('properties'); $context = array('folder_uid' => $folder); $this->output->send($list, $type . '-list', $context, $props); } /** * Returns count of objects (not folders) in specified folder */ protected function api_folder_count_objects() { $folder = $this->input->path[0]; $count = $this->backend->objects_count($folder); $this->output->headers(array('X-Count' => $count)); $this->output->send_status(kolab_api_output::STATUS_OK); } /** * Search objects in specified folder */ protected function api_folder_search() { $search = $this->input->input('search'); if (empty($search)) { throw new kolab_api_exception(kolab_api_exception::INVALID_REQUEST, null, "Missing search criteria"); } $folder = $this->input->path[0]; $type = $this->backend->folder_type($folder); $list = $this->backend->objects_list($folder, $search); - $props = $this->input->args['properties'] ? explode(',', $this->input->args['properties']) : null; + $props = $this->input->get_list_arg('properties'); $context = array('folder_uid' => $folder); $this->output->send($list, $type . '-list', $context, $props); } /** * Delete objects in specified folder */ protected function api_folder_delete_objects() { $folder = $this->input->path[0]; $set = $this->input->input(); if (empty($set)) { throw new kolab_api_exception(kolab_api_exception::INVALID_REQUEST); } $this->backend->objects_delete($folder, $set); $this->output->send_status(kolab_api_output::STATUS_OK); } /** * Move objects into specified folder */ protected function api_folder_move_objects() { $folder = $this->input->path[0]; $target = $this->input->path[2]; $set = $this->input->input(); if (empty($set)) { throw new kolab_api_exception(kolab_api_exception::INVALID_REQUEST); } if ($target === null || $target === '' || $target === $folder) { throw new kolab_api_exception(kolab_api_exception::INVALID_REQUEST); } $this->backend->objects_move($folder, $target, $set); $this->output->send_status(kolab_api_output::STATUS_OK); } /** * Remove all objects in specified folder */ protected function api_folder_empty() { $folder = $this->input->path[0]; $this->backend->objects_delete($folder, '*'); $this->output->send_status(kolab_api_output::STATUS_OK); } /** * Filter folders by parent */ protected function filter_folders(&$list, $parent) { // filter by parent if ($parent && ($parent = $this->backend->folder_info($parent))) { // we'll compare with 'fullpath' which is in UTF-8 $path = $parent->fullpath . $this->backend->delimiter; foreach ($list as $idx => $folder) { if (strpos($folder->fullpath, $path) !== 0) { unset($list[$idx]); } } } $list = array_values($list); } } diff --git a/lib/api/mails.php b/lib/api/mails.php index bf46776..b064b5e 100644 --- a/lib/api/mails.php +++ b/lib/api/mails.php @@ -1,133 +1,133 @@ | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | +--------------------------------------------------------------------------+ */ class kolab_api_mails extends kolab_api { protected $model = 'mail'; public function run() { $this->initialize_handler(); $path = $this->input->path; $method = $this->input->method; if ($path[0] === 'submit' && $method == 'POST') { // submit a new message $this->api_message_submit(); } else if (!$path[1] && $path[0] && $method == 'POST') { $this->api_object_create(); } else if ($path[1]) { switch (strtolower($path[2])) { case 'attachments': if ($method == 'HEAD') { $this->api_message_count_attachments(); } else if ($method == 'GET') { $this->api_message_list_attachments(); } break; /* case 'submit': if ($method == 'POST') { // submit an existing message $this->api_message_submit($path[0], $path[1]); } break; */ case '': if ($method == 'GET') { $this->api_object_info(); } else if ($method == 'PUT') { $this->api_object_update(); } else if ($method == 'HEAD') { $this->api_object_exists(); } else if ($method == 'DELETE') { $this->api_object_delete(); } } } throw new kolab_api_exception(kolab_api_exception::NOT_FOUND); } /** * Submit a message object into SMTP server */ protected function api_message_submit($folder = null, $uid = null) { // parse input and merge with current data (returns kolab_api_mail) $input = $this->input->input($this->model, false); // send the message $input->send(); // @TODO: option to save message in Sent folder // @TODO: option to send existing message // @TODO: option to remove message from Drafts $this->output->send_status(kolab_api_output::STATUS_EMPTY); } /** * Count message attachments */ protected function api_message_count_attachments() { $folder = $this->input->path[0]; $uid = $this->input->path[1]; $object = $this->backend->object_get($folder, $uid); $context = array( 'folder_uid' => $folder, 'object_uid' => $uid, 'object' => $object, ); $count = count($object->attachments); $this->output->headers(array('X-Count' => $count), $context); $this->output->send_status(kolab_api_output::STATUS_OK); } /** * List message attachments */ protected function api_message_list_attachments() { $folder = $this->input->path[0]; $uid = $this->input->path[1]; $object = $this->backend->object_get($folder, $uid); - $props = $this->input->args['properties'] ? explode(',', $this->input->args['properties']) : null; + $props = $this->input->get_list_arg('properties'); $context = array('folder_uid' => $folder, 'object_uid' => $uid, 'object' => $object); $list = $object->attachments; $this->output->send($list, 'attachment-list', $context, $props); } } diff --git a/lib/kolab_api_input.php b/lib/kolab_api_input.php index edc9a8f..a411e94 100644 --- a/lib/kolab_api_input.php +++ b/lib/kolab_api_input.php @@ -1,160 +1,179 @@ | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | +--------------------------------------------------------------------------+ */ abstract class kolab_api_input { public $method; public $action; public $path = array(); public $args = array(); public $supports = array(); public $headers = array(); public $api; public $filter; public $input_body; /** * Factory method to create input object * according to the API request input * * @param kolab_api The API * * @return kolab_api_input Output object */ public static function factory($api) { // default mode $mode = 'json'; $class = "kolab_api_input_$mode"; return new $class($api); } /** * Object constructor */ public function __construct($api) { $this->api = $api; $this->method = $_SERVER['REQUEST_METHOD']; if ($this->method == 'POST' && !empty($_SERVER['HTTP_X_HTTP_METHOD'])) { $this->method = $_SERVER['HTTP_X_HTTP_METHOD']; } $this->path = self::request_path($this->filter); // remove first argument - action name $this->action = array_shift($this->path); if ($this->api->config->get('kolab_api_debug')) { rcube::console($this->method . ': ' . self::request_uri()); // @TODO: log request input data for PUT/POST } $accept_header = strtolower(rcube_utils::request_header('Accept')); list($this->supports,) = explode(';', $accept_header); $this->supports = explode(',', $this->supports); // store GET arguments $this->args = $_GET; unset($this->args['api']); unset($this->args['request']); } /** * Parse request arguments * * @return array Request arguments */ public static function request_path(&$filter = null) { $api = (string) $_GET['api']; $path = explode('/', trim((string) $_GET['request'], ' /')); // map api specific request to Kolab API if ($api && class_exists("kolab_api_filter_$api")) { $class = "kolab_api_filter_$api"; $filter = new $class; $filter->path($path); } foreach ($path as $idx => $value) { $path[$idx] = strip_tags($value); } $path[0] = strtolower($path[0]); return $path; } /** * Return request URI (for logging) */ public static function request_uri() { $url = trim((string) $_GET['request'], ' /'); list($uri, $params) = explode('?', $_SERVER['REQUEST_URI']); if ($params) { $url .= '?' . $params; } return $url; } /** * Return specified request header value * * @param string $name Header name * * @return string Header value */ public function request_header($name) { if (empty($name)) { return; } if (!array_key_exists($name, $this->headers)) { $this->headers[$name] = rcube_utils::request_header($name); } return $this->headers[$name]; } + /** + * Parse comma-separated list of tokens from the request argument + * + * @param string $name Argument name + * + * @param array Tokens list + */ + public function get_list_arg($name) + { + if (empty($this->args[$name])) { + return array(); + } + + $list = explode(',', $this->args[$name]); + $list = array_map('trim', $list); + + return $list; + } + /** * Get request data (JSON) * * @param string Expected object type * @param bool Disable filters application * @param array Original object data (set on update requests) * * @return array Request data */ abstract function input($type = null, $disable_filters = false, $original = null); } diff --git a/lib/kolab_api_output.php b/lib/kolab_api_output.php index 8a18136..faf7052 100644 --- a/lib/kolab_api_output.php +++ b/lib/kolab_api_output.php @@ -1,131 +1,132 @@ | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | +--------------------------------------------------------------------------+ */ abstract class kolab_api_output { const STATUS_OK = 200; const STATUS_EMPTY = 204; protected $api; protected $messages = array( self::STATUS_OK => 'OK', self::STATUS_EMPTY => 'No content', ); /** * Factory method to create output object * according to the API request input * * @param kolab_api The API * * @return kolab_api_output Output object */ public static function factory($api) { // default mode of kolab format $mode = 'xml'; $modes_map = array( // 'text/html' => 'html', // 'application/html+xml' => 'xml', 'application/xml' => 'xml', 'application/json' => 'json', ); foreach ((array) $api->input->supports as $type) { if ($_mode = $modes_map[$type]) { $mode = $_mode; break; } } $mode = 'json'; $class = "kolab_api_output_$mode"; return new $class($api); } /** * Object constructor * * @param kolab_api The API */ public function __construct($api) { $this->api = $api; } /** * Set response headers (must be done before send()). * * @param array Response headers * @param array Context (folder_uid, object_uid, object) */ public function headers($headers, $context = null) { if ($this->api->filter) { $this->api->filter->headers($headers, $context); } if (!empty($headers)) { if ($this->api->config->get('kolab_api_debug')) { rcube::console($headers); } foreach ((array) $headers as $header => $value) { header($header . ': ' . $value); } } } /** * Send status of successful (empty) response * * @param int $status Status code * @param bool $exit Call exit() */ public function send_status($status, $exit = true) { if ($this->api->filter) { $this->api->filter->send_status($status); } $message = $this->messages[$status]; header("HTTP/1.1 $status $message"); if ($exit) { exit; } } /** * Send successful response * * @param mixed Response data * @param string Data type * @param array Context (folder_uid, object_uid, object) + * @param array Optional attributes filter */ - abstract function send($data, $type, $context = null); + abstract function send($data, $type, $context = null, $attrs_filter = array()); }