Anatomy of a typical page

Before diving into an explanation of Tuskfish subsystems it may be useful to step through the execution of a sample page. This will show the sequence of events and help place the subsystems in context.

So, let's take index.php as our example and step through it, as it uses all Tuskfish subsystems. This page presents a mixed stream of teasers for the most recent content on your site. It has pagination controls that can step back through the entire content of the site, and it can be filtered by tag.

Bootstrapping Tuskfish

// Enable strict type declaration.
declare(strict_types=1);
  • Enable strict type checks on method parameters. This declaration must be the first line on every file that contains PHP code. Although type checks are present in all Tuskfish methods, they are not honoured unless the script calling them has made this declaration (the declaration applies to the calling script).
// 1. Access trust path, DB credentials and preferences. This file must be included in *ALL* pages.
require_once "mainfile.php";

Begin to bootstrap Tuskfish:

  • Defines the root path, trust path and site URL constants.
  • Defines the path to the Tuskfish library and configuration file.
  • Includes the configuration file, which in turn:
  • Defines all other path constants.
  • Sets the default language file.
  • Sets the custom class auto loader.
  • Defines the site salt.
  • Defines the site security key.
  • Defines the database name.
// 2. Main Tuskfish header. This file bootstraps Tuskfish.
require_once TFISH_PATH . "tfHeader.php";

Continue to bootstrap Tuskfish. You should review this file carefully:

  • Initialise output buffering (with compression).
  • Locks input/output character set to UTF-8.
  • Include the HTMLPurifier library.
  • Set error reporting levels and define custom error handler.
  • Include the core language files.

Instantiates key dependencies used throughout Tuskfish scripts; tfHeader.php is where the chain of dependency injection begins. A single instance of each is set up and they flow through the application, keeping the memory footprint low.

  • The data validator ($tfValidator).
  • The error logger ($tfLogger).
  • The file handler ($tfFileHandler).
  • The database handler ($tfDatabase).
  • The query composer and item factories ($tfCriteriaFactory and $tfCriteriaItemFactory).
  • Site preferences ($tfPreference).
  • Page-level metadata ($tfMetadata).
  • The template object ($tfTemplate).
  • The cache ($tfCache).

Handle session security:

  • Start a PHP session.
  • Check if site is closed, if so redirect to the login page and exit.
// 3. Content header sets module-specific paths and makes TfContentHandlerFactory available.
require_once TFISH_MODULE_PATH . "content/tfContentHeader.php";

Including module headers

As this page makes use of the Content module, it also includes the content module header, which defines some additional, module-specific file paths, language constants and class autoloader. This header also instantiates the $contentHandlerFactory, used to instantiate various content subclass handlers.

// 3. Content header sets module-specific paths and makes TfContentHandlerFactory available.
require_once TFISH_MODULE_PATH . "content/tfContentHeader.php";

// Get the relevant handler for retrieving content objects.
$contentHandler = $contentHandlerFactory->getHandler('content');
  • The handler will be used to retrieve and manipulate content objects. TfContentHandler is the base handler type and can handle any kind of content. Use it when you need to manipulate mixed content types. Some content types have more specific handlers (eg. TfBlock, TfTag and TfCollection have TfBlockHandler, TfTagHandler and TfCollectionHandler, respectively). If you only want to work with a single content type then use its specific handler, if it has one, as it may have some additional methods that will be useful.

Configure the page

// Specify theme, otherwise 'default' will be used.
$tfTemplate->setTheme('default');
$indexTemplate = 'singleStream';
$targetFileName = 'index';
$tfTemplate->targetFileName = $targetFileName;
  • If you want to assign a different theme to this page, then change 'default' to the name of the relevant theme, as defined by its directory name.
  • $targetFileName is the page that the pagination control links should point to, without the file extension.
  • $indexTemplate is the name of the template file in the active theme directory that should be used to display this page. 'singleStream' displays a mixed feed of the latest content on your site in index view.

Validate input parameters

// Validate input parameters.
$cleanId = isset($_GET['id']) ? (int) $_GET['id'] : 0;
$cleanStart = isset($_GET['start']) ? (int) $_GET['start'] : 0;
$cleanTag = isset($_GET['tagId']) ? (int) $_GET['tagId'] : 0;
  • You should always validate input parameters to ensure they are of the type your script is expecting to handle. Tuskfish components validate their input independently of one another, so if you forget to validate input, or make a mistake, the parameters should - in theory - be validated again when they reach the next layer of the application. But don't rely on it - Tuskfish policy is that you validate input at every level.
  • id is the ID of a particular content object. If it is set, then that object will be retrieved and its full description displayed (single object view). If it is not set then a list of the most recent content will be displayed as teasers (index view).
  • start is used for the pagination control. If you are paging through the index page it determines which content object the list should start from (ie. it is the offset). The number of content objects displayed per page view is set by the User side pagination preference.
  • tagId is an optional parameter that specifies that the index should only display content labelled with a particular tag.

Set up the cache and RSS feed

// Set cache parameters.
$basename = basename(__FILE__);
$cacheParameters = array('id' => $cleanId, 'start' => $cleanStart, 'tagId' => $cleanTag);
  • These parameters are used to set the file name of the cached copy of this page view, so that it can be retrieved next time it is requested.
$rssUrl = !empty($cleanTag) ? TFISH_RSS_URL . '?tagId=' . $cleanTag : TFISH_RSS_URL;
  • Generate an RSS link, either for the whole site (if tagId is not set) or for a particular tag.

Option 1: View a single content object

Retrieve a single content object

// Retrieve a single object if an ID is set.
if ($cleanId) {

    // Retrieve target object.
    $content = $contentHandler->getObject($cleanId);
  • If the id parameter was set, retrieve the matching content object (if it exists) and display its full description (single object view).
// Update view counter and assign object to template. Only increment counter for
// non-downloadable objects.
if ($content->type != 'TfDownload' && !($content->type === 'TfCollection' 
    && $content->media)) {
    $content->counter += 1;
    $contentHandler->updateCounter($cleanId);
}
  • If a single object is being viewed, update its view counter, unless it is a download object or a collection with attached media (in which case the counter is only incremented when the actual media file is downloaded via enclosure.php).
// Check if cached page is available.
$tfCache->getCachedPage($basename, $cacheParameters);
  • If a cached copy of this page is found, execution of this script stops and the cached copy is returned. If there isn't a cached copy then execution continues.
// Assign content object to template.
$tfTemplate->content = $content;

...

Assign content to template and prepare for display

  • Assign the content object to $tfTemplate in preparation for display.
// Prepare meta information for display.
$contentInfo = array();
        
if ($content->creator) $contentInfo[] = $content->escapeForXss('creator');
if ($content->date) $contentInfo[] = $content->escapeForXss('date');
        
if ($content->counter) {
    switch ($content->type) {
        case "TfDownload":
            $contentInfo[] = $content->escapeForXss('counter') . ' ' . TFISH_DOWNLOADS;
            break;
        default:
            $contentInfo[] = $content->escapeForXss('counter') . ' ' . TFISH_VIEWS;
    }
}
        
if ($content->format) $contentInfo[] = '.' . $content->escapeForXss('format');
if ($content->fileSize) $contentInfo[] = $content->escapeForXss('fileSize');
        
// For a content type-specific page use $content->tags, $content->template
if ($content->tags) {
    $tags = $contentHandler->makeTagLinks($content->tags);
    $tags = TFISH_TAGS . ': ' . implode(', ', $tags);
    $contentInfo[] = $tags;
}
        
$tfTemplate->contentInfo = implode(' | ', $contentInfo);

if ($content->metaTitle) $tfMetadata->setTitle($content->metaTitle);
if ($content->metaDescription) $tfMetadata->setDescription($content->metaDescription);
  • Prepare various metadata fields for display.
// Check if has a parental object; if so display a thumbnail and teaser / link.
if (!empty($content->parent)) {
    $parent = $contentHandler->getObject($content->parent);
       
    if (is_object($parent) && $parent->online) {
        $tfTemplate->parent = $parent;
    }
}

...

Prepare parental object for display

  • If this content object has a parent object designated, then the details of the parent will be displayed below its description as a teaser.
// Initialise criteria object.
$criteria = $tfCriteriaFactory->getCriteria();
$criteria->setOrder('date');
$criteria->setOrderType('DESC');
$criteria->setSecondaryOrder('submissionTime');
$criteria->setSecondaryOrderType('DESC');

// If object is a collection check if has child objects; if so display teasers / links.
if ($content->type === 'TfCollection') {
    $criteria->add(new TfCriteriaItem($tfValidator, 'parent', $content->id));
    $criteria->add(new TfCriteriaItem($tfValidator, 'online', 1));

    if ($cleanStart) $criteria->setOffset($cleanStart);

    $criteria->setLimit($tfPreference->userPagination);
}

// If object is a tag, then a different method is required to call the related content.
if ($content->type === 'TfTag') {
    if ($cleanStart) $criteria->setOffset($cleanStart);

    $criteria->setLimit($tfPreference->userPagination);
    $criteria->setTag(array($content->id));
    $criteria->add(new TfCriteriaItem($tfValidator, 'type', 'TfBlock', '!='));
    $criteria->add(new TfCriteriaItem($tfValidator, 'online', 1));
}

Prepare child objects for display

  • If this object is a collection or a tag, then its child/member content objects will be displayed as a list of teasers below its description.
// Prepare internal pagination control (for collections/tags with lots of child objects).        
if ($content->type === 'TfCollection' || $content->type === 'TfTag') {
    $tfPagination = new TfPaginationControl($tfValidator, $tfPreference);
    $tfPagination->setUrl($targetFileName);
    $tfPagination->setCount($contentHandler->getCount($criteria));
    $tfPagination->setLimit($tfPreference->userPagination);
    $tfPagination->setStart($cleanStart);
    $tfPagination->setTag($cleanTag);
    $tfPagination->setExtraParams(array('id' => $cleanId));
    $tfTemplate->collectionPagination = $tfPagination->getPaginationControl();

    // Retrieve content objects and assign to template.
    $firstChildren = $contentHandler->getObjects($criteria);

    if (!empty($firstChildren)) {
        $tfTemplate->firstChildren = $firstChildren;
    }
}
  • A pagination control for child/member content objects will also be displayed, if necessary.
// Render template.
    $tfTemplate->tfMainContent = $tfTemplate->render($content->template);
} else {
    $tfTemplate->tfMainContent = TFISH_ERROR_NO_SUCH_CONTENT;
}
  • Assign the content object to $tfTemplate and render the template for display.

Option 2: View the index page (list of teasers)

// Otherwise retrieve an index page list of teasers.
} else {
  • If an id was not set, then display a list of the latest content as teasers (index view). This can optionally be filtered by tag, if a tagId was set.
// Check if cached page is available.
$tfCache->getCachedPage($basename, $cacheParameters);
  • Check if a cached version of the page is available. If so, the script stops executing here and the cached version is returned. Otherwise the script continues to run.
// Page title, customise it as you see fit.
$tfTemplate->pageTitle = TFISH_LATEST_POSTS;
  • Set the page title to "Latest posts".
// Exclude static pages, tags and blocks from the index page.
$criteria = $tfCriteriaFactory->getCriteria();

if ($cleanStart) $criteria->setOffset($cleanStart);

$criteria->setLimit($tfPreference->userPagination);

if ($cleanTag) $criteria->setTag(array($cleanTag));

$criteria->add(new TfCriteriaItem($tfValidator, 'type', 'TfTag', '!='));
$criteria->add(new TfCriteriaItem($tfValidator, 'type', 'TfStatic', '!='));
$criteria->add(new TfCriteriaItem($tfValidator, 'type', 'TfBlock', '!='));
$criteria->add(new TfCriteriaItem($tfValidator, 'online', 1));
  • Initialise a TfCriteria object, which will hold conditions that we want to apply to the query to retrieve content objects for display.
  • Set the offset (which object to start from or $cleanStart) and $limit (how many to retrieve) according to pagination requirements and the User-side pagination preference (userPagination).
  • Filter the results by tag id ($cleanTag), if set.
  • Exclude tags, static pages and blocks from the result set.
  • Only include content that is marked as online.

Prepare a pagination control

// Prepare pagination control.
$tfPagination = new TfPaginationControl($tfValidator, $tfPreference);
$tfPagination->setCount($contentHandler->getCount($criteria));
$tfPagination->setLimit($tfPreference->userPagination);
$tfPagination->setStart($cleanStart);
$tfPagination->setTag($cleanTag);
$tfTemplate->pagination = $tfPagination->getPaginationControl();
  • Count the number of records that meet the query conditions ($criteria). This value is needed to set up the pagination control.
  • Prepare the pagination control for display. The number of objects to display on one page in index view is set by the User-side pagination preference. The object to start from is determined by $cleanStart. Together these two values are used to calculate the pagination control links.
  • By default the pagination links will point back at your home page (index.php) but by overriding TFISH_URL you can point them at another page (for example if this was a page with some other file name).
  • The pagination control can also filter results by tag, if you pass in $cleanTag, which happens if someone uses the tag select box to filter the page.

Retrieve content from database

// Retrieve content objects and assign to template.
$criteria->setOrder('date');
$criteria->setOrderType('DESC');
$criteria->setSecondaryOrder('submissionTime');
$criteria->setSecondaryOrderType('DESC');
$contentObjects = $contentHandler->getObjects($criteria);
$tfTemplate->contentObjects = $contentObjects;
$tfTemplate->tfMainContent = $tfTemplate->render($indexTemplate);
  • Add a couple more query conditions to sort the result set by date, descending (most recent records first).
  • Retrieve the content objects matching the criteria.
  • Assign the retrieved content to $tfishTemplate in preparation for display. When a template is rendered, the properties of $tfishTemplate are extracted as variables, which means they can be accessed in the template.
  • Render the index template (singleStream.html, which was set earlier on) and assign it to the main content area of this page (the $tfMainContent zone in theme.html).
  • Take a look at the template files in the default theme for examples of how to output the extracted variables. Essentially you just echo them, although you must also escape them for ouput.

Prepare a tag select box

// Prepare tag select box.
$tfTemplate->selectAction = 'index.php';
$tagHandler = $contentHandlerFactory->getHandler('tag');
$tfTemplate->selectFilters = $tagHandler->getTagSelectBox($cleanTag);
$tfTemplate->selectFiltersForm = $tfTemplate->render('selectFilters');

...
  • Get a tag select box, set the destination page (the name of the current file, usually) and assign it to $tfTemplate in preparation for display.
  • Render the selectFilters form, which is the subtemplate that displays the tag select box.

Render the complete page

// Include the theme template and flush buffer
require_once TFISH_PATH . "tfFooter.php";
  • Include the main HTML file of the relevant template set.
  • Close the connection to database.
  • Write the contents of the output buffer to cache (if enabled).
  • Flush the output buffer to screen (render page) and clear it.

I hope this is enough to get you started on hacking or writing your own pages.

Copyright, all rights reserved.

Related

Tuskfish CMS Developer Guide

This guide will give you an overview of the architecture of Tuskfish CMS, how to write code to perform common operations and how to extend the system to suit yourself. The guide accompanies the Tuskfish API documentation. Keep a copy handy as you read this guide. It is best to review links to the API where provided, as not every detail will be discussed in the text. This is the first version of the guide, so it is still a work in progress.