Krang::Story - the Krang story class
# create a new story
$story = pkg('Story')->new(title => "Foo",
slug => 'foo',
class => 'article',
categories => [10, 20]);
# basic setable fields
$story->title("Life is very long");
$story->slug("life");
$story->cover_date(Time::Piece->strptime("1/1/2004 12:00", "%D %R"));
# get the root element for this story my $element = $story->element();
# add contributors $story->contribs(@contribs);
# find some stories about Sam
my @stories = pkg('Story')->find(title_like => '%sam%');
# load a single story by id
my ($story) = pkg('Story')->find(story_id => 1);
# load a group of stories by id
my ($story) = pkg('Story')->find(story_ids => [1, 20, 30, 100]);
# save a story, incrementing version $story->save();
# check it in, now other people can check it out $story->checkin();
# checkout the story, no one else can edit it now $story->checkout();
# revert to version 1 $story->revert(1);
# get list of stories linked to from this story my @linked_stories = $story->linked_stories;
# get list of media linked to from this story my @linked_media = $story->linked_media;
This class provides methods to operate on story objects. A story
contains some story-specific data (title, cover date, etc.) and an
element tree rooted in element, an object of the Krang::Element
class.
Stories may be associated with contributors (objects of Krang::Contrib) and assigned scheduled actions (publish and expire).
Stories are checked-in, checked-out and versioned like media (Krang::Media) and templates (Krang::Template). However, unlike media and templates, they may also be moved to desks (Krang::Desk).
Stories may be assigned to multiple categories. However, one category is the primary category and determines the primary URL.
Story objects are composed of the following attributes. Unless
otherwise noted all attributes are accessible via standard
accessor/mutators. For example, the title attribute can be set
with:
$story->title("New title here");
And accessed with:
$title = $story->title();
If an attribute is marked (readonly) then its value cannot be set.
For example, you may not set checked_out directly; instead, call
the checkout() method.
story_id (readonly)
story_uuid (readonly)
Unique ID for stories, valid across different machines when the story is moved via krang_export and krang_import.
title
slug
notes
cover_date
A Time::Piece object representing an arbitrary cover date.
publish_date
A Time::Piece object containing the date and time this story was last published.
desk_id
Returns the ID of the Krang::Desk that the story is currently on,
if any. Also see $story->move_to_desk() below.
version (readonly)
The current version of the story. Starts at 1, incremented every time the story is saved.
published_version (readonly)
Returns the version of the story that is currently published on the website.
Returns 0 if the story has never been published.
preview_version (readonly)
Returns the version of the story that has most recently been previewed.
Returns 0 if the story has never been previewed.
category (readonly)
$story->category(level => $integer)
$story->category(level => $integer, dir_only => 1)
$story->category(depth_only => 1)
Without arguments returns the primary category for the story.
undef until at least one category is assigned. This is just a
convenience method that returns the first category in categories.
Given the level argument, it returns the corresponding ancestor
category, level zero signifying the root category, level one the first
level category below the root category and so on. If the specified
category does not exist, returns undef.
If dir_only is true, the category's 'dir' property will be returned
If the category does not exist, returns undef.
Given the depth_only argument, returns the depth of the story's
primary category, zero signifying that the story primarily lives in
the root category.
url (readonly)
The primary URL for the story. undef until at least one category
is assigned.
preview_url (readonly)
The primary preview URL for the story. undef until at least one
category is assigned.
categories
A list of category objects associated with the story. The first category in this list is the primary category.
This attribute may be assigned with category_ids or Krang::Category objects. Only objects will be returned.
This attribute may be set with a list or an array-ref. For example:
# same result $story->categories(1024, 1028); $story->categories([1024, 1028]);
But a list of objects is always returned:
@categories = $story->categories;
This method may throw a Krang::Story::DuplicateURL exception if you
add a new category and it generates a duplicate URL. When this
exception is thrown the category list is still changed and you may
continue to operate on the story. However, if you try to call save()
you will receive the same exception.
urls (readonly)
A list of URLs for this story, in order by category.
preview_urls (readonly)
A list of preview URLs for this story, in order by category.
contribs
A list of contributor objects associated with the story. See the
contribs() method for interface details.
element (readonly)
The root element for this story. The children of this element contain the content for the story.
class (readonly)
The element class of the root element. This may be set with a string
or a Krang::ElementClass object, but only through new(). After new()
this method returns the class object for the root element, a
descendent of Krang::ElementClass. Another way to access the same
object is object is through $story->element->class, but calling
$story->class avoids loading the element tree for the story if
it hasn't already been loaded.
checked_out (readonly)
checked_out_by (readonly)
hidden (readonly)
Whether or not the story is by default hidden from find(). This is
determined by the story class, and set in
Krang::ElementClass::TopLevel.
$story = Krang::Story->new(class => 'Article', categories => [$cat, 1024, 1034], slug => "foo", title => "Foo")
Creates a new story object. Any object attribute listed can be set in
new(), but class, categories, slug and title are all
required. After this call the object is guaranteed to be in a valid
state and may be saved immediately with save().
Will throw a Krang::Story::DuplicateURL exception with a story_id
or category_id field if saving this story would conflict with an
existing story or category.
Called with no arguments, returns a list of contributor
(Krang::Contrib) objects. These objects will have
selected_contrib_type set according to their use with this story
object.
May be set two ways. First, a contributor may specified as a two-key hash containing the contrib_id and the contrib_type_id for the contributor. A single contributor can be present in the list multiple times with different contrib_type_ids.
Second, a list of contributor objects with selected_contrib_type() set
may be passed in.
clear_contribs()
Removes all contributor associatisons.
$all_version_numbers = $story->all_versions();
Returns an arrayref containing all the existing version numbers for this story.
$story->prune_versions(number_to_keep => 10);
Deletes old versions of this story. By default prune_versions() keeps
the number of versions specified by SavedVersionsPerStory in krang.conf;
this can be overridden as above. In either case, it returns the number of
versions actually deleted.
$story->save()
$story->save(keep_version => 1, no_history => 1, no_verify_checkout => 1)
Save the story to the database. This is the only call which
will make permanent changes in the database (checkin/checkout make
transient changes). Increments the version number unless called with
keep_version is true. Add appropriate entries to the history
and story_version tables unless no_history is true.
If the story is not checked out by the user attempting the save, then
an error will be thrown unless no_verify_checkout is true.
Will throw a Krang::Story::DuplicateURL exception with a story_id field
if saving this story would conflict with an existing story or category.
Will throw a Krang::Story::MissingCategory exception if this story
doesn't have at least one category. This can happen when a clone()
results in a story with no categories.
Will throw a Krang::Story::NoCategoryEditAccess exception if the
current user doesn't have edit access to the primary category set for
the story.
Will throw a Krang::Story::NoEditAccess exception if the current user
doesn't have edit access to the story.
@stories = Krang::Story->find(title => "Turtle Soup")
@story_ids = Krang::Story->find(title => "Turtle Soup", ids_only => 1)
$count = Krang::Story->find(title => "Turtle Soup", count => 1)
Finds stories in the database based on supplied criteria.
Fields may be matched using SQL matching. Appending ``_like'' to a field name will specify a case-insensitive SQL match. For example, to match a sub-string inside title:
@stories = pkg('Story')->find(title_like => '%' . $search . '%');
Notice that it is necessary to surround terms with '%' to perform sub-string matches.
Available search options are:
Search by title.
Search by slug.
Search by url.
Search by primary url.
Search by non-primary url.
Find stories by category.
Find stories by category, looking only at the primary location.
Find stories by site.
Find stories by site, looking only at the primary location.
Find stories by performing a full-text search. (Any words enclosed in quotes will be matched as a complete string, and whitespace around the edges of a quoted phrase will cause partial-word matches to be ignored.)
Set to 0 to find only non-checked-out stories. Set to 1 to find only
checked out stories. The default, undef returns all stories.
Set to a user_id to find stories checked-out by a user.
Set to a contributor_id to find stories associated with that contributor. NOT IMPLEMENTED
Set to a story_id to find stories that link to a specified story. NOT IMPLEMENTED
Set to a media_id to find stories that link to a specified media object. NOT IMPLEMENTED
May be either a single date (a Time::Piece object) or an
array of dates specifying a range. In ranges either member may be
undef, specifying no limit in that direction.
May be either a single date (a Time::Piece object) or an
array of dates specifying a range. In ranges either member may be
undef, specifying no limit in that direction.
Set this to an arrayref of element class name(s) to find stories of
the corresponding type(s) only.
This performs a simple search against contributors and finds stories which link to the contributor.
Load a story by ID. Given an array of story IDs, loads all the identified stories.
Load a story by UUID. Given an array of story UUIDs, loads all the identified stories.
Combined with story_id (and only story_id), loads a specific
version of a story. Unlike revert(), this object has version
set to the actual version number of the loaded object.
Performs a per-word LIKE match against title and URL, and an exact match against story_id if a word is a number.
If set to 1 and passed along with a simple_search, also performs a full-text search on the input.
Pass an array ref of IDs to be excluded from the result set
Pass an array ref of UUIDs to be excluded from the result set
Returns stories in the category and in categories below as well.
Returns stories in the category and in categories below as well, looking only at primary category relationships.
If set to 0, returns stories that are not published, set to 1 returns published stories.
This performs a simple search against users and finds stories created by matching users.
If set to 1 then only items which the current user has at least read permissions to are returned. Defaults to 0.
If set to 1 then only items which the current user has edit permissions to are returned. Defaults to 0.
This find option allows you to search againt indexed element data. For details on element indexing, see Krang::ElementClass. This option should be set with an array containing the element name and the value to match against. For example, to search for stories containing 'foo' in their deck, assuming deck is an indexed element:
@stories = pkg('Story')->find(element_index_like => [deck => '%foo%']);
Options affecting the search and the results returned:
Return only IDs, not full story objects.
Return just a count of the results for this query.
Return no more than this many results.
Start return results at this offset into the result set.
Output field to sort by. Defaults to 'story_id'.
Results will be in sorted in ascending order unless this is set to 1 (making them descending).
Include live stories in the search result. Live stories are stories
that are neither retired nor have been moved to the trashbin. Set
this option to 0, if find() should not return live stories. The
default is 1.
Set this option to 1 if you want to include retired stories in the search result. The default is 0.
Set this option to 1 if you want to include trashed stories in the search result. Trashed stories live in the trashbin. The default is 0.
NOTE:When searching for story_id, these three include_* flags are not taken into account!
Returns all stories, not just those where Krang::Story->hidden()
is false.
If you are developing an element set, you may or may not want this
option - See Krang::ElementClass::TopLevel for more information on
hidden().
NOTE: show_hidden is automatically enabled if any of the
following search terms are used: story_id, checked_out,
checked_out_by, class, desk_id, may_see, may_edit.
WARNING - A NOTE TO KRANG DEVELOPERS: Be aware that unless the
above search terms are used, you MUST use this modifier whenever UI
or bin/ scripts make calls to find()!
When true, any permission restrictions of the stories are ignored. WARNING - Be very careful when using this option so that data isn't exposed to the end user. This option should only be used in Admin scripts or actions where the resulting data is not shown to the end user.
Krang::Story->transform_stories(%args)
Transform desired stories. This method is useful for performing bulk transforms of stories. You can do things like add new elements or delete existing elements which makes it really handy for doing element library changes or upgrades.
It takes the following named arguments:
A subroutine that actually performs the translation of the story. It receives that story being transformed and a flag indicating whether or not the story is a live current version (and not a past version).
This subroutine is expected to return the transformed version of the story.
A boolean flag indicating whether or not you want to operate on past versions of stories. If this is false, then you will just be given the current version.
If for some reason a past version of a story cannot be thawed out (this can happen if the element libraries change too drastically) then there's not much that can be done for that version of the story. If this flag is true, then we will delete that version of the story completely from the database.
Any other arguments passed in will be sent to the find() method.
# add a foo element to all stories of the "bar" class
pkg('Story')->transform_stories(
class => ['bar'],
past_versions => 1,
callback => sub {
my %args = @_;
my ( $story, $live ) = @args{ qw( story live ) };
my $element = $story->element;
$element->add_child(class => 'foo', value => 'blah, blah');
return $story;
},
);
$story->move_to_desk($desk_id)
Move story to selected desk. Cannot move it if checked out.
If the story is checked out and thus can't be moved to the desired
desk, throws a Krang::Story::CheckedOut exception.
If the desk has been deleted in the meantime, throws a
Krang::Story::NoDesk exception.
$story->checkout()
Krang::Story->checkout($story_id)
Checkout the story, preventing other users from editing it. Croaks if the story is already checked out.
Krang::Story->checkin($story_id)
$story->checkin()
Checkin the story, allow other users to check it out. This will only fail if the story is not checked out.
$story->mark_as_published()
Mark the story as published. This will update the publish_date and
published_version attributes, and will also check the story back
in, removing it from any desk it's currently on.
This will also make an entry in the log that the story has been published.
$story->mark_as_previewed(unsaved => 1)
Mark the story as previewed. This will update the preview_version
attribute, setting it equal to version. This is used as a sanity
check by Krang::Publisher to prevent re-generation of content.
The argument unsaved defaults to 0. If true, it indicates that the
story being previewed is in the process of being edited, in which case
any previews made cannot be trusted for future use. In that case,
preview_version is set to -1.
Returns the publish path for the story object, using the site's publish_path and the story's URL. This is the filesystem path where the story object will be published.
If a category is not passed the primary category for the story is returned.
Returns the preview path for the story object, using the site's preview_path and the story's URL. This is the filesystem path where the story object will be previewed.
Takes an optional category argument, or will return a url based on the story's primary category.
$story->revert($version)
Creates a new version of this story with identical content to the version passed. (Version numbers always increase, never decrease.) If the new version is successfully written to disk (no duplicate URL errors, etc.), the object itself is returned; if not, an error is returned.
If you want to load an old version directly, see find().
$story->delete()
Krang::Story->delete($story_id)
Krang::Story->delete(story_id => $story_id)
Krang::Story->delete(class => 'article')
Krang::Story->delete(class => [ qw(article redirect) ])
Deletes the specified story/stories from the database.
This is a permanent operation and requires the admin permission may_delete.
Throws a Krang::Story::NoDeleteAccess exception if user may not delete assets. Stories will be checked-out before they are deleted, which will fail if the story is checked out to another user.
$copy = $story->clone()
$copy = $story->clone(category_id => $category_id)
Creates a copy of the story object, with most fields identical except
for story_id and element->element_id which will both be
undef. Also, the copy gets a new story_uuid. It will be checked
out by the current user and it will not be saved.
If no category ID is passed in, a raw clone is assumed: In this case,
the title will be set to ``Copy of $title'' and
$clone->resolve_url_conflict() will be called to provide the clone
with non-conflicting URL(s) derived from the URL(s) of the original.
If a category ID is specified, no further DuplicateURL checks will be performed, and the clone will live in this category.
$story->resolve_url_conflict(append => 'copy')
Resolves a URL conflict between $story and another live story.
If the story has a slug, the string specified in append will be
appended to the story's slug.
Otherwise, the story's category_ids, category_cache and url_cache lists will be cleared.
@linked_stories = $story->linked_stories
Returns a list of stories linked to from this story. These will be Krang::Story objects. If no stories are linked, returns an empty list. This list will not contain any duplicate stories, even if a story is linked more than once.
@linked_media = $story->linked_media
Returns a list of media linked to from this story. These will be Krang::Media objects. If no media are linked, returns an empty list. This list will not contain any duplicate media, even if a media object is linked more than once.
$data = Storable::freeze($story)
Serialize a story. Krang::Story implements STORABLE_freeze() to
ensure this works correctly.
$story = Storable::thaw($data)
Deserialize a frozen story. Krang::Story implements STORABLE_thaw()
to ensure this works correctly.
$story->serialize_xml(writer => $writer, [set => $set, no_elements => 1])
Serialize as XML. See Krang::DataSet for details.
$story = Krang::Story->deserialize_xml(xml => $xml, set => $set, no_update => 0)
Deserialize XML. See Krang::DataSet for details.
If an incoming story has the same primary URL as an existing story then an update will occur, unless no_update is set.
$story->retire()
Krang::Story->retire(story_id => $story_id)
Retire the story, i.e. remove it from its publish/preview location and don't show it on the Find Story screen. Throws a Krang::Story::NoEditAccess exception if user may not edit this story. Croaks if the story is checked out by another user.
$story->unretire()
Krang::Story->unretire(story_id => $story_id)
Unretire the story, i.e. show it again on the Find Story screen, but don't republish it. Throws a Krang::Story::NoEditAccess exception if user may not edit this story. Throws a Krang::Story::DuplicateURL exception if a story with the same URL has been created in Live. Croaks if the story is checked out by another user.
$story->trash()
Krang::Story->trash(story_id => $story_id)
Move the story to the trashbin, i.e. remove it from its publish/preview location and don't show it on the Find Story screen. Throws a Krang::Story::NoEditAccess exception if user may not edit this story. Croaks if the story is checked out by another user.
$story->untrash()
Krang::Story->untrash(story_id => $story_id)
Restore the story from the trashbin, i.e. show it again on the Find Story screen or Retired Stories screens (depending on the location from where it was deleted). Throws a Krang::Story::NoRestoreAccess exception if user may not edit this story. Croaks if the story is checked out by another user. This method is called by Krang::Trash->restore().
$story->wont_publish()
Convenience method returning true if story has been retired or trashed.
$story->turn_into_category_index(category => $category)
$story->turn_into_category_index(category => $category, steal => 1)
Convenience method to resolve URL conflicts when creating a $category whose 'dir' property equals the slug of $story.
Turns the slug-provided $story into a slugless index page of the specified $category.
Returns undef if user can't check out the story.
When specifiying the flag steal, stories checked out by another user will be stolen if we have the necessary user admin permission 'may_checkin_all'. Otherwise returns undef.