diff options
58 files changed, 1504 insertions, 1639 deletions
diff --git a/modules/akismet/tests/Akismet_Helper_Test.php b/modules/akismet/tests/Akismet_Helper_Test.php index d8605d5c..b32e9a02 100644 --- a/modules/akismet/tests/Akismet_Helper_Test.php +++ b/modules/akismet/tests/Akismet_Helper_Test.php @@ -23,19 +23,26 @@ class Akismet_Helper_Test extends Unit_Test_Case { public function setup() { Input::instance()->ip_address = "1.1.1.1"; request::set_user_agent("Akismet_Helper_Test"); + module::set_var("akismet", "api_key", "TEST_KEY"); + } + + private function _make_comment() { + $comment = ORM::factory("comment"); + $comment->item_id = item::root()->id; + $comment->author_id = identity::guest()->id; + $comment->text = "This is a comment"; + $comment->guest_name = "John Doe"; + $comment->guest_email = "john@gallery2.org"; + $comment->guest_url = "http://gallery2.org"; + $comment->save(); - $root = ORM::factory("item", 1); - $this->_comment = comment::create( - $root, identity::guest(), "This is a comment", - "John Doe", "john@gallery2.org", "http://gallery2.org"); - foreach ($this->_comment->list_fields("comments") as $name => $field) { + // Set the server fields to a known placeholder + foreach ($comment->list_fields("comments") as $name => $field) { if (strpos($name, "server_") === 0) { - $this->_comment->$name = substr($name, strlen("server_")); + $comment->$name = substr($name, strlen("server_")); } } - $this->_comment->save(); - - module::set_var("akismet", "api_key", "TEST_KEY"); + return $comment->save(); } public function build_verify_request_test() { @@ -51,8 +58,8 @@ class Akismet_Helper_Test extends Unit_Test_Case { } public function build_comment_check_request_test() { - $request = akismet::_build_request("comment-check", $this->_comment); - $id = $this->_comment->id; + $comment = $this->_make_comment(); + $request = akismet::_build_request("comment-check", $comment); $expected = "POST /1.1/comment-check HTTP/1.0\r\n" . "Host: TEST_KEY.rest.akismet.com\r\n" . "Content-Type: application/x-www-form-urlencoded; charset=UTF-8\r\n" . @@ -66,15 +73,15 @@ class Akismet_Helper_Test extends Unit_Test_Case { "SERVER_HTTP_ACCEPT_CHARSET=http_accept_charset&" . "blog=http%3A%2F%2F.%2F&comment_author=John+Doe&comment_author_email=john%40gallery2.org&" . "comment_author_url=http%3A%2F%2Fgallery2.org&comment_content=This+is+a+comment&" . - "comment_type=comment&permalink=http%3A%2F%2F.%2Findex.php%2Fcomments%2F{$id}&" . + "comment_type=comment&permalink=http%3A%2F%2F.%2Findex.php%2Fcomments%2F{$comment->id}&" . "referrer=http_referer&user_agent=http_user_agent&user_ip=remote_addr"; $this->assert_equal($expected, $request); } public function build_submit_spam_request_test() { - $request = akismet::_build_request("submit-spam", $this->_comment); - $id = $this->_comment->id; + $comment = $this->_make_comment(); + $request = akismet::_build_request("submit-spam", $comment); $expected = "POST /1.1/submit-spam HTTP/1.0\r\n" . "Host: TEST_KEY.rest.akismet.com\r\n" . @@ -89,15 +96,15 @@ class Akismet_Helper_Test extends Unit_Test_Case { "SERVER_HTTP_ACCEPT_CHARSET=http_accept_charset&" . "blog=http%3A%2F%2F.%2F&comment_author=John+Doe&comment_author_email=john%40gallery2.org&" . "comment_author_url=http%3A%2F%2Fgallery2.org&comment_content=This+is+a+comment&" . - "comment_type=comment&permalink=http%3A%2F%2F.%2Findex.php%2Fcomments%2F{$id}&" . + "comment_type=comment&permalink=http%3A%2F%2F.%2Findex.php%2Fcomments%2F{$comment->id}&" . "referrer=http_referer&user_agent=http_user_agent&user_ip=remote_addr"; $this->assert_equal($expected, $request); } public function build_submit_ham_request_test() { - $request = akismet::_build_request("submit-ham", $this->_comment); - $id = $this->_comment->id; + $comment = $this->_make_comment(); + $request = akismet::_build_request("submit-ham", $comment); $expected = "POST /1.1/submit-ham HTTP/1.0\r\n" . "Host: TEST_KEY.rest.akismet.com\r\n" . @@ -112,7 +119,7 @@ class Akismet_Helper_Test extends Unit_Test_Case { "SERVER_HTTP_ACCEPT_CHARSET=http_accept_charset&blog=http%3A%2F%2F.%2F&" . "comment_author=John+Doe&comment_author_email=john%40gallery2.org&" . "comment_author_url=http%3A%2F%2Fgallery2.org&comment_content=This+is+a+comment&" . - "comment_type=comment&permalink=http%3A%2F%2F.%2Findex.php%2Fcomments%2F{$id}&" . + "comment_type=comment&permalink=http%3A%2F%2F.%2Findex.php%2Fcomments%2F{$comment->id}&" . "referrer=http_referer&user_agent=http_user_agent&user_ip=remote_addr"; $this->assert_equal($expected, $request); diff --git a/modules/comment/controllers/comments.php b/modules/comment/controllers/comments.php index 068152a2..6c546321 100644 --- a/modules/comment/controllers/comments.php +++ b/modules/comment/controllers/comments.php @@ -26,50 +26,39 @@ class Comments_Controller extends Controller { access::required("view", $item); $form = comment::get_add_form($item); - $valid = $form->validate(); - if ($valid) { - if (identity::active_user()->guest && !$form->add_comment->inputs["name"]->value) { - $form->add_comment->inputs["name"]->add_error("missing", 1); - $valid = false; - } - - if (!$form->add_comment->text->value) { - $form->add_comment->text->add_error("missing", 1); - $valid = false; + try { + $valid = $form->validate(); + $comment = ORM::factory("comment"); + $comment->item_id = $id; + $comment->author_id = identity::active_user()->id; + $comment->text = $form->add_comment->text->value; + $comment->guest_name = $form->add_comment->inputs["name"]->value; + $comment->guest_email = $form->add_comment->email->value; + $comment->guest_url = $form->add_comment->url->value; + $comment->validate(); + } catch (ORM_Validation_Exception $e) { + // Translate ORM validation errors into form error messages + foreach ($e->validation->errors() as $key => $error) { + switch ($key) { + case "guest_name": $key = "name"; break; + case "guest_email": $key = "email"; break; + } + $form->add_comment->inputs[$key]->add_error($error, 1); } + $valid = false; } if ($valid) { - $comment = comment::create( - $item, identity::active_user(), - $form->add_comment->text->value, - $form->add_comment->inputs["name"]->value, - $form->add_comment->email->value, - $form->add_comment->url->value); - - $active = identity::active_user(); - if ($active->guest) { - $form->add_comment->inputs["name"]->value(""); - $form->add_comment->email->value(""); - $form->add_comment->url->value(""); - } else { - $form->add_comment->inputs["name"]->value($active->full_name); - $form->add_comment->email->value($active->email); - $form->add_comment->url->value($active->url); - } - - $form->add_comment->text->value(""); + $comment->save(); $view = new Theme_View("comment.html", "other", "comment-fragment"); $view->comment = $comment; print json_encode( array("result" => "success", - "view" => $view->__toString(), - "form" => $form->__toString())); + "view" => (string) $view, + "form" => (string) comment::get_add_form($item))); } else { - print json_encode( - array("result" => "error", - "form" => $form->__toString())); + print json_encode(array("result" => "error", "form" => (string) $form)); } } diff --git a/modules/comment/helpers/comment.php b/modules/comment/helpers/comment.php index 1e1e7d2f..389c8922 100644 --- a/modules/comment/helpers/comment.php +++ b/modules/comment/helpers/comment.php @@ -24,46 +24,6 @@ * Note: by design, this class does not do any permission checking. */ class comment_Core { - /** - * Create a new comment. - * @param Item_MOdel $item the parent item - * @param User_Model $author the author User_Model - * @param string $text comment body - * @param string $guest_name guest's name (if the author is a guest user, default empty) - * @param string $guest_email guest's email (if the author is a guest user, default empty) - * @param string $guest_url guest's url (if the author is a guest user, default empty) - * @return Comment_Model - */ - static function create($item, $author, $text, $guest_name=null, - $guest_email=null, $guest_url=null) { - $comment = ORM::factory("comment"); - $comment->author_id = $author->id; - $comment->guest_email = $guest_email; - $comment->guest_name = $guest_name; - $comment->guest_url = $guest_url; - $comment->item_id = $item->id; - $comment->text = $text; - $comment->state = "published"; - - // These values are useful for spam fighting, so save them with the comment. - $input = Input::instance(); - $comment->server_http_accept = substr($input->server("HTTP_ACCEPT"), 0, 128); - $comment->server_http_accept_charset = substr($input->server("HTTP_ACCEPT_CHARSET"), 0, 64); - $comment->server_http_accept_encoding = substr($input->server("HTTP_ACCEPT_ENCODING"), 0, 64); - $comment->server_http_accept_language = substr($input->server("HTTP_ACCEPT_LANGUAGE"), 0, 64); - $comment->server_http_connection = substr($input->server("HTTP_CONNECTION"), 0, 64); - $comment->server_http_host = substr($input->server("HTTP_HOST"), 0, 64); - $comment->server_http_referer = substr($input->server("HTTP_REFERER"), 0, 255); - $comment->server_http_user_agent = substr($input->server("HTTP_USER_AGENT"), 0, 128); - $comment->server_query_string = substr($input->server("QUERY_STRING"), 0, 64); - $comment->server_remote_addr = substr($input->server("REMOTE_ADDR"), 0, 32); - $comment->server_remote_host = substr($input->server("REMOTE_HOST"), 0, 64); - $comment->server_remote_port = substr($input->server("REMOTE_PORT"), 0, 16); - $comment->save(); - - return $comment; - } - static function get_add_form($item) { $form = new Forge("comments/create/{$item->id}", "", "post", array("id" => "g-comment-form")); $group = $form->group("add_comment")->label(t("Add comment")); diff --git a/modules/comment/models/comment.php b/modules/comment/models/comment.php index e0b82039..69b8505e 100644 --- a/modules/comment/models/comment.php +++ b/modules/comment/models/comment.php @@ -54,24 +54,62 @@ class Comment_Model extends ORM { } /** + * Add some custom per-instance rules. + */ + public function validate($array=null) { + // validate() is recursive, only modify the rules on the outermost call. + if (!$array) { + $this->rules = array( + "guest_name" => array("callbacks" => array(array($this, "valid_author"))), + "guest_email" => array("rules" => array("email")), + "guest_url" => array("rules" => array("url")), + "item_id" => array("callbacks" => array(array($this, "valid_item"))), + "state" => array("rules" => array("Comment_Model::valid_state")), + "text" => array("rules" => array("required")), + ); + } + + parent::validate($array); + } + + /** * @see ORM::save() */ public function save() { - if (!empty($this->changed)) { - $this->updated = time(); - if (!$this->loaded() && empty($this->created)) { - $this->created = $this->updated; - $created = true; + $this->updated = time(); + if (!$this->loaded()) { + // New comment + $this->created = $this->updated; + if (empty($this->state)) { + $this->state = "published"; } - } - $visible_change = $this->original()->state == "published" || $this->state == "published"; - - $original = clone $this->original(); - parent::save(); - if (isset($created)) { + // These values are useful for spam fighting, so save them with the comment. It's painful to + // check each one to see if it already exists before setting it, so just use server_http_host + // as a semaphore for now (we use that in g2_import.php) + if (empty($this->server_http_host)) { + $input = Input::instance(); + $this->server_http_accept = substr($input->server("HTTP_ACCEPT"), 0, 128); + $this->server_http_accept_charset = substr($input->server("HTTP_ACCEPT_CHARSET"), 0, 64); + $this->server_http_accept_encoding = substr($input->server("HTTP_ACCEPT_ENCODING"), 0, 64); + $this->server_http_accept_language = substr($input->server("HTTP_ACCEPT_LANGUAGE"), 0, 64); + $this->server_http_connection = substr($input->server("HTTP_CONNECTION"), 0, 64); + $this->server_http_host = substr($input->server("HTTP_HOST"), 0, 64); + $this->server_http_referer = substr($input->server("HTTP_REFERER"), 0, 255); + $this->server_http_user_agent = substr($input->server("HTTP_USER_AGENT"), 0, 128); + $this->server_query_string = substr($input->server("QUERY_STRING"), 0, 64); + $this->server_remote_addr = substr($input->server("REMOTE_ADDR"), 0, 32); + $this->server_remote_host = substr($input->server("REMOTE_HOST"), 0, 64); + $this->server_remote_port = substr($input->server("REMOTE_PORT"), 0, 16); + } + $visible_change = $this->original()->state == "published" || $this->state == "published"; + parent::save(); module::event("comment_created", $this); } else { + // Updated comment + $visible_change = $this->original()->state == "published" || $this->state == "published"; + $original = clone $this->original(); + parent::save(); module::event("comment_updated", $original, $this); } @@ -92,4 +130,34 @@ class Comment_Model extends ORM { $this->join("items", "items.id", "comments.item_id"); return item::viewable($this); } + + /** + * Make sure we have an appropriate author id set, or a guest name. + */ + public function valid_author(Validation $v, $field) { + if (empty($this->author_id)) { + $v->add_error("author_id", "required"); + } else if ($this->author_id == identity::guest()->id && empty($this->guest_name)) { + $v->add_error("guest_name", "required"); + } + } + + /** + * Make sure we have a valid associated item id. + */ + public function valid_item(Validation $v, $field) { + if (db::build() + ->from("items") + ->where("id", "=", $this->item_id) + ->count_records() != 1) { + $v->add_error("item_id", "invalid"); + } + } + + /** + * Make sure that the state is legal. + */ + static function valid_state($value) { + return in_array($value, array("published", "unpublished", "spam", "deleted")); + } } diff --git a/modules/comment/tests/Comment_Event_Test.php b/modules/comment/tests/Comment_Event_Test.php index ff7f1c26..5b7daef4 100644 --- a/modules/comment/tests/Comment_Event_Test.php +++ b/modules/comment/tests/Comment_Event_Test.php @@ -19,14 +19,17 @@ */ class Comment_Event_Test extends Unit_Test_Case { public function deleting_an_item_deletes_its_comments_too_test() { - $rand = rand(); - $album = album::create(ORM::factory("item", 1), "test_$rand", "test_$rand"); - $comment = comment::create( - $album, identity::guest(), "text_$rand", "name_$rand", "email_$rand", "url_$rand"); + $album = test::random_album(); + + $comment = ORM::factory("comment"); + $comment->item_id = $album->id; + $comment->author_id = identity::guest()->id; + $comment->guest_name = "test"; + $comment->text = "text"; + $comment->save(); $album->delete(); - $deleted_comment = ORM::factory("comment", $comment->id); - $this->assert_false($deleted_comment->loaded()); + $this->assert_false(ORM::factory("comment")->where("id", "=", $comment->id)->find()->loaded()); } } diff --git a/modules/comment/tests/Comment_Helper_Test.php b/modules/comment/tests/Comment_Helper_Test.php index 8e726869..d780aba6 100644 --- a/modules/comment/tests/Comment_Helper_Test.php +++ b/modules/comment/tests/Comment_Helper_Test.php @@ -48,15 +48,19 @@ class Comment_Helper_Test extends Unit_Test_Case { } public function create_comment_for_guest_test() { - $rand = rand(); - $root = ORM::factory("item", 1); - $comment = comment::create( - $root, identity::guest(), "text_$rand", "name_$rand", "email_$rand", "url_$rand"); + $comment = ORM::factory("comment"); + $comment->item_id = item::root()->id; + $comment->text = "text"; + $comment->author_id = identity::guest()->id; + $comment->guest_name = "name"; + $comment->guest_email = "email@email.com"; + $comment->guest_url = "http://url.com"; + $comment->save(); - $this->assert_equal("name_$rand", $comment->author_name()); - $this->assert_equal("email_$rand", $comment->author_email()); - $this->assert_equal("url_$rand", $comment->author_url()); - $this->assert_equal("text_$rand", $comment->text); + $this->assert_equal("name", $comment->author_name()); + $this->assert_equal("email@email.com", $comment->author_email()); + $this->assert_equal("http://url.com", $comment->author_url()); + $this->assert_equal("text", $comment->text); $this->assert_equal(1, $comment->item_id); $this->assert_equal("REMOTE_ADDR", $comment->server_remote_addr); @@ -78,16 +82,18 @@ class Comment_Helper_Test extends Unit_Test_Case { } public function create_comment_for_user_test() { - $rand = rand(); - $root = ORM::factory("item", 1); $admin = identity::admin_user(); - $comment = comment::create( - $root, $admin, "text_$rand", "name_$rand", "email_$rand", "url_$rand"); + + $comment = ORM::factory("comment"); + $comment->item_id = item::root()->id; + $comment->text = "text"; + $comment->author_id = $admin->id; + $comment->save(); $this->assert_equal($admin->full_name, $comment->author_name()); $this->assert_equal($admin->email, $comment->author_email()); $this->assert_equal($admin->url, $comment->author_url()); - $this->assert_equal("text_$rand", $comment->text); + $this->assert_equal("text", $comment->text); $this->assert_equal(1, $comment->item_id); $this->assert_equal("REMOTE_ADDR", $comment->server_remote_addr); diff --git a/modules/comment/tests/Comment_Model_Test.php b/modules/comment/tests/Comment_Model_Test.php index aa91d6f2..c98eb63c 100644 --- a/modules/comment/tests/Comment_Model_Test.php +++ b/modules/comment/tests/Comment_Model_Test.php @@ -20,21 +20,24 @@ class Comment_Model_Test extends Unit_Test_Case { public function cant_view_comments_for_unviewable_items_test() { - $root = ORM::factory("item", 1); - $album = album::create($root, rand(), rand(), rand()); - $comment = comment::create($album, identity::guest(), "text", "name", "email", "url"); + $album = test::random_album(); + + $comment = ORM::factory("comment"); + $comment->item_id = $album->id; + $comment->author_id = identity::admin_user()->id; + $comment->text = "text"; + $comment->save(); + identity::set_active_user(identity::guest()); // We can see the comment when permissions are granted on the album access::allow(identity::everybody(), "view", $album); - $this->assert_equal( - 1, + $this->assert_true( ORM::factory("comment")->viewable()->where("comments.id", "=", $comment->id)->count_all()); // We can't see the comment when permissions are denied on the album access::deny(identity::everybody(), "view", $album); - $this->assert_equal( - 0, + $this->assert_false( ORM::factory("comment")->viewable()->where("comments.id", "=", $comment->id)->count_all()); } } diff --git a/modules/digibug/tests/Digibug_Controller_Test.php b/modules/digibug/tests/Digibug_Controller_Test.php index 015a270a..38dcd8e9 100644 --- a/modules/digibug/tests/Digibug_Controller_Test.php +++ b/modules/digibug/tests/Digibug_Controller_Test.php @@ -18,41 +18,41 @@ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. */ class Digibug_Controller_Test extends Unit_Test_Case { - private $_proxy; - private $_item; private $_server; + public function setup() { + $this->_server = $_SERVER; + } + public function teardown() { $_SERVER = $this->_server; } - public function setup() { - $this->_server = $_SERVER; + private function _get_proxy() { + $album = test::random_album(); + $photo = test::random_photo($album); - $root = ORM::factory("item", 1); - $this->_album = album::create($root, rand(), "test album"); - access::deny(identity::everybody(), "view_full", $this->_album); - access::deny(identity::registered_users(), "view_full", $this->_album); + access::deny(identity::everybody(), "view_full", $album); + access::deny(identity::registered_users(), "view_full", $album); - $rand = rand(); - $this->_item = photo::create($this->_album, MODPATH . "gallery/tests/test.jpg", "$rand.jpg", - $rand, $rand); - $this->_proxy = ORM::factory("digibug_proxy"); - $this->_proxy->uuid = md5(rand()); - $this->_proxy->item_id = $this->_item->id; - $this->_proxy->save(); + $proxy = ORM::factory("digibug_proxy"); + $proxy->uuid = md5(rand()); + $proxy->item_id = $photo->id; + return $proxy->save(); } public function digibug_request_thumb_test() { + $proxy = $this->_get_proxy(); + $controller = new Digibug_Controller(); - $controller->print_proxy("thumb", $this->_proxy->uuid); + $controller->print_proxy("thumb", $proxy->uuid); } public function digibug_request_full_malicious_ip_test() { $_SERVER["REMOTE_ADDR"] = "123.123.123.123"; try { $controller = new Digibug_Controller(); - $controller->print_proxy("full", $this->_proxy->uuid); + $controller->print_proxy("full", $this->_get_proxy()->uuid); $this->assert_true(false, "Should have failed with an 404 exception"); } catch (Kohana_404_Exception $e) { // expected behavior @@ -69,6 +69,6 @@ class Digibug_Controller_Test extends Unit_Test_Case { $_SERVER["REMOTE_ADDR"] = long2ip(rand($low, $high)); $controller = new Digibug_Controller(); - $controller->print_proxy("full", $this->_proxy->uuid); + $controller->print_proxy("full", $this->_get_proxy()->uuid); } } diff --git a/modules/exif/tests/Exif_Test.php b/modules/exif/tests/Exif_Test.php index 5c5a5cb1..191bdb99 100644 --- a/modules/exif/tests/Exif_Test.php +++ b/modules/exif/tests/Exif_Test.php @@ -19,10 +19,9 @@ */ class Exif_Test extends Unit_Test_Case { public function exif_extract_test() { - $rand = rand(); - $root = ORM::factory("item", 1); - $photo = photo::create( - $root, MODPATH . "exif/tests/data/image.jpg", "$rand.jpg", $rand, $rand); + $photo = test::random_photo_unsaved() + ->set_data_file(MODPATH . "exif/tests/data/image.jpg") + ->save(); $expected = array( array("caption" => "Camera Maker", "value" => "Pentax Corporation"), diff --git a/modules/gallery/controllers/albums.php b/modules/gallery/controllers/albums.php index 2eeefdf1..7658a913 100644 --- a/modules/gallery/controllers/albums.php +++ b/modules/gallery/controllers/albums.php @@ -95,30 +95,36 @@ class Albums_Controller extends Items_Controller { access::required("view", $album); access::required("add", $album); - $input = Input::instance(); $form = album::get_add_form($album); - if ($form->validate()) { - $new_album = album::create( - $album, - $input->post("name"), - $input->post("title", $input->post("name")), - $input->post("description"), - identity::active_user()->id, - $input->post("slug")); + try { + $valid = $form->validate(); + $album = ORM::factory("item"); + $album->type = "album"; + $album->parent_id = $parent_id; + $album->name = $form->add_album->inputs["name"]->value; + $album->title = $form->add_album->title->value ? + $form->add_album->title->value : $form->add_album->inputs["name"]->value; + $album->description = $form->add_album->description->value; + $album->slug = $form->add_album->slug->value; + $album->validate(); + } catch (ORM_Validation_Exception $e) { + // Translate ORM validation errors into form error messages + foreach ($e->validation->errors() as $key => $error) { + $form->add_album->inputs[$key]->add_error($error, 1); + } + $valid = false; + } + if ($valid) { + $album->save(); log::success("content", "Created an album", - html::anchor("albums/$new_album->id", "view album")); + html::anchor("albums/$album->id", "view album")); message::success(t("Created album %album_title", - array("album_title" => html::purify($new_album->title)))); + array("album_title" => html::purify($album->title)))); - print json_encode( - array("result" => "success", - "location" => $new_album->url())); + print json_encode(array("result" => "success", "location" => $album->url())); } else { - print json_encode( - array( - "result" => "error", - "form" => $form->__toString())); + print json_encode(array("result" => "error", "form" => (string) $form)); } } @@ -129,42 +135,27 @@ class Albums_Controller extends Items_Controller { access::required("edit", $album); $form = album::get_edit_form($album); - if ($valid = $form->validate()) { - if ($album->id != 1 && - $form->edit_item->dirname->value != $album->name || - $form->edit_item->slug->value != $album->slug) { - // Make sure that there's not a conflict - if ($row = db::build() - ->select(array("name", "slug")) - ->from("items") - ->where("parent_id", "=", $album->parent_id) - ->where("id", "<>", $album->id) - ->and_open() - ->where("name", "=", $form->edit_item->dirname->value) - ->or_where("slug", "=", $form->edit_item->slug->value) - ->close() - ->execute() - ->current()) { - if ($row->name == $form->edit_item->dirname->value) { - $form->edit_item->dirname->add_error("name_conflict", 1); - } - if ($row->slug == $form->edit_item->slug->value) { - $form->edit_item->slug->add_error("slug_conflict", 1); - } - $valid = false; - } - } - } - - if ($valid) { + try { + $valid = $form->validate(); $album->title = $form->edit_item->title->value; $album->description = $form->edit_item->description->value; $album->sort_column = $form->edit_item->sort_order->column->value; $album->sort_order = $form->edit_item->sort_order->direction->value; - if ($album->id != 1) { - $album->rename($form->edit_item->dirname->value); - } + $album->name = $form->edit_item->dirname->value; $album->slug = $form->edit_item->slug->value; + $album->validate(); + } catch (ORM_Validation_Exception $e) { + // Translate ORM validation errors into form error messages + foreach ($e->validation->errors() as $key => $error) { + if ($key == "name") { + $key = "dirname"; + } + $form->edit_item->inputs[$key]->add_error($error, 1); + } + $valid = false; + } + + if ($valid) { $album->save(); module::event("item_edit_form_completed", $album, $form); @@ -180,9 +171,7 @@ class Albums_Controller extends Items_Controller { print json_encode(array("result" => "success")); } } else { - print json_encode( - array("result" => "error", - "form" => $form->__toString())); + print json_encode(array("result" => "error", "form" => (string) $form)); } } diff --git a/modules/gallery/controllers/login.php b/modules/gallery/controllers/login.php index 75ee6b9c..464db491 100644 --- a/modules/gallery/controllers/login.php +++ b/modules/gallery/controllers/login.php @@ -33,9 +33,7 @@ class Login_Controller extends Controller { print json_encode( array("result" => "success")); } else { - print json_encode( - array("result" => "error", - "form" => $form->__toString())); + print json_encode(array("result" => "error", "form" => (string) $form)); } } diff --git a/modules/gallery/controllers/movies.php b/modules/gallery/controllers/movies.php index 7a8e4d2a..0908e281 100644 --- a/modules/gallery/controllers/movies.php +++ b/modules/gallery/controllers/movies.php @@ -61,48 +61,25 @@ class Movies_Controller extends Items_Controller { access::required("edit", $movie); $form = movie::get_edit_form($movie); - $valid = $form->validate(); - - if ($valid) { - $new_ext = pathinfo($form->edit_item->filename->value, PATHINFO_EXTENSION); - $old_ext = pathinfo($movie->name, PATHINFO_EXTENSION); - if (strcasecmp($new_ext, $old_ext)) { - $form->edit_item->filename->add_error("illegal_extension", 1); - $valid = false; - } - } - - if ($valid) { - if ($form->edit_item->filename->value != $movie->name || - $form->edit_item->slug->value != $movie->slug) { - // Make sure that there's not a name or slug conflict - if ($row = db::build() - ->select(array("name", "slug")) - ->from("items") - ->where("parent_id", "=", $movie->parent_id) - ->where("id", "<>", $movie->id) - ->and_open() - ->where("name", "=", $form->edit_item->filename->value) - ->or_where("slug", "=", $form->edit_item->slug->value) - ->close() - ->execute() - ->current()) { - if ($row->name == $form->edit_item->filename->value) { - $form->edit_item->filename->add_error("name_conflict", 1); - } - if ($row->slug == $form->edit_item->slug->value) { - $form->edit_item->slug->add_error("slug_conflict", 1); - } - $valid = false; + try { + $valid = $form->validate(); + $movie->title = $form->edit_item->title->value; + $movie->description = $form->edit_item->description->value; + $movie->slug = $form->edit_item->slug->value; + $movie->name = $form->edit_item->filename->value; + $movie->validate(); + } catch (ORM_Validation_Exception $e) { + // Translate ORM validation errors into form error messages + foreach ($e->validation->errors() as $key => $error) { + if ($key == "name") { + $key = "filename"; } + $form->edit_item->inputs[$key]->add_error($error, 1); } + $valid = false; } if ($valid) { - $movie->title = $form->edit_item->title->value; - $movie->description = $form->edit_item->description->value; - $movie->slug = $form->edit_item->slug->value; - $movie->rename($form->edit_item->filename->value); $movie->save(); module::event("item_edit_form_completed", $movie, $form); @@ -118,9 +95,7 @@ class Movies_Controller extends Items_Controller { print json_encode(array("result" => "success")); } } else { - print json_encode( - array("result" => "error", - "form" => $form->__toString())); + print json_encode(array("result" => "error", "form" => (string) $form)); } } diff --git a/modules/gallery/controllers/photos.php b/modules/gallery/controllers/photos.php index 56b454ce..98f2126d 100644 --- a/modules/gallery/controllers/photos.php +++ b/modules/gallery/controllers/photos.php @@ -61,48 +61,25 @@ class Photos_Controller extends Items_Controller { access::required("edit", $photo); $form = photo::get_edit_form($photo); - $valid = $form->validate(); - - if ($valid) { - $new_ext = pathinfo($form->edit_item->filename->value, PATHINFO_EXTENSION); - $old_ext = pathinfo($photo->name, PATHINFO_EXTENSION); - if (strcasecmp($new_ext, $old_ext)) { - $form->edit_item->filename->add_error("illegal_extension", 1); - $valid = false; - } - } - - if ($valid) { - if ($form->edit_item->filename->value != $photo->name || - $form->edit_item->slug->value != $photo->slug) { - // Make sure that there's not a name or slug conflict - if ($row = db::build() - ->select(array("name", "slug")) - ->from("items") - ->where("parent_id", "=", $photo->parent_id) - ->where("id", "<>", $photo->id) - ->and_open() - ->where("name", "=", $form->edit_item->filename->value) - ->or_where("slug", "=", $form->edit_item->slug->value) - ->close() - ->execute() - ->current()) { - if ($row->name == $form->edit_item->filename->value) { - $form->edit_item->filename->add_error("name_conflict", 1); - } - if ($row->slug == $form->edit_item->slug->value) { - $form->edit_item->slug->add_error("slug_conflict", 1); - } - $valid = false; + try { + $valid = $form->validate(); + $photo->title = $form->edit_item->title->value; + $photo->description = $form->edit_item->description->value; + $photo->slug = $form->edit_item->slug->value; + $photo->name = $form->edit_item->filename->value; + $photo->validate(); + } catch (ORM_Validation_Exception $e) { + // Translate ORM validation errors into form error messages + foreach ($e->validation->errors() as $key => $error) { + if ($key == "name") { + $key = "filename"; } + $form->edit_item->inputs[$key]->add_error($error, 1); } + $valid = false; } if ($valid) { - $photo->title = $form->edit_item->title->value; - $photo->description = $form->edit_item->description->value; - $photo->slug = $form->edit_item->slug->value; - $photo->rename($form->edit_item->filename->value); $photo->save(); module::event("item_edit_form_completed", $photo, $form); @@ -118,9 +95,7 @@ class Photos_Controller extends Items_Controller { print json_encode(array("result" => "success")); } } else { - print json_encode( - array("result" => "error", - "form" => $form->__toString())); + print json_encode(array("result" => "error", "form" => (string) $form)); } } diff --git a/modules/gallery/controllers/simple_uploader.php b/modules/gallery/controllers/simple_uploader.php index 5d32e35f..16d1d241 100644 --- a/modules/gallery/controllers/simple_uploader.php +++ b/modules/gallery/controllers/simple_uploader.php @@ -40,39 +40,52 @@ class Simple_Uploader_Controller extends Controller { access::required("add", $album); access::verify_csrf(); + // The Flash uploader not call /start directly, so simulate it here for now. + if (!batch::in_progress()) { + batch::start(); + } + + $form = $this->_get_add_form($album); + + // Uploadify adds its own field to the form, so validate that separately. $file_validation = new Validation($_FILES); $file_validation->add_rules( "Filedata", "upload::valid", "upload::required", "upload::type[gif,jpg,jpeg,png,flv,mp4]"); - if ($file_validation->validate()) { - // SimpleUploader.swf does not yet call /start directly, so simulate it here for now. - if (!batch::in_progress()) { - batch::start(); - } + if ($form->validate() && $file_validation->validate()) { $temp_filename = upload::save("Filedata"); try { - $name = substr(basename($temp_filename), 10); // Skip unique identifier Kohana adds - $title = item::convert_filename_to_title($name); + $item = ORM::factory("item"); + $item->name = substr(basename($temp_filename), 10); // Skip unique identifier Kohana adds + $item->title = item::convert_filename_to_title($item->name); + $item->parent_id = $album->id; + $item->set_data_file($temp_filename); + $path_info = @pathinfo($temp_filename); if (array_key_exists("extension", $path_info) && in_array(strtolower($path_info["extension"]), array("flv", "mp4"))) { - $item = movie::create($album, $temp_filename, $name, $title); + $item->type = "movie"; + $item->save(); log::success("content", t("Added a movie"), html::anchor("movies/$item->id", t("view movie"))); } else { - $item = photo::create($album, $temp_filename, $name, $title); + $item->type = "photo"; + $item->save(); log::success("content", t("Added a photo"), html::anchor("photos/$item->id", t("view photo"))); } - // We currently have no way of showing errors if validation fails, so only call our event - // handlers if validation passes. - $form = $this->_get_add_form($album); - if ($form->validate()) { - module::event("add_photos_form_completed", $item, $form); - } + module::event("add_photos_form_completed", $item, $form); } catch (Exception $e) { - Kohana_Log::add("alert", $e->__toString()); + // The Flash uploader has no good way of reporting complex errors, so just keep it simple. + Kohana_Log::add("error", $e->getMessage() . "\n" . $e->getTraceAsString()); + + // Ugh. I hate to use instanceof, But this beats catching the exception separately since + // we mostly want to treat it the same way as all other exceptions + if ($e instanceof ORM_Validation_Exception) { + Kohana_Log::add("error", "Validation errors: " . print_r($e->validation->errors(), 1)); + } + if (file_exists($temp_filename)) { unlink($temp_filename); } @@ -84,7 +97,7 @@ class Simple_Uploader_Controller extends Controller { print "FILEID: $item->id"; } else { header("HTTP/1.1 400 Bad Request"); - print "ERROR: " . t("Invalid Upload"); + print "ERROR: " . t("Invalid upload"); } } diff --git a/modules/gallery/helpers/album.php b/modules/gallery/helpers/album.php index feaf74cc..e99770e9 100644 --- a/modules/gallery/helpers/album.php +++ b/modules/gallery/helpers/album.php @@ -24,72 +24,6 @@ * Note: by design, this class does not do any permission checking. */ class album_Core { - /** - * Create a new album. - * @param integer $parent_id id of parent album - * @param string $name the name of this new album (it will become the directory name on disk) - * @param integer $title the title of the new album - * @param string $description (optional) the longer description of this album - * @param string $slug (optional) the url component for this photo - * @return Item_Model - */ - static function create($parent, $name, $title, $description=null, $owner_id=null, $slug=null) { - if (!$parent->loaded() || !$parent->is_album()) { - throw new Exception("@todo INVALID_PARENT"); - } - - if (strpos($name, "/")) { - throw new Exception("@todo NAME_CANNOT_CONTAIN_SLASH"); - } - - // We don't allow trailing periods as a security measure - // ref: http://dev.kohanaphp.com/issues/684 - if (rtrim($name, ".") != $name) { - throw new Exception("@todo NAME_CANNOT_END_IN_PERIOD"); - } - - if (empty($slug)) { - $slug = item::convert_filename_to_slug($name); - } - - $album = ORM::factory("item"); - $album->type = "album"; - $album->title = $title; - $album->description = $description; - $album->name = $name; - $album->owner_id = $owner_id; - $album->thumb_dirty = 1; - $album->resize_dirty = 1; - $album->slug = $slug; - $album->rand_key = ((float)mt_rand()) / (float)mt_getrandmax(); - $album->sort_column = "created"; - $album->sort_order = "ASC"; - - // Randomize the name or slug if there's a conflict - // @todo Improve this. Random numbers are not user friendly - while (ORM::factory("item") - ->where("parent_id", "=", $parent->id) - ->and_open() - ->where("name", "=", $album->name) - ->or_where("slug", "=", $album->slug) - ->close() - ->find()->id) { - $rand = rand(); - $album->name = "{$name}-$rand"; - $album->slug = "{$slug}-$rand"; - } - - $album = $album->add_to_parent($parent); - mkdir($album->file_path()); - mkdir(dirname($album->thumb_path())); - mkdir(dirname($album->resize_path())); - - // @todo: publish this from inside Item_Model::save() when we refactor to the point where - // there's only one save() happening here. - module::event("item_created", $album); - - return $album; - } static function get_add_form($parent) { $form = new Forge("albums/create/{$parent->id}", "", "post", array("id" => "g-add-album-form")); @@ -98,16 +32,13 @@ class album_Core { $group->input("title")->label(t("Title")); $group->textarea("description")->label(t("Description")); $group->input("name")->label(t("Directory name")) - ->callback("item::validate_no_slashes") ->error_messages("no_slashes", t("The directory name can't contain the \"/\" character")); $group->input("slug")->label(t("Internet Address")) - ->callback("item::validate_url_safe") ->error_messages( "not_url_safe", t("The internet address should contain only letters, numbers, hyphens and underscores")); $group->hidden("type")->value("album"); $group->submit("")->value(t("Create")); - $form->add_rules_from(ORM::factory("item")); $form->script("") ->url(url::abs_file("modules/gallery/js/albums_form_add.js")); return $form; @@ -124,15 +55,12 @@ class album_Core { $group->input("dirname")->label(t("Directory Name"))->value($parent->name) ->rules("required") ->error_messages( - "name_conflict", t("There is already a movie, photo or album with this name")) - ->callback("item::validate_no_slashes") + "conflict", t("There is already a movie, photo or album with this name")) ->error_messages("no_slashes", t("The directory name can't contain a \"/\"")) - ->callback("item::validate_no_trailing_period") ->error_messages("no_trailing_period", t("The directory name can't end in \".\"")); $group->input("slug")->label(t("Internet Address"))->value($parent->slug) ->error_messages( - "slug_conflict", t("There is already a movie, photo or album with this internet address")) - ->callback("item::validate_url_safe") + "conflict", t("There is already a movie, photo or album with this internet address")) ->error_messages( "not_url_safe", t("The internet address should contain only letters, numbers, hyphens and underscores")); @@ -159,7 +87,6 @@ class album_Core { $group = $form->group("buttons")->label(""); $group->hidden("type")->value("album"); $group->submit("")->value(t("Modify")); - $form->add_rules_from(ORM::factory("item")); return $form; } diff --git a/modules/gallery/helpers/gallery_event.php b/modules/gallery/helpers/gallery_event.php index 679d65c2..db3b34fe 100644 --- a/modules/gallery/helpers/gallery_event.php +++ b/modules/gallery/helpers/gallery_event.php @@ -73,6 +73,24 @@ class gallery_event_Core { static function item_created($item) { access::add_item($item); + + if ($item->is_photo() || $item->is_movie()) { + // Build our thumbnail/resizes. + try { + graphics::generate($item); + } catch (Exception $e) { + log::error("graphics", t("Couldn't create a thumbnail or resize for %item_title", + array("item_title" => $item->title)), + html::anchor($item->abs_url(), t("details"))); + Kohana_Log::add("error", $e->getMessage() . "\n" . $e->getTraceAsString()); + } + + // If the parent has no cover item, make this it. + $parent = $item->parent(); + if (access::can("edit", $parent) && $parent->album_cover_item_id == null) { + item::make_album_cover($item); + } + } } static function item_deleted($item) { diff --git a/modules/gallery/helpers/gallery_installer.php b/modules/gallery/helpers/gallery_installer.php index 1e0ad28c..aa297236 100644 --- a/modules/gallery/helpers/gallery_installer.php +++ b/modules/gallery/helpers/gallery_installer.php @@ -209,19 +209,26 @@ class gallery_installer { t("Edit"); t("Add"); - $root = ORM::factory("item"); - $root->type = "album"; - $root->title = "Gallery"; - $root->description = ""; - $root->left_ptr = 1; - $root->right_ptr = 2; - $root->parent_id = 0; - $root->level = 1; - $root->thumb_dirty = 1; - $root->resize_dirty = 1; - $root->sort_column = "weight"; - $root->sort_order = "ASC"; - $root->save(); + // Hardcode the first item to sidestep ORM validation rules + $now = time(); + db::build()->insert( + "items", + array("created" => $now, + "description" => "", + "left_ptr" => 1, + "level" => 1, + "parent_id" => 0, + "resize_dirty" => 1, + "right_ptr" => 2, + "sort_column" => "weight", + "sort_order" => "ASC", + "thumb_dirty" => 1, + "title" => "Gallery", + "type" => "album", + "updated" => $now, + "weight" => 1)) + ->execute(); + $root = ORM::factory("item")->where("id", "=", 1)->find(); access::add_item($root); module::set_var("gallery", "active_site_theme", "wind"); diff --git a/modules/gallery/helpers/gallery_rest.php b/modules/gallery/helpers/gallery_rest.php index a87ebb4e..24733f20 100644 --- a/modules/gallery/helpers/gallery_rest.php +++ b/modules/gallery/helpers/gallery_rest.php @@ -17,232 +17,147 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. */ -class gallery_rest_Core { - static function get($request) { - $path = implode("/", $request->arguments); - - $item = gallery_rest::_get_item($path); - - $parent = $item->parent(); - $response_data = array("type" => $item->type, - "name" => $item->name, - "path" => $item->relative_url(), - "parent_path" => empty($parent) ? null : $parent->relative_url(), - "title" => $item->title, - "thumb_url" => $item->thumb_url(true), - "thumb_size" => array("height" => $item->thumb_height, - "width" => $item->thumb_width), - "resize_url" => $item->resize_url(true), - "resize_size" => array("height" => (int)$item->resize_height, - "width" => (int)$item->resize_width), - "url" => $item->file_url(true), - "size" => array("height" => $item->height, - "width" => $item->width), - "description" => $item->description, - "slug" => $item->slug); - - $children = self::_get_children($item, $request); - if (!empty($children) || $item->is_album()) { - $response_data["children"] = $children; - } - return rest::success(array("resource" => $response_data)); - } - static function put($request) { - if (empty($request->arguments)) { - throw new Rest_Exception(400, "Bad request"); - } - $path = implode("/", $request->arguments); - $item = gallery_rest::_get_item($path, "edit"); - - // Validate the request data - $new_values = gallery_rest::_validate($request, $item->parent_id, $item->id); - $errors = $new_values->errors(); - if (empty($errors)) { - $item->title = $new_values->title; - $item->description = $new_values->description; - if ($item->id != 1) { - $item->rename($new_values->name); - } - $item->slug = $new_values->slug; - $item->save(); +// @todo Add logging + +// Validation questions +// +// We need to be able to properly validate anything we want to enter here. But all of our +// validation currently happens at the controller / form level, and we're not using the same +// controllers or forms. +// +// Possible solutions: +// 1) Move validation into the model and use it both here and in the regular controllers. But +// if we do that, how do we translate validation failures into a user-consumable output which +// we need so that we can return proper error responses to form submissions? +// +// 2) Create some kind of validation helper that can validate every field. Wait, isn't this +// just like #1 except in a helper instead of in the model? - log::success("content", "Updated $item->type", - "<a href=\"{$item->type}s/$item->id\">view</a>"); +class gallery_rest_Core { - return rest::success(); - } else { - return rest::validation_error($errors); - } - } + /** + * For items that are collections, you can specify the following additional query parameters to + * query the collection. You can specify them in any combination. + * + * scope=direct + * only return items that are immediately under this one + * scope=all + * return items anywhere under this one + * + * name=<substring> + * only return items where the name contains this substring + * + * random=true + * return a single random item + * + * type=<comma separate list of photo, movie or album> + * 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); - static function post($request) { - if (empty($request->arguments)) { - throw new Rest_Exception(400, "Bad request"); + $p = $request->params; + if (isset($p->random)) { + $orm = item::random_query()->offset(0)->limit(1); + } else { + $orm = ORM::factory("item")->viewable(); } - $components = $request->arguments; - $name = urldecode(array_pop($components)); - - $parent = gallery_rest::_get_item(implode("/", $components), "edit"); - - // Validate the request data - $request->name = $name; - $new_values = gallery_rest::_validate($request, $parent->id); - $errors = $new_values->errors(); - if (!empty($errors)) { - return rest::validation_error($errors); + if (!empty($p->scope) && !in_array($p->scope, array("direct", "all"))) { + throw new Exception("Bad Request", 400); } - - if (empty($new_values["image"])) { - $new_item = album::create( - $parent, - $name, - empty($new_values["title"]) ? $name : $new_values["title"], - empty($new_values["description"]) ? null : $new_values["description"], - identity::active_user()->id, - empty($new_values["slug"]) ? $name : $new_values["slug"]); - $log_message = t("Added an album"); - } else { - $temp_filename = upload::save("image"); - $path_info = @pathinfo($temp_filename); - if (array_key_exists("extension", $path_info) && - in_array(strtolower($path_info["extension"]), array("flv", "mp4"))) { - $new_item = - movie::create($parent, $temp_filename, $new_values["name"], $new_values["title"]); - $log_message = t("Added a movie"); + if (!empty($p->scope)) { + if ($p->scope == "direct") { + $orm->where("parent_id", "=", $item->id); } else { - $new_item = - photo::create($parent, $temp_filename, $new_values["name"], $new_values["title"]); - $log_message = t("Added a photo"); + $orm->where("left_ptr", ">=", $item->left_ptr); + $orm->where("right_ptr", "<=", $item->left_ptr); + $orm->where("id", "<>", $item->id); } } - log::success("content", $log_message, "<a href=\"{$new_item->type}s/$new_item->id\">view</a>"); - - return rest::success(array("path" => $new_item->relative_url())); - } - - static function delete($request) { - if (empty($request->arguments)) { - throw new Rest_Exception(400, "Bad request"); + if (isset($p->name)) { + $orm->where("name", "LIKE", "%{$p->name}%"); } - $path = implode("/", $request->arguments); - - $item = gallery_rest::_get_item($path, "edit"); - if ($item->id == 1) { - throw new Rest_Exception(400, "Bad request"); + if (isset($p->type)) { + $orm->where("type", "IN", explode(",", $p->type)); } - $parent = $item->parent(); - $item->delete(); - - if ($item->is_album()) { - $msg = t("Deleted album <b>%title</b>", array("title" => html::purify($item->title))); - } else { - $msg = t("Deleted photo <b>%title</b>", array("title" => html::purify($item->title))); + $members = array(); + foreach ($orm->find_all() as $child) { + $members[] = url::abs_site("rest/gallery/" . $child->relative_url()); } - log::success("content", $msg); - return rest::success(array("resource" => array("parent_path" => $parent->relative_url()))); + return rest::reply(array("resource" => $item->as_array(), "members" => $members)); } - private static function _get_item($path, $permission="view") { - $item = url::get_item_from_uri($path); - - if (!$item->loaded()) { - throw new Kohana_404_Exception(); - } - - if (!access::can($permission, $item)) { - throw new Kohana_404_Exception(); + 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 (array_key_exists($key, $request->params)) { + $item->$key = $request->params->$key; + } } + $item->save(); - return $item; + return rest::reply(array("url" => url::abs_site("/rest/gallery/" . $item->relative_url()))); } - private static function _get_children($item, $request) { - $children = array(); - $limit = empty($request->limit) ? null : $request->limit; - $offset = empty($request->offset) ? null : $request->offset; - $where = empty($request->filter) ? array() : array("type" => $request->filter); - foreach ($item->viewable()->children($limit, $offset, $where) as $child) { - $children[] = array("type" => $child->type, - "has_children" => $child->children_count() > 0, - "path" => $child->relative_url(), - "thumb_url" => $child->thumb_url(true), - "thumb_dimensions" => array("width" => $child->thumb_width, - "height" => $child->thumb_height), - "has_thumb" => $child->has_thumb(), - "title" => $child->title); + 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->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 : $name; + $item->description = isset($params->description) ? $params->description : null; + $item->save(); + break; + + default: + throw new Rest_Exception("Invalid type: $args->type", 400); } - return $children; + return rest::reply(array("url" => url::abs_site("/rest/gallery/" . $item->relative_url()))); } - private static function _validate($request, $parent_id, $item_id=0) { - $item = ORM::factory("item", $item_id); - - // Normalize the inputs so all fields have a value - $new_values = Validation::factory(array()); - foreach ($item->form_rules as $field => $rule_set) { - if (isset($request->$field)) { - $new_values[$field] = $request->$field; - } else if (isset($item->$field)) { - $new_values[$field] = $item->$field; - } - foreach (explode("|", $rule_set) as $rule) { - $new_values->add_rules($field, $rule); - } - } - $name = $new_values["name"]; - $new_values["title"] = empty($new_values["title"]) ? $name : $new_values["title"]; - $new_values["description"] = - empty($new_values["description"]) ? null : $new_values["description"]; - $new_values["slug"] = empty($new_values["slug"]) ? $name : $new_values["slug"]; - - if (!empty($request->image)) { - $new_values["image"] = $request->image; - $new_values->add_rules( - "image", "upload::valid", "upload::required", "upload::type[gif,jpg,jpeg,png,flv,mp4]"); - } - - if ($new_values->validate() && $item_id != 1) { - $errors = gallery_rest::_check_for_conflicts($parent_id, $item_id, - $new_values["name"], $new_values["slug"]); - if (!empty($errors)) { - !empty($errors["name_conflict"]) OR $new_values->add_error("name", "Duplicate name"); - !empty($errors["slug_conflict"]) OR - $new_values->add_error("slug", "Duplicate Internet address"); - } - } + static function delete($request) { + $item = rest::resolve($request->url); + access::required("edit", $item); - return $new_values; + $item->delete(); + return rest::reply(); } - private static function _check_for_conflicts($parent_id, $item_id, $new_name, $new_slug) { - $errors = array(); - - if ($row = db::build() - ->select(array("name", "slug")) - ->from("items") - ->where("parent_id", "=", $parent_id) - ->where("id", "<>", $item_id) - ->and_open() - ->where("name", "=", $new_name) - ->or_where("slug", "=", $new_slug) - ->close() - ->execute() - ->current()) { - if ($row->name == $new_name) { - $errors["name_conflict"] = 1; - } - if ($row->slug == $new_slug) { - $errors["slug_conflict"] = 1; - } - } - - return $errors; + static function resolve($path) { + return url::get_item_from_uri($path); } } diff --git a/modules/gallery/helpers/identity.php b/modules/gallery/helpers/identity.php index eae0ea3e..ef93d72f 100644 --- a/modules/gallery/helpers/identity.php +++ b/modules/gallery/helpers/identity.php @@ -155,8 +155,8 @@ class identity_Core { /** * @see IdentityProvider_Driver::create_user. */ - static function create_user($name, $full_name, $password) { - return IdentityProvider::instance()->create_user($name, $full_name, $password); + static function create_user($name, $full_name, $password, $email) { + return IdentityProvider::instance()->create_user($name, $full_name, $password, $email); } /** diff --git a/modules/gallery/helpers/item.php b/modules/gallery/helpers/item.php index f6181f8a..53291ccc 100644 --- a/modules/gallery/helpers/item.php +++ b/modules/gallery/helpers/item.php @@ -78,24 +78,6 @@ class item_Core { graphics::generate($album); } - static function validate_no_slashes($input) { - if (strpos($input->value, "/") !== false) { - $input->add_error("no_slashes", 1); - } - } - - static function validate_no_trailing_period($input) { - if (rtrim($input->value, ".") !== $input->value) { - $input->add_error("no_trailing_period", 1); - } - } - - static function validate_url_safe($input) { - if (preg_match("/[^A-Za-z0-9-_]/", $input->value)) { - $input->add_error("not_url_safe", 1); - } - } - /** * Sanitize a filename into something presentable as an item title * @param string $filename @@ -173,4 +155,20 @@ class item_Core { static function root() { return model_cache::get("item", 1); } + + /** + * Return a query to get a random Item_Model, with optional filters + * + * @param array (optional) where tuple + */ + static function random_query($where=null) { + // Pick a random number and find the item that's got nearest smaller number. + // This approach works best when the random numbers in the system are roughly evenly + // distributed so this is going to be more efficient with larger data sets. + return ORM::factory("item") + ->viewable() + ->where("rand_key", "<", ((float)mt_rand()) / (float)mt_getrandmax()) + ->merge_where($where) + ->order_by("rand_key", "DESC"); + } }
\ No newline at end of file diff --git a/modules/gallery/helpers/movie.php b/modules/gallery/helpers/movie.php index 01859924..b2e846d3 100644 --- a/modules/gallery/helpers/movie.php +++ b/modules/gallery/helpers/movie.php @@ -24,110 +24,6 @@ * Note: by design, this class does not do any permission checking. */ class movie_Core { - /** - * Create a new movie. - * @param integer $parent_id id of parent album - * @param string $filename path to the photo file on disk - * @param string $name the filename to use for this photo in the album - * @param integer $title the title of the new photo - * @param string $description (optional) the longer description of this photo - * @param string $slug (optional) the url component for this photo - * @return Item_Model - */ - static function create($parent, $filename, $name, $title, - $description=null, $owner_id=null, $slug=null) { - if (!$parent->loaded() || !$parent->is_album()) { - throw new Exception("@todo INVALID_PARENT"); - } - - if (!is_file($filename)) { - throw new Exception("@todo MISSING_MOVIE_FILE"); - } - - if (strpos($name, "/")) { - throw new Exception("@todo NAME_CANNOT_CONTAIN_SLASH"); - } - - // We don't allow trailing periods as a security measure - // ref: http://dev.kohanaphp.com/issues/684 - if (rtrim($name, ".") != $name) { - throw new Exception("@todo NAME_CANNOT_END_IN_PERIOD"); - } - - try { - $movie_info = movie::getmoviesize($filename); - } catch (Exception $e) { - // Assuming this is MISSING_FFMPEG for now - $movie_info = getimagesize(MODPATH . "gallery/images/missing_movie.png"); - } - - // Force an extension onto the name - $pi = pathinfo($filename); - if (empty($pi["extension"])) { - $pi["extension"] = image_type_to_extension($movie_info[2], false); - $name .= "." . $pi["extension"]; - } - - if (empty($slug)) { - $slug = item::convert_filename_to_slug($name); - } - - $movie = ORM::factory("item"); - $movie->type = "movie"; - $movie->title = $title; - $movie->description = $description; - $movie->name = $name; - $movie->owner_id = $owner_id ? $owner_id : identity::active_user()->id; - $movie->width = $movie_info[0]; - $movie->height = $movie_info[1]; - $movie->mime_type = strtolower($pi["extension"]) == "mp4" ? "video/mp4" : "video/x-flv"; - $movie->thumb_dirty = 1; - $movie->resize_dirty = 1; - $movie->sort_column = "weight"; - $movie->slug = $slug; - $movie->rand_key = ((float)mt_rand()) / (float)mt_getrandmax(); - - // Randomize the name if there's a conflict - // @todo Improve this. Random numbers are not user friendly - while (ORM::factory("item") - ->where("parent_id", "=", $parent->id) - ->and_open() - ->where("name", "=", $movie->name) - ->or_where("slug", "=", $movie->slug) - ->close() - ->find()->id) { - $rand = rand(); - $movie->name = "{$name}.$rand.{$pi['extension']}"; - $movie->slug = "{$slug}-$rand"; - } - - // This saves the photo - $movie->add_to_parent($parent); - - // If the thumb or resize already exists then rename it - if (file_exists($movie->resize_path()) || - file_exists($movie->thumb_path())) { - $movie->name = $pi["filename"] . "-" . rand() . "." . $pi["extension"]; - $movie->save(); - } - - copy($filename, $movie->file_path()); - - // @todo: publish this from inside Item_Model::save() when we refactor to the point where - // there's only one save() happening here. - module::event("item_created", $movie); - - // Build our thumbnail - graphics::generate($movie); - - // If the parent has no cover item, make this it. - if (access::can("edit", $parent) && $parent->album_cover_item_id == null) { - item::make_album_cover($movie); - } - - return $movie; - } - static function get_edit_form($movie) { $form = new Forge("movies/update/$movie->id", "", "post", array("id" => "g-edit-movie-form")); $form->hidden("from_id"); @@ -135,18 +31,14 @@ class movie_Core { $group->input("title")->label(t("Title"))->value($movie->title); $group->textarea("description")->label(t("Description"))->value($movie->description); $group->input("filename")->label(t("Filename"))->value($movie->name) - ->rules("required") ->error_messages( - "name_conflict", t("There is already a movie, photo or album with this name")) - ->callback("item::validate_no_slashes") + "conflict", t("There is already a movie, photo or album with this name")) ->error_messages("no_slashes", t("The movie name can't contain a \"/\"")) - ->callback("item::validate_no_trailing_period") ->error_messages("no_trailing_period", t("The movie name can't end in \".\"")) ->error_messages("illegal_extension", t("You cannot change the filename extension")); $group->input("slug")->label(t("Internet Address"))->value($movie->slug) - ->callback("item::validate_url_safe") ->error_messages( - "slug_conflict", t("There is already a movie, photo or album with this internet address")) + "conflict", t("There is already a movie, photo or album with this internet address")) ->error_messages( "not_url_safe", t("The internet address should contain only letters, numbers, hyphens and underscores")); @@ -155,7 +47,7 @@ class movie_Core { $group = $form->group("buttons")->label(""); $group->submit("")->value(t("Modify")); - $form->add_rules_from(ORM::factory("item")); + return $form; } diff --git a/modules/gallery/helpers/photo.php b/modules/gallery/helpers/photo.php index 4e20e610..cb94772e 100644 --- a/modules/gallery/helpers/photo.php +++ b/modules/gallery/helpers/photo.php @@ -24,119 +24,6 @@ * Note: by design, this class does not do any permission checking. */ class photo_Core { - /** - * Create a new photo. - * @param integer $parent parent album - * @param string $filename path to the photo file on disk - * @param string $name the filename to use for this photo in the album - * @param integer $title the title of the new photo - * @param string $description (optional) the longer description of this photo - * @param string $slug (optional) the url component for this photo - * @return Item_Model - */ - static function create($parent, $filename, $name, $title, - $description=null, $owner_id=null, $slug=null) { - if (!$parent->loaded() || !$parent->is_album()) { - throw new Exception("@todo INVALID_PARENT"); - } - - if (!is_file($filename)) { - throw new Exception("@todo MISSING_IMAGE_FILE"); - } - - if (strpos($name, "/")) { - throw new Exception("@todo NAME_CANNOT_CONTAIN_SLASH"); - } - - // We don't allow trailing periods as a security measure - // ref: http://dev.kohanaphp.com/issues/684 - if (rtrim($name, ".") != $name) { - throw new Exception("@todo NAME_CANNOT_END_IN_PERIOD"); - } - - if (filesize($filename) == 0) { - throw new Exception("@todo EMPTY_INPUT_FILE"); - } - - $image_info = getimagesize($filename); - - // Force an extension onto the name - $pi = pathinfo($filename); - if (empty($pi["extension"])) { - $pi["extension"] = image_type_to_extension($image_info[2], false); - $name .= "." . $pi["extension"]; - } - - if (empty($slug)) { - $slug = item::convert_filename_to_slug($name); - } - - $photo = ORM::factory("item"); - $photo->type = "photo"; - $photo->title = $title; - $photo->description = $description; - $photo->name = $name; - $photo->owner_id = $owner_id ? $owner_id : identity::active_user()->id; - $photo->width = $image_info[0]; - $photo->height = $image_info[1]; - $photo->mime_type = empty($image_info['mime']) ? "application/unknown" : $image_info['mime']; - $photo->thumb_dirty = 1; - $photo->resize_dirty = 1; - $photo->sort_column = "weight"; - $photo->slug = $slug; - $photo->rand_key = ((float)mt_rand()) / (float)mt_getrandmax(); - - // Randomize the name or slug if there's a conflict - // @todo Improve this. Random numbers are not user friendly - while (ORM::factory("item") - ->where("parent_id", "=", $parent->id) - ->and_open() - ->where("name", "=", $photo->name) - ->or_where("slug", "=", $photo->slug) - ->close() - ->find()->id) { - $rand = rand(); - $photo->name = "{$name}.$rand.{$pi['extension']}"; - $photo->slug = "{$slug}-$rand"; - } - - // This saves the photo - $photo->add_to_parent($parent); - - /* - * If the thumb or resize already exists then rename it. We need to do this after the save - * because the resize_path and thumb_path both call relative_path which caches the - * path. Before add_to_parent the relative path will be incorrect. - */ - if (file_exists($photo->resize_path()) || - file_exists($photo->thumb_path())) { - $photo->name = $pi["filename"] . "-" . rand() . "." . $pi["extension"]; - $photo->save(); - } - - copy($filename, $photo->file_path()); - - // @todo: publish this from inside Item_Model::save() when we refactor to the point where - // there's only one save() happening here. - module::event("item_created", $photo); - - // Build our thumbnail/resizes. If we fail to build thumbnail/resize we assume that the image - // is bad in some way and discard it. - try { - graphics::generate($photo); - } catch (Exception $e) { - $photo->delete(); - throw $e; - } - - // If the parent has no cover item, make this it. - if (access::can("edit", $parent) && $parent->album_cover_item_id == null) { - item::make_album_cover($photo); - } - - return $photo; - } - static function get_edit_form($photo) { $form = new Forge("photos/update/$photo->id", "", "post", array("id" => "g-edit-photo-form")); $form->hidden("from_id"); @@ -144,18 +31,13 @@ class photo_Core { $group->input("title")->label(t("Title"))->value($photo->title); $group->textarea("description")->label(t("Description"))->value($photo->description); $group->input("filename")->label(t("Filename"))->value($photo->name) - ->rules("required") - ->error_messages( - "name_conflict", t("There is already a movie, photo or album with this name")) - ->callback("item::validate_no_slashes") + ->error_messages("conflict", t("There is already a movie, photo or album with this name")) ->error_messages("no_slashes", t("The photo name can't contain a \"/\"")) - ->callback("item::validate_no_trailing_period") ->error_messages("no_trailing_period", t("The photo name can't end in \".\"")) ->error_messages("illegal_extension", t("You cannot change the filename extension")); $group->input("slug")->label(t("Internet Address"))->value($photo->slug) - ->callback("item::validate_url_safe") ->error_messages( - "slug_conflict", t("There is already a movie, photo or album with this internet address")) + "conflict", t("There is already a movie, photo or album with this internet address")) ->error_messages( "not_url_safe", t("The internet address should contain only letters, numbers, hyphens and underscores")); @@ -164,7 +46,6 @@ class photo_Core { $group = $form->group("buttons")->label(""); $group->submit("")->value(t("Modify")); - $form->add_rules_from(ORM::factory("item")); return $form; } diff --git a/modules/gallery/libraries/IdentityProvider.php b/modules/gallery/libraries/IdentityProvider.php index bcb3056a..30d4efa4 100644 --- a/modules/gallery/libraries/IdentityProvider.php +++ b/modules/gallery/libraries/IdentityProvider.php @@ -119,8 +119,8 @@ class IdentityProvider_Core { /** * @see IdentityProvider_Driver::create_user. */ - public function create_user($name, $full_name, $password) { - return $this->driver->create_user($name, $full_name, $password); + public function create_user($name, $full_name, $password, $email) { + return $this->driver->create_user($name, $full_name, $password, $email); } /** diff --git a/modules/gallery/libraries/MY_Forge.php b/modules/gallery/libraries/MY_Forge.php index 9564f941..ee2a0bef 100644 --- a/modules/gallery/libraries/MY_Forge.php +++ b/modules/gallery/libraries/MY_Forge.php @@ -35,20 +35,6 @@ class Forge extends Forge_Core { } /** - * Associate validation rules defined in the model with this form. - */ - public function add_rules_from($model) { - foreach ($this->inputs as $name => $input) { - if (isset($input->inputs)) { - $input->add_rules_from($model); - } - if (isset($model->form_rules[$name])) { - $input->rules($model->form_rules[$name]); - } - } - } - - /** * Validate our CSRF value as a mandatory part of all form validation. */ public function validate() { diff --git a/modules/gallery/libraries/MY_ORM.php b/modules/gallery/libraries/MY_ORM.php index 56c776aa..8c0f084f 100644 --- a/modules/gallery/libraries/MY_ORM.php +++ b/modules/gallery/libraries/MY_ORM.php @@ -49,6 +49,10 @@ class ORM extends ORM_Core { } public function original() { + if (!isset($this->original)) { + $this->original = clone $this; + } + return $this->original; } } diff --git a/modules/gallery/libraries/ORM_MPTT.php b/modules/gallery/libraries/ORM_MPTT.php index 0ea519c9..404d61ff 100644 --- a/modules/gallery/libraries/ORM_MPTT.php +++ b/modules/gallery/libraries/ORM_MPTT.php @@ -40,43 +40,45 @@ class ORM_MPTT_Core extends ORM { } /** - * Add this node as a child of the parent provided. + * Overload ORM::save() to update the MPTT tree when we add new items to the hierarchy. * * @chainable - * @param integer $parent_id the id of the parent node - * @return ORM + * @return ORM */ - function add_to_parent($parent) { - $this->lock(); - $parent->reload(); // Assume that the prior lock holder may have changed the parent - - try { - // Make a hole in the parent for this new item - $this->db_builder - ->update($this->table_name) - ->set("left_ptr", new Database_Expression("`left_ptr` + 2")) - ->where("left_ptr", ">=", $parent->right_ptr) - ->execute(); - $this->db_builder - ->update($this->table_name) - ->set("right_ptr", new Database_Expression("`right_ptr` + 2")) - ->where("right_ptr", ">=", $parent->right_ptr) - ->execute(); - $parent->right_ptr += 2; + function save() { + if (!$this->loaded()) { + $this->lock(); + $parent = ORM::factory("item")->where("id", "=", $this->parent_id)->find(); - // Insert this item into the hole - $this->left_ptr = $parent->right_ptr - 2; - $this->right_ptr = $parent->right_ptr - 1; - $this->parent_id = $parent->id; - $this->level = $parent->level + 1; - $this->save(); - $parent->reload(); - } catch (Exception $e) { + try { + // Make a hole in the parent for this new item + $this->db_builder + ->update($this->table_name) + ->set("left_ptr", new Database_Expression("`left_ptr` + 2")) + ->where("left_ptr", ">=", $parent->right_ptr) + ->execute(); + $this->db_builder + ->update($this->table_name) + ->set("right_ptr", new Database_Expression("`right_ptr` + 2")) + ->where("right_ptr", ">=", $parent->right_ptr) + ->execute(); + $parent->right_ptr += 2; + + // Insert this item into the hole + $this->left_ptr = $parent->right_ptr - 2; + $this->right_ptr = $parent->right_ptr - 1; + $this->parent_id = $parent->id; + $this->level = $parent->level + 1; + } catch (Exception $e) { + $this->unlock(); + throw $e; + } + parent::save(); $this->unlock(); - throw $e; + } else { + parent::save(); } - $this->unlock(); return $this; } @@ -165,11 +167,8 @@ class ORM_MPTT_Core extends ORM { * @return array ORM */ function children($limit=null, $offset=null, $where=null, $order_by=array("id" => "ASC")) { - if ($where) { - $this->merge_where($where); - } - return $this + ->merge_where($where) ->where("parent_id", "=", $this->id) ->order_by($order_by) ->find_all($limit, $offset); @@ -183,11 +182,8 @@ class ORM_MPTT_Core extends ORM { * @return array ORM */ function children_count($where=null) { - if ($where) { - $this->merge_where($where); - } - return $this + ->merge_where($where) ->where("parent_id", "=", $this->id) ->count_all(); } @@ -202,11 +198,8 @@ class ORM_MPTT_Core extends ORM { * @return object ORM_Iterator */ function descendants($limit=null, $offset=null, $where=null, $order_by=array("id" => "ASC")) { - if ($where) { - $this->merge_where($where); - } - return $this + ->merge_where($where) ->where("left_ptr", ">", $this->left_ptr) ->where("right_ptr", "<=", $this->right_ptr) ->order_by($order_by) @@ -220,11 +213,8 @@ class ORM_MPTT_Core extends ORM { * @return integer child count */ function descendants_count($where=null) { - if ($where) { - $this->merge_where($where); - } - return $this + ->merge_where($where) ->where("left_ptr", ">", $this->left_ptr) ->where("right_ptr", "<=", $this->right_ptr) ->count_all(); diff --git a/modules/gallery/libraries/drivers/IdentityProvider.php b/modules/gallery/libraries/drivers/IdentityProvider.php index a808c7e8..b7b1fbe8 100644 --- a/modules/gallery/libraries/drivers/IdentityProvider.php +++ b/modules/gallery/libraries/drivers/IdentityProvider.php @@ -38,9 +38,10 @@ interface IdentityProvider_Driver { * @param string $name * @param string $full_name * @param string $password + * @param string $email * @return User_Definition the user object */ - public function create_user($name, $full_name, $password); + public function create_user($name, $full_name, $password, $email); /** * Is the password provided correct? diff --git a/modules/gallery/models/item.php b/modules/gallery/models/item.php index 6851e1a3..fcd9b8e6 100644 --- a/modules/gallery/models/item.php +++ b/modules/gallery/models/item.php @@ -18,15 +18,9 @@ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. */ class Item_Model extends ORM_MPTT { - protected $children = 'items'; + protected $children = "items"; protected $sorting = array(); - - var $form_rules = array( - "name" => "required|length[0,255]", - "title" => "required|length[0,255]", - "description" => "length[0,65535]", - "slug" => "required|length[0,255]" - ); + protected $data_file = null; /** * Add a set of restrictions to any following queries to restrict access only to items @@ -146,21 +140,12 @@ class Item_Model extends ORM_MPTT { } /** - * Rename the underlying file for this item to a new name. Move all the files. This requires a - * save. + * Rename the underlying file for this item to a new name and move all related files. * * @chainable */ - public function rename($new_name) { - if ($new_name == $this->name) { - return; - } - - if (strpos($new_name, "/")) { - throw new Exception("@todo NAME_CANNOT_CONTAIN_SLASH"); - } - - $old_relative_path = urldecode($this->relative_path()); + private function rename($new_name) { + $old_relative_path = urldecode($this->original()->relative_path()); $new_relative_path = dirname($old_relative_path) . "/" . $new_name; if (file_exists(VARPATH . "albums/$new_relative_path")) { throw new Exception("@todo INVALID_RENAME_FILE_EXISTS: $new_relative_path"); @@ -178,18 +163,16 @@ class Item_Model extends ORM_MPTT { @rename(VARPATH . "thumbs/$old_relative_path", VARPATH . "thumbs/$new_relative_path"); } - $this->name = $new_name; - - if ($this->is_album()) { - db::build() - ->update("items") - ->set("relative_url_cache", null) - ->set("relative_path_cache", null) - ->where("left_ptr", ">", $this->left_ptr) - ->where("right_ptr", "<", $this->right_ptr) - ->execute(); - } + return $this; + } + /** + * Specify the path to the data file associated with this item. To actually associate it, + * you still have to call save(). + * @chainable + */ + public function set_data_file($data_file) { + $this->data_file = $data_file; return $this; } @@ -323,7 +306,7 @@ class Item_Model extends ORM_MPTT { } $this->relative_path_cache = implode($names, "/"); $this->relative_url_cache = implode($slugs, "/"); - $this->save(); + return $this; } /** @@ -338,7 +321,7 @@ class Item_Model extends ORM_MPTT { } if (!isset($this->relative_path_cache)) { - $this->_build_relative_caches(); + $this->_build_relative_caches()->save(); } return $this->relative_path_cache; } @@ -353,7 +336,7 @@ class Item_Model extends ORM_MPTT { } if (!isset($this->relative_url_cache)) { - $this->_build_relative_caches(); + $this->_build_relative_caches()->save(); } return $this->relative_url_cache; } @@ -376,30 +359,10 @@ class Item_Model extends ORM_MPTT { } /** - * @see ORM::__set() - */ - public function __set($column, $value) { - if ($column == "name") { - $this->relative_path_cache = null; - } else if ($column == "slug") { - if ($this->slug != $value) { - // Clear the relative url cache for this item and all children - $this->relative_url_cache = null; - if ($this->is_album()) { - db::build() - ->update("items") - ->set("relative_url_cache", null) - ->where("left_ptr", ">", $this->left_ptr) - ->where("right_ptr", "<", $this->right_ptr) - ->execute(); - } - } - } - parent::__set($column, $value); - } - - /** + * Handle any business logic necessary to create or modify an item. * @see ORM::save() + * + * @return ORM Item_Model */ public function save() { $significant_changes = $this->changed; @@ -410,18 +373,147 @@ class Item_Model extends ORM_MPTT { if (!empty($this->changed) && $significant_changes) { $this->updated = time(); if (!$this->loaded()) { + // Create a new item. Use whatever fields are set, and specify defaults for the rest. $this->created = $this->updated; $this->weight = item::get_max_weight(); + $this->rand_key = ((float)mt_rand()) / (float)mt_getrandmax(); + $this->thumb_dirty = 1; + $this->resize_dirty = 1; + if (empty($this->sort_column)) { + $this->sort_column = "created"; + } + if (empty($this->sort_order)) { + $this->sort_order = "ASC"; + } + if (empty($this->owner_id)) { + $this->owner_id = identity::active_user()->id; + } + + // Make an url friendly slug from the name, if necessary + if (empty($this->slug)) { + $tmp = pathinfo($this->name, PATHINFO_FILENAME); + $tmp = preg_replace("/[^A-Za-z0-9-_]+/", "-", $tmp); + $this->slug = trim($tmp, "-"); + } + + // Get the width, height and mime type from our data file for photos and movies. + if ($this->is_movie() || $this->is_photo()) { + $pi = pathinfo($this->data_file); + + if ($this->is_photo()) { + $image_info = getimagesize($this->data_file); + $this->width = $image_info[0]; + $this->height = $image_info[1]; + $this->mime_type = $image_info["mime"]; + + // Force an extension onto the name if necessary + if (empty($pi["extension"])) { + $pi["extension"] = image_type_to_extension($image_info[2], false); + $this->name .= "." . $pi["extension"]; + } + } else { + list ($this->width, $this->height) = movie::getmoviesize($this->data_file); + + // No extension? Assume FLV. + if (empty($pi["extension"])) { + $pi["extension"] = "flv"; + $this->name .= "." . $pi["extension"]; + } + + $this->mime_type = strtolower($pi["extension"]) == "mp4" ? "video/mp4" : "video/x-flv"; + } + } + + // Randomize the name or slug if there's a conflict. Preserve the extension. + // @todo Improve this. Random numbers are not user friendly + $base_name = pathinfo($this->name, PATHINFO_FILENAME); + $base_ext = pathinfo($this->name, PATHINFO_EXTENSION); + $base_slug = $this->slug; + while (ORM::factory("item") + ->where("parent_id", "=", $this->parent_id) + ->and_open() + ->where("name", "=", $this->name) + ->or_where("slug", "=", $this->slug) + ->close() + ->find()->id) { + $rand = rand(); + if ($base_ext) { + $this->name = "$base_name-$rand.$base_ext"; + } else { + $this->name = "$base_name-$rand"; + } + $this->slug = "$base_slug-$rand"; + } + + parent::save(); + + // Build our url caches, then save again. We have to do this after it's already been + // saved once because we use only information from the database to build the paths. If we + // could depend on a save happening later we could defer this 2nd save. + $this->_build_relative_caches(); + parent::save(); + + // Take any actions that we can only do once all our paths are set correctly after saving. + switch ($this->type) { + case "album": + mkdir($this->file_path()); + mkdir(dirname($this->thumb_path())); + mkdir(dirname($this->resize_path())); + break; + + case "photo": + case "movie": + // The thumb or resize may already exist in the case where a movie and a photo generate + // a thumbnail of the same name (eg, foo.flv movie and foo.jpg photo will generate + // foo.jpg thumbnail). If that happens, randomize and save again. + if (file_exists($this->resize_path()) || + file_exists($this->thumb_path())) { + $pi = pathinfo($this->name); + $this->name = $pi["filename"] . "-" . rand() . "." . $pi["extension"]; + parent::save(); + } + + copy($this->data_file, $this->file_path()); + break; + } + + // This will almost definitely trigger another save, so put it at the end so that we're + // tail recursive. + module::event("item_created", $this); } else { - $send_event = 1; + // Update an existing item + if ($this->original()->name != $this->name) { + $this->rename($this->name); + $this->relative_path_cache = null; + } + + if ($this->original()->slug != $this->slug) { + // Clear the relative url cache for this item and all children + $this->relative_url_cache = null; + } + + // Changing the name or the slug ripples downwards + if ($this->is_album() && + ($this->original()->name != $this->name || + $this->original()->slug != $this->slug)) { + db::build() + ->update("items") + ->set("relative_url_cache", null) + ->set("relative_path_cache", null) + ->where("left_ptr", ">", $this->left_ptr) + ->where("right_ptr", "<", $this->right_ptr) + ->execute(); + } + $original = clone $this->original(); + parent::save(); + module::event("item_updated", $original, $this); } + } else if (!empty($this->changed)) { + // Insignificant changes only. Don't fire events or do any special checking to try to keep + // this lightweight. + parent::save(); } - $original = clone $this->original(); - parent::save(); - if (isset($send_event)) { - module::event("item_updated", $original, $this); - } return $this; } @@ -657,4 +749,188 @@ class Item_Model extends ORM_MPTT { } return parent::descendants($limit, $offset, $where, $order_by); } + + /** + * Specify our rules here so that we have access to the instance of this model. + */ + public function validate($array=null) { + if (!$array) { + $this->rules = array( + "album_cover_item_id" => array("callbacks" => array(array($this, "valid_item"))), + "description" => array("rules" => array("length[0,65535]")), + "mime_type" => array("callbacks" => array(array($this, "valid_field"))), + "name" => array("rules" => array("length[0,255]", "required"), + "callbacks" => array(array($this, "valid_name"))), + "parent_id" => array("callbacks" => array(array($this, "valid_parent"))), + "rand_key" => array("rule" => array("decimal")), + "slug" => array("rules" => array("length[0,255]", "required"), + "callbacks" => array(array($this, "valid_slug"))), + "sort_column" => array("callbacks" => array(array($this, "valid_field"))), + "sort_order" => array("callbacks" => array(array($this, "valid_field"))), + "title" => array("rules" => array("length[0,255]", "required")), + "type" => array("callbacks" => array(array($this, "read_only"), + array($this, "valid_field"))), + ); + + // Conditional rules + if ($this->id == 1) { + // Root album can't have a name or slug so replace the rules + $this->rules["name"] = array("rules" => array("length[0]")); + $this->rules["slug"] = array("rules" => array("length[0]")); + } + + // Movies and photos must have data files + if (($this->is_photo() || $this->is_movie()) && !$this->loaded()) { + $this->rules["name"]["callbacks"][] = array($this, "valid_data_file"); + } + } + + parent::validate($array); + } + + /** + * Validate that the desired slug does not conflict. + */ + public function valid_slug(Validation $v, $field) { + if (preg_match("/[^A-Za-z0-9-_]/", $this->slug)) { + $v->add_error("slug", "not_url_safe"); + } else if (db::build() + ->from("items") + ->where("parent_id", "=", $this->parent_id) + ->where("id", "<>", $this->id) + ->where("slug", "=", $this->slug) + ->count_records()) { + $v->add_error("slug", "conflict"); + } + } + + /** + * Validate the item name. It can't conflict with other names, can't contain slashes or + * trailing periods. + */ + public function valid_name(Validation $v, $field) { + if (strpos($this->name, "/") !== false) { + $v->add_error("name", "no_slashes"); + return; + } + + if (rtrim($this->name, ".") !== $this->name) { + $v->add_error("name", "no_trailing_period"); + return; + } + + if ($this->is_movie() || $this->is_photo()) { + if ($this->loaded()) { + // Existing items can't change their extension + $new_ext = pathinfo($this->name, PATHINFO_EXTENSION); + $old_ext = pathinfo($this->original()->name, PATHINFO_EXTENSION); + if (strcasecmp($new_ext, $old_ext)) { + $v->add_error("name", "illegal_data_file_extension"); + return; + } + } else { + // New items must have an extension + if (!pathinfo($this->name, PATHINFO_EXTENSION)) { + $v->add_error("name", "illegal_data_file_extension"); + } + } + } + + if (db::build() + ->from("items") + ->where("parent_id", "=", $this->parent_id) + ->where("id", "<>", $this->id) + ->where("name", "=", $this->name) + ->count_records()) { + $v->add_error("name", "conflict"); + } + } + + /** + * Make sure that the data file is well formed (it exists and isn't empty). + */ + public function valid_data_file(Validation $v, $field) { + if (!is_file($this->data_file)) { + $v->add_error("name", "bad_data_file_path"); + } else if (filesize($this->data_file) == 0) { + $v->add_error("name", "empty_data_file"); + } + } + + /** + * Make sure that the parent id refers to an album. + */ + public function valid_parent(Validation $v, $field) { + if ($this->id == 1) { + if ($this->parent_id != 0) { + $v->add_error("parent_id", "invalid"); + } + } else { + if (db::build() + ->from("items") + ->where("id", "=", $this->parent_id) + ->where("type", "=", "album") + ->count_records() != 1) { + $v->add_error("parent_id", "invalid"); + } + } + } + + /** + * Make sure the field refers to a valid item by id, or is null. + */ + public function valid_item(Validation $v, $field) { + if ($this->$field && db::build() + ->from("items") + ->where("id", "=", $this->$field) + ->count_records() != 1) { + $v->add_error($field, "invalid_item"); + } + } + + /** + * Make sure that the type is valid. + */ + public function valid_field(Validation $v, $field) { + switch($field) { + case "mime_type": + if ($this->is_movie()) { + $legal_values = array("video/flv", "video/mp4"); + } if ($this->is_photo()) { + $legal_values = array("image/jpeg", "image/gif", "image/png"); + } + break; + + case "sort_column": + if (!array_key_exists($this->sort_column, $this->object)) { + $v->add_error($field, "invalid"); + } + break; + + case "sort_order": + $legal_values = array("ASC", "DESC", "asc", "desc"); + break; + + case "type": + $legal_values = array("album", "photo", "movie"); + break; + + default: + $v->add_error($field, "unvalidated_field"); + break; + } + + if (isset($legal_values) && !in_array($this->$field, $legal_values)) { + $v->add_error($field, "invalid"); + } + } + + /** + * This field cannot be changed after it's been set. + */ + public function read_only(Validation $v, $field) { + if ($this->loaded() && $this->original()->$field != $this->$field) { + $v->add_error($field, "read_only"); + } + } } diff --git a/modules/gallery/tests/Access_Helper_Test.php b/modules/gallery/tests/Access_Helper_Test.php index b2244766..da72f12f 100644 --- a/modules/gallery/tests/Access_Helper_Test.php +++ b/modules/gallery/tests/Access_Helper_Test.php @@ -40,8 +40,7 @@ class Access_Helper_Test extends Unit_Test_Case { } catch (Exception $e) { } // Reset some permissions that we mangle below - $root = ORM::factory("item", 1); - access::allow(identity::everybody(), "view", $root); + access::allow(identity::everybody(), "view", item::root()); } public function setup() { @@ -67,16 +66,15 @@ class Access_Helper_Test extends Unit_Test_Case { public function user_can_access_test() { $access_test = identity::create_group("access_test"); - $root = ORM::factory("item", 1); - access::allow($access_test, "view", $root); + access::allow($access_test, "view", item::root()); - $item = album::create($root, rand(), "test album"); + $item = test::random_album(); access::deny(identity::everybody(), "view", $item); access::deny(identity::registered_users(), "view", $item); $item->reload(); - $user = identity::create_user("access_test", "Access Test", ""); + $user = identity::create_user("access_test", "Access Test", "*****", "user@user.com"); foreach ($user->groups() as $group) { $user->remove($group); } @@ -87,13 +85,12 @@ class Access_Helper_Test extends Unit_Test_Case { } public function user_can_no_access_test() { - $root = ORM::factory("item", 1); - $item = album::create($root, rand(), "test album"); + $item = test::random_album(); access::deny(identity::everybody(), "view", $item); access::deny(identity::registered_users(), "view", $item); - $user = identity::create_user("access_test", "Access Test", ""); + $user = identity::create_user("access_test", "Access Test", "*****", "user@user.com"); foreach ($user->groups() as $group) { $user->remove($group); } @@ -103,8 +100,7 @@ class Access_Helper_Test extends Unit_Test_Case { } public function adding_and_removing_items_adds_ands_removes_rows_test() { - $root = ORM::factory("item", 1); - $item = album::create($root, rand(), "test album"); + $item = test::random_album(); // New rows exist $this->assert_true(ORM::factory("access_cache")->where("item_id", "=", $item->id)->find()->loaded()); @@ -119,19 +115,16 @@ class Access_Helper_Test extends Unit_Test_Case { } public function new_photos_inherit_parent_permissions_test() { - $root = ORM::factory("item", 1); - - $album = album::create($root, rand(), "test album"); + $album = test::random_album(); access::allow(identity::everybody(), "view", $album); - $photo = photo::create($album, MODPATH . "gallery/images/gallery.png", "", ""); + $photo = test::random_photo($album); $this->assert_true($photo->__get("view_" . identity::everybody()->id)); } public function can_allow_deny_and_reset_intent_test() { - $root = ORM::factory("item", 1); - $album = album::create($root, rand(), "test album"); + $album = test::random_album(); $intent = ORM::factory("access_intent")->where("item_id", "=", $album->id)->find(); // Allow @@ -167,23 +160,21 @@ class Access_Helper_Test extends Unit_Test_Case { } public function can_view_item_test() { - $root = ORM::factory("item", 1); - access::allow(identity::everybody(), "view", $root); - $this->assert_true(access::group_can(identity::everybody(), "view", $root)); + access::allow(identity::everybody(), "view", item::root()); + $this->assert_true(access::group_can(identity::everybody(), "view", item::root())); } public function can_always_fails_on_unloaded_items_test() { - $root = ORM::factory("item", 1); - access::allow(identity::everybody(), "view", $root); - $this->assert_true(access::group_can(identity::everybody(), "view", $root)); + access::allow(identity::everybody(), "view", item::root()); + $this->assert_true(access::group_can(identity::everybody(), "view", item::root())); $bogus = ORM::factory("item", -1); $this->assert_false(access::group_can(identity::everybody(), "view", $bogus)); } public function cant_view_child_of_hidden_parent_test() { - $root = ORM::factory("item", 1); - $album = album::create($root, rand(), "test album"); + $root = item::root(); + $album = test::random_album(); $root->reload(); access::deny(identity::everybody(), "view", $root); @@ -194,28 +185,26 @@ class Access_Helper_Test extends Unit_Test_Case { } public function view_permissions_propagate_down_test() { - $root = ORM::factory("item", 1); - $album = album::create($root, rand(), "test album"); + $album = test::random_album(); - access::allow(identity::everybody(), "view", $root); + access::allow(identity::everybody(), "view", item::root()); access::reset(identity::everybody(), "view", $album); $album->reload(); $this->assert_true(access::group_can(identity::everybody(), "view", $album)); } public function can_toggle_view_permissions_propagate_down_test() { - $root = ORM::factory("item", 1); - $album1 = album::create($root, rand(), "test album"); - $album2 = album::create($album1, rand(), "test album"); - $album3 = album::create($album2, rand(), "test album"); - $album4 = album::create($album3, rand(), "test album"); + $album1 = test::random_album(item::root()); + $album2 = test::random_album($album1); + $album3 = test::random_album($album2); + $album4 = test::random_album($album3); $album1->reload(); $album2->reload(); $album3->reload(); $album4->reload(); - access::allow(identity::everybody(), "view", $root); + access::allow(identity::everybody(), "view", item::root()); access::deny(identity::everybody(), "view", $album1); access::reset(identity::everybody(), "view", $album2); access::reset(identity::everybody(), "view", $album3); @@ -230,9 +219,9 @@ class Access_Helper_Test extends Unit_Test_Case { } public function revoked_view_permissions_cant_be_allowed_lower_down_test() { - $root = ORM::factory("item", 1); - $album1 = album::create($root, rand(), "test album"); - $album2 = album::create($album1, rand(), "test album"); + $root = item::root(); + $album1 = test::random_album($root); + $album2 = test::random_album($album1); $root->reload(); access::deny(identity::everybody(), "view", $root); @@ -246,38 +235,30 @@ class Access_Helper_Test extends Unit_Test_Case { } public function can_edit_item_test() { - $root = ORM::factory("item", 1); + $root = item::root(); access::allow(identity::everybody(), "edit", $root); $this->assert_true(access::group_can(identity::everybody(), "edit", $root)); } public function non_view_permissions_propagate_down_test() { - $root = ORM::factory("item", 1); - $album = album::create($root, rand(), "test album"); + $album = test::random_album(); - access::allow(identity::everybody(), "edit", $root); + access::allow(identity::everybody(), "edit", item::root()); access::reset(identity::everybody(), "edit", $album); $this->assert_true(access::group_can(identity::everybody(), "edit", $album)); } public function non_view_permissions_can_be_revoked_lower_down_test() { - $root = ORM::factory("item", 1); - $outer = album::create($root, rand(), "test album"); - $outer_photo = ORM::factory("item"); - $outer_photo->type = "photo"; - $outer_photo->add_to_parent($outer); - access::add_item($outer_photo); - - $inner = album::create($outer, rand(), "test album"); - $inner_photo = ORM::factory("item"); - $inner_photo->type = "photo"; - $inner_photo->add_to_parent($inner); - access::add_item($inner_photo); + $outer = test::random_album(); + $outer_photo = test::random_photo($outer); + + $inner = test::random_album($outer); + $inner_photo = test::random_photo($inner); $outer->reload(); $inner->reload(); - access::allow(identity::everybody(), "edit", $root); + access::allow(identity::everybody(), "edit", item::root()); access::deny(identity::everybody(), "edit", $outer); access::allow(identity::everybody(), "edit", $inner); @@ -288,7 +269,7 @@ class Access_Helper_Test extends Unit_Test_Case { public function i_can_edit_test() { // Create a new user that belongs to no groups - $user = identity::create_user("access_test", "Access Test", ""); + $user = identity::create_user("access_test", "Access Test", "*****", "user@user.com"); foreach ($user->groups() as $group) { $user->remove($group); } @@ -296,7 +277,7 @@ class Access_Helper_Test extends Unit_Test_Case { identity::set_active_user($user); // This user can't edit anything - $root = ORM::factory("item", 1); + $root = item::root(); $this->assert_false(access::can("edit", $root)); // Now add them to a group that has edit permission @@ -313,8 +294,7 @@ class Access_Helper_Test extends Unit_Test_Case { } public function everybody_view_permission_maintains_htaccess_files_test() { - $root = ORM::factory("item", 1); - $album = album::create($root, rand(), "test album"); + $album = test::random_album(); $this->assert_false(file_exists($album->file_path() . "/.htaccess")); @@ -332,8 +312,7 @@ class Access_Helper_Test extends Unit_Test_Case { } public function everybody_view_full_permission_maintains_htaccess_files_test() { - $root = ORM::factory("item", 1); - $album = album::create($root, rand(), "test album"); + $album = test::random_album(); $this->assert_false(file_exists($album->file_path() . "/.htaccess")); $this->assert_false(file_exists($album->resize_path() . "/.htaccess")); @@ -363,16 +342,15 @@ class Access_Helper_Test extends Unit_Test_Case { public function moved_items_inherit_new_permissions_test() { identity::set_active_user(identity::lookup_user_by_name("admin")); - $root = ORM::factory("item", 1); - $public_album = album::create($root, rand(), "public album"); - $public_photo = photo::create($public_album, MODPATH . "gallery/images/gallery.png", "", ""); + $public_album = test::random_album(); + $public_photo = test::random_photo($public_album); access::allow(identity::everybody(), "view", $public_album); - $root->reload(); // Account for MPTT changes + item::root()->reload(); // Account for MPTT changes - $private_album = album::create($root, rand(), "private album"); + $private_album = test::random_album(); access::deny(identity::everybody(), "view", $private_album); - $private_photo = photo::create($private_album, MODPATH . "gallery/images/gallery.png", "", ""); + $private_photo = test::random_photo($private_album); // Make sure that we now have a public photo and private photo. $this->assert_true(access::group_can(identity::everybody(), "view", $public_photo)); diff --git a/modules/gallery/tests/Gallery_Rest_Helper_Test.php b/modules/gallery/tests/Gallery_Rest_Helper_Test.php index f8cf6190..dac221b3 100644 --- a/modules/gallery/tests/Gallery_Rest_Helper_Test.php +++ b/modules/gallery/tests/Gallery_Rest_Helper_Test.php @@ -136,7 +136,8 @@ class Gallery_Rest_Helper_Test extends Unit_Test_Case { try { gallery_rest::put($request); } catch (Rest_Exception $e) { - $this->assert_equal("400 Bad request", $e->getMessage()); + $this->assert_equal("Bad request", $e->getMessage()); + $this->assert_equal(400, $e->getCode()); } catch (Exception $e) { $this->assert_false(true, $e->__toString()); } diff --git a/modules/gallery/tests/Item_Model_Test.php b/modules/gallery/tests/Item_Model_Test.php index d03a03f4..b41740d6 100644 --- a/modules/gallery/tests/Item_Model_Test.php +++ b/modules/gallery/tests/Item_Model_Test.php @@ -19,20 +19,13 @@ */ class Item_Model_Test extends Unit_Test_Case { public function saving_sets_created_and_updated_dates_test() { - $item = self::_create_random_item(); + $item = test::random_photo(); $this->assert_true(!empty($item->created)); $this->assert_true(!empty($item->updated)); } - private static function _create_random_item($root=null, $rand=null) { - $root = $root ? $root : ORM::factory("item", 1); - $rand = $rand ? $rand : rand(); - $item = photo::create($root, MODPATH . "gallery/tests/test.jpg", "$rand.jpg", $rand, $rand); - return $item; - } - public function updating_doesnt_change_created_date_test() { - $item = self::_create_random_item(); + $item = test::random_photo(); // Force the creation date to something well known db::build() @@ -50,7 +43,7 @@ class Item_Model_Test extends Unit_Test_Case { } public function updating_view_count_only_doesnt_change_updated_date_test() { - $item = self::_create_random_item(); + $item = test::random_photo(); $item->reload(); $this->assert_equal(0, $item->view_count); @@ -69,8 +62,7 @@ class Item_Model_Test extends Unit_Test_Case { } public function rename_photo_test() { - // Create a test photo - $item = self::_create_random_item(); + $item = test::random_photo(); file_put_contents($item->thumb_path(), "thumb"); file_put_contents($item->resize_path(), "resize"); @@ -80,7 +72,8 @@ class Item_Model_Test extends Unit_Test_Case { $new_name = rand(); // Now rename it - $item->rename($new_name)->save(); + $item->name = $new_name; + $item->save(); // Expected: the name changed, the name is now baked into all paths, and all files were moved. $this->assert_equal($new_name, $item->name); @@ -93,10 +86,9 @@ class Item_Model_Test extends Unit_Test_Case { } public function rename_album_test() { - // Create an album with a photo in it - $root = ORM::factory("item", 1); - $album = album::create($root, rand(), rand(), rand()); - $photo = self::_create_random_item($album); + $album = test::random_album(); + $photo = test::random_photo($album); + $album->reload(); file_put_contents($photo->thumb_path(), "thumb"); file_put_contents($photo->resize_path(), "resize"); @@ -107,7 +99,8 @@ class Item_Model_Test extends Unit_Test_Case { $new_album_name = rand(); // Now rename the album - $album->rename($new_album_name)->save(); + $album->name = $new_album_name; + $album->save(); $photo->reload(); // Expected: @@ -130,8 +123,7 @@ class Item_Model_Test extends Unit_Test_Case { } public function item_rename_wont_accept_slash_test() { - // Create a test photo - $item = self::_create_random_item(); + $item = test::random_photo(); $new_name = rand() . "/"; @@ -146,24 +138,23 @@ class Item_Model_Test extends Unit_Test_Case { public function item_rename_fails_with_existing_name_test() { // Create a test photo - $item = self::_create_random_item(); - $item2 = self::_create_random_item(); - - $new_name = $item2->name; + $item = test::random_photo(); + $item2 = test::random_photo(); try { - $item->rename($new_name)->save(); - } catch (Exception $e) { - // pass - $this->assert_true(strpos($e->getMessage(), "INVALID_RENAME_FILE_EXISTS") !== false, - "incorrect exception."); + $item->name = $item2->name; + $item->validate(); // @todo: switch this to save() once + // http://dev.kohanaphp.com/issues/2504 is fixed. + } catch (ORM_Validation_Exception $e) { + $this->assert_true(in_array("conflict", $e->validation->errors())); return; } - $this->assert_false(true, "Item_Model::rename should fail."); + + $this->assert_false(true, "rename should conflict"); } public function save_original_values_test() { - $item = self::_create_random_item(); + $item = test::random_photo_unsaved(); $item->title = "ORIGINAL_VALUE"; $item->save(); $item->title = "NEW_VALUE"; @@ -172,29 +163,17 @@ class Item_Model_Test extends Unit_Test_Case { $this->assert_same("NEW_VALUE", $item->title); } - public function urls_are_rawurlencoded_test() { - $item = self::_create_random_item(); - $item->slug = "foo bar"; - $item->name = "foo bar.jpg"; - $item->save(); - - $this->assert_equal("foo%20bar", $item->relative_url()); - $this->assert_equal("foo%20bar.jpg", $item->relative_path()); - } - public function move_album_test() { - // Create an album with a photo in it - $root = ORM::factory("item", 1); - $album2 = album::create($root, rand(), rand(), rand()); - $album = album::create($album2, rand(), rand(), rand()); - $photo = self::_create_random_item($album); + $album2 = test::random_album(); + $album = test::random_album($album2); + $photo = test::random_photo($album); file_put_contents($photo->thumb_path(), "thumb"); file_put_contents($photo->resize_path(), "resize"); file_put_contents($photo->file_path(), "file"); // Now move the album - $album->move_to($root); + $album->move_to(item::root()); $photo->reload(); // Expected: @@ -212,11 +191,9 @@ class Item_Model_Test extends Unit_Test_Case { } public function move_photo_test() { - // Create an album with a photo in it - $root = ORM::factory("item", 1); - $album2 = album::create($root, rand(), rand(), rand()); - $album = album::create($album2, rand(), rand(), rand()); - $photo = self::_create_random_item($album); + $album2 = test::random_album(); + $album = test::random_album($album2); + $photo = test::random_photo($album); file_put_contents($photo->thumb_path(), "thumb"); file_put_contents($photo->resize_path(), "resize"); @@ -241,42 +218,61 @@ class Item_Model_Test extends Unit_Test_Case { } public function move_album_fails_invalid_target_test() { - // Create an album with a photo in it - $root = ORM::factory("item", 1); - $name = rand(); - $album = album::create($root, $name, $name, $name); - $source = album::create($album, $name, $name, $name); + $album = test::random_album(); + $source = test::random_album($album); try { - $source->move_to($root); + $source->move_to(item::root()); } catch (Exception $e) { // pass $this->assert_true(strpos($e->getMessage(), "INVALID_MOVE_TARGET_EXISTS") !== false, "incorrect exception."); return; } - - $this->assert_false(true, "Item_Model::rename should not accept / characters"); } public function move_photo_fails_invalid_target_test() { - // Create an album with a photo in it - $root = ORM::factory("item", 1); - $photo_name = rand(); - $photo1 = self::_create_random_item($root, $photo_name); - $name = rand(); - $album = album::create($root, $name, $name, $name); - $photo2 = self::_create_random_item($album, $photo_name); + $photo1 = test::random_photo(); + $album = test::random_album(); + $photo2 = test::random_photo($album); try { - $photo2->move_to($root); + $photo2->move_to(item::root()); } catch (Exception $e) { // pass $this->assert_true(strpos($e->getMessage(), "INVALID_MOVE_TARGET_EXISTS") !== false, "incorrect exception."); return; } + } - $this->assert_false(true, "Item_Model::rename should not accept / characters"); + public function basic_validation_test() { + $item = ORM::factory("item"); + $item->album_cover_item_id = rand(); // invalid + $item->description = str_repeat("x", 70000); // invalid + $item->name = null; + $item->parent_id = rand(); + $item->slug = null; + $item->sort_column = "bogus"; + $item->sort_order = "bogus"; + $item->title = null; + $item->type = "bogus"; + try { + $item->save(); + } catch (ORM_Validation_Exception $e) { + $this->assert_same(array("description" => "length", + "name" => "required", + "slug" => "required", + "title" => "required", + "album_cover_item_id" => "invalid_item", + "parent_id" => "invalid", + "sort_column" => "invalid", + "sort_order" => "invalid", + "type" => "invalid"), + $e->validation->errors()); + return; + } + + $this->assert_false(true, "Shouldn't get here"); } } diff --git a/modules/gallery/tests/Kohana_Bug_Test.php b/modules/gallery/tests/Kohana_Bug_Test.php new file mode 100644 index 00000000..61f5d69e --- /dev/null +++ b/modules/gallery/tests/Kohana_Bug_Test.php @@ -0,0 +1,38 @@ +<?php defined("SYSPATH") or die("No direct script access."); +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2009 Bharat Mediratta + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. + */ +class Kohana_Bug_Test extends Unit_Test_Case { + function double_save_test_should_fail_test() { + // http://dev.kohanaframework.org/issues/2504 + $group = ORM::factory("group"); + $group->name = rand(); + $group->save(); // this save works + + try { + $group->name = null; // now I change to an illegal value + $group->save(); // this passes, but it shouldn't. My model is broken! + + // This is the normal state when the bug is not fixed. + } catch (ORM_Validation_Exception $e) { + // When this triggers, the bug is fixed. Find any references to ticket #2504 in the code + // and update those accordingly + $this->assert_true(false, "Bug #2504 has been fixed"); + } + } +} diff --git a/modules/gallery_unit_test/controllers/gallery_unit_test.php b/modules/gallery_unit_test/controllers/gallery_unit_test.php index 498dd756..e05fcbaa 100644 --- a/modules/gallery_unit_test/controllers/gallery_unit_test.php +++ b/modules/gallery_unit_test/controllers/gallery_unit_test.php @@ -127,6 +127,12 @@ class Gallery_Unit_Test_Controller extends Controller { $filter = count($_SERVER["argv"]) > 2 ? $_SERVER["argv"][2] : null; print new Unit_Test($modules, $filter); + } catch (ORM_Validation_Exception $e) { + print "Validation Exception: {$e->getMessage()}\n"; + print $e->getTraceAsString() . "\n"; + foreach ($e->validation->errors() as $field => $msg) { + print "$field: $msg\n"; + } } catch (Exception $e) { print "Exception: {$e->getMessage()}\n"; print $e->getTraceAsString() . "\n"; diff --git a/modules/gallery_unit_test/helpers/test.php b/modules/gallery_unit_test/helpers/test.php new file mode 100644 index 00000000..5368539f --- /dev/null +++ b/modules/gallery_unit_test/helpers/test.php @@ -0,0 +1,50 @@ +<?php defined("SYSPATH") or die("No direct script access."); +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2009 Bharat Mediratta + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. + */ +class test_Core { + static function random_album_unsaved($parent=null) { + $rand = rand(); + + $album = ORM::factory("item"); + $album->type = "album"; + $album->parent_id = $parent ? $parent->id : 1; + $album->name = "name_$rand"; + $album->title = "title_$rand"; + return $album; + } + + static function random_album($parent=null) { + return test::random_album_unsaved($parent)->save(); + } + + static function random_photo_unsaved($parent=null) { + $rand = rand(); + $photo = ORM::factory("item"); + $photo->type = "photo"; + $photo->parent_id = $parent ? $parent->id : 1; + $photo->set_data_file(MODPATH . "gallery/tests/test.jpg"); + $photo->name = "name_$rand.jpg"; + $photo->title = "title_$rand"; + return $photo; + } + + static function random_photo($parent=null) { + return test::random_photo_unsaved($parent)->save(); + } +} diff --git a/modules/gallery_unit_test/views/kohana_unit_test_cli.php b/modules/gallery_unit_test/views/kohana_unit_test_cli.php index 3203ee44..a0de0f52 100644 --- a/modules/gallery_unit_test/views/kohana_unit_test_cli.php +++ b/modules/gallery_unit_test/views/kohana_unit_test_cli.php @@ -57,6 +57,12 @@ foreach ($results as $class => $methods) { if ($result->getMessage()) { echo " ", $result->getMessage(), "\n"; } + if ($result instanceof ORM_Validation_Exception) { + echo " Validation errors:\n"; + foreach ($result->validation->errors() as $key => $value) { + echo " $key: $value\n"; + } + } echo " ", $result->getFile(), " (Line ", $result->getLine(), ")\n"; echo "\n"; echo $result->getTraceAsString(), "\n"; diff --git a/modules/image_block/helpers/image_block_block.php b/modules/image_block/helpers/image_block_block.php index f591e8d1..f28e775f 100644 --- a/modules/image_block/helpers/image_block_block.php +++ b/modules/image_block/helpers/image_block_block.php @@ -30,29 +30,9 @@ class image_block_block_Core { $block->css_id = "g-image-block"; $block->title = t("Random image"); $block->content = new View("image_block_block.html"); + $block->content->items = item::random_query(array(array("type", "!=", "album")))->find_all(1); - $random = ((float)mt_rand()) / (float)mt_getrandmax(); - - $items = ORM::factory("item") - ->viewable() - ->where("type", "!=", "album") - ->where("rand_key", "<", $random) - ->order_by(array("rand_key" => "DESC")) - ->find_all(1); - - if ($items->count() == 0) { - // Try once more. If this fails, just ditch the block altogether - $items = ORM::factory("item") - ->viewable() - ->where("type", "!=", "album") - ->where("rand_key", ">=", $random) - ->order_by(array("rand_key" => "DESC")) - ->find_all(1); - } - - if ($items->count() > 0) { - $block->content->item = $items->current(); - } else { + if ($block->content->items->count() == 0) { $block = ""; } break; diff --git a/modules/image_block/helpers/image_block_rest.php b/modules/image_block/helpers/image_block_rest.php deleted file mode 100644 index 7afd974c..00000000 --- a/modules/image_block/helpers/image_block_rest.php +++ /dev/null @@ -1,62 +0,0 @@ -<?php defined("SYSPATH") or die("No direct script access."); -/** - * Gallery - a web based photo album viewer and editor - * Copyright (C) 2000-2009 Bharat Mediratta - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or (at - * your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. - */ -class image_block_rest_Core { - static function get($request) { - $type = empty($request->type) ? "random" : $request->type; - switch ($type) { - case "random": - $random = ((float)mt_rand()) / (float)mt_getrandmax(); - - $items = ORM::factory("item") - ->viewable() - ->where("type", "!=", "album") - ->where("rand_key", "<", $random) - ->order_by(array("rand_key" => "DESC")) - ->find_all(1); - - if ($items->count() == 0) { - // Try once more. If this fails, just ditch the block altogether - $items = ORM::factory("item") - ->viewable() - ->where("type", "!=", "album") - ->where("rand_key", ">= ", $random) - ->order_by(array("rand_key" => "DESC")) - ->find_all(1); - } - break; - default: - return rest::fail("Unsupported image block type: '{$type}'"); - } - - if ($items->count() > 0) { - $item = $items->current(); - $response_data = array("name" => $item->name, - "path" => $item->relative_url(), - "title" => $item->title, - "thumb_url" => $item->thumb_url(true), - "thumb_size" => array("height" => $item->thumb_height, - "width" => $item->thumb_width)); - - return rest::success(array("resource" => $response_data)); - } else { - return rest::fail("No Image found"); - } - } -} diff --git a/modules/kohana23_compat/libraries/MY_Database_Builder.php b/modules/kohana23_compat/libraries/MY_Database_Builder.php index c82b6ac4..b2a5c92a 100644 --- a/modules/kohana23_compat/libraries/MY_Database_Builder.php +++ b/modules/kohana23_compat/libraries/MY_Database_Builder.php @@ -23,8 +23,10 @@ class Database_Builder extends Database_Builder_Core { * @chainable */ public function merge_where($tuples) { - foreach ($tuples as $tuple) { - $this->where($tuple[0], $tuple[1], $tuple[2]); + if ($tuples) { + foreach ($tuples as $tuple) { + $this->where($tuple[0], $tuple[1], $tuple[2]); + } } return $this; } @@ -34,8 +36,10 @@ class Database_Builder extends Database_Builder_Core { * @chainable */ public function merge_or_where($tuples) { - foreach ($tuples as $tuple) { - $this->or_where($tuple[0], $tuple[1], $tuple[2]); + if ($tuples) { + foreach ($tuples as $tuple) { + $this->or_where($tuple[0], $tuple[1], $tuple[2]); + } } return $this; } diff --git a/modules/rest/controllers/rest.php b/modules/rest/controllers/rest.php index 26e5b31a..cac49740 100644 --- a/modules/rest/controllers/rest.php +++ b/modules/rest/controllers/rest.php @@ -18,20 +18,14 @@ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. */ class Rest_Controller extends Controller { - public function access_key() { + public function index() { try { - $request = (object)Input::instance()->get(); - if (empty($request->user) || empty($request->password)) { - throw new Rest_Exception(403, "Forbidden"); - } - - $user = identity::lookup_user_by_name($request->user); - if (empty($user)) { - throw new Rest_Exception(403, "Forbidden"); - } + $username = Input::instance()->post("user"); + $password = Input::instance()->post("password"); - if (!identity::is_correct_password($user, $request->password)) { - throw new Rest_Exception(403, "Forbidden"); + $user = identity::lookup_user_by_name($username); + if (empty($user) || !identity::is_correct_password($user, $password)) { + throw new Rest_Exception("Forbidden", 403); } $key = ORM::factory("user_access_token") @@ -42,27 +36,52 @@ class Rest_Controller extends Controller { $key->access_key = md5($user->name . rand()); $key->save(); } - print rest::success(array("token" => $key->access_key)); - } catch (Rest_Exception $e) { - $e->sendHeaders(); + + rest::reply($key->access_key); + } catch (Exception $e) { + rest::send_headers($e); } } public function __call($function, $args) { - $request = rest::normalize_request($args); + $input = Input::instance(); + switch ($method = strtolower($input->server("REQUEST_METHOD"))) { + case "get": + $request->params = (object) Input::instance()->get(); + break; + + case "post": + $request->params = (object) Input::instance()->post(); + if (isset($_FILES["file"])) { + $request->file = upload::save("file"); + } + break; + } + + $request->method = strtolower($input->server("HTTP_X_GALLERY_REQUEST_METHOD", $method)); + $request->access_token = $input->server("HTTP_X_GALLERY_REQUEST_KEY"); + $request->url = url::abs_current(true); + try { - if (rest::set_active_user($request->access_token)) { - $handler_class = "{$function}_rest"; - $handler_method = $request->method; + rest::set_active_user($request->access_token); - if (!method_exists($handler_class, $handler_method)) { - throw new Rest_Exception(403, "Forbidden"); - } + $handler_class = "{$function}_rest"; + $handler_method = $request->method; + if (!method_exists($handler_class, $handler_method)) { + throw new Rest_Exception("Forbidden", 403); + } + + try { print call_user_func(array($handler_class, $handler_method), $request); + } catch (ORM_Validation_Exception $e) { + foreach ($e->validation->errors() as $key => $value) { + $msgs[] = "$key: $value"; + } + throw new Rest_Exception("Bad Request: " . join(", ", $msgs), 400); } } catch (Rest_Exception $e) { - $e->sendHeaders(); + rest::send_headers($e); } } }
\ No newline at end of file diff --git a/modules/rest/helpers/rest.php b/modules/rest/helpers/rest.php index be0644f2..121191f2 100644 --- a/modules/rest/helpers/rest.php +++ b/modules/rest/helpers/rest.php @@ -18,87 +18,57 @@ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. */ class rest_Core { - /** - * Request failed - */ - static function fail($log_message=null) { - if (!empty($log_message)) { - Kohana_Log::add("info", $log_message); - } - // We don't need to save the session for this request + static function reply($data=array()) { Session::abort_save(); - return json_encode(array("status" => "ERROR", "message" => (string)$message)); - } - /** - * Success - */ - static function success($response_data=array(), $message=null) { - $response = array("status" => "OK"); - if (!empty($message)) { - $response["message"] = (string)$message; + if ($data) { + print json_encode($data); } - $response = array_merge($response, $response_data); - - // We don't need to save the session for this request - Session::abort_save(); - return json_encode($response); } - /** - * Validation Error - */ - static function validation_error($error_data) { - $response = array("status" => "VALIDATE_ERROR"); - $response = array_merge($response, array("fields" => $error_data)); - - // We don't need to save the session for this request - Session::abort_save(); - return json_encode($response); - } + static function set_active_user($access_token) { + if (empty($access_token)) { + identity::set_active_user(identity::guest()); + return; + } + $key = ORM::factory("user_access_token") + ->where("access_key", "=", $access_token) + ->find(); - static function normalize_request($args=array()) { - $input = Input::instance(); - $method = strtolower($input->server("REQUEST_METHOD")); - $request = new stdClass(); - foreach (array_keys($input->get()) as $key) { - $request->$key = $input->get($key); + if (!$key->loaded()) { + throw new Rest_Exception("Forbidden", 403); } - if ($method != "get") { - foreach (array_keys($input->post()) as $key) { - $request->$key = $input->post($key); - } - foreach (array_keys($_FILES) as $key) { - $request->$key = $_FILES[$key]; - } + + $user = identity::lookup_user($key->user_id); + if (empty($user)) { + throw new Rest_Exception("Forbidden", 403); } - $request->method = strtolower($input->server("HTTP_X_GALLERY_REQUEST_METHOD", $method)); - $request->access_token = $input->server("HTTP_X_GALLERY_REQUEST_KEY"); - $request->arguments = $args; // Let the rest handler figure out what the arguments mean + identity::set_active_user($user); + } - return $request; + static function send_headers($exception) { + header("HTTP/1.1 " . $exception->getCode() . " " . $exception->getMessage()); } - static function set_active_user($access_token) { - if (empty($access_token)) { - $user = identity::guest(); - } else { - $key = ORM::factory("user_access_token") - ->where("access_key", "=", $access_token) - ->find(); + /** + * Convert a REST url into an object. + * Eg: "http://example.com/gallery3/index.php/rest/gallery/Family/Wedding" -> Item_Model + * + * @param string the fully qualified REST url + * @return mixed the corresponding object (usually a model of some kind) + */ + static function resolve($url) { + $relative_url = substr($url, strlen(url::abs_site("rest"))); + $path = parse_url($relative_url, PHP_URL_PATH); + $components = explode("/", $path, 3); - if ($key->loaded()) { - $user = identity::lookup_user($key->user_id); - if (empty($user)) { - throw new Rest_Exception(403, "Forbidden"); - } - } else { - throw new Rest_Exception(403, "Forbidden"); - } + $class = "$components[1]_rest"; + if (!method_exists($class, "resolve")) { + throw new Kohana_404_Exception($url); } - identity::set_active_user($user); - return true; + + return call_user_func(array($class, "resolve"), !empty($components[2]) ? $components[2] : null); } } diff --git a/modules/rest/libraries/Rest_Exception.php b/modules/rest/libraries/Rest_Exception.php index 905b94a0..596b3712 100644 --- a/modules/rest/libraries/Rest_Exception.php +++ b/modules/rest/libraries/Rest_Exception.php @@ -18,19 +18,4 @@ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. */ class Rest_Exception_Core extends Exception { - /** - * Set internal properties. - */ - public function __construct($code, $text) { - parent::__construct("$code $text"); - } - - /** - * Sends the headers, to emulate server behavior. - * - * @return void - */ - public function sendHeaders() { - header('HTTP/1.1 {$this->getMessage()}'); - } -} // End Rest Exception
\ No newline at end of file +}
\ No newline at end of file diff --git a/modules/rest/tests/Rest_Controller_Test.php b/modules/rest/tests/Rest_Controller_Test.php index 83bd9db6..c881583c 100644 --- a/modules/rest/tests/Rest_Controller_Test.php +++ b/modules/rest/tests/Rest_Controller_Test.php @@ -84,7 +84,8 @@ class Rest_Controller_Test extends Unit_Test_Case { try { $this->_call_controller(); } catch (Rest_Exception $e) { - $this->assert_equal("403 Forbidden", $e->getMessage()); + $this->assert_equal(403, $e->getCode()); + $this->assert_equal("Forbidden", $e->getMessage()); } catch (Exception $e) { $this->assert_false(true, $e->__toString()); } @@ -97,7 +98,8 @@ class Rest_Controller_Test extends Unit_Test_Case { try { $this->_call_controller(); } catch (Rest_Exception $e) { - $this->assert_equal("403 Forbidden", $e->getMessage()); + $this->assert_equal(403, $e->getCode()); + $this->assert_equal("Forbidden", $e->getMessage()); } catch (Exception $e) { $this->assert_false(true, $e->__toString()); } @@ -109,7 +111,8 @@ class Rest_Controller_Test extends Unit_Test_Case { try { $this->_call_controller(); } catch (Rest_Exception $e) { - $this->assert_equal("403 Forbidden", $e->getMessage()); + $this->assert_equal(403, $e->getCode()); + $this->assert_equal("Forbidden", $e->getMessage()); } catch (Exception $e) { $this->assert_false(true, $e->__toString()); } @@ -137,7 +140,8 @@ class Rest_Controller_Test extends Unit_Test_Case { try { $this->_call_controller(); } catch (Rest_Exception $e) { - $this->assert_equal("403 Forbidden", $e->getMessage()); + $this->assert_equal(403, $e->getCode()); + $this->assert_equal("Forbidden", $e->getMessage()); } catch (Exception $e) { $this->assert_false(true, $e->__toString()); } @@ -155,7 +159,8 @@ class Rest_Controller_Test extends Unit_Test_Case { try { $this->_call_controller("rest", explode("/", $photo->relative_url())); } catch (Rest_Exception $e) { - $this->assert_equal("403 Forbidden", $e->getMessage()); + $this->assert_equal(403, $e->getCode()); + $this->assert_equal("Forbidden", $e->getMessage()); } catch (Exception $e) { $this->assert_false(true, $e->__toString()); } @@ -171,7 +176,8 @@ class Rest_Controller_Test extends Unit_Test_Case { try { $this->_call_controller("rest", explode("/", $photo->relative_url())); } catch (Rest_Exception $e) { - $this->assert_equal("501 Not Implemented", $e->getMessage()); + $this->assert_equal(501, $e->getCode()); + $this->assert_equal("Not Implemented", $e->getMessage()); } catch (Exception $e) { $this->assert_false(true, $e->__toString()); } @@ -218,7 +224,7 @@ class rest_rest { $response["thumb_url"] = $item->thumb_url(); $response["description"] = $item->description; $response["internet_address"] = $item->slug; - return rest::success(array($item->type => $response), t("Processed")); + return rest::reply(array($item->type => $response)); } } diff --git a/modules/tag/controllers/admin_tags.php b/modules/tag/controllers/admin_tags.php index e20b8ac8..3b605a4e 100644 --- a/modules/tag/controllers/admin_tags.php +++ b/modules/tag/controllers/admin_tags.php @@ -60,9 +60,7 @@ class Admin_Tags_Controller extends Admin_Controller { array("result" => "success", "location" => url::site("admin/tags"))); } else { - print json_encode( - array("result" => "error", - "form" => $form->__toString())); + print json_encode(array("result" => "error", "form" => (string) $form)); } } diff --git a/modules/tag/controllers/tags.php b/modules/tag/controllers/tags.php index 992c7411..e28b7a83 100644 --- a/modules/tag/controllers/tags.php +++ b/modules/tag/controllers/tags.php @@ -71,9 +71,7 @@ class Tags_Controller extends Controller { array("result" => "success", "cloud" => tag::cloud(30)->__toString())); } else { - print json_encode( - array("result" => "error", - "form" => $form->__toString())); + print json_encode(array("result" => "error", "form" => (string) $form)); } } diff --git a/modules/tag/helpers/tag.php b/modules/tag/helpers/tag.php index 8075afe4..d895e08f 100644 --- a/modules/tag/helpers/tag.php +++ b/modules/tag/helpers/tag.php @@ -41,7 +41,7 @@ class tag_Core { } if (!$tag->has($item)) { - if (!$tag->add($item, $tag)) { + if (!$tag->add($item)) { throw new Exception("@todo {$tag->name} WAS_NOT_ADDED_TO {$item->id}"); } $tag->count++; diff --git a/modules/tag/helpers/tag_rest.php b/modules/tag/helpers/tag_rest.php index cd1ca6c6..0aac5291 100644 --- a/modules/tag/helpers/tag_rest.php +++ b/modules/tag/helpers/tag_rest.php @@ -18,143 +18,76 @@ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. */ class tag_rest_Core { - // If no arguments just return all the tags. If 2 or more then it is a path then - // return the tags for that item. But if its only 1, then is it a path or a tag? - // Assume a tag first, if nothing is found then try finding the item. static function get($request) { - $resources = array(); - switch (count($request->arguments)) { - case 0: - $tags = ORM::factory("tag") - ->select("name", "count") - ->order_by("count", "DESC"); - if (!empty($request->limit)) { - $tags->limit($request->limit); - } - if (!empty($request->offset)) { - $tags->offset($request->offset); - } - $resources = array("tags" => array()); - foreach ($tags->find_all() as $row) { - $resources["tags"][] = array("name" => $row->name, "count" => $row->count); - } - break; - case 1: - $resources = tag_rest::_get_items($request); - if (!empty($resources)) { - $resources = array("resources" => $resources); - break; - } - default: - $item = ORM::factory("item") - ->where("relative_url_cache", "=", implode("/", $request->arguments)) - ->viewable() - ->find(); - if ($item->loaded()) { - $resources = array("tags" => tag::item_tags($item)); - } + $tag = rest::resolve($request->url); + $items = array(); + foreach ($tag->items() as $item) { + $items[] = url::abs_site("rest/gallery/" . $item->relative_url()); } - return rest::success($resources); + return rest::reply(array("resource" => $tag->as_array(), "members" => $items)); } static function post($request) { - if (empty($request->arguments) || count($request->arguments) != 1 || empty($request->path)) { - throw new Rest_Exception(400, "Bad request"); - } - $path = $request->path; - $tags = explode(",", $request->arguments[0]); - - $item = ORM::factory("item") - ->where("relative_url_cache", "=", $path) - ->viewable() - ->find(); - if (!$item->loaded()) { - throw new Kohana_404_Exception(); + if (empty($request->params->url)) { + throw new Rest_Exception("Bad request", 400); } - if (!access::can("edit", $item)) { - throw new Kohana_404_Exception(); - } + $tag = rest::resolve($request->url); + $item = rest::resolve($request->params->url); + access::required("edit", $item); - foreach ($tags as $tag) { - tag::add($item, $tag); - } - return rest::success(); + tag::add($item, $tag->name); + return rest::reply(array("url" => url::abs_site("rest/tag/" . rawurlencode($tag->name)))); } static function put($request) { - if (empty($request->arguments[0]) || empty($request->new_name)) { - throw new Rest_Exception(400, "Bad request"); - } + $tag = rest::resolve($request->url); - $name = $request->arguments[0]; + if (isset($request->params->remove)) { + if (!is_array($request->params->remove)) { + throw new Exception("Bad request", 400); + } - $tag = ORM::factory("tag") - ->where("name", "=", $name) - ->find(); - if (!$tag->loaded()) { - throw new Kohana_404_Exception(); + foreach ($request->params->remove as $item_url) { + $item = rest::resolve($item_url); + access::required("edit", $item); + $tag->remove($item); + } } - $tag->name = $request->new_name; - $tag->save(); + if (isset($request->params->name)) { + $tag->name = $request->params->name; + } - return rest::success(); + $tag->save(); + return rest::reply(array("url" => url::abs_site("rest/tag/" . rawurlencode($tag->name)))); } static function delete($request) { - if (empty($request->arguments[0])) { - throw new Rest_Exception(400, "Bad request"); - } - $tags = explode(",", $request->arguments[0]); - if (!empty($request->path)) { - $tag_list = ORM::factory("tag") - ->join("items_tags", "tags.id", "items_tags.tag_id") - ->join("items", "items.id", "items_tags.item_id") - ->where("tags.name", "IN", $tags) - ->where("relative_url_cache", "=", $request->path) - ->viewable() - ->find_all(); - } else { - $tag_list = ORM::factory("tag") - ->where("name", "IN", $tags) - ->find_all(); - } + $tag = rest::resolve($request->url); - foreach ($tag_list as $row) { - $row->delete(); - }; + if (empty($request->params->url)) { + // Delete the tag + $tag->delete(); + return rest::reply(); + } else { + // Remove an item from the tag + $item = rest::resolve($request->params->url); + $tag->remove($item); + $tag->save(); - tag::compact(); - return rest::success(); + tag::compact(); + return rest::reply(array("url" => url::abs_site("rest/tag/" . rawurlencode($tag->name)))); + } } - private static function _get_items($request) { - $tags = explode(",", $request->arguments[0]); - $items = ORM::factory("item") - ->select_distinct("*") - ->join("items_tags", "items.id", "items_tags.item_id") - ->join("tags", "tags.id", "items_tags.tag_id") - ->where("tags.name", "IN", $tags); - if (!empty($request->limit)) { - $items->limit($request->limit); - } - if (!empty($request->offset)) { - $items->offset($request->offset); - } - $resources = array(); - foreach ($items->find_all() as $item) { - $resources[] = array("type" => $item->type, - "has_children" => $item->children_count() > 0, - "path" => $item->relative_url(), - "thumb_url" => $item->thumb_url(true), - "thumb_dimensions" => array("width" => $item->thumb_width, - "height" => $item->thumb_height), - "has_thumb" => $item->has_thumb(), - "title" => $item->title); + static function resolve($tag_name) { + $tag = ORM::factory("tag")->where("name", "=", $tag_name)->find(); + if (!$tag->loaded()) { + throw new Kohana_404_Exception(); } - return $resources; + return $tag; } } diff --git a/modules/tag/helpers/tags_rest.php b/modules/tag/helpers/tags_rest.php new file mode 100644 index 00000000..7f0ed66a --- /dev/null +++ b/modules/tag/helpers/tags_rest.php @@ -0,0 +1,48 @@ +<?php defined("SYSPATH") or die("No direct script access."); +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2009 Bharat Mediratta + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. + */ +class tags_rest_Core { + static function get($request) { + $tags = array(); + foreach (ORM::factory("tag")->find_all() as $tag) { + $tags[$tag->name] = url::abs_site("rest/tags/" . rawurlencode($tag->name)); + } + return rest::reply(array("members" => $tags)); + } + + static function post($request) { + // @todo: what permission should be required to create a tag here? + // for now, require edit at the top level. Perhaps later, just require any edit perms, + // anywhere in the gallery? + access::required("edit", item::root()); + + if (empty($request->params->name)) { + throw new Rest_Exception("Bad Request: missing name", 400); + } + + $tag = ORM::factory("tag")->where("name", "=", $request->params->name)->find(); + if (!$tag->loaded()) { + $tag->name = $request->params->name; + $tag->count = 0; + $tag->save(); + } + + return rest::reply(array("url" => url::abs_site("rest/tag/" . rawurlencode($tag->name)))); + } +} diff --git a/modules/tag/tests/Tag_Rest_Helper_Test.php b/modules/tag/tests/Tag_Rest_Helper_Test.php index 514538d4..555539fd 100644 --- a/modules/tag/tests/Tag_Rest_Helper_Test.php +++ b/modules/tag/tests/Tag_Rest_Helper_Test.php @@ -133,7 +133,8 @@ class Tag_Rest_Helper_Test extends Unit_Test_Case { try { tag_rest::post($request); } catch (Rest_Exception $e) { - $this->assert_equal("400 Bad request", $e->getMessage()); + $this->assert_equal(400, $e->getCode()); + $this->assert_equal("Bad request", $e->getMessage()); } catch (Exception $e) { $this->assert_false(true, $e->__toString()); } @@ -191,7 +192,8 @@ class Tag_Rest_Helper_Test extends Unit_Test_Case { try { tag_rest::put($request); } catch (Rest_Exception $e) { - $this->assert_equal("400 Bad request", $e->getMessage()); + $this->assert_equal(400, $e->getCode()); + $this->assert_equal("Bad request", $e->getMessage()); } catch (Exception $e) { $this->assert_false(true, $e->__toString()); } @@ -202,7 +204,8 @@ class Tag_Rest_Helper_Test extends Unit_Test_Case { try { tag_rest::put($request); } catch (Rest_Exception $e) { - $this->assert_equal("400 Bad request", $e->getMessage()); + $this->assert_equal(400, $e->getCode()); + $this->assert_equal("Bad request", $e->getMessage()); } catch (Exception $e) { $this->assert_false(true, $e->__toString()); } @@ -211,7 +214,8 @@ class Tag_Rest_Helper_Test extends Unit_Test_Case { try { tag_rest::put($request); } catch (Rest_Exception $e) { - $this->assert_equal("400 Bad request", $e->getMessage()); + $this->assert_equal(400, $e->getCode()); + $this->assert_equal("Bad request", $e->getMessage()); } catch (Exception $e) { $this->assert_false(true, $e->__toString()); } diff --git a/modules/user/controllers/admin_users.php b/modules/user/controllers/admin_users.php index 96b86fff..bc68d154 100644 --- a/modules/user/controllers/admin_users.php +++ b/modules/user/controllers/admin_users.php @@ -21,12 +21,8 @@ class Admin_Users_Controller extends Admin_Controller { public function index() { $view = new Admin_View("admin.html"); $view->content = new View("admin_users.html"); - $view->content->users = ORM::factory("user") - ->order_by("name", "ASC") - ->find_all(); - $view->content->groups = ORM::factory("group") - ->order_by("name", "ASC") - ->find_all(); + $view->content->users = ORM::factory("user")->order_by("name", "ASC")->find_all(); + $view->content->groups = ORM::factory("group")->order_by("name", "ASC")->find_all(); print $view; } @@ -34,31 +30,32 @@ class Admin_Users_Controller extends Admin_Controller { access::verify_csrf(); $form = $this->_get_user_add_form_admin(); - $valid = $form->validate(); - $name = $form->add_user->inputs["name"]->value; - if ($user = user::lookup_by_name($name)) { - $form->add_user->inputs["name"]->add_error("in_use", 1); + try { + $user = ORM::factory("user"); + $valid = $form->validate(); + $user->name = $form->add_user->inputs["name"]->value; + $user->full_name = $form->add_user->full_name->value; + $user->password = $form->add_user->password->value; + $user->email = $form->add_user->email->value; + $user->url = $form->edit_user->url->value; + $user->locale = $form->add_user->locale->value; + $user->admin = $form->edit_user->admin->checked; + $user->validate(); + } catch (ORM_Validation_Exception $e) { + // Translate ORM validation errors into form error messages + foreach ($e->validation->errors() as $key => $error) { + $form->add_user->inputs[$key]->add_error($error, 1); + } $valid = false; } if ($valid) { - $user = user::create( - $name, $form->add_user->full_name->value, $form->add_user->password->value); - $user->email = $form->add_user->email->value; - $user->admin = $form->add_user->admin->checked; - - if ($form->add_user->locale) { - $desired_locale = $form->add_user->locale->value; - $user->locale = $desired_locale == "none" ? null : $desired_locale; - } $user->save(); module::event("user_add_form_admin_completed", $user, $form); - message::success(t("Created user %user_name", array("user_name" => $user->name))); print json_encode(array("result" => "success")); } else { - print json_encode(array("result" => "error", - "form" => $form->__toString())); + print json_encode(array("result" => "error", "form" => (string) $form)); } } @@ -85,8 +82,7 @@ class Admin_Users_Controller extends Admin_Controller { $name = $user->name; $user->delete(); } else { - print json_encode(array("result" => "error", - "form" => $form->__toString())); + print json_encode(array("result" => "error", "form" => (string) $form)); } $message = t("Deleted user %user_name", array("user_name" => $name)); @@ -112,43 +108,34 @@ class Admin_Users_Controller extends Admin_Controller { } $form = $this->_get_user_edit_form_admin($user); - $valid = $form->validate(); - if ($valid) { - $new_name = $form->edit_user->inputs["name"]->value; - $temp_user = user::lookup_by_name($new_name); - if ($new_name != $user->name && - ($temp_user && $temp_user->id != $user->id)) { - $form->edit_user->inputs["name"]->add_error("in_use", 1); - $valid = false; - } else { - $user->name = $new_name; - } - } - - if ($valid) { + try { + $valid = $form->validate(); + $user->name = $form->edit_user->inputs["name"]->value; $user->full_name = $form->edit_user->full_name->value; - if ($form->edit_user->password->value) { - $user->password = $form->edit_user->password->value; - } + $user->password = $form->edit_user->password->value; $user->email = $form->edit_user->email->value; $user->url = $form->edit_user->url->value; - if ($form->edit_user->locale) { - $desired_locale = $form->edit_user->locale->value; - $user->locale = $desired_locale == "none" ? null : $desired_locale; - } - - // An admin can change the admin status for any user but themselves + $user->locale = $form->edit_user->locale->value; if ($user->id != identity::active_user()->id) { $user->admin = $form->edit_user->admin->checked; } + + $user->validate(); + } catch (ORM_Validation_Exception $e) { + // Translate ORM validation errors into form error messages + foreach ($e->validation->errors() as $key => $error) { + $form->edit_user->inputs[$key]->add_error($error, 1); + } + $valid = false; + } + + if ($valid) { $user->save(); module::event("user_edit_form_admin_completed", $user, $form); - message::success(t("Changed user %user_name", array("user_name" => $user->name))); print json_encode(array("result" => "success")); } else { - print json_encode(array("result" => "error", - "form" => $form->__toString())); + print json_encode(array("result" => "error", "form" => (string) $form)); } } @@ -193,25 +180,26 @@ class Admin_Users_Controller extends Admin_Controller { access::verify_csrf(); $form = $this->_get_group_add_form_admin(); - $valid = $form->validate(); - if ($valid) { - $new_name = $form->add_group->inputs["name"]->value; - $group = group::lookup_by_name($new_name); - if (!empty($group)) { - $form->add_group->inputs["name"]->add_error("in_use", 1); - $valid = false; + try { + $valid = $form->validate(); + $group = ORM::factory("group"); + $group->name = $form->add_group->inputs["name"]->value; + $group->validate(); + } catch (ORM_Validation_Exception $e) { + // Translate ORM validation errors into form error messages + foreach ($e->validation->errors() as $key => $error) { + $form->add_group->inputs[$key]->add_error($error, 1); } + $valid = false; } if ($valid) { - $group = group::create($new_name); $group->save(); message::success( t("Created group %group_name", array("group_name" => $group->name))); print json_encode(array("result" => "success")); } else { - print json_encode(array("result" => "error", - "form" => $form->__toString())); + print json_encode(array("result" => "error", "form" => (string) $form)); } } @@ -232,8 +220,7 @@ class Admin_Users_Controller extends Admin_Controller { $name = $group->name; $group->delete(); } else { - print json_encode(array("result" => "error", - "form" => $form->__toString())); + print json_encode(array("result" => "error", "form" => (string) $form)); } $message = t("Deleted group %group_name", array("group_name" => $name)); @@ -260,19 +247,19 @@ class Admin_Users_Controller extends Admin_Controller { } $form = $this->_get_group_edit_form_admin($group); - $valid = $form->validate(); - - if ($valid) { - $new_name = $form->edit_group->inputs["name"]->value; - $group = group::lookup_by_name($name); - if ($group->loaded()) { - $form->edit_group->inputs["name"]->add_error("in_use", 1); - $valid = false; + try { + $valid = $form->validate(); + $group->name = $form->edit_group->inputs["name"]->value; + $group->validate(); + } catch (ORM_Validation_Exception $e) { + // Translate ORM validation errors into form error messages + foreach ($e->validation->errors() as $key => $error) { + $form->edit_group->inputs[$key]->add_error($error, 1); } + $valid = false; } if ($valid) { - $group->name = $form->edit_group->inputs["name"]->value; $group->save(); message::success( t("Changed group %group_name", array("group_name" => $group->name))); @@ -280,8 +267,7 @@ class Admin_Users_Controller extends Admin_Controller { } else { message::error( t("Failed to change group %group_name", array("group_name" => $group->name))); - print json_encode(array("result" => "error", - "form" => $form->__toString())); + print json_encode(array("result" => "error", "form" => (string) $form)); } } @@ -301,7 +287,7 @@ class Admin_Users_Controller extends Admin_Controller { $group = $form->group("edit_user")->label(t("Edit user")); $group->input("name")->label(t("Username"))->id("g-username")->value($user->name); $group->inputs["name"]->error_messages( - "in_use", t("There is already a user with that username")); + "conflict", t("There is already a user with that username")); $group->input("full_name")->label(t("Full name"))->id("g-fullname")->value($user->full_name); self::_add_locale_dropdown($group, $user); $group->password("password")->label(t("Password"))->id("g-password"); @@ -310,10 +296,6 @@ class Admin_Users_Controller extends Admin_Controller { $group->input("email")->label(t("Email"))->id("g-email")->value($user->email); $group->input("url")->label(t("URL"))->id("g-url")->value($user->url); $group->checkbox("admin")->label(t("Admin"))->id("g-admin")->checked($user->admin); - $form->add_rules_from($user); - $minimum_length = module::get_var("user", "mininum_password_length", 5); - $form->edit_user->password - ->rules($minimum_length ? "length[$minimum_length, 40]" : "length[40]"); module::event("user_edit_form_admin", $user, $form); $group->submit("")->value(t("Modify User")); @@ -324,7 +306,7 @@ class Admin_Users_Controller extends Admin_Controller { $form = new Forge("admin/users/add_user", "", "post", array("id" => "g-add-user-form")); $group = $form->group("add_user")->label(t("Add user")); $group->input("name")->label(t("Username"))->id("g-username") - ->error_messages("in_use", t("There is already a user with that username")); + ->error_messages("conflict", t("There is already a user with that username")); $group->input("full_name")->label(t("Full name"))->id("g-fullname"); $group->password("password")->label(t("Password"))->id("g-password"); $group->password("password2")->label(t("Confirm password"))->id("g-password2") @@ -333,11 +315,6 @@ class Admin_Users_Controller extends Admin_Controller { $group->input("url")->label(t("URL"))->id("g-url"); self::_add_locale_dropdown($group); $group->checkbox("admin")->label(t("Admin"))->id("g-admin"); - $form->add_rules_from(ORM::factory("user")); - - $minimum_length = module::get_var("user", "mininum_password_length", 5); - $form->add_user->password - ->rules($minimum_length ? "required|length[$minimum_length, 40]" : "length[40]"); module::event("user_add_form_admin", $user, $form); $group->submit("")->value(t("Add user")); @@ -349,15 +326,14 @@ class Admin_Users_Controller extends Admin_Controller { foreach ($locales as $locale => $display_name) { $locales[$locale] = SafeString::of_safe_html($display_name); } - if (count($locales) > 1) { - // Put "none" at the first position in the array - $locales = array_merge(array("" => t("« none »")), $locales); - $selected_locale = ($user && $user->locale) ? $user->locale : ""; - $form->dropdown("locale") - ->label(t("Language Preference")) - ->options($locales) - ->selected($selected_locale); - } + + // Put "none" at the first position in the array + $locales = array_merge(array("" => t("« none »")), $locales); + $selected_locale = ($user && $user->locale) ? $user->locale : ""; + $form->dropdown("locale") + ->label(t("Language Preference")) + ->options($locales) + ->selected($selected_locale); } private function _get_user_delete_form_admin($user) { @@ -375,9 +351,8 @@ class Admin_Users_Controller extends Admin_Controller { $form_group = $form->group("edit_group")->label(t("Edit group")); $form_group->input("name")->label(t("Name"))->id("g-name")->value($group->name); $form_group->inputs["name"]->error_messages( - "in_use", t("There is already a group with that name")); + "conflict", t("There is already a group with that name")); $form_group->submit("")->value(t("Save")); - $form->add_rules_from($group); return $form; } @@ -386,9 +361,8 @@ class Admin_Users_Controller extends Admin_Controller { $form_group = $form->group("add_group")->label(t("Add group")); $form_group->input("name")->label(t("Name"))->id("g-name"); $form_group->inputs["name"]->error_messages( - "in_use", t("There is already a group with that name")); + "conflict", t("There is already a group with that name")); $form_group->submit("")->value(t("Add group")); - $form->add_rules_from(ORM::factory("group")); return $form; } diff --git a/modules/user/controllers/users.php b/modules/user/controllers/users.php index ca218393..b3f39b47 100644 --- a/modules/user/controllers/users.php +++ b/modules/user/controllers/users.php @@ -26,34 +26,36 @@ class Users_Controller extends Controller { } $form = $this->_get_edit_form($user); - $valid = $form->validate(); - if ($valid) { + try { + $valid = $form->validate(); $user->full_name = $form->edit_user->full_name->value; - if ($form->edit_user->password->value) { - $user->password = $form->edit_user->password->value; - } + $user->password = $form->edit_user->password->value; $user->email = $form->edit_user->email->value; $user->url = $form->edit_user->url->value; - if ($form->edit_user->locale) { - $desired_locale = $form->edit_user->locale->value; - $new_locale = $desired_locale == "none" ? null : $desired_locale; - if ($new_locale != $user->locale) { - // Delete the session based locale preference - setcookie("g_locale", "", time() - 24 * 3600, "/"); - } - $user->locale = $new_locale; + $user->locale = $form->edit_user->locale->value; + $user->validate(); + } catch (ORM_Validation_Exception $e) { + // Translate ORM validation errors into form error messages + foreach ($e->validation->errors() as $key => $error) { + $form->edit_user->inputs[$key]->add_error($error, 1); + } + $valid = false; + } + + if ($valid) { + if ($user->original()->locale != $user->locale) { + // Delete the session based locale preference + setcookie("g_locale", "", time() - 24 * 3600, "/"); } + $user->save(); module::event("user_edit_form_completed", $user, $form); - message::success(t("User information updated.")); print json_encode( array("result" => "success", "resource" => url::site("users/{$user->id}"))); } else { - print json_encode( - array("result" => "error", - "form" => $form->__toString())); + print json_encode(array("result" => "error", "form" => (string) $form)); } } @@ -78,11 +80,6 @@ class Users_Controller extends Controller { ->matches($group->password); $group->input("email")->label(t("Email"))->id("g-email")->value($user->email); $group->input("url")->label(t("URL"))->id("g-url")->value($user->url); - $form->add_rules_from($user); - - $minimum_length = module::get_var("user", "mininum_password_length", 5); - $form->edit_user->password - ->rules($minimum_length ? "length[$minimum_length, 40]" : "length[40]"); module::event("user_edit_form", $user, $form); $group->submit("")->value(t("Save")); @@ -95,14 +92,13 @@ class Users_Controller extends Controller { foreach ($locales as $locale => $display_name) { $locales[$locale] = SafeString::of_safe_html($display_name); } - if (count($locales) > 1) { - // Put "none" at the first position in the array - $locales = array_merge(array("" => t("« none »")), $locales); - $selected_locale = ($user && $user->locale) ? $user->locale : ""; - $form->dropdown("locale") - ->label(t("Language Preference")) - ->options($locales) - ->selected($selected_locale); - } + + // Put "none" at the first position in the array + $locales = array_merge(array("" => t("« none »")), $locales); + $selected_locale = ($user && $user->locale) ? $user->locale : ""; + $form->dropdown("locale") + ->label(t("Language Preference")) + ->options($locales) + ->selected($selected_locale); } } diff --git a/modules/user/helpers/group.php b/modules/user/helpers/group.php index 2ada0ac1..38124b0d 100644 --- a/modules/user/helpers/group.php +++ b/modules/user/helpers/group.php @@ -25,23 +25,6 @@ */ class group_Core { /** - * Create a new group. - * - * @param string $name - * @return Group_Definition the group object - */ - static function create($name) { - $group = ORM::factory("group")->where("name", "=", $name)->find(); - if ($group->loaded()) { - throw new Exception("@todo GROUP_ALREADY_EXISTS $name"); - } - - $group->name = $name; - $group->save(); - return $group; - } - - /** * The group of all possible visitors. This includes the guest user. * * @return Group_Definition the group object diff --git a/modules/user/helpers/user.php b/modules/user/helpers/user.php index e092aecc..3561021f 100644 --- a/modules/user/helpers/user.php +++ b/modules/user/helpers/user.php @@ -36,32 +36,6 @@ class user_Core { } /** - * Create a new user. - * - * @param string $name - * @param string $full_name - * @param string $password - * @return User_Model - */ - static function create($name, $full_name, $password) { - $user = ORM::factory("user")->where("name", "=", $name)->find(); - if ($user->loaded()) { - throw new Exception("@todo USER_ALREADY_EXISTS $name"); - } - - $user->name = $name; - $user->full_name = $full_name; - $user->password = $password; - - // Required groups - $user->add(group::everybody()); - $user->add(group::registered_users()); - - $user->save(); - return $user; - } - - /** * Is the password provided correct? * * @param user User Model diff --git a/modules/user/helpers/user_installer.php b/modules/user/helpers/user_installer.php index 0cba502f..f7e3b60b 100644 --- a/modules/user/helpers/user_installer.php +++ b/modules/user/helpers/user_installer.php @@ -53,21 +53,40 @@ class user_installer { UNIQUE KEY(`user_id`, `group_id`)) DEFAULT CHARSET=utf8;"); - $everybody = group::create("Everybody"); + $everybody = ORM::factory("group"); + $everybody->name = "Everybody"; $everybody->special = true; $everybody->save(); - $registered = group::create("Registered Users"); + $registered = ORM::factory("group"); + $registered->name = "Registered Users"; $registered->special = true; $registered->save(); - $guest = user::create("guest", "Guest User", ""); - $guest->guest = true; - $guest->remove($registered); + // Avoid ORM to sidestep validation. + db::build()->insert( + "users", + array("name" => "guest", + "full_name" => "Guest User", + "password" => "", + "guest" => true)) + ->execute(); + + $guest = ORM::factory("user")->where("id", "=", 1)->find(); + $guest->add($everybody); $guest->save(); - $admin = user::create("admin", "Gallery Administrator", "admin"); - $admin->admin = true; + db::build()->insert( + "users", + array("name" => "admin", + "full_name" => "Gallery Administrator", + "password" => "admin", + "admin" => true)) + ->execute(); + + $admin = ORM::factory("user")->where("id", "=", 2)->find(); + $admin->add($everybody); + $admin->add($registered); $admin->save(); $current_provider = module::get_var("gallery", "identity_provider"); diff --git a/modules/user/libraries/drivers/IdentityProvider/Gallery.php b/modules/user/libraries/drivers/IdentityProvider/Gallery.php index d874512c..666f185f 100644 --- a/modules/user/libraries/drivers/IdentityProvider/Gallery.php +++ b/modules/user/libraries/drivers/IdentityProvider/Gallery.php @@ -38,8 +38,13 @@ class IdentityProvider_Gallery_Driver implements IdentityProvider_Driver { /** * @see IdentityProvider_Driver::create_user. */ - public function create_user($name, $full_name, $password) { - return user::create($name, $full_name, $password); + public function create_user($name, $full_name, $password, $email) { + $user = ORM::factory("user"); + $user->name = $name; + $user->full_name = $full_name; + $user->password = $password; + $user->email = $email; + return $user->save(); } /** @@ -91,7 +96,9 @@ class IdentityProvider_Gallery_Driver implements IdentityProvider_Driver { * @see IdentityProvider_Driver::create_group. */ public function create_group($name) { - return group::create($name); + $group = ORM::factory("group"); + $group->name = $name; + return $group->save(); } /** diff --git a/modules/user/models/group.php b/modules/user/models/group.php index 10f6f4b3..c00bf5c9 100644 --- a/modules/user/models/group.php +++ b/modules/user/models/group.php @@ -20,9 +20,6 @@ class Group_Model extends ORM implements Group_Definition { protected $has_and_belongs_to_many = array("users"); - var $form_rules = array( - "name" => "required|length[4,255]"); - /** * @see ORM::delete() */ @@ -37,18 +34,44 @@ class Group_Model extends ORM implements Group_Definition { return $this->users->find_all(); } - public function save() { - if (!$this->loaded()) { - $created = 1; + /** + * Specify our rules here so that we have access to the instance of this model. + */ + public function validate($array=null) { + // validate() is recursive, only modify the rules on the outermost call. + if (!$array) { + $this->rules = array( + "name" => array("rules" => array("required", "length[4,255]"), + "callbacks" => array(array($this, "valid_name")))); } - $original = clone $this->original(); - parent::save(); - if (isset($created)) { + parent::validate($array); + } + + public function save() { + if (!$this->loaded()) { + // New group + parent::save(); module::event("group_created", $this); } else { + // Updated group + $original = clone $this->original(); + parent::save(); module::event("group_updated", $original, $this); } + return $this; } + + /** + * Validate the user name. Make sure there are no conflicts. + */ + public function valid_name(Validation $v, $field) { + if (db::build()->from("groups") + ->where("name", "=", $this->name) + ->where("id", "<>", $this->id) + ->count_records() == 1) { + $v->add_error("name", "conflict"); + } + } }
\ No newline at end of file diff --git a/modules/user/models/user.php b/modules/user/models/user.php index edba2a2c..451b5ffb 100644 --- a/modules/user/models/user.php +++ b/modules/user/models/user.php @@ -19,14 +19,7 @@ */ class User_Model extends ORM implements User_Definition { protected $has_and_belongs_to_many = array("groups"); - - var $form_rules = array( - "name" => "required|length[1,32]", - "full_name" => "length[0,255]", - "email" => "required|valid_email|length[1,255]", - "password" => "length[1,40]", - "url" => "valid_url", - "locale" => "length[2,10]"); + protected $password_length = null; public function __set($column, $value) { switch ($column) { @@ -35,6 +28,7 @@ class User_Model extends ORM implements User_Definition { break; case "password": + $this->password_length = strlen($value); $value = user::hash_password($value); break; } @@ -65,18 +59,48 @@ class User_Model extends ORM implements User_Definition { return $this->groups->find_all(); } + /** + * Specify our rules here so that we have access to the instance of this model. + */ + public function validate($array=null) { + // validate() is recursive, only modify the rules on the outermost call. + if (!$array) { + $this->rules = array( + "admin" => array("callbacks" => array(array($this, "valid_admin"))), + "email" => array("rules" => array("length[1,255]", "required", "valid::email")), + "full_name" => array("rules" => array("length[0,255]")), + "locale" => array("rules" => array("length[2,10]")), + "name" => array("rules" => array("length[1,32]", "required"), + "callbacks" => array(array($this, "valid_name"))), + "password" => array("callbacks" => array(array($this, "valid_password"))), + "url" => array("rules" => array("valid::url")), + ); + } + + parent::validate($array); + } + + /** + * Handle any business logic necessary to create or update a user. + * @see ORM::save() + * + * @return ORM User_Model + */ public function save() { if (!$this->loaded()) { - $created = 1; - } + // New user + $this->add(group::everybody()); + $this->add(group::registered_users()); - $original = clone $this->original(); - parent::save(); - if (isset($created)) { + parent::save(); module::event("user_created", $this); } else { + // Updated user + $original = clone $this->original(); + parent::save(); module::event("user_updated", $original, $this); } + return $this; } @@ -88,4 +112,37 @@ class User_Model extends ORM implements User_Definition { public function display_name() { return empty($this->full_name) ? $this->name : $this->full_name; } + + /** + * Validate the user name. Make sure there are no conflicts. + */ + public function valid_name(Validation $v, $field) { + if (db::build()->from("users") + ->where("name", "=", $this->name) + ->where("id", "<>", $this->id) + ->count_records() == 1) { + $v->add_error("name", "conflict"); + } + } + + /** + * Validate the password. + */ + public function valid_password(Validation $v, $field) { + if (!$this->loaded() || $this->password_length) { + $minimum_length = module::get_var("user", "mininum_password_length", 5); + if ($this->password_length < $minimum_length) { + $v->add_error("password", "min_length"); + } + } + } + + /** + * Validate the admin bit. + */ + public function valid_admin(Validation $v, $field) { + if ($this->id == identity::active_user()->id && !$this->admin) { + $v->add_error("admin", "locked"); + } + } } diff --git a/modules/user/views/user_form.html.php b/modules/user/views/user_form.html.php index 039ae8a5..4ce2b532 100644 --- a/modules/user/views/user_form.html.php +++ b/modules/user/views/user_form.html.php @@ -1,5 +1,5 @@ <?php defined("SYSPATH") or die("No direct script access.") ?> -<script type="text/javascript"> +<script type="text/javascript"> $("form").ready(function(){ $('input[name="password"]').user_password_strength(); }); diff --git a/modules/watermark/controllers/admin_watermarks.php b/modules/watermark/controllers/admin_watermarks.php index 2a1d5f60..f535ad08 100644 --- a/modules/watermark/controllers/admin_watermarks.php +++ b/modules/watermark/controllers/admin_watermarks.php @@ -52,9 +52,7 @@ class Admin_Watermarks_Controller extends Admin_Controller { array("result" => "success", "location" => url::site("admin/watermarks"))); } else { - print json_encode( - array("result" => "error", - "form" => $form->__toString())); + print json_encode(array("result" => "error", "form" => (string) $form)); } } @@ -84,9 +82,7 @@ class Admin_Watermarks_Controller extends Admin_Controller { array("result" => "success", "location" => url::site("admin/watermarks"))); } else { - print json_encode( - array("result" => "error", - "form" => $form->__toString())); + print json_encode(array("result" => "error", "form" => (string) $form)); } } @@ -127,9 +123,7 @@ class Admin_Watermarks_Controller extends Admin_Controller { array("result" => "success", "location" => url::site("admin/watermarks"))); } else { - print json_encode( - array("result" => "error", - "form" => $form->__toString())); + print json_encode(array("result" => "error", "form" => (string) $form)); } } |