You are currently viewing Use content element relations in TYPO3 extensions

Use content element relations in TYPO3 extensions

Wouldn’t it be cool to enable editors to use standard or custom content elements in your extensions? The consequence is that you do not have to develop each and every functionality again. In this post I will show you, how you can use all available content elements of your installation.

A very prominent example is EXT:news. This is also the extension where I looked at, while developing a extension for a customer, as I needed a similar functionality. Thanks to Georg Ringer and open source, I am able to share some kind of howto about this topic. Here are two screenshots that show, how the content element relations will be shown on the backend:

typo3worx_ce-relations-example03
Multiple CE relations possible
typo3worx_ce-example-02
Inline editing of content elements

Prerequisites

1) A running TYPO3 instance

Hm, ok that sounds obvious, but for completeness I want to mention it here.

2) A minimal TYPO3 extension

In order to use this, you need at least a minimal TYPO3 extension, to which you can add this functionality. If you don’t have one and want to follow this howto codewise, please check the EXT:extension_builder and create an minimal one.

How to use content element relations in your TYPO3 extension

In the following sections I will show the various parts of you extension, which must be touched. If a mentioned file is not already there, you must create it.

Data structure: ext_tables.sql

First, we must tell TYPO3, where to store the content element relations. Therefore we need to extend the table ‚tt_content‘, add a column to the table of the extension containing the data and create an additional mm table. The necessary lines must be added in the file „ext_tables.sql“ of your extension.

#
# Table structure for table 'tt_content'
#
CREATE TABLE tt_content (
   tx_myextension_content_elements int(11) DEFAULT '0' NOT NULL,
);

#
# Table structure for table 'tx_myextension'
#
CREATE TABLE tx_myextension (
   content_elements TEXT
)

#
# Table structure for table 'tx_myextension_domain_model_mymodel_ttcontent_mm'
#
CREATE TABLE tx_myextension_domain_model_mymodel_ttcontent_mm (
   uid_local int(11) DEFAULT '0' NOT NULL,
   uid_foreign int(11) DEFAULT '0' NOT NULL,
   sorting int(11) DEFAULT '0' NOT NULL,
   KEY uid_local (uid_local),
   KEY uid_foreign (uid_foreign)
);

TCA für content element relations

Then the content elements must be added to the TCA. Therefore the parts „interface“, „types“ and „columns“ must be touched. As I did not want to add the complete TCA here, you must replace the “[…]” with the parts of your code.

'interface' => [
   'showRecordFieldList' => [...] . 'content_about, ' . [...]
],
'types' => [
   '1' => [
      'showitem' => [...] .'--div--; Content Elements, content_elements, ' . [...]
   ]
],
'columns' => [
   [...]
   'content_elements' => [
      'exclude' => 1,
      'l10n_mode' => 'mergeIfNotBlank',
      'label' => 'Content Elements',
      'config' => [
         'type' => 'inline',
         'allowed' => 'tt_content',
         'foreign_table' => 'tt_content',
         'foreign_sortby' => 'sorting',
         'foreign_field' => 'tx_myextension_content_elements',
         'minitems' => 0,
         'maxitems' => 99,
         'appearance' => [
            'collapseAll' => 1,
            'expandSingle' => 1,
            'levelLinksPosition' => 'bottom',
            'useSortable' => 1,
            'showPossibleLocalizationRecords' => 1,
            'showRemovedLocalizationRecords' => 1,
            'showAllLocalizationLink' => 1,
            'showSynchronizationLink' => 1,
            'enabledControls' => [
               'info' => false,
            ]
         ]
      ]
   ],
[...]
],

Domain – Model for tt_content

In order to use normal content elements, you must also provide a model in your extension for it. This happens in the file “EXT:myextension/Classes/Domain/Model/TtContent.php“. It provides all setter and getter methods for the table „tt_content“. As the complete source code would be too much for this post: there is a gist for it: https://gist.github.com/mschwemer/f0814450009204fc9a16d05957d77226

Repository for tt_content

Additional to the domain model the repository must be also provided. This is the purpose of the file: “EXT:myextension/Classes/Domain/Repository/TtContentRepository.php“. This is the code, you need:

<?php

namespace MyNameSpace\Myextension\Domain\Repository;

/**
* Repository for tt_content objects
*
*/
class TtContentRepository extends \TYPO3\CMS\Extbase\Persistence\Repository
{
   protected $objectType = '\MyNameSpace\Myextension\Domain\Model\Ttcontent';
}

Main Model of your extension

In the next step the content elements must be made reachable by your main domain model. Basically it is the normal stuff like defining the property, adding the functions add*, remove* and the getter. But what you in addition is the function “getContentElementIdList“. This return all the IDs which are associated with your record. The output is needed in the next snippet.

/**
* @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\MyNameSpace\Myextension\Domain\Model\TtContent>
* @lazy
*/
protected $contentElements;

/**
* Initialize content element relation
*
* @return \MyNameSpace\Myextension\Domain\Model\Mymodel
*/
public function __construct()
{
   $this->contentElements = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage();
}

/**
* Adds a content element to the record
*
* @param \MyNameSpace\Myextension\Domain\Model\TtContent $contentElement
* @return void
*/
public function addContentElement(\MyNameSpace\MyExtension\Domain\Model\TtContent $contentElement)
{
   if ($this->getContentElements() === null) {
      $this->contentElements = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage();
   }
   $this->contentElements->attach($contentElement);
}

/**
* Get id list of content elements
*
* @return string
*/
public function getContentElementIdList()
{
   $idList = [];
   $contentElements = $this->getContentElements();
   if ($contentElements) {
      foreach ($this->getContentElements() as $contentElement) {
         $idList[] = $contentElement->getUid();
      }
   }
   return implode(',', $idList);
}

/**
* Get content elements
*
* @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage
*/
public function getContentElements()
{
   return $this->contentElements;
}

TypoScript

This is the TypoScript – Template, that defines the selection of the records, based on the table „tt_content“. It looks quite simple, but is an essential part reusing content elements. It is needed for the fluid template

# Rendering of content elements in detail view
lib.tx_myextension.contentElementRendering = RECORDS
lib.tx_myextension.contentElementRendering {
   tables = tt_content
   source.current = 1
   dontCheckPid = 1
}

Fluid – Template

The fluid template finally renders the content elements with in the plugin of your extension to the frontend.

<f:cObject typoscriptObjectPath="lib.tx_myextension.contentElementRendering">
   {myModel.contentElementIdList}
</f:cObject>

Conclusion

I think this is a nice way to use and add normal content elements in a custom extension. This way you don’t have to re-invent the wheel again and again and blow up your model. Currently all available content elements are allowed. I can imagine, that in most cases a restriction to certain content elements or plugins would be helpful. I did not dig deeper into it, but would be probably worth another post. What do you think?

I want to thank Georg Ringer using this code in his extension “news” and sharing it with us all. It always a good inspiration for me!

Credits
The blogpost image was originally published on pixabay by Snufkin using the CC0 Public Domain License. I modified using Pablo by Buffer.

This Post Has 15 Comments

  1. Erdal

    Great article. Thanks for sharing. Thus with this enhancement I can have e.g. text / media CE in my own extension without much effort, right? For me one or two screenshots of the practical use would make it easier to understand.

    1. Phil

      Check tx_news on how you can do it.

    2. Marcus Schwemer

      I will add one or two screenshots later, but it is “just” a backend screenshot of the backend showing a little bit more … nothing magic or special. So I thought, I can omit this.

      But you are not the only one who asked for it. ;-)

  2. Ralf Merz

    Hi Markus,

    thank you very much for writing this topic down. One thing I would like to add to this is the mapping of the own TtContent model in the extension´s ext_typoscript_setup.txt.

    config.tx_extbase{
        persistence {
            classes {
    MyNameSpace\ExtensionName\Domain\Model\TtContent {
                    mapping {
                        tableName = tt_content
                        columns {
                            altText.mapOnProperty = altText
                            titleText.mapOnProperty = titleText
                            colPos.mapOnProperty = colPos
                            CType.mapOnProperty = CType
                        }
                    }
                }
            } 
        }
    }

    Without that I got the error that table tx_myextension_domain_model_ttcontent does not exist.

    Regards,

    Ralf

    1. Urs

      Although this is an old entry, I might add that you also need to add the TtContentModel to your own extension. You can take the model from tt_news (Classes/Domain/Model/TtContent.php) and just change the namespace to your own namespace.

      Without this, I got an error saying «Could not find class definition for name “MyNameSpace\MyExt\Domain\Model\TtContent”».

  3. Alex

    First of all: Thank you guys for the tutorial, this is the first one that actually helped me get things going.

    But:

    Is there a way to hide the created Elements in the backend? I have a folder with multiple Models, that each can have multiple Content elements.

    Since the created content elements only matter in connection with their models, i would like to hide them from the folder list view.

    Reading in some Extensions like Tx_mask i think this could be done by assigning them a unique colPos that isnt rendered in the backend. But i wasnt able to get this done.

    Sidenote: the Table-Creation-Wizard throws an exeption  when i click save, though the data is saved. Any Ideas?

  4. Robert Wildling

    That’s a great tutorial!!! Thanks so much, works peachy!

    Two question:
    1. Why is it necessary to define this line in my ttContent repo?
    protected $objectType = ‘\Rowild\Rwfm\Domain\Model\TtContent’;
    2. Would that also work for flexforms? (I tried, but can’t get it working, so I wonder if there is something special I have to look out for?)

  5. Katharina

    Hello
    i want to add content-elements to my extsion but after all I get:
    #1472074379: Table ‘myDb.tx_myext_domain_model_ttcontent’ doesn’t exist

  6. Sascha

    Besides the missing class mapping in typoscript that Ralf Merz mentioned,

    the Line protected $objectType = '\MyNameSpace\Myextension\Domain\Model\Ttcontent'; is not needed, as the repository already builds the correct namespace based on the namespace of the repository.

    Also the entry content_about inside the showRecordFieldList of the TCA is nowhere else mentioned or defined. I would guess that should be content_elements too.

  7. Anonymous

    4.5

  8. Philippe Moreau

    Awesome post! Worked on first try ;)

  9. Anonymous

    5

  10. N1ck

    I have a lot of content elements in one folder and the data records into which they are included take a long time to load.
    With few contentelements, this was not a problem. I suspect that the content elements are all checked when they are saved because they have the same pid (so they were all on one page). Is there a workaround or can you turn this off behavior?

  11. HAMADA SAIDI

    CREATE FILE IN YOUR EXT UNDER => Configuration/Extbase/Persistence/Classes.php
    ADD THE FOLLOWING

    [
    'tableName' => 'tt_content',
    'properties' => [
    'altText' => [
    'fieldName' => 'altText'
    ],
    'titleText' => [
    'fieldName' => 'titleText'
    ],
    'colPos' => [
    'fieldName' => 'colPos'
    ],
    'CType' => [
    'fieldName' => 'CType'
    ],
    ],
    ]
    ];

    THEN YOU GOOD TO GO

Leave a Reply