diff options
-rw-r--r-- | modules/gallery/controllers/simple_uploader.php | 40 | ||||
-rw-r--r-- | modules/gallery/helpers/gallery_event.php | 16 | ||||
-rw-r--r-- | modules/gallery/helpers/photo.php | 112 | ||||
-rw-r--r-- | modules/gallery/models/item.php | 139 |
4 files changed, 151 insertions, 156 deletions
diff --git a/modules/gallery/controllers/simple_uploader.php b/modules/gallery/controllers/simple_uploader.php index 5d32e35f..7a7e7557 100644 --- a/modules/gallery/controllers/simple_uploader.php +++ b/modules/gallery/controllers/simple_uploader.php @@ -40,39 +40,45 @@ 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()); if (file_exists($temp_filename)) { unlink($temp_filename); } @@ -84,7 +90,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/gallery_event.php b/modules/gallery/helpers/gallery_event.php index 679d65c2..9452e855 100644 --- a/modules/gallery/helpers/gallery_event.php +++ b/modules/gallery/helpers/gallery_event.php @@ -73,6 +73,22 @@ 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::failure("Unable to create a thumbnail for item id {$item->id}"); + 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/photo.php b/modules/gallery/helpers/photo.php index aeae7f56..74e30409 100644 --- a/modules/gallery/helpers/photo.php +++ b/modules/gallery/helpers/photo.php @@ -24,118 +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; - - // 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"); diff --git a/modules/gallery/models/item.php b/modules/gallery/models/item.php index 46b0304e..977b9771 100644 --- a/modules/gallery/models/item.php +++ b/modules/gallery/models/item.php @@ -20,13 +20,13 @@ class Item_Model extends ORM_MPTT { protected $children = 'items'; protected $sorting = array(); + protected $data_file = null; var $rules = array( "name" => array("rules" => array("length[0,255]", "required")), "title" => array("rules" => array("length[0,255]", "required")), "slug" => array("rules" => array("length[0,255]", "required")), "description" => array("rules" => array("length[0,65535]")), - "parent_id" => array("rules" => array("Item_Model::valid_parent")), "type" => array("rules" => array("Item_Model::valid_type")), ); @@ -175,6 +175,14 @@ class Item_Model extends ORM_MPTT { } /** + * Specify the path to the data file associated with this item. To actually associate it, + * you still have to call save(). + */ + public function set_data_file($data_file) { + $this->data_file = $data_file; + } + + /** * Return the server-relative url to this item, eg: * /gallery3/index.php/BobsWedding?page=2 * /gallery3/index.php/BobsWedding/Eating-Cake.jpg @@ -304,7 +312,7 @@ class Item_Model extends ORM_MPTT { } $this->relative_path_cache = implode($names, "/"); $this->relative_url_cache = implode($slugs, "/"); - $this->save(); + return $this; } /** @@ -319,7 +327,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; } @@ -334,7 +342,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; } @@ -368,6 +376,7 @@ class Item_Model extends ORM_MPTT { unset($significant_changes["relative_url_cache"]); unset($significant_changes["relative_path_cache"]); + if (!empty($this->changed) && $significant_changes) { $this->updated = time(); if (!$this->loaded()) { @@ -386,15 +395,37 @@ class Item_Model extends ORM_MPTT { 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, "-"); } - // Randomize the name or slug if there's a conflict + if ($this->is_movie() || $this->is_photo()) { + $image_info = getimagesize($this->data_file); + + if ($this->is_photo()) { + $this->width = $image_info[0]; + $this->height = $image_info[1]; + $this->mime_type = + empty($image_info['mime']) ? "application/unknown" : $image_info['mime']; + } + + // Force an extension onto the name if necessary + $pi = pathinfo($this->data_file); + if (empty($pi["extension"])) { + $pi["extension"] = image_type_to_extension($image_info[2], false); + $this->name .= "." . $pi["extension"]; + } + + } + + // Randomize the name or slug if there's a conflict. Preserve the extension. // @todo Improve this. Random numbers are not user friendly - $base_name = $this->name; + $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) @@ -404,19 +435,46 @@ class Item_Model extends ORM_MPTT { ->close() ->find()->id) { $rand = rand(); - $this->name = "$base_name-$rand"; + if ($base_ext) { + $this->name = "$base_name-$rand.$base_ext"; + } else { + $this->name = "$base_name-$rand"; + } $this->slug = "$base_slug-$rand"; } parent::save(); - // Call this after we finish saving so that the paths are correct. - if ($this->is_album()) { + // Build our url caches and save again. 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": + // 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 { // Update an existing item @@ -691,8 +749,8 @@ class Item_Model extends ORM_MPTT { if (!$array) { // The root item has different rules for the name and slug. if ($this->id == 1) { - $this->rules["name"]["rules"][] = "length[0]"; - $this->rules["slug"]["rules"][] = "length[0]"; + $this->rules["name"] = array("rules" => array("length[0]")); + $this->rules["slug"] = array("rules" => array("length[0]")); } // Names and slugs can't conflict @@ -700,20 +758,28 @@ class Item_Model extends ORM_MPTT { $this->rules["slug"]["callbacks"][] = array($this, "valid_slug"); } + // 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"); + } + + // All items must have a legal parent + $this->rules["parent_id"]["callbacks"][] = array($this, "valid_parent"); + parent::validate($array); } /** * Validate that the desired slug does not conflict. */ - public function valid_slug(Validation $v, $value) { - if (preg_match("/[^A-Za-z0-9-_]/", $value)) { + 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", "=", $value) + ->where("slug", "=", $this->slug) ->count_records()) { $v->add_error("slug", "conflict"); } @@ -723,36 +789,55 @@ class Item_Model extends ORM_MPTT { * Validate the item name. It can't conflict with other names, can't contain slashes or * trailing periods. */ - public function valid_name(Validation $v, $value) { - if (strpos($value, "/") !== false) { + public function valid_name(Validation $v, $field) { + if (strpos($this->name, "/") !== false) { $v->add_error("name", "no_slashes"); - } else if (rtrim($value, ".") !== $value) { + } else if (rtrim($this->name, ".") !== $this->name) { $v->add_error("name", "no_trailing_period"); } else if (db::build() ->from("items") ->where("parent_id", "=", $this->parent_id) ->where("id", "<>", $this->id) - ->where("name", "=", $value) + ->where("name", "=", $this->name) ->count_records()) { $v->add_error("name", "conflict"); } } /** - * Make sure that the type is valid. + * Make sure that the data file is well formed (it exists and isn't empty). */ - static function valid_type($value) { - return in_array($value, array("album", "photo", "movie")); + public function valid_data_file(Validation $v, $field) { + if (!is_file($this->data_file)) { + $v->add_error("file", "bad_path"); + } else if (filesize($this->data_file) == 0) { + $v->add_error("file", "empty_file"); + } } /** * Make sure that the parent id refers to an album. */ - static function valid_parent($value) { - return db::build() - ->from("items") - ->where("id", "=", $value) - ->where("type", "=", "album") - ->count_records() == 1; + 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 that the type is valid. + */ + static function valid_type($value) { + return in_array($value, array("album", "photo", "movie")); } } |