From: <no...@kr...> - 2007-10-31 05:50:09
|
Revision: 4234 Author: noam Date: 2007-10-31 01:49:55 -0400 (Wed, 31 Oct 2007) Log Message: ----------- Implemented first round of requested "Replace Story" feature, where instead of a simple duplicate-URL error, creating a story that conflicts with existing URLs now yields a dialogue box giving users the option of overwriting the duplicate URLs. (As part of this change, modified all the dupe-checking code to return arrays rather than single dupes, and added a number of new messages; also clarified some earlier messages, simplified new alert_duplicate_URL() methods in CGI Story, and fixed a couple edge-case bugs in process_slug_input() and update_categories()). Modified Paths: -------------- trunk/krang/conf/messages.conf trunk/krang/docs/changelog.pod trunk/krang/lib/Krang/CGI/Story.pm trunk/krang/lib/Krang/Story.pm trunk/krang/t/story.t Modified: trunk/krang/conf/messages.conf =================================================================== --- trunk/krang/conf/messages.conf 2007-10-31 05:43:03 UTC (rev 4233) +++ trunk/krang/conf/messages.conf 2007-10-31 05:49:55 UTC (rev 4234) @@ -4,31 +4,23 @@ missing_title "Title requires a value." missing_slug "Slug requires a value for this story type because it is used to construct the URL." missing_category "Site / Category requires a value." -missing_cover_date "Cover Date requires a value." +missing_cover_date "Cover Date requires values for date (MM/DD/YYYY) and time (H:MM AM)." no_slug_no_cat_idx "Story must either have a Slug or be a Category Index." no_categories_for_chooser "No Sites are defined. You must add a Site before you can create stories, media and templates." <Module "Krang::CGI::Story"> - duplicate_url "Duplicate URL detected. Story ID $story_id has the URL $url which your choices also generate. Please change your settings for $which to generate a new URL." - - duplicate_url_on_add_category "Duplicate URL detected. Adding category $category_url would cause a conflict with story ID $story_id, which already has the URL $url." - - category_has_url "Duplicate URL detected. Category ID $category_id has the URL $url which your choices also generate. Please change your settings for $which to generate a new URL." - - category_has_url_on_add_category "Duplicate URL detected. Adding category $category_url would cause a conflict with Category ID $category_id, which already has the URL $url." - bad_slug "You have entered an invalid slug. The slug may contain only letters, numbers, underscores and hyphens." story_save "Saved version $version of Story $story_id (URL: $url)." story_delete "Deleted Story $story_id (URL: $url)." - story_checked_in_during_edit "Someone with admin privileges checked in Story ID $id while you were editing it!" + story_checked_in_during_edit "Someone with admin privileges checked in Story $id while you were editing it!" - story_stolen_during_edit "$thief used admin privileges to steal Story ID $id while you were editing it!" + story_stolen_during_edit "$thief used admin privileges to steal Story $id while you were editing it!" - story_deleted_during_edit "Story ID $id was deleted by another user while you were editing it! It no longer exists in the CMS." + story_deleted_during_edit "Story $id was deleted by another user while you were editing it! It no longer exists in the CMS." duplicate_category "Cannot add category because the chosen category is already associated with the story." @@ -36,17 +28,17 @@ added_no_category "No categories selected for addition!" - replaced_category "Moved story from $old_url to $new_url." + replaced_category "Story will be moved from $old_url to $new_url." replaced_no_category "You must specify both the category to replace and what to replace it with!" - new_slug_prevented_category_change "There is a problem with your slug field and existing categories that would not be corrected by the action you have chosen:" + new_slug_prevented_category_change "There is a problem (described below) with your slug field and existing categories that would not be corrected by the action you've chosen. Please fix this before making further changes.<hr>" set_primary_category "Set primary category to $url." - deleted_categories "Removed story from categories $urls." + deleted_a_category "Story will be removed from Category $url." - deleted_a_category "Removed story from category $url." + deleted_categories "Story will be removed from Categories $urls." deleted_no_categories "No categories marked for deletion!" @@ -60,17 +52,17 @@ selected_stories_checkout_one "The selected story has been checked out. (In the future, if you want to check out only one story you can also click 'Edit'.)" - selected_story_stolen "Story ID $id has been stolen from $victim and opened for edit." + one_story_stolen_and_opened "Story $id has been stolen from $victim and opened for edit." - selected_story_yours "Story ID $id was already checked out to you; it has been opened for edit." + one_story_yours_and_opened "Story $id was already checked out to you; it has been opened for edit." - multiple_stories_stolen "Story IDs $ids have been stolen (from $victims) and moved to your Workspace." + one_story_stolen "Story $id has been stolen from $victim and moved to your Workspace." - multiple_stories_yours "Story IDs $ids were already checked out to you." + one_story_yours "Story $id was already checked out to you." - one_story_stolen "Story ID $id has been stolen from $victim and moved to your Workspace." + multiple_stories_stolen "Story $ids have been stolen (from $victims) and moved to your Workspace." - one_story_yours "Story ID $id was already checked out to you." + multiple_stories_yours "Story $ids were already checked out to you." all_selected_stories_yours "The selected stories were all checked out to you; switching to Workspace view." @@ -78,9 +70,9 @@ missing_category_on_save "You must choose at least one site/category for your story." - copied_story "Created copy of story ID $id. Title and slug have been updated to '$title' and '$slug'." + copied_story "Created copy of story $id. Title and slug have been updated to '$title' and '$slug'." - copied_story_no_cats "Created copy of story ID $id. Title has been updated to '$title'. You must assign at least one category to this story before you can save it." + copied_story_no_cats "Created copy of story $id. Title has been updated to '$title'. You must assign at least one category to this story before you can save it." story_cant_move_checked_out "Cannot checkin story $id to the $desk desk." @@ -88,6 +80,24 @@ moved_story "Moved story $id to '$desk' desk." + category_has_url "Duplicate URL$s detected. Your choices generate the exact URL$s of Category $ids ($urls). Please change your settings for $attributes to avoid this conflict." + + category_has_url_on_add_cat "Duplicate URL detected. Adding your slug to $cat would generate the exact URL of Category $id ($url)." + + duplicate_url "Duplicate URL detected. Your choices for $attributes conflict with another story's URL:" + + duplicate_urls "Duplicate URLs detected. Your choices for $attributes conflict with URLs of other stories:" + + duplicate_url_on_add_cat "Duplicate URL detected. Adding category $cat would conflict with another story's URL:" + + duplicate_url_table "<center> <table border=1 padding=5 style='text-align:center'> <tr> <td>Story ID</td> <td>URL (click to view)</td> </tr> $dupe_rows <tr> <td><input type='button' value='Cancel' onclick='Krang.Messages.hide($qalerts$q)'></td> <td><input type='submit' value='Delete & Replace With Your Story' onclick='if (confirm($q Are you SURE? $q)) { Krang.Messages.hide($qalerts$q); Krang.Form.submit($q$form$q, { rm: $qreplace_dupes$q }, { to_top: false })}'></td> </tr> </table> <br><br>" + + dupe_story_deleted "Story $id has been deleted from the CMS." + + dupe_story_modified "Story $id has been removed from conflicting locations." + + dupe_story_checked_out "Story $id ($url) cannot be modified because it is currently checked out to another user." + </Module> # used by MediaLink elements, but they might be in different modules... @@ -313,8 +323,8 @@ missing_parent_id "Parent requires a value." missing_dir "Directory requires a value." bad_dir "Directory name must contain only letters, numbers, underscores and dashes. No spaces or other special characters are allowed." - duplicate_url "Duplicate URL detected. Category ID $category_id has the URL $url which is the same as your choices generate. Please change your settings for parent or directory to generate a new URL." - story_has_url "Duplicate URL detected. Story ID $story_id has the URL $url which is the same as your choices generate. Please change your settings for parent or directory to generate a new URL." + duplicate_url "Duplicate URL detected. Category $category_id has the URL $url which is the same as your choices generate. Please change your settings for parent or directory to generate a new URL." + story_has_url "Duplicate URL detected. Story $story_id has the URL $url which is the same as your choices generate. Please change your settings for parent or directory to generate a new URL." add_not_allowed "Cannot add category. You do not have access to edit parent category '$url'." Modified: trunk/krang/docs/changelog.pod =================================================================== --- trunk/krang/docs/changelog.pod 2007-10-31 05:43:03 UTC (rev 4233) +++ trunk/krang/docs/changelog.pod 2007-10-31 05:49:55 UTC (rev 4234) @@ -10,6 +10,17 @@ =item * +Implemented requested "Replace Story" feature where instead of a simple duplicate-URL +error, creating a story that conflicts with existing URLs now yields a dialogue box +giving users the option of overwriting the duplicate URLs. [Noam Weinstein] + +=item * + +Fixed small bugs in TimeChooser where hour/mins of 0 were taken to be null, +and midnight hour was misunderstood during conversion. [Noam Weinstein] + +=item * + Added alerts notifying user if the story being edited was checked-in, stolen, or deleted by another user while they were editing it. [Noam Weinstein] Modified: trunk/krang/lib/Krang/CGI/Story.pm =================================================================== --- trunk/krang/lib/Krang/CGI/Story.pm 2007-10-31 05:43:03 UTC (rev 4233) +++ trunk/krang/lib/Krang/CGI/Story.pm 2007-10-31 05:49:55 UTC (rev 4234) @@ -71,6 +71,7 @@ replace_category => 'replace_category', set_primary_category => 'set_primary_category', copy => 'copy', + replace_dupes => 'replace_dupes', db_save => 'db_save', db_save_and_stay => 'db_save_and_stay', @@ -226,9 +227,9 @@ my @bad; push(@bad, 'type'), add_alert('missing_type') unless $type; push(@bad, 'title'), add_alert('missing_title') unless $title; - push(@bad, 'slug'), if ($type && !$self->verify_slug_input(slug => $slug, - type => $type, - cat_idx => $cat_idx)); + push(@bad, 'slug'), if ($type && !$self->process_slug_input(slug => $slug, + type => $type, + cat_idx => $cat_idx)); push(@bad, 'category_id'), add_alert('missing_category') unless $category_id; push(@bad, 'cover_date'), add_alert('missing_cover_date') unless $cover_date; @@ -246,13 +247,14 @@ # is it a dup? if ($@ and ref($@) and $@->isa('Krang::Story::DuplicateURL')) { - - my $class = pkg('ElementLibrary')->top_level(name => $type); - $self->alert_duplicate_url($@, $class); - return $self->new_story(bad => ['category_id',$class->url_attributes]); + + my $class = pkg('ElementLibrary')->top_level(name => $type); + $self->alert_duplicate_url(error => $@, class => $class); + return $self->new_story(bad => ['category_id',$class->url_attributes]); + } elsif ($@) { - # rethrow - die($@); + # rethrow + die($@); } # save it @@ -305,7 +307,7 @@ # is it a dup? if ($@ and ref($@) and $@->isa('Krang::Story::DuplicateURL')) { - $self->alert_duplicate_url($@, $story->class); + $self->alert_duplicate_url(error => $@, class => $story->class); return $self->edit; } elsif ($@ and ref($@) and $@->isa('Krang::Story::MissingCategory')) { add_alert('missing_category_on_save'); @@ -713,6 +715,82 @@ +=item replace_dupes + +This mode gathers a list of stories whose URLs duplicate the current story's +and either removes them from the conflicting locations, or - if all their +locations conflict - deletes them entirely. + +=cut + +sub replace_dupes { + my $self = shift; + my $query = $self->query; + my $story = $session{story}; + + # grab list of dupes from session + my @dupes = @{$session{KRANG_PERSIST}{DUPE_STORIES}->{DUPES}}; + + # turn them into a hash of arrayrefs like ( ID1 => [url1, url2, ..] ) + my %dupes; + foreach (@dupes) { push @{$dupes{$_->{id}}}, $_->{url}; } + + # grab the story objects, and make sure we can modify them! + my @dupe_stories; + my %admin_perms = pkg('Group')->user_admin_permissions(); + my $may_checkin_all = $admin_perms{may_checkin_all}; + + foreach my $id (keys %dupes) { + my ($dupe_story) = Krang::Story->find(story_id => $id); + if ($dupe_story->checked_out && $dupe_story->checked_out_by ne $ENV{REMOTE_USER}) { + if ($may_checkin_all && $dupe_story->may_edit) { + $dupe_story->checkin; + } else { + add_alert('dupe_story_checked_out', id => $id, url => $dupes{$id}[0]); + foreach (@dupe_stories) { $_->checkin }; # undo our checkouts + return 0; + } + } else { + $dupe_story->checkout; + } + push @dupe_stories, $dupe_story; + } + + # now we have everything safely in our hands, so make changes! + foreach my $dupe_story (@dupe_stories) { + my @all_cats = $dupe_story->categories; + my @dupe_urls = @{$dupes{$dupe_story->story_id}}; + + # if every one of the dupe story's URLs is a dupe.... + if (@all_cats == @dupe_urls) { + # delete it entirely + add_message('dupe_story_deleted', id => $dupe_story->story_id); + $dupe_story->checkin; + $dupe_story->delete; + } else { + # otherwise, replace full list of cats with list of non-dupe cats + my %dupe_cats; + my $dupe_slug = $dupe_story->slug; + foreach my $dupe_url (@dupe_urls) { + my ($cat_url, $slug) = ($dupe_url =~ /^(.*)$dupe_slug$/); + $dupe_cats{$cat_url} = 1; + } + my @safe_cats = grep { !$dupe_cats{$_->url} } @all_cats; + $dupe_story->categories(@safe_cats); + $dupe_story->save; + $dupe_story->checkin; + add_message('dupe_story_modified', id => $dupe_story->story_id); + } + } + + # at this point we've succeeded, so re-submit user's failed query + my $last_query = $session{KRANG_PERSIST}{DUPE_STORIES}->{QUERY}; + foreach (keys %$last_query) { $self->query->param($_ => $last_query->{$_}) } + delete $session{KRANG_PERSIST}{DUPE_STORIES}; + my $rm = $self->query->param('rm'); + $self->$rm; +} + =item db_save This mode saves the story to the database and leaves the story editor, @@ -734,7 +812,7 @@ # is it a dup? if ($@ and ref($@) and $@->isa('Krang::Story::DuplicateURL')) { - $self->alert_duplicate_url($@, $story->class); + $self->alert_duplicate_url(error => $@, class => $story->class); return $self->edit; } elsif ($@ and ref($@) and $@->isa('Krang::Story::MissingCategory')) { add_alert('missing_category_on_save'); @@ -779,7 +857,7 @@ # is it a dup? if ($@ and ref($@) and $@->isa('Krang::Story::DuplicateURL')) { - $self->alert_duplicate_url($@, $story->class); + $self->alert_duplicate_url(error => $@, class => $story->class); return $self->edit; } elsif ($@ and ref($@) and $@->isa('Krang::Story::MissingCategory')) { add_alert('missing_category_on_save'); @@ -861,7 +939,7 @@ # is it a dup? if ($@ and ref($@) and $@->isa('Krang::Story::DuplicateURL')) { - $self->alert_duplicate_url($@, $story->class); + $self->alert_duplicate_url(error => $@, class => $story->class); return $self->edit; } elsif ($@ and ref($@) and $@->isa('Krang::Story::MissingCategory')) { add_alert('missing_category_on_save'); @@ -1122,10 +1200,10 @@ push(@bad, 'cover_date'), add_alert('missing_cover_date') unless $cover_date; push(@bad, 'slug') - unless $self->verify_slug_input(slug => $slug, - story => $story, - cat_idx => $cat_idx, - categories => [$story->categories]); + unless $self->process_slug_input(slug => $slug, + story => $story, + cat_idx => $cat_idx, + categories => [$story->categories]); # return to edit mode if there were problems return $self->edit(bad => \@bad) if @bad; @@ -1224,7 +1302,8 @@ if ($self->update_categories(query => $query, story => $story, categories => \@categories)) { - add_message('set_primary_category', url => $url); + add_message('set_primary_category', url => $url); + $query->delete('category_to_replace_id'); # put replace button on new primary } return $self->edit(); @@ -1316,30 +1395,33 @@ # shuffle list of categories to remove the deleted my (@categories, @urls); foreach my $cat ($story->categories()) { - if ($delete_ids{$cat->category_id}) { - push(@urls, $cat->url); - } else { - push(@categories, $cat); - } + if ($delete_ids{$cat->category_id}) { + push(@urls, $cat->url); + if ($query->param('category_to_replace_id') == $cat->category_id) { + $query->delete('category_to_replace_id'); # reset replace button + } + } else { + push(@categories, $cat); + } } - + # and, assuming update_categories() succeeds... if ($self->update_categories(query => $query, story => $story, categories => \@categories)) { - - # put together a reasonable summary of what happened - if (@urls == 0) { - add_alert('deleted_no_categories'); - } elsif (@urls == 1) { - add_message('deleted_a_category', url => $urls[0]); - } else { - add_message('deleted_categories', - urls => join(', ', @urls[0..$#urls-1]) . - ' and ' . $urls[-1]); - } + + # put together a reasonable summary of what happened + if (@urls == 0) { + add_alert('deleted_no_categories'); + } elsif (@urls == 1) { + add_message('deleted_a_category', url => $urls[0]); + } else { + add_message('deleted_categories', + urls => join(', ', @urls[0..$#urls-1]). + ' and '.$urls[-1]); + } } - + return $self->edit(); } @@ -1751,11 +1833,15 @@ } } + # if there's only one story, grab it so we can check access + my ($single_story) = Krang::Story->find(story_id => $story_ids[0]) + if (@story_ids) == 1; + # explain our actions to user - if (@story_ids == 1) { + if ((@story_ids == 1) && $single_story->may_edit()) { %victims ? - add_message('selected_story_stolen', id => $story_ids[0], victim => (keys %victims)[0]) : - add_message('selected_story_yours', id => $story_ids[0]); + add_message('one_story_stolen_and_opened', id => $story_ids[0], victim => (keys %victims)[0]) : + add_message('one_story_yours_and_opened', id => $story_ids[0]); } elsif (@owned_ids && !@stolen_ids) { add_message('all_selected_stories_yours'); } else { @@ -1771,9 +1857,9 @@ } } - # if user selected one story, open it for editing - if (@story_ids == 1) { - ($session{story}) = pkg('Story')->find(story_id => $story_ids[0]); + # if user selected one story, hopefully we can open it for editing + if ((@story_ids == 1) && ($single_story->may_edit)) { + ($session{story}) = $single_story; return $self->edit; } else { # otherwise send user to Workspace my $url = "workspace.pl"; @@ -1903,32 +1989,48 @@ my $old_slug = $story->slug || ''; my $new_slug = $query->param('slug') || ''; if ($new_slug ne $old_slug) { - - # is new slug valid? will it build unique URLs with the unchanged categories? - if (!$self->verify_slug_input(slug => $new_slug, - story => $story, - cat_idx => $query->param('cat_idx') || 0, - categories => \@unchanged_cats)) { - add_alert('new_slug_prevented_category_change'); - return 0; - } + + # is new slug valid? will it build unique URLs with the unchanged categories? + if (!$self->process_slug_input(slug => $new_slug, + story => $story, + cat_idx => $query->param('cat_idx') || 0, + categories => \@unchanged_cats)) { + add_alert('new_slug_prevented_category_change'); + return 0; + } } # slug is safe on current cats, so now let's try the new cats eval { $story->categories(@new_cats) }; - return 1 unless $@; + if (!$@) { - # throw any errors - if (ref($@) and $@->isa('Krang::Story::DuplicateURL')) { - $self->alert_duplicate_url_on_add_category($@, \@added_cats); - $story->categories(@old_cats); + # success! + return 1; + + } else { + + # failure... + if (ref($@) and $@->isa('Krang::Story::DuplicateURL')) { + + $self->alert_duplicate_url(error => $@, class => $story->class, added_cats => \@added_cats); + eval { $story->categories(@old_cats) }; + + # if slug has changed, even the old categories may fail... + if (@$ && ($new_slug ne $old_slug)) { + $story->slug($old_slug); # revert slug just long + $story->categories(@old_cats); # enough to load old URLs + $story->slug($new_slug); # and return user to Edit + } + + # in either case - return failure return 0; - } else { + } else { die ($@); + } } } -sub verify_slug_input { +sub process_slug_input { my ($self, %args) = @_; @@ -1951,23 +2053,30 @@ } elsif ($slug_required && !$slug) { add_alert('missing_slug'); return 0; - } elsif ($story && @categories && ($story->slug ne $slug)) { + } if ($story && ($story->slug ne $slug)) { - # store old slug/cats in case we need to revert + # and if we've been given categories to check against new slug... + if (@categories) { + + # store old slug/categories in case we need to revert my $old_slug = $story->slug; my @old_cats = $story->categories; - + # try out new slug on category list to see if it causes any dupes $story->slug($slug); - eval { $story->categories(@categories) }; - if ($@ and ref($@) and $@->isa('Krang::Story::DuplicateURL')) { - $self->alert_duplicate_url($@, $story->class); - $story->slug($old_slug); - $story->categories(@old_cats); - return 0; + eval { $story->categories(@categories) }; + if ($@ and ref($@) and $@->isa('Krang::Story::DuplicateURL')) { + $self->alert_duplicate_url(error => $@, class => $story->class); + $story->slug($old_slug); + $story->categories(@old_cats); + return 0; } elsif ($@) { - die ($@); + die ($@); } + } else { + # even if we're not checking categories, update slug + $story->slug($slug); + } } # success return 1; @@ -1975,47 +2084,60 @@ sub alert_duplicate_url { - my ($self, $dupe, $class) = @_; + my ($self, %args) = @_; + + my $class = $args{class}; + my $error = $args{error}; + my $added_cats = $args{added_cats}; + + # if we're adding a category, get its URL + # (currently GUI only allows one add at a time) + my $new_cat = $added_cats && $added_cats->[0]->url; - # figure out how our story builds the URL (to remind user) - my $which = join(' or ', - join(', ', $class->url_attributes), - "site/category"); + # figure out how our story builds its URL (to remind user) + my $url_attributes = join(' and ', + join(', ', $class->url_attributes), + "site/category"); - # load clashing story/category, and add alert - if ($dupe->story_id) { - add_alert('duplicate_url', - story_id => $dupe->story_id, - url => $dupe->url, - which => $which); - } elsif ($dupe->category_id) { - add_alert('category_has_url', - category_id => $dupe->category_id, - url => $dupe->url, - which => $which); - } else { - croak ("DuplicateURL didn't include story_id OR category_id"); - } -} + # find clashing stories/categories, and add alert + if ($error->stories) { + + # dupe story alerts get a special easily-readable table of IDs/URLs; we build the rows here + my $dupes = join ('', map { sprintf(qq{<tr> <td> %d </td> <td> <a href="%s">%s</a> </td> </tr>}, + $_->{id}, 'http://'.$_->{url}, $_->{url}) } @{$error->stories}); -sub alert_duplicate_url_on_add_category { + # and we throw (using $s for plural messages, $q for quotes, $f for form)... + my $s = @{$error->stories} > 1 ? 's' : ''; + my $f = $self->query->param('returning_from_root') ? 'edit' : 'new_story'; + add_alert('duplicate_url_table', dupe_rows => $dupes, q => '"', form => $f); + + # message differs slightly when dupe is caused by adding a new category + $new_cat ? + add_alert('duplicate_url_on_add_cat', cat => $new_cat) : + add_alert("duplicate_url$s", attributes => $url_attributes); - my ($self, $dupe, $added_cats) = @_; - my $new_cat_url = join(' & ', map { $_->{url} } @{$added_cats}); + # finally, store dupes & query in session hash in case a subsequent replace_dupes() needs them + $session{KRANG_PERSIST}{DUPE_STORIES}->{DUPES} = $error->stories; + $session{KRANG_PERSIST}{DUPE_STORIES}->{QUERY} = { map { $_ => $self->query->param($_) } $self->query->param }; + + } elsif ($error->categories) { - if ($dupe->story_id) { - add_alert('duplicate_url_on_add_category', - story_id => $dupe->story_id, - url => $dupe->url, - category_url => $new_cat_url); - } elsif ($dupe->category_id) { - add_alert('category_has_url_on_add_category', - category_id => $dupe->category_id, - url => $dupe->url, - category_url => $new_cat_url); + # a simpler, non-overwritable alert is thrown when a story URL conflicts with a category... + $new_cat ? + add_alert('category_has_url_on_add_cat', + id => $error->categories->[0]->{id}, # adding a cat can cause at + url => $error->categories->[0]->{url}, # most one new duplicate URL + cat => $new_cat) : + add_alert('category_has_url', + ids => join(', ', map { $_->{id} } @{$error->categories}), + urls => join(', ', map { $_->{url} } @{$error->categories}), + s => @{$error->categories} > 1 ? 's' : '', # plural + attributes => $url_attributes); } else { - croak ("DuplicateURL didn't include story_id OR category_id"); + croak ("DuplicateURL didn't include stories OR categories"); } + + return 1; } sub make_sure_story_is_still_ours { Modified: trunk/krang/lib/Krang/Story.pm =================================================================== --- trunk/krang/lib/Krang/Story.pm 2007-10-31 05:43:03 UTC (rev 4233) +++ trunk/krang/lib/Krang/Story.pm 2007-10-31 05:49:55 UTC (rev 4234) @@ -18,7 +18,7 @@ # setup exceptions use Exception::Class - 'Krang::Story::DuplicateURL' => { fields => [ 'story_id', 'category_id', 'url' ] }, + 'Krang::Story::DuplicateURL' => { fields => [ 'stories', 'categories' ] }, 'Krang::Story::MissingCategory' => { fields => [ ] }, 'Krang::Story::NoCategoryEditAccess' => { fields => [ 'category_id' ] }, 'Krang::Story::NoEditAccess' => { fields => [ 'story_id' ] }, @@ -798,32 +798,34 @@ sub _verify_unique { my $self = shift; my $dbh = dbh; - - # first look for a story with the same URL - my @urls = $self->urls; + + my @urls = $self->urls; return unless @urls; + # first - unless we're a category index - make sure no categories have one of our URLs + if ($self->{slug}) { + my $query = 'SELECT category_id, url FROM category WHERE ('. + join(' OR ', ('url = ?') x @urls) . ')'; + my $result = $dbh->selectall_arrayref($query, undef, map { $_.'/' } $self->urls); + if ($result && @$result) { + my @dupes = map { { id => $_->[0], url => $_->[1] } } @$result; + Krang::Story::DuplicateURL->throw(message => "Category has our URL", + categories => \@dupes); + } + } + + # then look for stories that have one of our URLs my $query = 'SELECT story_id, url FROM story_category WHERE ('. join(' OR ', ('url = ?') x @urls) . ')' . ($self->{story_id} ? ' AND story_id != ?' : ''); - my ($dup_id, $dup_url) = $dbh->selectrow_array($query, undef, $self->urls, - ($self->{story_id} ? - ($self->{story_id}) : ())); - # throw exception on dup - Krang::Story::DuplicateURL->throw(message => "Duplicate URL", - story_id => $dup_id, - url => $dup_url) - if $dup_id; - - # then - unless we're a category index - make sure no category has our URL! - if ($self->{slug}) { - $query = 'SELECT category_id, url FROM category WHERE ('. - join(' OR ', ('url = ?') x @urls) . ')'; - ($dup_id, $dup_url) = $dbh->selectrow_array($query, undef, map { $_.'/' } $self->urls); - Krang::Story::DuplicateURL->throw(message => "Category has our URL", - category_id => $dup_id, - url => $dup_url) - if $dup_id; + my $result = $dbh->selectall_arrayref($query, undef, $self->urls, + ($self->{story_id} ? + ($self->{story_id}) : ())); + if ($result && @$result) { + my @dupes = map { { id => $_->[0], url => $_->[1] } } @$result; + @dupes = sort { $a->{id} > $b->{id} ? 1 : # sort dupes by ID and then URL + ($a->{id} < $b->{id} ? -1 : $a->{url} cmp $b->{url}) } @dupes; + Krang::Story::DuplicateURL->throw(message => "Duplicate URL", stories => \@dupes); } } Modified: trunk/krang/t/story.t =================================================================== --- trunk/krang/t/story.t 2007-10-31 05:43:03 UTC (rev 4233) +++ trunk/krang/t/story.t 2007-10-31 05:49:55 UTC (rev 4234) @@ -298,6 +298,7 @@ # make another copy, this should result in a slug ending in _copy2 my $copy2; + eval { $copy2 = $story->clone() }; ok(not $copy2->story_id); ok($copy2->story_uuid ne $story->story_uuid); |