Example #1
0
 /**
  * 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;
     foreach (array("view_count", "relative_url_cache", "relative_path_cache", "resize_width", "resize_height", "resize_dirty", "thumb_width", "thumb_height", "thumb_dirty") as $key) {
         unset($significant_changes[$key]);
     }
     if (!empty($this->changed) && $significant_changes || isset($this->data_file)) {
         $this->updated = time();
         if (!$this->loaded()) {
             // Create a new item.
             module::event("item_before_create", $this);
             // Set a weight if it's missing.  We don't do this in the constructor because it's not a
             // simple assignment.
             if (empty($this->weight)) {
                 $this->weight = item::get_max_weight();
             }
             if ($this->is_album()) {
                 // Sanitize the album name.
                 $this->name = legal_file::sanitize_dirname($this->name);
             } else {
                 // Process the data file info.  This also sanitizes the item name.
                 if (isset($this->data_file)) {
                     $this->_process_data_file_info();
                 } else {
                     // New photos and movies must have a data file.
                     $this->data_file_error = true;
                 }
             }
             // Make an url friendly slug from the name, if necessary
             if (empty($this->slug)) {
                 $this->slug = item::convert_filename_to_slug(pathinfo($this->name, PATHINFO_FILENAME));
                 // If the filename is all invalid characters, then the slug may be empty here.  We set a
                 // generic name ("photo", "movie", or "album") based on its type, then rely on
                 // check_and_fix_conflicts to ensure it doesn't conflict with another name.
                 if (empty($this->slug)) {
                     $this->slug = $this->type;
                 }
             }
             $this->_check_and_fix_conflicts();
             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":
                     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.  Null out the data file variable first, otherwise the next save will
             // trigger an item_updated_data_file event.
             $this->data_file = null;
             module::event("item_created", $this);
         } else {
             // Update an existing item
             module::event("item_before_update", $this);
             // If any significant fields have changed, load up a copy of the original item and
             // keep it around.
             $original = ORM::factory("item", $this->id);
             // If we have a new data file, process its info.  This will get its metadata and
             // preserve the extension of the data file. Many helpers, (e.g. ImageMagick), assume
             // the MIME type from the extension. So when we adopt the new data file, it's important
             // to adopt the new extension. That ensures that the item's extension is always
             // appropriate for its data. We don't try to preserve the name of the data file, though,
             // because the name is typically a temporary randomly-generated name.
             if (isset($this->data_file)) {
                 $this->_process_data_file_info();
             } else {
                 if (!$this->is_album() && array_key_exists("name", $this->changed)) {
                     // There's no new data file, but the name changed.  If it's a photo or movie,
                     // make sure the new name still agrees with the file type.
                     $this->name = legal_file::sanitize_filename($this->name, pathinfo($original->name, PATHINFO_EXTENSION), $this->type);
                 }
             }
             // If an album's name changed, sanitize it.
             if ($this->is_album() && array_key_exists("name", $this->changed)) {
                 $this->name = legal_file::sanitize_dirname($this->name);
             }
             // If an album's cover has changed (or been removed), delete any existing album cover,
             // reset the thumb metadata, and mark the thumb as dirty.
             if (array_key_exists("album_cover_item_id", $this->changed) && $this->is_album()) {
                 @unlink($original->thumb_path());
                 $this->thumb_dirty = 1;
                 $this->thumb_height = 0;
                 $this->thumb_width = 0;
             }
             if (array_intersect($this->changed, array("parent_id", "name", "slug"))) {
                 $original->_build_relative_caches();
                 $this->relative_path_cache = null;
                 $this->relative_url_cache = null;
             }
             $this->_check_and_fix_conflicts();
             parent::save();
             // Now update the filesystem and any database caches if there were significant value
             // changes.  If anything past this point fails, then we'll have an inconsistent database
             // so this code should be as robust as we can make it.
             // Update the MPTT pointers, if necessary.  We have to do this before we generate any
             // cached paths!
             if ($original->parent_id != $this->parent_id) {
                 parent::move_to($this->parent());
             }
             if ($original->parent_id != $this->parent_id || $original->name != $this->name) {
                 $this->_build_relative_caches();
                 // If there is a data file, then we want to preserve both the old data and the new data.
                 // (Third-party event handlers would like access to both). The old data file will be
                 // accessible via the $original item, and the new one via $this item. But in that case,
                 // we don't want to rename the original as below, because the old data would end up being
                 // clobbered by the new data file. Also, the rename isn't necessary, because the new item
                 // data is coming from the data file anyway. So we only perform the rename if there isn't
                 // a data file. Another way to solve this would be to copy the original file rather than
                 // conditionally rename it, but a copy would cost far more than the rename.
                 if (!isset($this->data_file)) {
                     @rename($original->file_path(), $this->file_path());
                 }
                 // Move all of the items associated data files
                 if ($this->is_album()) {
                     @rename(dirname($original->resize_path()), dirname($this->resize_path()));
                     @rename(dirname($original->thumb_path()), dirname($this->thumb_path()));
                 } else {
                     @rename($original->resize_path(), $this->resize_path());
                     @rename($original->thumb_path(), $this->thumb_path());
                 }
             }
             // Changing the name, slug or parent ripples downwards
             if ($this->is_album() && ($original->name != $this->name || $original->slug != $this->slug || $original->parent_id != $this->parent_id)) {
                 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();
             }
             // Replace the data file, if requested.
             if ($this->data_file && ($this->is_photo() || $this->is_movie())) {
                 copy($this->data_file, $this->file_path());
                 $this->thumb_dirty = 1;
                 $this->resize_dirty = 1;
             }
             if ($original->parent_id != $this->parent_id) {
                 // This will result in 2 events since we'll still fire the item_updated event below
                 module::event("item_moved", $this, $original->parent());
             }
             module::event("item_updated", $original, $this);
             if ($this->data_file) {
                 // Null out the data file variable here, otherwise this event will trigger another
                 // save() which will think that we're doing another file move.
                 $this->data_file = null;
                 if ($original->file_path() != $this->file_path()) {
                     @unlink($original->file_path());
                 }
                 module::event("item_updated_data_file", $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();
         }
     }
     return $this;
 }
 public function sanitize_filename_with_corrections_test()
 {
     $this->assert_equal("foo_bar", legal_file::sanitize_dirname("/foo/bar/"));
     $this->assert_equal("foo_bar", legal_file::sanitize_dirname("\\foo\\bar\\"));
     $this->assert_equal(".foo..bar", legal_file::sanitize_dirname(".foo..bar."));
     $this->assert_equal("foo_bar", legal_file::sanitize_dirname("_foo__bar_"));
     $this->assert_equal("album", legal_file::sanitize_dirname("_"));
     $this->assert_equal("album", legal_file::sanitize_dirname(null));
 }