Customizing the Publish Process in Krang


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:


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.


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.

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 template_data() 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());
 }

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 Krang::ElementClass::StoryLink -

 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 ($args{publisher}) to determine if the mode is publish or preview (returning an error if it's neither). $element-data()> returns a Krang::Story object (if this was Krang::ElementClass::MediaLink, it would be a Krang::Media object). Depending on the mode. the appropriate URL is returned.


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.

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 fill_template().

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)

Sample Templates

The story element will use the following templates:

Story.tmpl

 <tmpl_loop page_loop>
   <html>
   <head>
     <title><tmpl_var title></title>
   </head>
   <body>
   <tmpl_if __first__>
     <h1><tmpl_var title></h1>
     <b><tmpl_var deck></b>
   </tmpl_if>
     <tmpl_var page>
   </body>
   </html>
   <tmpl_unless __last__>
     <tmpl_var page_break>
   </tmpl_unless>
 </tmpl_loop>

Page.tmpl

 <h2><tmpl_var header></h2>
 <tmpl_loop element_loop>
   <tmpl_if is_paragraph>
     <p><tmpl_var paragraph></p>
   </tmpl_if>
   <tmpl_if is_pull_quote>
     <p><blockquote><i>
       <tmpl_var pull_quote>
     </i></blockquote></p>
   </tmpl_if>
 </tmpl_loop>

IMPORTANT NOTE: Starting in Krang v3.02, developers have the option of using one complex template for each story type rather than multiple simple ones. The procedure for using a single template is beyond the scope of this document but described in Writing HTML::Template Templates in Krang. (The examples in this document all make use of the multiple-template approach.)

Example - Adding one variable

As a simple example, we want to add a variable greeting to the page template. This can be done by overriding fill_template in the article element, and adding a variable publish_time to the template. Other than this variable, the template should be populated as usual.

The new method

This is the new fill_template() 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 greeting to the template, and then call the parent fill_template() 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 publish_time.

Page.tmpl

This new Page template will display the greeting:

 <h1><tmpl_var greeting></h1>
 <h2><tmpl_var header></h2>
 <tmpl_loop element_loop>
   <tmpl_if is_paragraph>
     <p><tmpl_var paragraph></p>
   </tmpl_if>
   <tmpl_if is_pull_quote>
     <p><blockquote><i>
       <tmpl_var pull_quote>
     </i></blockquote></p>
   </tmpl_if>
 </tmpl_loop>

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

 <h1>Hello World!</h1>
 <h2>Header Header</h2>
    <p>paragraph1 paragraph1 paragraph1</p>
    <p><blockquote><i>
       Quote Quote Quote
    </i></blockquote</p>
    <p>paragraph2 paragraph2 paragraph2</p>

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.

Submitted Parameters

fill_template() takes a set of named parameters -

fill_template() is expected to return the HTML that results from populating the template.

Read the Krang::ElementClass API documentation for further documentation on the actual interface.

Example 1 - A single variable

This example re-uses the example from Option 1 - adding a single variable greeting to the template.

fill_template() for the Page element - take 1

 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 $self-SUPER::fill_template()>, this example simply calls $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 greeting.

Page.tmpl

The template from the previous example will still work:

 <h1><tmpl_var greeting></h1>
 <h2><tmpl_var header</h2>
  <!-- Contributors -->
  <tmpl_if contrib_loop>
     By:
     <tmpl_loop contrib_loop>
        <!-- Determine whether we need a comma or an "and" to separate -->
        <tmpl_unless __first__>
         <tmpl_if __last__>
          and
         <tmpl_else>
          ,
         </tmpl_if>
        </tmpl_unless>
        <!-- First Middle Last (Job) -->
        <tmpl_var first> <tmpl_var middle> <tmpl_var last>
        <tmpl_if contrib_type_loop>
           (
           <tmpl_loop contrib_type_loop>
              <tmpl_var contrib_type_name>
              <tmpl_if __last__>)</tmpl_if>
           </tmpl_loop>
        </tmpl_if>
     </tmpl_loop>
  </tmpl_if>
 <tmpl_loop element_loop>
   <tmpl_if is_paragraph>
     <p><tmpl_var paragraph></p>
   </tmpl_if>
   <tmpl_if is_pull_quote>
     <p><blockquote><i>
       <tmpl_var pull_quote>
     </i></blockquote></p>
   </tmpl_if>
 </tmpl_loop>

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

 <h1>Hello World!</h1>
 <h2></h2>

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:

fill_template() for the Page element - take 2

 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 $template-param()>, parameters are stored in %params until all work is finished. The loop at the bottom iterates over the list of children (@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:

 <h1>Hello World!</h1>
 <h2>Header Header</h2>
    <p>paragraph1 paragraph1 paragraph1</p>
    <p><blockquote><i>
       Quote Quote Quote
    </i></blockquote</p>
    <p>paragraph2 paragraph2 paragraph2</p>

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 fill_template() as follows:

fill_template() for the Page element - take 3

 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 contrib_loop now added to the template:

Page.tmpl

 <h1><tmpl_var greeting></h1>
 <h2><tmpl_var header</h2>
  <!-- Contributors -->
  <tmpl_if contrib_loop>
     By:
     <tmpl_loop contrib_loop>
        <!-- Determine whether we need a comma or an "and" to separate -->
        <tmpl_unless __first__>
         <tmpl_if __last__>
          and
         <tmpl_else>
          ,
         </tmpl_if>
        </tmpl_unless>
        <!-- First Middle Last (Job) -->
        <tmpl_var first> <tmpl_var middle> <tmpl_var last>
        <tmpl_if contrib_type_loop>
           (
           <tmpl_loop contrib_type_loop>
              <tmpl_var contrib_type_name>
              <tmpl_if __last__>)</tmpl_if>
           </tmpl_loop>
        </tmpl_if>
     </tmpl_loop>
  </tmpl_if>
  <!-- /Contributors -->
 <tmpl_loop element_loop>
   <tmpl_if is_paragraph>
     <p><tmpl_var paragraph></p>
   </tmpl_if>
   <tmpl_if is_pull_quote>
     <p><blockquote><i>
       <tmpl_var pull_quote>
     </i></blockquote></p>
   </tmpl_if>
 </tmpl_loop>

The resulting output will look something like this:

 <h1>Hello World!</h1>
 <h2>Header Header</h2>
  <!-- Contributors -->
    By: JR Bob Dobb (Writer, Photographer) and Venus Dee Milo (Illustrator)
  <!-- /Contributors -->
    <p>paragraph1 paragraph1 paragraph1</p>
    <p><blockquote><i>
       Quote Quote Quote
    </i></blockquote</p>
    <p>paragraph2 paragraph2 paragraph2</p>

Go back to the Contributors section of Writing HTML::Template Templates in Krang for further documentation on using Contributors.

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 $child-publish()>.

fill_template_args

If the child element you are calling is still using the fill_template() method provided by Krang, you can use the parameter fill_template_args 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 fill_template() method will add greeting to template, provided the template is looking for a variable greeting. Keep in mind, you don't need to override fill_template() 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:

Page.tmpl

 <h1><tmpl_var greeting></h1>
 <h2><tmpl_var header></h2>
 <tmpl_loop element_loop>
   <tmpl_if is_paragraph>
     <p><tmpl_var paragraph></p>
   </tmpl_if>
   <tmpl_if is_pull_quote>
     <p><blockquote><i>
       <tmpl_var pull_quote>
     </i></blockquote></p>
   </tmpl_if>
 </tmpl_loop>

Rather than override the Page element's fill_template() method, we're going to use the one provided by Krang. Instead, we're going to override the fill_template() method for the Story element, and have it pass along the greeting to the page element.

fill_template() for the Story Element

 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 greeting parameter along, you don't need to override the fill_template() method in the Page element - the standard method used by Krang will suffice.

Handling fill_template_args When Overriding fill_template()

Continuing from the previous example, if we were to override the Page element fill_template() method, we'd need to handle the fill_template_args parameter as well:

Page Element

 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 fill_template_args.

A Note on Passing Parameters

While we use fill_template_args 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 fill_template() 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 fill_template_args.


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 Krang::Publisher 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 additional_content separately from the main story, and write it to disk as 'extra.html' (or whatever you set filename to be).

What is Supported:

Restrictions:

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)

Example - Creating an RSS File

If you aren't familiar with RSS (RDF Site Summary), take a look here: RDF Site Summary 1.0

This example uses XML::RSS, which is not part of Krang - it would have to be installed separately.

To generate the RSS file, we're going to override the fill_template() 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, $self->SUPER::fill_template(@_). That output is concatenated with the output generated by the XML::RSS module (after being tagged properly by $publisher->additional_content_block()), and returned to the Publisher, which will then write the two files out.

The use_category option to 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.

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.

fill_template() - Story Element

In overriding the fill_template() 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 $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.


Changing How an Element Chooses a Template

The process by which Krang chooses a publish template for a given element is as follows:

  1. )
  2. Determine the template name - by default, element-name.tmpl .

  3. )
  4. Given a category path of /SITENAME/foo/bar, start by looking for the template in /SITENAME/foo/bar/element-name.tmpl.

  5. )
  6. If the template is found, attempt to load and parse it. If successful, return an instantiated HTML::Template::Expr object. Otherwise, throw an error (Krang::ElementClass::TemplateParseError).

  7. )
  8. If the template is not found at /SITENAME/foo/bar/element-name.tmpl, try /SITENAME/foo/element-name.tmpl, /SITENAME/element-name.tmpl, finally /element-name.tmpl. If the template is found at any point, load the template as seen in step 3.

  9. )
  10. If the template cannot be found, throw an error (Krang::ElementClass::TemplateNotFound).

You can make changes to this process by overriding find_template() to change what template Krang will look for, where Krang will look for it, or even what kind of template will be loaded.

Loading a Template

Loading an HTML::Template::Expr 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 $filename or @search_path (ordered from first to last in terms of directories to search), you can affect what template gets loaded. $self->_load_template() handles the actual process of finding, loading, and throwing any required errors for HTML::Template::Expr templates.

If you want to change the type of template being loaded (e.g. you don't want to use HTML::Template::Expr templates), you need to roll your own code to find, load and parse the templates, throwing appropriate errors as needed. Be aware that find_template() and publish() are expecting an HTML::Template::Expr template, so you will need to override fill_template() and publish() functionality as well.


Changing the Publish Process for an Element

publish() acts as the coordinator of the publish process for a given element, making sure that the entire process runs smoothly. It works as follows:

  1. )
  2. Find a template for the current element using find_template().

  3. )
  4. If no template is found, decide if this is a problem.

    By default, if an element has no children, publish() will simply return $element-template_data()>. If the element has children, however, it will propegate the Krang::ElementClass::TemplateNotFound error thrown by find_template().

  5. )
  6. If the template is found, pass it to fill_template().

  7. )
  8. Once fill_template() is finished, return $template->output().

Preventing an Element from Publishing

While Krang, by default, does not call publish() 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;
 }

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();
 }

Publishing Media Elements

Starting in Krang v3.04, Media objects can include element data, and this data can in turn be published when the Media objects are published. (See Krang::ElementClass::Media for an example.) This is an advanced feature which is still being developed.


Conclusion

This covers the major aspects of customizing the Krang publish process. By overriding the three methods fill_template(), find_template() and publish(), 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 Krang::ElementClass and Krang::Publisher. Good luck!