TYPO3 allows us to create our own page types and make them available to our editors. In this article I present what you can use this for, when it is handy and when rather not and how it works.

Why do you need your own page types?

In TYPO3, websites (or whatever you actually want to fill with content) are always structured in pages. This makes it easy for even inexperienced editors to find their way around the website - even when it gets bigger. These pages are displayed in the backend in the page tree and usually map the structure in the frontend 1:1. The editor can thus very easily insert a new page at the correct place or change content elements on a page.

TYPO3 comes with some page types by default. Pages of the type "standard" are used to build the normal pages of an internet presence. The page type "external URL" can be used, for example, to display links to external websites in the main navigation. And "system folders" are not displayed in the menu, but are very well suited to structure e.g. footer menus or to provide data storage for news or other data sets.

So what do you need more page types for?

I'm a fan of adding as few dependencies to one's system as possible. A great extension that is in use in many TYPO3 systems (and definitely has its right to exist in the TYPO3 universe) is news(herein TER) by Georg Ringer. Unfortunately this extension is often used to squeeze really everything into a news record, even things that really don't belong there. Often this is done by inexperienced integrators who want to put together overview pages this way. Please forget this immediately: you are not doing editors any favors.

I recently worked for a large client on the relaunch of a corporate website. On this website there are different contents, e.g. "articles" which should be teased in other places. A task for news? Maybe. Certainly this could have been solved with news; but we went a different way and the editors were thrilled.

Overview in the backend

The different articles had different requirements. Some types required GPS coordinates, others requested data for an external API to work with, and still others could be color-coded, for example. We decided to implement all this with individual page types. This is very clear for the editor: he can see directly in the page tree what type of page it is. A new page can simply be inserted into the page tree as usual. And each page type queries exactly the information that is needed - and nothing more: all other fields are hidden.

Data aggregation made easy

For the desired content elements that display one or more teasers, the client can now freely decide which page types should be included and which not. Since all page types are real TYPO3 pages, the editor can easily use them in the main menu, without any contortions and without having to ask a developer.

Easy extensibility

Every developer knows this: no sooner is the specification written down than the requirements change. In our concrete example, the desire arose to be able to include links to external pages in the teasers. This requirement was very easy to implement, because "Link to external URL" is a page type already supplied by TYPO3, and we therefore only had to define the required fields (e.g. a preview image for the teaser) as mandatory fields for this page type and configure an additional page type in the query.

Since all page types are stored in the same TYPO3 table pages, we can do all the database magic we can think of about it. We want all articles and external URLs of a certain category, sorted by date in ascending order? No witchcraft - so be it!

 

So you don't need extensions like news anymore?

I wouldn't go that far. News is a very powerful tool (and apart from that, you can learn a lot by taking a closer look at the source code of the extension). My problem with news is more that it is too powerful and you usually only need a fraction of the functionality.

When news or a comparable extension is a good idea and when you rather build a solution like we did is always an individual decision. In addition, you have to ask yourself some questions before making a decision. I would formulate the following rule of thumb:

If a large number of datasets are to be displayed in a structured way, where the datasets always look the same, this speaks for an extension like news. If the datasets are not uniform, but very individual - in the presentation in the frontend and/or the data to be entered in the backend, I would rather go the way described here.

There is no absolute right or wrong here; from my point of view, we developers should have the ease of use by the editors in mind as the prime directive. Here, it often helps me to explicitly discuss with a non-developer from our team to take off the developer glasses and find the best solution for the editors.

Let's build a page type

So now that our decision has been made and we want to use new page types, it's time to build one. I'm assuming best practice that we have an extension that includes our template and that we can extend for our purposes. I call this extension sitepackage, but of course any extension will do.

All the information TYPO3 stores about pages ends up in the database in the pages table. And this is exactly the configuration we adjust in typo3conf/ext/sitepackage/Configuration/TCA/Overrides/pages.php:

 

<?php
defined('TYPO3_MODE') || die();

\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTcaSelectItem(
    'pages',
    }, 'doctype',
    [
        'LLL:EXT:sitepackage/Resources/Private/Language/locallang_be.xlf:pagetype.article',
        90,
        'actions-newspaper'
    ],
    '6',
    'after'
);


$fields = [
    'reading_time' => [
        'exclude' => true,
        'label' => 'LLL:EXT:sitepackage/Resources/Private/Language/locallang_be.xlf:pages.reading_time',
        'config' => [
            'type' => 'input',
            'size' => 30,
            'eval' => 'int,trim'
        ],
    ],
];

\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTCAcolumns(
    'pages',
    $fields
);

\TYPO3\CMS\Core\Utility\ArrayUtility::mergeRecursiveWithOverrule(
    $GLOBALS['TCA']['pages'],
    [
        'ctrl' => [
            'typeicon_classes' => [
                90 => 'actions-newspaper'
            ],
        ],
        'types' => [
           
            // Article
            90 => [
                'showitem' => '
                    --div--;LLL:EXT:sitepackage/Resources/Private/Language/locallang_be.xlf:pages.tabs.article,
                        --palette--;;article,
                        --palette--;;headlines,
                        --palette--;;teaser_image,
                    --div--;LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.tabs.access,
                        slug, --linebreak--,
                        starttime, endtime,--linebreak--,
                        --palette--;;visibility,
                        no_search,
                    --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:extended,
                    '
            ],

 

here at the example of doktype 90 (chosen by me) for the new page type "article". This gets its own icon, if necessary one or more new fields (here: reading_time) are defined and then assigned depending on the page type. This way, the editors now only see the fields that are relevant for this page type.

If we add new fields, we of course have to define them in the database. We do this in ext_tables.sql

 

CREATE TABLE pages (
    reading_time int DEFAULT '0' NOT NULL  
);

 

When installing the extension, the table will be extended accordingly: during development we can do this e.g. with

 

vendor/bin/typo3cms database:updateschema

 

to force this.

So that the editors can use the new page type like all others by drag'n'drop, a TSconfig setting is still missing:

 

options {
  pageTree {
    doktypesToShowInNewPageDragArea := addToList(90)
  }
}

 

Now editors can drag pages of type article to the desired location and the page properties will only show the fields we need. So far so good. :-)

Field configuration depending on the page type

If you now build some page types into the system, it can happen that a field appears in several page types (no problem!) and is a mandatory field in one page type, but not in another. What now?

This case can also be solved with the TCA. We go back to the typo3conf/ext/sitepackage/Configuration/TCA/Overrides/pages.php created above and make the field reading_time once mandatory and once optional:

 

\TYPO3\CMS\Core\Utility\ArrayUtility::mergeRecursiveWithOverrule(
    $GLOBALS['TCA']['pages'],
    [
        'ctrl' => [
            'typeicon_classes' => [
                90 => 'actions-newspaper',
                100 => 'actions-globe'
            ],
        ],
        'types' => [
            // default page
            1 => [
                'columnsOverrides' => [
                    'reading_time' => [
                        'config' => [
                            'eval' => 'required,int,trim'
                        ],
                    ],
                ],
                'showitem' => '...'
            ],
            [...]
        ],
    ],
);

 

We defined the field as optional earlier; however, it is now required on the default page. Here we have now only overwritten a single value; however, we can also overwrite the complete field if required and thus, for example, display reading_time on the default page as an input field and on an article page as a select box with predefined values in the backend.

Adjusting the display in the frontend

We may want to display individual page types differently in the frontend. In our use case, numerous other fields have been added in addition to the read duration, so editors can, for example, color-code individual page types and much more. Of course, we want to work with the values saved in the backend by the editors in the frontend. For this we can e.g. control another template for the rendering:

 

page {
  10 = FLUIDTEMPLATE
  10 {
    layoutRootPaths {
      10 = EXT:sitepackage/Resources/Private/Layouts/
    }
    templateRootPaths {
      10 = EXT:sitepackage/Resources/Private/Templates/
    }
    partialRootPaths {
      10 = EXT:sitepackage/Resources/Private/Partials/
    }

    file.stdWrap.cObject = CASE
    file.stdWrap.cObject {
      key.data = levelfield:-1,doktype
      1 = TEXT
      1.value = EXT:sitepackage/Resources/Private/Templates/Default.html
      90 = TEXT
      90.value = EXT:sitepackage/Resources/Private/Templates/Article.html
    )
}

 

If necessary, we can still enrich the data with DataProcessors, for example if we need information from other database tables that are linked to our dataset:

 

page.10 {
  dataProcessing {
    900 = TYPO3\CMS\Frontend\DataProcessing\DatabaseQueryProcessor
    900 {
      if {
        isEqual {
          field = doktype
          value = 90
        }
        isTrue {
          field = related_tags
        }
      }
      selectFields = pages.uid, pages.byline, pages.title
      table = pages
      join = tx_sitepackage_domain_model_tag_mm as mm ON mm.uid_local=pages.uid JOIN tx_sitepackage_domain_model_tag as tag ON mm.uid_foreign=tag.uid
      where {
        field = related_tags
        dataWrap = doktype=90 AND pages.deleted=0 AND pages.hidden=0 AND tag.uid IN (|)
      }
      groupBy = pages.uid
      orderBy = pages.crdate DESC
      max = 5
      pidInList = 1
      recursive = 99
      as = relatedArticles
      dataProcessing {
        10 = TYPO3\CMS\Frontend\DataProcessing\FilesProcessor
        10 {
          references.fieldName = media
          as = media
        }
      }
    }
  }
}

 

Here in the example, we get related articles from the database and pass them to the view, which can then use them further:

 

<f:if condition="{relatedArticles}">
  <f:for each="{relatedArticles}" as="relatedArticle">
    <div class="col-12">
     <f:link.page pageUid="{relatedArticle.data.uid}">
       <picture class="teaser__image">
          <f:for each="{relatedArticle.media}" as="image">
            <f:image image="{image}" />
		  </f:for>
	   </picture>
       <p>{relatedArticle.data.title}</p>
	 </f:link.page>
    </div>
  </f:for>
</f:if>

 

 

Create an overview page

Luckily for us, we are still missing the overview page of the pages. I already told you how to do that in the previous paragraph: the relatedArticles are nothing more than teasers to other pages, which we simply get with all the properties we need via a TYPO3 DataProcessor. Maybe one or the other reader has already noticed: this article only works with TYPO3 on-board resources. We also didn't create a repository or similar, this solution doesn't need Extbase and only uses the standard functions in the core.

To generate an overview of all our pages (of one type, or mixed, or whatever the customer wants), we create a content element that gives us the desired overview and that the editor can use in all possible places where he needs it.

We use a DataProcessor to find all the data we need from the database and render it in the content element's view. For this project, we have created our own DataProcessor, which masters some magic and encapsulates business logic. For normal use cases, however, you can also get very far with the TYPO3 DataProcessors and build very cool things with them.

The DataProcessors can be nested (see above), so that TYPO3 provides you e.g. with the correct file object for each page, which you can then easily use in the template.

Comments (0)

No comments found!

Write new comment reply