[RC] Centurion module: aggregate content around names, pages or anything
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Failed to load latest commit information.


This module is not documented enough. This should be done soon!

If you're looking for a quick HOWTO, you should jump to The implementation chapter.


A highlight content is an aggregation of many mixed types contents. Theoretically, it is possible to add to this collection any content by its type and id.

Concepts and contents


A highlight content carries the following information:

  • a name. optional but necessary if the highlight can't be found by the content it is attached to.
  • a reference to a content by its type and id. It is possible to find a highlight content from the content it is attached to.
  • a ordered collection of objects representing the contents to bring up. Required. we'll call these objects items. These are defined just below


An item content carries these informations:

  • a reference to a mixed typed content. This is the content that will be displayed.
  • an image. optional. Will be used as cover in place of the default one of the linked content.
  • a paragraph. Optional. Will be used as introduction in place of the default one of the linked content.
  • a link. optional. Will be used as replacement for the permalink of the linked content.


A crawler is an object that, given a specific query, can return a list of contents that match this query. This module provides an abstract crawler. that has close to no crawling logic. it could have been an interface. it also provides a default crawler. That is configurable and can crawl through tables with keywords. have a look at Highlight_Model_Crawler_Abstract and Highlight_Model_Crawler_Default as well as the module config


Because the contents returned in a highlight can be of such different structures, it is necessary, when you need to display them, to have simple tools to unify these structures a little. A field mapper is an object that, given a row, can return an array with, at least, the following fields

  • title: The title of the highlight item
  • description: a short paragraph about the highlight item
  • link: A link to the highlighted content
  • cover: an image to go with the highlighted content

This module provides an Interface and a default mapper that reads which fields to check in a given order. have a look at the module config files and the field mapper classes. Of course, if the item itself defines any of the fields described above, those will see themselves overriden


Here follows a little guide on how to add highlights to your project

These very simple things will allow you to make lists of contents attached to a name you define in configuration or a content of any type

Named highlights


The concept of named highlights is very simple. Somewhere in your config files, you make a list of highlights that you will be able to retrieve from your view in order to display them.

Here's how you go about that: create or open the config file in application/configs/highlight.ini

highlight.named_highlights[] = "home_carousel"
highlight.named_highlights[] = "home_footer"

In this example. We created two named highlights that were added to the highlight.named_highlights config array.


How do we edit the content that will show up in these highlights? go to the following url on your project: /highlight/admin-highlight.

You will be presented with and interface with one or more blue squares. each of these represents an item. The last one is an empty one, ready to be added.

You can add one by clicking on the 'edit' action button. (little pen icon) A form pops up at the top of the page with a text field. If you type in it, you should see some smart autocomplete pop just under it. The default crawler looks for flatpages only. but that should do fine for now.

Click add, and your item is added and is linked to the flatpage you selected !

If you repeat the operation you'll find yourself with a bunch of highlight items. you can drap and drop them to reorder them. Don't forget to click "save order" at the bottom of the page.

displaying highlights

Still using the simplest way here. let's see how, in a view script, we can display a named highlight.

let's look at this sample code:

    $container = $this->getHighlightContainer('home_footer');
<?php if($container) : ?>
    <?php foreach($container->getHighlights() as $highlight) : ?>
            <h3><?php echo $highlight['title'] ?></h3>
            <p><?php echo $highlight['description'] ?></p>
            <a href="<?php echo $highlight['link'] ?>">
                <img src="<?php echo $highlight['cover']->getStaticUrl() ?>" />
    <?php endforeach ?>
<?php endif ?>
  • first we retrieve the highlight container object by its name, using the view helper GetHighlightContainer.
  • after checking that we actually did get a container we start to display a unordered list
  • we retrieve each highlight item with the getHighlights method.
  • for each of those, we display a list element with various information

The GetHighlightContainer helper takes a name as parameter and retrieve the corresponding highlight container. we'll see later that it can do a bit more than that.

The getHighlights method of a container instance returns a collection of mapped arrays of our items. We used it with no parameters, but you would in fact be able to override the field mapper in use. The default one is used otherwise.

Highlights for a specific content

Well all this is nice and swell but. let's say I want to pick 3 articles that will show in the sidebar when reading my article about my last trip in Switzerland. I could define a named highlight for this particular case of course, and just check in my controller if i should display this highlight.

But what if I want to do this on any other article as well? Well, this module lets you attached a highlight container by proxy model. In effect, you attach a container to the content of your choice. Let's go through the step of doing so

Letting the highlight admin interface know what is your current content

The highlight admin interface check for various parameters in the url to see if it should manage highlights for a specific content. These parameters are:

  • proxy_pk: the Primary key of your content.
  • proxy_content_type_id: The id of the content type of your content.

Of course you could write those by hand. But let's all agree that this is a bit tedious. I personally prefer something that generates the correct URL for me.

Let's open the admin CRUD controller of your favourite content. I like user profiles.


class User_AdminProfileController extends Centurion_Controller_CRUD
                                  implements Highlight_Traits_Controller_CRUD_Interface
    public function init()

        // ... nothing left to change actually

See what I did? I just added the implements Highlight_Traits_Controller_CRUD_Interface part to the class declaration. This is the way Centurion handles traits mechanisms, because not everyone can get the latest PHP version, right?

What does this trait does? I simply adds, in the crud list, a column at the very end with a link to manage highlights. This is the generated url I was talking about. It contains the parameters needed for the highlight admin to attach a container to our custom content.

/!\ Due to some weird urlencoding of callback url parameters, There's no way to return to the content whose highlights we are editing right now. This is however definitely on the feature list

Retrieving highlights from a content

Well this paragraph is gonna be small. You do exactly the same way as for a named highlights, except you don't give a name as a string to the view helper, but the row instance of the content you are handling.

Overriding default behaviours

What I described until here is what you can achieve using a very tiny amount of code writing or configuration.

This module also provied some base classes and utilities to override the default behaviour of the different entities

Custom crawlers

The first thing you'll probably need to override is the crawler in use for your various highlights containers. This is hard to make generic because, we could hardly assume what your specific content type will look like.

TODO: a wiki page about the crawler component alone

letting your highlight interface know what crawler to use

There are different ways to tell the admin highlight controller which crawler to use. If it fails finding any, it will use the default one.

First of all, you can declare any crawler you like in the configuration in the namespace highlight.crawlers.*. The crawler Factory (Highlight_Model_Crawler_Factory::get) takes a string as first argument. From this string 'crawlername' it reads the config array highlight.crawlers.crawlername. It then instanciate an object of the class defined in highlight.crawlers.crawlername.className, giving the whole config array as parameter to the constructor.

url parameter:

add a crawler parameter to the url and it will use the crawler by that name from the configuration files. You should look how to override the construction of the highlight url in the Highlight_Traits_Controller_CRUD

in the configuration:

for a named highlight. Instead of adding your name highlight to an array with

highlight.named_highlights[] = "home_carousel"

you can declare it with a crawler as parameter like so:

highlight.named_highlights.home_carousel.crawler = "my_funny_crawler"
In the proxy row:

When managing a highlight of a proxy, if the given proxy row model implement the Highlight_Traits_Model_Row_HasHighlights_Interface it then has to implement a getCrawler method which returns -you gessed it- a crawler. This is probably the finer grain of control you can get over which crawler to use for a given content.

The hard way: implement the crawler interface

Let's start with the hard way, for a change.

The hard way is actually the simplest to explain. You'll have to implement your own crawler by extending the abstract one provided. Let's see what's in there:

  • a unique abstract method crawl: it takes an array as parameter because, you never really know what to expect. Most the time it will be populated with a unique key query with a string. It expected in return, an array of rows
  • an autocomplete method: You probably don't need to override that one. it only formats the result of crawl for the highlight controller
  • a crawlTable method: This method actually does something. It splits the query string into keywords, and crawls a table for the given fields matching these keywords

Obviously, you'll have to start with the crawl method. And since you're in your IDE, you probably should have a look at the source code.

The simle way: configure the default crawler

What if I told you, you don't need to write code? You could just configure a crawler, very much like the default one which would result in the instanciation of the same class but with different parameters.

The default crawlers reads from its parameters which tables it has to crawl and which fields for each of these tables. pretty straightforward. Have a look at the module.ini file to see how the default one is configured.

; the class to instanciate
highlight.crawlers.default.className = "Highlight_Model_Crawler_Default";

; each key of the config defines a model it has to crawl
; the table config define which table it has to crawl
; the fields config array lists the fields in which to look for matches with the query terms
highlight.crawlers.default.models.profile.table = "user/profile";
highlight.crawlers.default.models.profile.fields[] = "nickname"
highlight.crawlers.default.models.profile.fields[] = "user__email"
highlight.crawlers.default.models.flatpages.table = "cms/flatpage";
highlight.crawlers.default.models.flatpages.fields[] = "title";
highlight.crawlers.default.models.flatpages.fields[] = "url";
highlight.crawlers.default.models.flatpages.fields[] = "slug";

Custom field mappers

As said before, field mappers are little utility objects whose role is to format any single content into a unified structure of data. Pretty much any method that needs a field mapper as argument will take either a FieldMapper object or a string for retrieval from the factory.

TODO: a wiki page about the crawler component alone

Mapper factory

Very much like the Crawler factory, the field mapper factory reads a Mapper's configuration from configuration in the highlight.mappers.* namespace.

Implement the field mapper interface

The interface defines two methods.

  • map(Centurion_Db_Table_Row_Abstract $row) takes a row of any kind and must return an associative array with the necessary keys for a highlight item
  • mapRowSet($rowset) takes a collection of rows (can also be an array) and must return an array of the same size with each entry mapped

When implementing this, make sure you take into account the case where the row given to the map function is an instance of Highlight_Model_DbTable_Row_Row that is a Highlight entry.

Configure the default mapper class

The behaviour of the default field mapper class is for each field it has to find a value for, try a list of field in the given row and take the first one that exist and is not empty.

The best way to understand it is to look at the config file

; config for the default field mapper
; the class to instanciate
highlight.mappers.default.className = "Highlight_Model_FieldMapper_Default"
; each key within the mapper's config describe the components of the final field it names
; the fields array lists the fields of a row the mappers will look in to find the content of the final field
highlight.mappers.default.title.fields[] = "title"
highlight.mappers.default.title.fields[] = "name"
highlight.mappers.default.link.fields[] = "permalink"
highlight.mappers.default.link.fields[] = "url"
highlight.mappers.default.description.fields[] = "abstract"
highlight.mappers.default.description.fields[] = "intro"
highlight.mappers.default.description.fields[] = "introduction"
highlight.mappers.default.description.fields[] = "introduction"
highlight.mappers.default.description.fields[] = "body"
highlight.mappers.default.description.fields[] = "description"
highlight.mappers.default.cover.fields[] = "cover"
highlight.mappers.default.cover.fields[] = "image"
highlight.mappers.default.cover.fields[] = "media"

; pixelOnEmpty allows to populate the value with the empty pixel if nothing is set
highlight.mappers.default.cover.pixelOnEmpty = 1

As you can see, for each field, we build a list of fields to check to find a value.