From cc2aed656c7289881ba23e8ec700a4daf7889688 Mon Sep 17 00:00:00 2001 From: shadlaws Date: Sat, 26 Jan 2013 08:38:31 +0100 Subject: #1946, 1947 - Make altered names/slugs more user-friendly, make conflict-finding code check filenames with no extensions - Reduced from four places that alter names/slugs to two (one to populate empty slugs, one to handle conflicting names/slugs). - For empty slugs, fill with generic human-readable name (e.g. "photo") instead of random value. - For conflicting names/slugs, add suffix that's sequential (e.g. "-01"), only using random after 99 conflicts. - Made conflict-finding code check filenames with no extensions. - Renamed _randomize_name_or_slug_on_conflict to _check_and_fix_conflicts. - Added unit tests. Also cleaned up existing unit tests to reflect new logic and removed a redundant test. - Added installer logic to correct existing items now considered in conflict. - Revised gallery_task to look for duplicate names based on new criteria. --- installer/install.sql | 2 +- modules/gallery/helpers/gallery_installer.php | 43 +++++- modules/gallery/helpers/gallery_task.php | 113 +++++++++++++-- modules/gallery/helpers/item.php | 63 +++------ modules/gallery/models/item.php | 127 +++++++++++------ modules/gallery/module.info | 2 +- modules/gallery/tests/Item_Model_Test.php | 194 ++++++++++++++++++++++---- 7 files changed, 418 insertions(+), 126 deletions(-) diff --git a/installer/install.sql b/installer/install.sql index b01c5a7c..70cf0c86 100644 --- a/installer/install.sql +++ b/installer/install.sql @@ -244,7 +244,7 @@ CREATE TABLE {modules} ( KEY `weight` (`weight`) ) AUTO_INCREMENT=10 DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -INSERT INTO {modules} VALUES (1,1,'gallery',53,1); +INSERT INTO {modules} VALUES (1,1,'gallery',54,1); INSERT INTO {modules} VALUES (2,1,'user',4,2); INSERT INTO {modules} VALUES (3,1,'comment',7,3); INSERT INTO {modules} VALUES (4,1,'organize',4,4); diff --git a/modules/gallery/helpers/gallery_installer.php b/modules/gallery/helpers/gallery_installer.php index fe651a4e..86ff7ce5 100644 --- a/modules/gallery/helpers/gallery_installer.php +++ b/modules/gallery/helpers/gallery_installer.php @@ -315,7 +315,7 @@ class gallery_installer { module::set_var("gallery", "lock_timeout", 1); module::set_var("gallery", "movie_extract_frame_time", 3); - module::set_version("gallery", 53); + module::set_version("gallery", 54); } static function upgrade($version) { @@ -743,6 +743,47 @@ class gallery_installer { module::set_var("gallery", "movie_extract_frame_time", 3); module::set_version("gallery", $version = 53); } + + if ($version == 53) { + // In v54, we changed how we check for name and slug conflicts in Item_Model. Previously, + // we checked the whole filename. As a result, "foo.jpg" and "foo.png" were not considered + // conflicting if their slugs were different (a rare case in practice since server_add and + // uploader would give them both the same slug "foo"). Now, we check the filename without its + // extension. This upgrade stanza fixes any conflicts where they were previously allowed. + + // This might be slow, but if it times out it can just pick up where it left off. + + // Find and loop through each conflict (e.g. "foo.jpg", "foo.png", and "foo.flv" are one + // conflict; "bar.jpg", "bar.png", and "bar.flv" are another) + foreach (db::build() + ->select_distinct(array("parent_base_name" => + db::expr("CONCAT(`parent_id`, ':', LOWER(SUBSTR(`name`, 1, LOCATE('.', `name`) - 1)))"))) + ->select(array("C" => "COUNT(\"*\")")) + ->from("items") + ->where("type", "<>", "album") + ->having("C", ">", 1) + ->group_by("parent_base_name") + ->execute() as $conflict) { + list ($parent_id, $base_name) = explode(":", $conflict->parent_base_name, 2); + $base_name_escaped = Database::escape_for_like($base_name); + // Loop through the items for each conflict + foreach (db::build() + ->from("items") + ->select("id") + ->where("type", "<>", "album") + ->where("parent_id", "=", $parent_id) + ->where("name", "LIKE", "{$base_name_escaped}.%") + ->limit(1000000) // required to satisfy SQL syntax (no offset without limit) + ->offset(1) // skips the 0th item + ->execute() as $row) { + set_time_limit(30); + $item = ORM::factory("item", $row->id); + $item->name = $item->name; // this will force Item_Model to check for conflicts on save + $item->save(); + } + } + module::set_version("gallery", $version = 54); + } } static function uninstall() { diff --git a/modules/gallery/helpers/gallery_task.php b/modules/gallery/helpers/gallery_task.php index 82ca9ffc..e986b822 100644 --- a/modules/gallery/helpers/gallery_task.php +++ b/modules/gallery/helpers/gallery_task.php @@ -26,11 +26,13 @@ class gallery_task_Core { const FIX_STATE_RUN_DUPE_SLUGS = 5; const FIX_STATE_START_DUPE_NAMES = 6; const FIX_STATE_RUN_DUPE_NAMES = 7; - const FIX_STATE_START_REBUILD_ITEM_CACHES = 8; - const FIX_STATE_RUN_REBUILD_ITEM_CACHES = 9; - const FIX_STATE_START_MISSING_ACCESS_CACHES = 10; - const FIX_STATE_RUN_MISSING_ACCESS_CACHES = 11; - const FIX_STATE_DONE = 12; + const FIX_STATE_START_DUPE_BASE_NAMES = 8; + const FIX_STATE_RUN_DUPE_BASE_NAMES = 9; + const FIX_STATE_START_REBUILD_ITEM_CACHES = 10; + const FIX_STATE_RUN_REBUILD_ITEM_CACHES = 11; + const FIX_STATE_START_MISSING_ACCESS_CACHES = 12; + const FIX_STATE_RUN_MISSING_ACCESS_CACHES = 13; + const FIX_STATE_DONE = 14; static function available_tasks() { $dirty_count = graphics::find_dirty_images_query()->count_records(); @@ -348,8 +350,9 @@ class gallery_task_Core { // album audit (permissions and bogus album covers): 1 operation for every album $total += db::build()->where("type", "=", "album")->count_records("items"); - // one operation for each missing slug, name and access cache - foreach (array("find_dupe_slugs", "find_dupe_names", "find_missing_access_caches") as $func) { + // one operation for each dupe slug, dupe name, dupe base name, and missing access cache + foreach (array("find_dupe_slugs", "find_dupe_names", "find_dupe_base_names", + "find_missing_access_caches") as $func) { foreach (self::$func() as $row) { $total++; } @@ -489,11 +492,12 @@ class gallery_task_Core { $task->set("stack", implode(" ", $stack)); $state = self::FIX_STATE_RUN_DUPE_NAMES; } else { - $state = self::FIX_STATE_START_ALBUMS; + $state = self::FIX_STATE_START_DUPE_BASE_NAMES; } break; case self::FIX_STATE_RUN_DUPE_NAMES: + // NOTE: This does *not* attempt to fix the file system! $stack = explode(" ", $task->get("stack")); list ($parent_id, $name) = explode(":", array_pop($stack)); @@ -505,9 +509,16 @@ class gallery_task_Core { ->find_all(1, 1); if ($conflicts->count() && $conflict = $conflicts->current()) { $task->log("Fixing conflicting name for item id {$conflict->id}"); + if (!$conflict->is_album() && preg_match("/^(.*)(\.[^\.\/]*?)$/", $conflict->name, $matches)) { + $item_base_name = $matches[1]; + $item_extension = $matches[2]; // includes a leading dot + } else { + $item_base_name = $conflict->name; + $item_extension = ""; + } db::build() ->update("items") - ->set("name", $name . "-" . (string)rand(1000, 9999)) + ->set("name", $item_base_name . "-" . (string)rand(1000, 9999) . $item_extension) ->where("id", "=", $conflict->id) ->execute(); @@ -521,6 +532,74 @@ class gallery_task_Core { $task->set("stack", implode(" ", $stack)); $completed++; + if (empty($stack)) { + $state = self::FIX_STATE_START_DUPE_BASE_NAMES; + } + break; + + case self::FIX_STATE_START_DUPE_BASE_NAMES: + $stack = array(); + foreach (self::find_dupe_base_names() as $row) { + list ($parent_id, $base_name) = explode(":", $row->parent_base_name, 2); + $stack[] = join(":", array($parent_id, $base_name)); + } + if ($stack) { + $task->set("stack", implode(" ", $stack)); + $state = self::FIX_STATE_RUN_DUPE_BASE_NAMES; + } else { + $state = self::FIX_STATE_START_ALBUMS; + } + break; + + case self::FIX_STATE_RUN_DUPE_BASE_NAMES: + // NOTE: This *does* attempt to fix the file system! So, it must go *after* run_dupe_names. + $stack = explode(" ", $task->get("stack")); + list ($parent_id, $base_name) = explode(":", array_pop($stack)); + $base_name_escaped = Database::escape_for_like($base_name); + + $fixed = 0; + // We want to leave the first one alone and update all conflicts to be random values. + $conflicts = ORM::factory("item") + ->where("parent_id", "=", $parent_id) + ->where("name", "LIKE", "{$base_name_escaped}.%") + ->where("type", "<>", "album") + ->find_all(1, 1); + if ($conflicts->count() && $conflict = $conflicts->current()) { + $task->log("Fixing conflicting name for item id {$conflict->id}"); + if (preg_match("/^(.*)(\.[^\.\/]*?)$/", $conflict->name, $matches)) { + $item_base_name = $matches[1]; // unlike $base_name, this always maintains capitalization + $item_extension = $matches[2]; // includes a leading dot + } else { + $item_base_name = $conflict->name; + $item_extension = ""; + } + // Unlike conflicts found in run_dupe_names, these items are likely to have an intact + // file system. Let's use the item save logic to rebuild the paths and rename the files + // if possible. + try { + $conflict->name = $item_base_name . "-" . (string)rand(1000, 9999) . $item_extension; + $conflict->validate(); + // If we get here, we're safe to proceed with save + $conflict->save(); + } catch (Exception $e) { + // Didn't work. Edit database directly without fixing file system. + db::build() + ->update("items") + ->set("name", $item_base_name . "-" . (string)rand(1000, 9999) . $item_extension) + ->where("id", "=", $conflict->id) + ->execute(); + } + + // We fixed one conflict, but there might be more so put this parent back on the stack + // and try again. We won't consider it completed when we don't fix a conflict. This + // guarantees that we won't spend too long fixing one set of conflicts, and that we + // won't stop before all are fixed. + $stack[] = "$parent_id:$base_name"; + break; + } + $task->set("stack", implode(" ", $stack)); + $completed++; + if (empty($stack)) { $state = self::FIX_STATE_START_ALBUMS; } @@ -669,18 +748,32 @@ class gallery_task_Core { } static function find_dupe_names() { + // looking for photos, movies, and albums return db::build() ->select_distinct( array("parent_name" => db::expr("CONCAT(`parent_id`, ':', LOWER(`name`))"))) ->select("id") ->select(array("C" => "COUNT(\"*\")")) ->from("items") - ->where("type", "<>", "album") ->having("C", ">", 1) ->group_by("parent_name") ->execute(); } + static function find_dupe_base_names() { + // looking for photos or movies, not albums + return db::build() + ->select_distinct( + array("parent_base_name" => db::expr("CONCAT(`parent_id`, ':', LOWER(SUBSTR(`name`, 1, LOCATE('.', `name`) - 1)))"))) + ->select("id") + ->select(array("C" => "COUNT(\"*\")")) + ->from("items") + ->where("type", "<>", "album") + ->having("C", ">", 1) + ->group_by("parent_base_name") + ->execute(); + } + static function find_empty_item_caches($limit) { return db::build() ->select("items.id") diff --git a/modules/gallery/helpers/item.php b/modules/gallery/helpers/item.php index b2c4cadf..386eeb08 100644 --- a/modules/gallery/helpers/item.php +++ b/modules/gallery/helpers/item.php @@ -39,55 +39,28 @@ class item_Core { } } - $source->parent_id = $target->id; - - // Moving may result in name or slug conflicts. If that happens, try up to 5 times to pick a - // random name (or slug) to avoid the conflict. $orig_name = $source->name; - $orig_name_filename = pathinfo($source->name, PATHINFO_FILENAME); - $orig_name_extension = pathinfo($source->name, PATHINFO_EXTENSION); - $orig_slug = $source->slug; - for ($i = 0; $i < 5; $i++) { - try { - $source->save(); - if ($orig_name != $source->name) { - switch ($source->type) { - case "album": - message::info( - t("Album %old_name renamed to %new_name to avoid a conflict", - array("old_name" => $orig_name, "new_name" => $source->name))); - break; - - case "photo": - message::info( - t("Photo %old_name renamed to %new_name to avoid a conflict", - array("old_name" => $orig_name, "new_name" => $source->name))); - break; + $source->parent_id = $target->id; + $source->save(); + if ($orig_name != $source->name) { + switch ($source->type) { + case "album": + message::info( + t("Album %old_name renamed to %new_name to avoid a conflict", + array("old_name" => $orig_name, "new_name" => $source->name))); + break; - case "movie": - message::info( - t("Movie %old_name renamed to %new_name to avoid a conflict", - array("old_name" => $orig_name, "new_name" => $source->name))); - break; - } - } + case "photo": + message::info( + t("Photo %old_name renamed to %new_name to avoid a conflict", + array("old_name" => $orig_name, "new_name" => $source->name))); break; - } catch (ORM_Validation_Exception $e) { - $rand = rand(10, 99); - $errors = $e->validation->errors(); - if (isset($errors["name"])) { - $source->name = $orig_name_filename . "-{$rand}." . $orig_name_extension; - unset($errors["name"]); - } - if (isset($errors["slug"])) { - $source->slug = $orig_slug . "-{$rand}"; - unset($errors["slug"]); - } - if ($errors) { - // There were other validation issues-- we don't know how to handle those - throw $e; - } + case "movie": + message::info( + t("Movie %old_name renamed to %new_name to avoid a conflict", + array("old_name" => $orig_name, "new_name" => $source->name))); + break; } } diff --git a/modules/gallery/models/item.php b/modules/gallery/models/item.php index bbfeee47..e4b997aa 100644 --- a/modules/gallery/models/item.php +++ b/modules/gallery/models/item.php @@ -338,10 +338,11 @@ class Item_Model_Core extends ORM_MPTT { 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. Pick a - // random value. + // 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 = (string)rand(1000, 9999); + $this->slug = $this->type; } } @@ -362,7 +363,7 @@ class Item_Model_Core extends ORM_MPTT { } } - $this->_randomize_name_or_slug_on_conflict(); + $this->_check_and_fix_conflicts(); parent::save(); @@ -382,16 +383,6 @@ class Item_Model_Core extends ORM_MPTT { 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"] . "-" . random::int() . "." . $pi["extension"]; - parent::save(); - } - copy($this->data_file, $this->file_path()); break; } @@ -435,7 +426,7 @@ class Item_Model_Core extends ORM_MPTT { $this->relative_url_cache = null; } - $this->_randomize_name_or_slug_on_conflict(); + $this->_check_and_fix_conflicts(); parent::save(); @@ -528,30 +519,60 @@ class Item_Model_Core extends ORM_MPTT { /** * Check to see if there's another item that occupies the same name or slug that this item - * intends to use, and if so choose a new name/slug while preserving the extension. - * @todo Improve this. Random numbers are not user friendly + * intends to use, and if so choose a new name/slug while preserving the extension. Since this + * checks the name without its extension, it covers possible collisions with thumbs and resizes + * as well (e.g. between the thumbs of movie "foo.flv" and photo "foo.jpg"). */ - private function _randomize_name_or_slug_on_conflict() { - $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) - ->where("id", $this->id ? "<>" : "IS NOT", $this->id) - ->and_open() - ->where("name", "=", $this->name) - ->or_where("slug", "=", $this->slug) - ->close() - ->find()->id) { - $rand = random::int(); - if ($base_ext) { - $this->name = "$base_name-$rand.$base_ext"; + private function _check_and_fix_conflicts() { + $suffix_num = 1; + $suffix = ""; + if ($this->is_album()) { + while (db::build() + ->from("items") + ->where("parent_id", "=", $this->parent_id) + ->where("id", $this->id ? "<>" : "IS NOT", $this->id) + ->and_open() + ->where("name", "=", "{$this->name}{$suffix}") + ->or_where("slug", "=", "{$this->slug}{$suffix}") + ->close() + ->count_records()) { + $suffix = "-" . (($suffix_num <= 99) ? sprintf("%02d", $suffix_num++) : random::int()); + } + if ($suffix) { + $this->name = "{$this->name}{$suffix}"; + $this->slug = "{$this->slug}{$suffix}"; + $this->relative_path_cache = null; + $this->relative_url_cache = null; + } + } else { + // Split the filename into its base and extension. This uses a regexp similar to + // legal_file::change_extension (which isn't always the same as pathinfo). + if (preg_match("/^(.*)(\.[^\.\/]*?)$/", $this->name, $matches)) { + $base_name = $matches[1]; + $extension = $matches[2]; // includes a leading dot } else { - $this->name = "$base_name-$rand"; + $base_name = $this->name; + $extension = ""; + } + $base_name_escaped = Database::escape_for_like($base_name); + // Note: below query uses LIKE with wildcard % at end, which is still sargable (i.e. quick) + while (db::build() + ->from("items") + ->where("parent_id", "=", $this->parent_id) + ->where("id", $this->id ? "<>" : "IS NOT", $this->id) + ->and_open() + ->where("name", "LIKE", "{$base_name_escaped}{$suffix}.%") + ->or_where("slug", "=", "{$this->slug}{$suffix}") + ->close() + ->count_records()) { + $suffix = "-" . (($suffix_num <= 99) ? sprintf("%02d", $suffix_num++) : random::int()); + } + if ($suffix) { + $this->name = "{$base_name}{$suffix}{$extension}"; + $this->slug = "{$this->slug}{$suffix}"; + $this->relative_path_cache = null; + $this->relative_url_cache = null; } - $this->slug = "$base_slug-$rand"; - $this->relative_path_cache = null; - $this->relative_url_cache = null; } } @@ -871,14 +892,32 @@ class Item_Model_Core extends ORM_MPTT { } } - if (db::build() - ->from("items") - ->where("parent_id", "=", $this->parent_id) - ->where("name", "=", $this->name) - ->merge_where($this->id ? array(array("id", "<>", $this->id)) : null) - ->count_records()) { - $v->add_error("name", "conflict"); - return; + if ($this->is_album()) { + if (db::build() + ->from("items") + ->where("parent_id", "=", $this->parent_id) + ->where("name", "=", $this->name) + ->merge_where($this->id ? array(array("id", "<>", $this->id)) : null) + ->count_records()) { + $v->add_error("name", "conflict"); + return; + } + } else { + if (preg_match("/^(.*)(\.[^\.\/]*?)$/", $this->name, $matches)) { + $base_name = $matches[1]; + } else { + $base_name = $this->name; + } + $base_name_escaped = Database::escape_for_like($base_name); + if (db::build() + ->from("items") + ->where("parent_id", "=", $this->parent_id) + ->where("name", "LIKE", "{$base_name_escaped}.%") + ->merge_where($this->id ? array(array("id", "<>", $this->id)) : null) + ->count_records()) { + $v->add_error("name", "conflict"); + return; + } } if ($this->parent_id == 1 && Kohana::auto_load("{$this->slug}_Controller")) { diff --git a/modules/gallery/module.info b/modules/gallery/module.info index 566ca2eb..b6cefe39 100644 --- a/modules/gallery/module.info +++ b/modules/gallery/module.info @@ -1,6 +1,6 @@ name = "Gallery 3" description = "Gallery core application" -version = 53 +version = 54 author_name = "Gallery Team" author_url = "http://codex.galleryproject.org/Gallery:Team" info_url = "http://codex.galleryproject.org/Gallery3:Modules:gallery" diff --git a/modules/gallery/tests/Item_Model_Test.php b/modules/gallery/tests/Item_Model_Test.php index c45bbc3c..41361b32 100644 --- a/modules/gallery/tests/Item_Model_Test.php +++ b/modules/gallery/tests/Item_Model_Test.php @@ -136,19 +136,6 @@ class Item_Model_Test extends Gallery_Unit_Test_Case { $this->assert_true(false, "Shouldn't get here"); } - public function item_rename_over_existing_name_gets_uniqified_test() { - // Create a test photo - $item = test::random_photo(); - $item2 = test::random_photo(); - - $item->name = $item2->name; - $item->save(); - - // foo.jpg should become foo-####.jpg - $this->assert_true( - preg_match("/" . str_replace(".jpg", "", $item2->name) . "-\d+\.jpg/", $item->name)); - } - public function move_album_test() { $album2 = test::random_album(); $album1 = test::random_album($album2); @@ -205,7 +192,7 @@ class Item_Model_Test extends Gallery_Unit_Test_Case { $this->assert_equal($fullsize_file, file_get_contents($photo->file_path())); } - public function move_album_with_conflicting_target_gets_uniqified_test() { + public function move_album_with_conflicting_target_gets_uniquified_test() { $album = test::random_album(); $source = test::random_album_unsaved($album); $source->name = $album->name; @@ -217,9 +204,9 @@ class Item_Model_Test extends Gallery_Unit_Test_Case { $source->parent_id = item::root()->id; $source->save(); - // foo should become foo-#### - $this->assert_true(preg_match("/{$album->name}-\d+/", $source->name)); - $this->assert_true(preg_match("/{$album->slug}-\d+/", $source->slug)); + // foo should become foo-01 + $this->assert_same("{$album->name}-01", $source->name); + $this->assert_same("{$album->slug}-01", $source->slug); } public function move_album_fails_wrong_target_type_test() { @@ -239,7 +226,7 @@ class Item_Model_Test extends Gallery_Unit_Test_Case { $this->assert_true(false, "Shouldn't get here"); } - public function move_photo_with_conflicting_target_gets_uniqified_test() { + public function move_photo_with_conflicting_target_gets_uniquified_test() { $photo1 = test::random_photo(); $album = test::random_album(); $photo2 = test::random_photo_unsaved($album); @@ -247,17 +234,16 @@ class Item_Model_Test extends Gallery_Unit_Test_Case { $photo2->save(); // $photo1 and $photo2 have the same name, so if we move $photo1 into the root they should - // conflict and get uniqified. + // conflict and get uniquified. $photo2->parent_id = item::root()->id; $photo2->save(); - // foo.jpg should become foo-####.jpg - $this->assert_true( - preg_match("/" . str_replace(".jpg", "", $photo1->name) . "-\d+\.jpg/", $photo2->name)); + // foo.jpg should become foo-01.jpg + $this->assert_same(pathinfo($photo1->name, PATHINFO_FILENAME) . "-01.jpg", $photo2->name); - // foo should become foo - $this->assert_true(preg_match("/{$photo1->slug}/", $photo2->name)); + // foo should become foo-01 + $this->assert_same("{$photo1->slug}-01", $photo2->slug); } public function move_album_inside_descendent_fails_test() { @@ -535,4 +521,164 @@ class Item_Model_Test extends Gallery_Unit_Test_Case { $album->name = $album->name . ".foo.bar"; $album->save(); } + + public function no_conflict_when_parents_different_test() { + $parent1 = test::random_album(); + $parent2 = test::random_album(); + $photo1 = test::random_photo($parent1); + $photo2 = test::random_photo($parent2); + + $photo2->name = $photo1->name; + $photo2->slug = $photo1->slug; + $photo2->save(); + + // photo2 has same name and slug as photo1 but different parent - no conflict. + $this->assert_same($photo1->name, $photo2->name); + $this->assert_same($photo1->slug, $photo2->slug); + } + + public function fix_conflict_when_names_identical_test() { + $parent = test::random_album(); + $photo1 = test::random_photo($parent); + $photo2 = test::random_photo($parent); + + $photo1_orig_base = pathinfo($photo1->name, PATHINFO_FILENAME); + $photo2_orig_slug = $photo2->slug; + + $photo2->name = $photo1->name; + $photo2->save(); + + // photo2 has same name as photo1 - conflict resolved by renaming with -01. + $this->assert_same("{$photo1_orig_base}-01.jpg", $photo2->name); + $this->assert_same("{$photo2_orig_slug}-01", $photo2->slug); + } + + public function fix_conflict_when_slugs_identical_test() { + $parent = test::random_album(); + $photo1 = test::random_photo($parent); + $photo2 = test::random_photo($parent); + + $photo2_orig_base = pathinfo($photo2->name, PATHINFO_FILENAME); + + $photo2->slug = $photo1->slug; + $photo2->save(); + + // photo2 has same slug as photo1 - conflict resolved by renaming with -01. + $this->assert_same("{$photo2_orig_base}-01.jpg", $photo2->name); + $this->assert_same("{$photo1->slug}-01", $photo2->slug); + } + + public function no_conflict_when_parents_different_for_albums_test() { + $parent1 = test::random_album(); + $parent2 = test::random_album(); + $album1 = test::random_album($parent1); + $album2 = test::random_album($parent2); + + $album2->name = $album1->name; + $album2->slug = $album1->slug; + $album2->save(); + + // album2 has same name and slug as album1 but different parent - no conflict. + $this->assert_same($album1->name, $album2->name); + $this->assert_same($album1->slug, $album2->slug); + } + + public function fix_conflict_when_names_identical_for_albums_test() { + $parent = test::random_album(); + $album1 = test::random_album($parent); + $album2 = test::random_album($parent); + + $album2_orig_slug = $album2->slug; + + $album2->name = $album1->name; + $album2->save(); + + // album2 has same name as album1 - conflict resolved by renaming with -01. + $this->assert_same("{$album1->name}-01", $album2->name); + $this->assert_same("{$album2_orig_slug}-01", $album2->slug); + } + + public function fix_conflict_when_slugs_identical_for_albums_test() { + $parent = test::random_album(); + $album1 = test::random_album($parent); + $album2 = test::random_album($parent); + + $album2_orig_name = $album2->name; + + $album2->slug = $album1->slug; + $album2->save(); + + // album2 has same slug as album1 - conflict resolved by renaming with -01. + $this->assert_same("{$album2_orig_name}-01", $album2->name); + $this->assert_same("{$album1->slug}-01", $album2->slug); + } + + public function no_conflict_when_base_names_identical_between_album_and_photo_test() { + $parent = test::random_album(); + $album = test::random_album($parent); + $photo = test::random_photo($parent); + + $photo_orig_slug = $photo->slug; + + $photo->name = "{$album->name}.jpg"; + $photo->save(); + + // photo has same base name as album - no conflict. + $this->assert_same("{$album->name}.jpg", $photo->name); + $this->assert_same($photo_orig_slug, $photo->slug); + } + + public function fix_conflict_when_full_names_identical_between_album_and_photo_test() { + $parent = test::random_album(); + $photo = test::random_photo($parent); + $album = test::random_album($parent); + + $album_orig_slug = $album->slug; + + $album->name = $photo->name; + $album->save(); + + // album has same full name as album - conflict resolved by renaming with -01. + $this->assert_same("{$photo->name}-01", $album->name); + $this->assert_same("{$album_orig_slug}-01", $album->slug); + } + + public function fix_conflict_when_slugs_identical_between_album_and_photo_test() { + $parent = test::random_album(); + $album = test::random_album($parent); + $photo = test::random_photo($parent); + + $photo_orig_base = pathinfo($photo->name, PATHINFO_FILENAME); + + $photo->slug = $album->slug; + $photo->save(); + + // photo has same slug as album - conflict resolved by renaming with -01. + $this->assert_same("{$photo_orig_base}-01.jpg", $photo->name); + $this->assert_same("{$album->slug}-01", $photo->slug); + } + + public function fix_conflict_when_base_names_identical_between_jpg_png_flv_test() { + $parent = test::random_album(); + $item1 = test::random_photo($parent); + $item2 = test::random_photo($parent); + $item3 = test::random_movie($parent); + + $item1_orig_base = pathinfo($item1->name, PATHINFO_FILENAME); + $item2_orig_slug = $item2->slug; + $item3_orig_slug = $item3->slug; + + $item2->set_data_file(MODPATH . "gallery/images/graphicsmagick.png"); + $item2->name = "{$item1_orig_base}.png"; + $item2->save(); + + $item3->name = "{$item1_orig_base}.flv"; + $item3->save(); + + // item2 and item3 have same base name as item1 - conflict resolved by renaming with -01 and -02. + $this->assert_same("{$item1_orig_base}-01.png", $item2->name); + $this->assert_same("{$item2_orig_slug}-01", $item2->slug); + $this->assert_same("{$item1_orig_base}-02.flv", $item3->name); + $this->assert_same("{$item3_orig_slug}-02", $item3->slug); + } } -- cgit v1.2.3