From bcf1caad1459a458a7923335a4a6bc521816de40 Mon Sep 17 00:00:00 2001 From: Bharat Mediratta Date: Fri, 22 Jan 2010 00:27:00 -0800 Subject: Reshape the rest code to be more consistent with regards to relationships. Now when you view a resource, it has 4 top level elements: url: the url of this resource resource: array of key value pairs describing the resource members: array of urls to members of this collection relationships: array of array of members. Relationships are a special type of collection that links two different resources together. To remove a relationship, just DELETE its url. To create a relationship, POST to its collection. Individual modules can add their own relationships to any resource via a callback mechanism. Example: Array( [url] => http://g3.com/rest/item/1 [resource] => Array ( [id] => 1 [album_cover_item_id] => 4 [captured] => [created] => 1264056417 [description] => [height] => ... ) [members] => Array( [0] => http://g3.com/rest/item/2 [1] => http://g3.com/rest/item/3 [2] => http://g3.com/rest/item/4 [3] => http://g3.com/rest/item/5 ... ) [relationships] => Array( [tags] => Array ( [0] => http://g3.com/rest/tag_item/2,1 [1] => http://g3.com/rest/tag_item/23,1 ) ) ) --- modules/gallery/helpers/gallery_rest.php | 152 ----------------------------- modules/gallery/helpers/item_rest.php | 158 +++++++++++++++++++++++++++++++ modules/rest/controllers/rest.php | 2 +- modules/rest/helpers/rest.php | 50 ++++++++-- modules/tag/helpers/tag.php | 14 +-- modules/tag/helpers/tag_event.php | 12 ++- modules/tag/helpers/tag_item_rest.php | 50 ++++++++++ modules/tag/helpers/tag_rest.php | 43 +++++---- modules/tag/helpers/tags_rest.php | 4 +- 9 files changed, 289 insertions(+), 196 deletions(-) delete mode 100644 modules/gallery/helpers/gallery_rest.php create mode 100644 modules/gallery/helpers/item_rest.php create mode 100644 modules/tag/helpers/tag_item_rest.php diff --git a/modules/gallery/helpers/gallery_rest.php b/modules/gallery/helpers/gallery_rest.php deleted file mode 100644 index c5838ea5..00000000 --- a/modules/gallery/helpers/gallery_rest.php +++ /dev/null @@ -1,152 +0,0 @@ - - * only return items where the name contains this substring - * - * random=true - * return a single random item - * - * type= - * limit the type to types in this list. eg, "type=photo,movie" - */ - static function get($request) { - $item = rest::resolve($request->url); - access::required("view", $item); - - $p = $request->params; - if (isset($p->random)) { - $orm = item::random_query()->offset(0)->limit(1); - } else { - $orm = ORM::factory("item")->viewable(); - } - - if (empty($p->scope)) { - $p->scope = "direct"; - } - - if (!in_array($p->scope, array("direct", "all"))) { - throw new Rest_Exception("Bad Request", 400); - } - - if ($p->scope == "direct") { - $orm->where("parent_id", "=", $item->id); - } else { - $orm->where("left_ptr", ">", $item->left_ptr); - $orm->where("right_ptr", "<", $item->right_ptr); - } - - if (isset($p->name)) { - $orm->where("name", "LIKE", "%{$p->name}%"); - } - - if (isset($p->type)) { - $orm->where("type", "IN", explode(",", $p->type)); - } - - $members = array(); - foreach ($orm->find_all() as $child) { - $members[] = rest::url("gallery", $child); - } - - return array("resource" => $item->as_array(), "members" => $members); - } - - static function put($request) { - $item = rest::resolve($request->url); - access::required("edit", $item); - - $params = $request->params; - - // Only change fields from a whitelist. - foreach (array("album_cover_item_id", "captured", "description", - "height", "mime_type", "name", "parent_id", "rand_key", "resize_dirty", - "resize_height", "resize_width", "slug", "sort_column", "sort_order", - "thumb_dirty", "thumb_height", "thumb_width", "title", "view_count", - "weight", "width") as $key) { - if (property_exists($request->params, $key)) { - $item->$key = $request->params->$key; - } - } - $item->save(); - - return array("url" => rest::url("gallery", $item)); - } - - static function post($request) { - $parent = rest::resolve($request->url); - access::required("edit", $parent); - - $params = $request->params; - $item = ORM::factory("item"); - switch ($params->type) { - case "album": - $item->type = "album"; - $item->parent_id = $parent->id; - $item->name = $params->name; - $item->title = isset($params->title) ? $params->title : $name; - $item->description = isset($params->description) ? $params->description : null; - $item->slug = isset($params->slug) ? $params->slug : null; - $item->save(); - break; - - case "photo": - case "movie": - $item->type = $params->type; - $item->parent_id = $parent->id; - $item->set_data_file($request->file); - $item->name = $params->name; - $item->title = isset($params->title) ? $params->title : $params->name; - $item->description = isset($params->description) ? $params->description : null; - $item->slug = isset($params->slug) ? $params->slug : null; - $item->save(); - break; - - default: - throw new Rest_Exception("Invalid type: $params->type", 400); - } - - return array("url" => rest::url("gallery", $item)); - } - - static function delete($request) { - $item = rest::resolve($request->url); - access::required("edit", $item); - - $item->delete(); - } - - static function resolve($path) { - return url::get_item_from_uri($path); - } - - static function url($item) { - return url::abs_site("rest/gallery/" . $item->relative_url()); - } -} diff --git a/modules/gallery/helpers/item_rest.php b/modules/gallery/helpers/item_rest.php new file mode 100644 index 00000000..edc44c45 --- /dev/null +++ b/modules/gallery/helpers/item_rest.php @@ -0,0 +1,158 @@ + + * only return items where the name contains this substring + * + * random=true + * return a single random item + * + * type= + * limit the type to types in this list. eg, "type=photo,movie" + */ + static function get($request) { + $item = rest::resolve($request->url); + access::required("view", $item); + + $p = $request->params; + if (isset($p->random)) { + $orm = item::random_query()->offset(0)->limit(1); + } else { + $orm = ORM::factory("item")->viewable(); + } + + if (empty($p->scope)) { + $p->scope = "direct"; + } + + if (!in_array($p->scope, array("direct", "all"))) { + throw new Rest_Exception("Bad Request", 400); + } + + if ($p->scope == "direct") { + $orm->where("parent_id", "=", $item->id); + } else { + $orm->where("left_ptr", ">", $item->left_ptr); + $orm->where("right_ptr", "<", $item->right_ptr); + } + + if (isset($p->name)) { + $orm->where("name", "LIKE", "%{$p->name}%"); + } + + if (isset($p->type)) { + $orm->where("type", "IN", explode(",", $p->type)); + } + + $members = array(); + foreach ($orm->find_all() as $child) { + $members[] = rest::url("item", $child); + } + + return array( + "url" => $request->url, + "resource" => $item->as_array(), + "members" => $members, + "relationships" => rest::relationships("item", $item)); + } + + static function put($request) { + $item = rest::resolve($request->url); + access::required("edit", $item); + + $params = $request->params; + + // Only change fields from a whitelist. + foreach (array("album_cover_item_id", "captured", "description", + "height", "mime_type", "name", "parent_id", "rand_key", "resize_dirty", + "resize_height", "resize_width", "slug", "sort_column", "sort_order", + "thumb_dirty", "thumb_height", "thumb_width", "title", "view_count", + "weight", "width") as $key) { + if (property_exists($request->params, $key)) { + $item->$key = $request->params->$key; + } + } + if ($item->changed) { + $item->save(); + } + } + + static function post($request) { + $parent = rest::resolve($request->url); + access::required("edit", $parent); + + $params = $request->params; + $item = ORM::factory("item"); + switch ($params->type) { + case "album": + $item->type = "album"; + $item->parent_id = $parent->id; + $item->name = $params->name; + $item->title = isset($params->title) ? $params->title : $name; + $item->description = isset($params->description) ? $params->description : null; + $item->slug = isset($params->slug) ? $params->slug : null; + $item->save(); + break; + + case "photo": + case "movie": + $item->type = $params->type; + $item->parent_id = $parent->id; + $item->set_data_file($request->file); + $item->name = $params->name; + $item->title = isset($params->title) ? $params->title : $params->name; + $item->description = isset($params->description) ? $params->description : null; + $item->slug = isset($params->slug) ? $params->slug : null; + $item->save(); + break; + + default: + throw new Rest_Exception("Invalid type: $params->type", 400); + } + } + + static function delete($request) { + $item = rest::resolve($request->url); + access::required("edit", $item); + + $item->delete(); + } + + static function resolve($id) { + $item = ORM::factory("item")->where("id", "=", $id)->find(); + if (!access::can("view", $item)) { + throw new Kohana_404_Exception(); + } + return $item; + } + + static function url($item) { + return url::abs_site("rest/item/{$item->id}"); + } +} diff --git a/modules/rest/controllers/rest.php b/modules/rest/controllers/rest.php index 9f0bc5b3..ba996b84 100644 --- a/modules/rest/controllers/rest.php +++ b/modules/rest/controllers/rest.php @@ -56,7 +56,7 @@ class Rest_Controller extends Controller { $handler_method = $request->method; if (!method_exists($handler_class, $handler_method)) { - throw new Rest_Exception("Forbidden", 403); + throw new Rest_Exception("Bad Request", 400); } try { diff --git a/modules/rest/helpers/rest.php b/modules/rest/helpers/rest.php index 85987ca1..fe704a9e 100644 --- a/modules/rest/helpers/rest.php +++ b/modules/rest/helpers/rest.php @@ -22,8 +22,16 @@ class rest_Core { Session::abort_save(); if ($data) { - header("Content-type: application/json"); - print json_encode($data); + if (Input::instance()->get("output_type") == "html") { + header("Content-type: text/html"); + $html = preg_replace( + "#(^|[\n ])([\w]+?://[\w]+[^ \"\n\r\t<]*)#ise", "'\\1\\2'", + print_r($data, 1)); + print "
$html
"; + } else { + header("Content-type: application/json"); + print json_encode($data); + } } } @@ -64,7 +72,10 @@ class rest_Core { /** * Convert a REST url into an object. - * Eg: "http://example.com/gallery3/index.php/rest/gallery/Family/Wedding" -> Item_Model + * Eg: + * http://example.com/gallery3/index.php/rest/item/35 -> Item_Model + * http://example.com/gallery3/index.php/rest/tag/16 -> Tag_Model + * http://example.com/gallery3/index.php/rest/tagged_item/1,16 -> [Tag_Model, Item_Model] * * @param string the fully qualified REST url * @return mixed the corresponding object (usually a model of some kind) @@ -88,15 +99,38 @@ class rest_Core { /** * Return an absolute url used for REST resource location. - * @param string module name (eg, "gallery", "tags") + * @param string resource type (eg, "item", "tag") * @param object resource */ - static function url($module, $resource) { - $class = "{$module}_rest"; + static function url() { + $args = func_get_args(); + $resource_type = array_shift($args); + + $class = "{$resource_type}_rest"; if (!method_exists($class, "url")) { - throw new Exception("@todo MISSING REST CLASS: $class"); + throw new Rest_Exception("Bad Request", 400); + } + + $url = call_user_func_array(array($class, "url"), $args); + if (Input::instance()->get("output_type") == "html") { + $url .= "?output_type=html"; + } + return $url; + } + + static function relationships($resource_type, $resource) { + $results = array(); + foreach (module::active() as $module) { + foreach (glob(MODPATH . "{$module->name}/helpers/*_rest.php") as $filename) { + $class = str_replace(".php", "", basename($filename)); + if (method_exists($class, "relationships")) { + $results = array_merge( + $results, + call_user_func(array($class, "relationships"), $resource_type, $resource)); + } + } } - return call_user_func(array($class, "url"), $resource); + return $results; } } diff --git a/modules/tag/helpers/tag.php b/modules/tag/helpers/tag.php index c49a2d0f..9e59b527 100644 --- a/modules/tag/helpers/tag.php +++ b/modules/tag/helpers/tag.php @@ -91,16 +91,10 @@ class tag_Core { * @return array */ static function item_tags($item) { - $tags = array(); - foreach (db::build() - ->select("name") - ->from("tags") - ->join("items_tags", "tags.id", "items_tags.tag_id", "left") - ->where("items_tags.item_id", "=", $item->id) - ->execute() as $row) { - $tags[] = $row->name; - } - return $tags; + return ORM::factory("tag") + ->join("items_tags", "tags.id", "items_tags.tag_id", "left") + ->where("items_tags.item_id", "=", $item->id) + ->find_all(); } static function get_add_form($item) { diff --git a/modules/tag/helpers/tag_event.php b/modules/tag/helpers/tag_event.php index 6ee8e708..403ccd52 100644 --- a/modules/tag/helpers/tag_event.php +++ b/modules/tag/helpers/tag_event.php @@ -71,9 +71,13 @@ class tag_event_Core { $('form input[id=tags]').autocomplete( '$url', {max: 30, multiple: true, multipleSeparator: ',', cacheLength: 1}); });"); - $tag_value = implode(", ", tag::item_tags($item)); + + $tag_names = array(); + foreach (tag::item_tags($item) as $tag) { + $tag_names[] = $tag->name; + } $form->edit_item->input("tags")->label(t("Tags (comma separated)")) - ->value($tag_value); + ->value(implode(", ", $tag_names)); } static function item_edit_form_completed($item, $form) { @@ -95,7 +99,9 @@ class tag_event_Core { } static function item_index_data($item, $data) { - $data[] = join(" ", tag::item_tags($item)); + foreach (tag::item_tags($item) as $tag) { + $data[] = $tag->name; + } } static function add_photos_form($album, $form) { diff --git a/modules/tag/helpers/tag_item_rest.php b/modules/tag/helpers/tag_item_rest.php new file mode 100644 index 00000000..cd9bb6fe --- /dev/null +++ b/modules/tag/helpers/tag_item_rest.php @@ -0,0 +1,50 @@ +url); + return array( + "url" => $request->url, + "members" => array( + rest::url("tag", $tag), + rest::url("item", $item))); + } + + static function delete($request) { + list ($tag, $item) = rest::resolve($request->url); + $tag->remove($item); + $tag->save(); + } + + static function resolve($tuple) { + list ($tag_id, $item_id) = split(",", $tuple); + $tag = ORM::factory("tag")->where("id", "=", $tag_id)->find(); + $item = ORM::factory("item")->where("id", "=", $item_id)->find(); + if (!$tag->loaded() || !$item->loaded() || !$tag->has($item)) { + throw new Kohana_404_Exception(); + } + + return array($tag, $item); + } + + static function url($tag, $item) { + return url::abs_site("rest/tag_item/{$tag->id},{$item->id}"); + } +} diff --git a/modules/tag/helpers/tag_rest.php b/modules/tag/helpers/tag_rest.php index 0226c6d3..d68cb73d 100644 --- a/modules/tag/helpers/tag_rest.php +++ b/modules/tag/helpers/tag_rest.php @@ -20,12 +20,18 @@ class tag_rest_Core { static function get($request) { $tag = rest::resolve($request->url); - $items = array(); + $tag_items = array(); foreach ($tag->items() as $item) { - $items[] = rest::url("gallery", $item); + if (access::can("view", $item)) { + $tag_items[] = rest::url("tag_item", $tag, $item); + } } - return array("resource" => $tag->as_array(), "members" => $items); + return array( + "url" => $request->url, + "resource" => $tag->as_array(), + "relationships" => array( + "items" => $tag_items)); } static function post($request) { @@ -38,37 +44,34 @@ class tag_rest_Core { access::required("edit", $item); tag::add($item, $tag->name); - return array("url" => rest::url("tag", $tag)); } static function put($request) { $tag = rest::resolve($request->url); if (isset($request->params->name)) { $tag->name = $request->params->name; + $tag->save(); } - - $tag->save(); - return array("url" => rest::url("tag", $tag)); } static function delete($request) { $tag = rest::resolve($request->url); + $tag->delete(); + } - if (empty($request->params->url)) { - // Delete the tag - $tag->delete(); - } else { - // Remove an item from the tag - $item = rest::resolve($request->params->url); - access::required("edit", $item); - $tag->remove($item); - $tag->save(); - tag::compact(); + static function relationships($resource_type, $resource) { + switch ($resource_type) { + case "item": + $tags = array(); + foreach (tag::item_tags($resource) as $tag) { + $tags[] = rest::url("tag_item", $tag, $resource); + } + return array("tags" => $tags); } } - static function resolve($tag_name) { - $tag = ORM::factory("tag")->where("name", "=", $tag_name)->find(); + static function resolve($id) { + $tag = ORM::factory("tag")->where("id", "=", $id)->find(); if (!$tag->loaded()) { throw new Kohana_404_Exception(); } @@ -77,6 +80,6 @@ class tag_rest_Core { } static function url($tag) { - return url::abs_site("rest/tag/" . rawurlencode($tag->name)); + return url::abs_site("rest/tag/{$tag->id}"); } } diff --git a/modules/tag/helpers/tags_rest.php b/modules/tag/helpers/tags_rest.php index 41317ecd..57461125 100644 --- a/modules/tag/helpers/tags_rest.php +++ b/modules/tag/helpers/tags_rest.php @@ -21,7 +21,7 @@ class tags_rest_Core { static function get($request) { $tags = array(); foreach (ORM::factory("tag")->find_all() as $tag) { - $tags[$tag->name] = rest::url("tag", $tag); + $tags[] = rest::url("tag", $tag); } return array("members" => $tags); } @@ -33,7 +33,7 @@ class tags_rest_Core { access::required("edit", item::root()); if (empty($request->params->name)) { - throw new Rest_Exception("Bad Request: missing name", 400); + throw new Rest_Exception("Bad Request", 400); } $tag = ORM::factory("tag")->where("name", "=", $request->params->name)->find(); -- cgit v1.2.3