=head1 Customizing the Publish Process in Krang =over =item * Introduction =item * Why Customize Publish Behavior in the Element Library =item * Changing the data returned by an element =over =item * template_data() =item * Story and Media Links =back =item * Changing how an element populates a template =over =item * fill_template() =item * Sample Element =item * Sample Templates =item * Option 1 - Make Small Changes, Let Krang Finish Template Population =over =item * Example - Adding one variable =back =item * Option 2 - Populating the Template Manually =over =item * Example 1 - A Single Variable =item * Example 2 - Element Children =item * Example 3 - Adding Contributors =item * Example 4 - Passing Parameters to Child Elements =over =item * fill_template_args =item * Handling fill_template_args When Overriding fill_template() =item * A Note on Passing Parameters =back =back =back =item * Generating Additional Content =over =item * Example - Creating an RSS File =item * Example - Generating a Wall Page =back =item * Changing How an Element Chooses a Template =over =item * Loading a Template =back =item * Changing the Publish Process for an Element =over =item * Preventing an Element from Publishing =item * Forcing Publish Without a Template =back =item * Conclusion =back =head1 Introduction This document covers the concept of customizing the publish process by making changes to the element library. It assumes that you've already got an understanding of templates and element libraries in Krang. It's a good idea to have read the following documents before going any further: =over =item * HREF[Writing HTML::Template Templates in Krang|writing_htmltemplate.html] =item * Creating an Element Library (TBD - Sam) =item * The POD for the CPAN modules L and L =item * The API documentation (L, L, L, L, L) =back =head1 Why Customize Publish Behavior in the Element Library Out of the box, Krang populates element templates according to a fixed set of rules. The standard publish process should be sufficient for publication of most sites. That being said, choices in how data is returned and how data is organized in the templates have been made - if these choices don't work with what you're attempting to accomplish, your next step is to change the behavior of the elements themselves. =head1 Changing the data returned by the element The simplest thing to change in an element is the form the element's data takes when returned. With a few exceptions (see below) elements return data in the same form as it was when stored. The advantage to this technique is that the results will be seen, regardless of whether or not a template is used. =over =item * template_data() This returns the data stored in the element. In most cases, it's the actual data stored in the element, unformatted. In the case of L or L objects, C will return the fully-qualified URL of the object. =back Suppose you wanted all header elements to return their data in all-caps when published, regardless of how they were entered into the system. At the same time, you don't want to actually make that change to the content itself, in case you change your mind later. Overriding C in your element library's header.pm as follows will do the trick: sub template_data { my $self = shift; my %args = @_; my $element = $args{element}; return uc($element->data()); } =head2 Story and Media Links Elements that handle links to Stories and Media need to be handled a little bit differently - they need to return the URL of the object being pointed to, rather than the data itself, and they need to return a URL that's consistent with the current output mode - publish or preview. Keep this in mind if you consider changing the behavior for either of these two. Here is how template_data() currently works for elements using L - sub template_data { my $self = shift; my %args = @_; my $element = $args{element}; if ($args{publisher}->is_publish()) { return 'http://' . $element->data()->url(); } elsif ($args{publisher}->is_preview()) { return 'http://' . $element->data()->preview_url(); } else { croak (__PACKAGE__ . ': Not in publish or preview mode. Cannot return proper URL.'); } } In short, it queries the publisher (C<$args{publisher}>) to determine if the mode is publish or preview (returning an error if it's neither). C<$element->data()> returns a L object (if this was L, it would be a L object). Depending on the mode. the appropriate URL is returned. =head1 Changing how an element populates a template The next option is more ambitious - changing how an element goes about populating the variables in a template. At this point, you have two options - you can piggyback your changes on top of the work that Krang does, or you can choose to do it all yourself. =over =item * fill_template() fill_template() is responsible for filling the template object with data built from the element tree. Generally, it traverses the element tree, creating scalars and loops on an as-needed basis, populating the template objects with the results. The rules by which fill_template() operates can be found in the section HREF[How Krang Builds Template Data|writing_htmltemplate.html#how%20krang%20builds%20template%20data] in HREF[Writing HTML::Template Templates in Krang|writing_htmltemplate.html]. If you are familiar with Bricolage, fill_template() functions using the same rules as the autofill() functionality found in Bricolage. =back =head2 Option 1 - Make Small Changes, Let Krang Finish Template Population With the object hierarchy Krang provides, you can make small additions to fill_template() and then let Krang pick things up from there by calling the parent method's C. =head3 Sample Element For these examples, we will be using the following Story element: Story - Deck (subclass of Krang::ElementClass::Text) + Page (subclass of Krang::ElementClass) - Header (subclass of Krang::ElementClass::Text) - Paragraph (subclass of Krang::ElementClass::TextArea) - Pull Quote (subclass of Krang::ElementClass::Text) - Paragraph (subclass of Krang::ElementClass::TextArea) =head3 Sample Templates The story element will use the following templates: =head4 Story.tmpl <tmpl_var title>

=head4 Page.tmpl

=head3 Example - Adding one variable As a simple example, we want to add a variable C to the page template. This can be done by overriding C in the article element, and adding a variable C to the template. Other than this variable, the template should be populated as usual. =head4 The new method This is the new C that would be used in the Page element: sub fill_template { my $self = shift; my %args = @_; my $template = $args{tmpl}; $template->param(greeting => 'Hello World!'); return $self->SUPER::fill_template(@_); } In short, add the variable C to the template, and then call the parent C method that was overridden by this method (passing the original set of parameters along). The rest of the publish process is unaffected, and nothing will be noticed on output until the article template uses C. =head4 Page.tmpl This new Page template will display the greeting:

The output for Page.tmpl (not the entire story, mind you) will look something like this:

Hello World!

Header Header

paragraph1 paragraph1 paragraph1

Quote Quote Quote

paragraph2 paragraph2 paragraph2

=head2 Option 2 - Populating the Template Manually If you choose to populate the template manually, all the variables that Krang builds no longer apply. Additionally, it will be up to you to build variables based on the child elements of the current element. =head3 Submitted Parameters C takes a set of named parameters - =over =item * publisher The Krang::Publisher object for this publish run. =item * tmpl The HTML::Template object for the template being with this element. =item * element The Krang::Element object currently being published. =back C is expected to return the HTML that results from populating the template. Read the L API documentation for further documentation on the actual interface. =head3 Example 1 - A single variable This example re-uses the example from Option 1 - adding a single variable C to the template. B sub fill_template { my $self = shift; my %args = @_; my $template = $args{tmpl}; $template->param(greeting => 'Hello World!'); return $template->output(); } Where the previous example in option 1 made a call to C<$self->SUPER::fill_template()>, this example simply calls C<$template->output()>. The result is that in this example, with no parent method to do additional work populating the template, the only variable available to the template is C. B The template from the previous example will still work:

By: and , ( )

However, with Krang not providing any additional variables, the template output will look like this:

Hello World!

=head3 Example 2 - Element Children Clearly, the output for the above template isn't what we're looking for - the header is missing, along with the entire element loop. The next step is to add these: B sub fill_template { my $self = shift; my %args = @_; my @element_loop; my %params; my $template = $args{tmpl}; my $element = $args{element}; my $publisher = $args{publisher}; $params{greeting} = 'Hello World!'; # retrieve the list of child elements my @children = $element->children(); foreach my $child (@children) { my $name = $child->name(); my $html = $child->publish(publisher => $publisher); unless (exists($params{$name})) { $params{$name} = $html; } push @{$params{element_loop}}, { "is_$name" => 1, $name => $html }; } $template->param(%params); return $template->output(); } Make sense? Rather than make a lot of calls to C<$template->param()>, parameters are stored in %params until all work is finished. The loop at the bottom iterates over the list of children (C<@children>), building HTML for each child, and then placing the results in %params - you can see the element_loop being built there as well. The resulting output looks like what we want:

Hello World!

Header Header

paragraph1 paragraph1 paragraph1

Quote Quote Quote

paragraph2 paragraph2 paragraph2

=head3 Example 3 - Adding Contributors Adding contributors here is a straightforward process - a single method call makes it possible: $contrib_loop = $self->_build_contrib_loop(@_); This can be added to C as follows: B sub fill_template { my $self = shift; my %args = @_; my @element_loop; my %params; my $template = $args{tmpl}; my $element = $args{element}; my $publisher = $args{publisher}; $params{greeting} = 'Hello World!'; $params{contrib_loop} = $self->_build_contrib_loop(@_); # retrieve the list of child elements my @children = $element->children(); foreach my $child (@children) { my $name = $child->name(); my $html = $child->publish(publisher => $publisher); unless (exists($params{$name})) { $params{$name} = $html; } push @{$params{element_loop}}, { "is_$name" => 1, $name => $html }; } $template->param(%params); return $template->output(); } With the C now added to the template: B

By: and , ( )

The resulting output will look something like this:

Hello World!

Header Header

By: JR Bob Dobb (Writer, Photographer) and Venus Dee Milo (Illustrator)

paragraph1 paragraph1 paragraph1

Quote Quote Quote

paragraph2 paragraph2 paragraph2

Go back to the HREF[Contributors|writing_htmltemplate.html#contributors] section of HREF[Writing HTML::Template Templates in Krang|writing_htmltemplate.html] for further documentation on using Contributors. =head3 Example 4 - Passing Parameters to Child Elements It may come about that you want to pass information along to a child element for use when it goes through the publish process. This can be done by adding arguments to the named parameters passed to C<$child->publish()>. =head4 fill_template_args If the child element you are calling is still using the C method provided by Krang, you can use the parameter C in the fashion below: foreach my $child ($element->children()) { my %new_args = (greeting => 'Hello World!'); my $html = $child->publish(publisher => $publisher, fill_template_args => \%new_args); $params{$child->name} = $html; } When the child element goes through the publish process, its C method will add C to template, provided the template is looking for a variable C. Keep in mind, you don't need to override C in the child element - this functionality is supported out-of-the-box. Using the same Page.tmpl we started with at the beginning of these examples: B

Rather than override the Page element's C method, we're going to use the one provided by Krang. Instead, we're going to override the C method for the Story element, and have it pass along the greeting to the page element. B sub fill_template { my $self = shift; my %args = @_; my @element_loop; my %params; my $template = $args{tmpl}; my $element = $args{element}; my $publisher = $args{publisher}; my $story = $publisher->story(); $params{title} = $story->title(); # retrieve the list of child elements my @children = $element->children(); foreach my $child (@children) { my $name = $child->name(); my $html = $child->publish(publisher => $publisher, fill_template_args => { greeting => 'Hello World!' }); unless (exists($params{$name})) { $params{$name} = $html; } if ($name eq 'page') { push @{$params{"$name_loop"}}, { $name => $html }; } } $template->param(%params); return $template->output(); } With the Story element passing the C parameter along, you don't need to override the C method in the Page element - the standard method used by Krang will suffice. =head4 Handling fill_template_args When Overriding fill_template() Continuing from the previous example, if we were to override the Page element C method, we'd need to handle the C parameter as well: B sub fill_template { my $self = shift; my %args = @_; my %params; my $template = $args{tmpl}; my $element = $args{element}; my $publisher = $args{publisher}; # Additional template params passed in by the parent element. if (exists($args{fill_template_args})) { foreach my $arg (keys %{$args{fill_template_args}}) { $params{$arg} = $args{fill_template_args}{$arg}; } } # retrieve the list of child elements my @children = $element->children(); foreach my $child (@children) { my $name = $child->name(); my $html = $child->publish(publisher => $publisher); unless (exists($params{$name})) { $params{$name} = $html; } push @{$params{element_loop}}, { "is_$name" => 1, $name => $html }; } $template->param(%params); return $template->output(); } The small block that consists of: if (exists($args{fill_template_args})) { foreach my $arg (keys %{$args{fill_template_args}}) { $params{$arg} = $args{fill_template_args}{$arg}; } } Is the extent of what's needed to handle C. =head4 A Note on Passing Parameters While we use C in the previous examples, you can call $child->publish(publisher => $publisher, any_param_name_you_want => $foo); And the additional parameter(s) will get passed along to C method of the child object. It is up to you, of course, to make use of it on that end - the standard Krang implementation only makes use of C. =head1 Generating Additional Content While publishing a given story, you may want to publish additional files containing data related to the story. For example, an RDF file for syndication, or an XML file containing keywords for search-engines, or an article preview page for subscription purposes. While Krang doesn't provide direct support for such things within the UI, it provides a framework for you to use within your element library, allowing you to build the content in any way you see fit. During the publish process, you can add additional content at any point that you have the C object available. For example: # Write out 'extra.html' in conjunction with this story. my $additional_content = create_sidebar_story(); $publisher->additional_content_block(content => $additional_content, filename => 'extra.html', use_category => 1); At the end of the publish process, Krang will handle the entry in C separately from the main story, and write it to disk as 'extra.html' (or whatever you set C to be). B =over =item * An arbitrary number of additional files can be added created - just be careful filenames do not overlap. =item * You have the option to add (or not add) the current Category templates (e.g. header/footer) to your output. Simply set C to 1 if you want to wrap C<$additional_content> in the category templates, or set it to 0 if you want it to be written to disk as-is. =back B =over =item * No pagination - you can only create single-page files. =item * The file will get written out to the same directory as the story itself - writing to other directories is not supported (L does a lot of work to protect against directory conflicts, don't want to interfere with that). =back For the following examples, we're going to use the following Story element tree: Story - Deck (subclass of Krang::ElementClass::Text) + Page (subclass of Krang::ElementClass) - Header (subclass of Krang::ElementClass::Text) - Paragraph (subclass of Krang::ElementClass::TextArea) - Pull Quote (subclass of Krang::ElementClass::Text) - Paragraph (subclass of Krang::ElementClass::TextArea) - Leadin (subclass of Krang::ElementClass::StoryLink) - Leadin (subclass of Krang::ElementClass::StoryLink) - Leadin (subclass of Krang::ElementClass::StoryLink) + Page (subclass of Krang::ElementClass) - Paragraph (subclass of Krang::ElementClass::TextArea) - Paragraph (subclass of Krang::ElementClass::TextArea) - Pull Quote (subclass of Krang::ElementClass::Text) - Paragraph (subclass of Krang::ElementClass::TextArea) + Page (subclass of Krang::ElementClass) - Paragraph (subclass of Krang::ElementClass::TextArea) - Paragraph (subclass of Krang::ElementClass::TextArea) - Paragraph (subclass of Krang::ElementClass::TextArea) =head2 Example - Creating an RSS File If you aren't familiar with RSS (RDF Site Summary), take a look here: HREF[RDF Site Summary 1.0|http://www.purl.org/rss/] This example uses L, which is not part of Krang - it would have to be installed separately. To generate the RSS file, we're going to override the C method for the Story element to generate the RSS file, and then continue the publish process, adding the RSS file to the final output. sub fill_template { my $self = shift; my %args = @_; my $rss = new XML::RSS; my $publisher = $args{publisher}; my $story = $publisher->story(); $rss->channel(title => $story->title(), link => 'http://' . $story->url(), description => $story->slug() ); foreach my $leadin ($story->linked_stories()) { $rss->add_item(title => $leadin->title(), link => 'http://' . $leadin->url()); } my $rss_output = $rss->as_string(); return $self->SUPER::fill_template(@_) . $publisher->additional_content_block(content => $rss_output, filename => 'rss.xml', use_category => 0); } As you can see, the regular publish work is still done by Krang, in the call at the bottom, C<< $self->SUPER::fill_template(@_) >>. That output is concatenated with the output generated by the XML::RSS module (after being tagged properly by C<< $publisher->additional_content_block() >>), and returned to the Publisher, which will then write the two files out. The C option to C<< Krang::Publisher->additional_content_block() >> tells the Publisher to not combine the output from XML::RSS with any template output from the categories (e.g. headers/footers). While this is a desireable feature sometimes, we don't want to mix HTML templates with XML output in this case. =head2 Example - Generating a Wall Page We want have two goals here - first, we want to build a three-page story out of this tree. Second, we want to create a wall page using the content of the first page, and a wall template. Again, the best angle of attack here will be to work from the Story element - we want to manipulate content depending on which page element we're on, and page elements don't know about eachother. Additionally, we need access to elements that won't be available to Page elements. =head3 fill_template() - Story Element In overriding the C method in the story element, we're going to maintain two separate hashes of parameters - one will be used for the regular story template, the other for the wall page template. sub fill_template { my $self = shift; my %args = @_; my %params; my %wall_params; my $wall_template = $self->_load_template(@_, filename => 'wall.tmpl', search_path => ['/foo/bar']); my $template = $args{tmpl}; my $element = $args{element}; my $publisher = $args{publisher}; my $story = $publisher->story(); $params{title} = $story->title(); $wall_params{title} = $story->title(); # retrieve the list of child elements my @children = $element->children(); foreach my $child (@children) { my $name = $child->name(); my $html = $child->publish(publisher => $publisher, fill_template_args => { greeting => 'Hello World!' }); unless (exists($params{$name})) { $params{$name} = $html; $wall_params{$name} = $html; } if ($name eq 'page') { push @{$params{"$name_loop"}}, { $name => $html }; unless (exists($wall_params{"$name_loop"})) { push @{$wall_params{"$name_loop"}}, { $name => $html }; } } } $template->param(%params); $wall_template->param(%wall_params); my $html = $template->output(); my $wall_html = $wall_template->output(); $html .= $publisher->additional_content_block(filename => 'wall.html', content => $wall_html, use_category => 1 ); return $html; } The end result is the original three-page story, along with a wall.html file. Note - using the approach of the first example, calling C<< $self->SUPER::fill_template() >> to generate the content for the story itself could have been done here as well, but it would have incurred additional overhead, as some elements would have been published multiple times (the first page and all its child elements), creating a performance penalty. While the penalty is negligible in this case, be careful. =head1 Changing How an Element Chooses a Template =over =item * find_template(publisher => $publisher, element => $element); Returns an HTML::Template::Expr object with the template to be used by the element. Follows a defined protocol for locating the template on the filesystem. =back The process by which Krang chooses a publish template for a given element is as follows: =over =item 1) Determine the template name - by default, C< element-name.tmpl >. =item 2) Given a category path of C, start by looking for the template in C. =item 3) If the template is found, attempt to load and parse it. If successful, return an instantiated L object. Otherwise, throw an error (C). =item 4) If the template is not found at C, try C, C, finally C. If the template is found at any point, load the template as seen in step 3. =item 5) If the template cannot be found, throw an error (C). =back You can make changes to this process by overriding C to change what template Krang will look for, where Krang will look for it, or even what kind of template will be loaded. =head2 Loading a Template Loading an L template requires two things - the template filename, and a list of directories to search for the template. sub find_template { my $self = shift; my %args = @_; my $tmpl; my $publisher = $args{publisher}; my $element = $args{element}; my @search_path = $publisher->template_search_path(); my $filename = $element->name() . '.tmpl'; my $template = $self->_load_template(publisher => $publisher, element => $element, filename => $filename, search_path => \@search_path); return $template; } By making changes to either C<$filename> or C<@search_path> (ordered from first to last in terms of directories to search), you can affect what template gets loaded. C<< $self->_load_template() >> handles the actual process of finding, loading, and throwing any required errors for L templates. If you want to change the type of template being loaded (e.g. you don't want to use L templates), you need to roll your own code to find, load and parse the templates, throwing appropriate errors as needed. Be aware that C and C are expecting an L template, so you will need to override C and C functionality as well. =head1 Changing the Publish Process for an Element =over =item * publish(story => $story, category => $category_id) Ties the C and C methods together. Returns publish output for the current element (and therefore, any children beneath it). See L for more info on publish(). =back C acts as the coordinator of the publish process for a given element, making sure that the entire process runs smoothly. It works as follows: =over =item 1) Find a template for the current element using C. =item 2) If no template is found, decide if this is a problem. By default, if an element has no children, C will simply return C<$element->template_data()>. If the element has children, however, it will propegate the C error thrown by C. =item 3) If the template is found, pass it to C. =item 4) Once C is finished, return $template->output(). =back =head2 Preventing an Element from Publishing While Krang, by default, does not call C on any element that is not explicitly included in a template, this may not offer enough protection for elements you don't want published. Overriding publish to return will make sure that an element (and all of its children) will never be published. sub publish { return; } =head2 Forcing Publish Without a Template On the other hand, if you know an element will never have a template (or want to make sure that noone goofs things up by creating a template for that element), you can simplify the publish process greatly (again, no children would get published): sub publish { my $self = shift; my %args = @_; my $element = $args{element}; return $element->template_data(); } =head1 Conclusion This covers the major aspects of customizing the Krang publish process. By overriding the three methods C, C and C, there's a lot that can be done to change how a story publishes itself. At this point, if you want to learn more about how the publish process works, and what can be done, read the POD and the code itself for L and L. Good luck!