Welcome to xm_tools’s documentation!

A framework for TYPO3 extensions.

Contents:

Overview

xm_tools was built to facilitate common use cases a TYPO3 extension developer runs into. It is an extension for extension developers, so you will not find any frontend or backend plugin in it. The purpose was to generalize certain workflows and so outsource commonly used source code.

A common requirement for TYPO3 extensions is to list and filter data. Using session data more quickly for developers is described here. Sometimes we want to access another extension from our extension, such as the other extension’s configuration, assets or translations. You can learn how to do this in the section Extension Manager.

You can use xm_tools to store global translations that you want to use from other extensions as well. Some usefull Fluid view helpers are also included. Hooks for existing extensions we sometimes use are covered here.

Another part of xm_tools is to help connect an extensions to an API. There is a workflow described here which covers the generation of an extension based on the data structure and how to query it. xm_tools makes it possible to use your entities and repositories the ‘extbase way’ as if the data was in your TYPO3 database.

Configuration

Adding the static template Xima Tools makes four configuration settings available:

  • plugin.tx_xmtools.settings.loggingIsEnabled: enable some logging through the TYPO3CMSCoreLogLogger
  • plugin.tx_xmtools.settings.devModeIsEnabled: currently not in use
  • plugin.tx_xmtools.settings.jsSupportIsEnabled: use the integrated Javascript functions, e.g. serve parameters in Javascript.
  • plugin.tx_xmtools.settings.jsL10nIsEnabled: use global and translations from other extensions in Javascript as well.

Tools

Contents:

Session handling

xm_tools provides a convient interface to the wer server’s session. To write data to and retrieve data from the session, use the SessionManager:

class MyController
{
    /**
     * session
     *
     * @var XmTools\Classes\Typo3\SessionManager
     * @inject
     */
    protected $session;

    function updateSessionData()
    {
        $data = $this->session->get('myData');
        $date['newStuff'] = 'new text';

        $this->session->set('myData', $data);
    }
}

This session is kept for the current extension and the current page. If you want to store data somewhere specific to be able to use it on other pages and/or by another extensions, you can use (see SessionManager::set) with key of your choice:

function updateSessionDataForSomewhereElse()
{
    $data = $this->session->get('myData');
    $date['newStuff'] = 'new text';

    //send it to another extension on another page
    $this->session->set('myData', $data, 100, 'anotherExtensionKey');
}

Query

xm_tools provides a basic query class which can be used to represent a user’s query to the database. The QueryTrait simply provides common filters and their getter and setter functions:

  • limit
  • currentPage
  • searchTerm
  • lang
  • sort
  • context (currently used in connection with the API: different contexts decide about which properties (or just all) of an entity are to be sent back)

In order to set up a query object specific for your domain, create your custom query object and use the QueryTrait in it:

class BlogQuery
{
    use \Xima\XmTools\Classes\Typo3\Query\QueryTrait
    {
        getParamKeys as traitGetParamKeys;
    }

    /**
     * subject
     *
     * @var string
     */
    protected $subject;

    public function getSubject() {

        return $this->subject;
    }

    public function setSubject($subject) {

        $this->subject = $subject;
        return $this;
    }
}

Paging

The Paginator generates links to walk through a paged result set. It returns an array of links (see Paginator::getPageBrowser).

$paginator = new \Xima\XmTools\Classes\Helper\Paginator();
$pageBrowser = $paginator->getPageBrowser(
        $countAllResult,
        $countItemsPerPage,
        $currentPage,
        $url
    );

$this->view->assign('pageBrowser', $pageBrowser);

A template to render the returned array is not yet part of xm_tools. An example template would look like:

{namespace xmTools = Xima\XmTools\Classes\ViewHelpers}

<f:if condition="{pageBrowser.countPages}">
    <nav class="pager-nav nav-js">
        <ul class="pagination">
            <f:if condition="{pageBrowser.prev}">
                <li>
                    <f:link.action arguments="{page: pageBrowser.prev, tab: tab}" title="{dict.list_pager_previous_page}">
                        <span class="icon icon-backward"></span>
                    </f:link.action>
                </li>
            </f:if>

            <f:if condition="{xmTools:object.ArrayCheck(array:pageBrowser.pages,needle:1,check:'NOT_IN_KEYS')}">
                <li>
                    <f:link.action arguments="{page: 1, tab: tab}">
                        1
                    </f:link.action>
                </li>
                <f:if condition="{xmTools:object.ArrayCheck(array:pageBrowser.pages,needle:2,check:'NOT_IN_KEYS')}">
                    <li>
                        ...
                    </li>
                </f:if>
            </f:if>

            <f:for each="{pageBrowser.pages}" as="page" key="n">
                <f:if condition="{page.current} == 0">
                    <f:then>
                        <li>
                            <f:link.action arguments="{page: n, tab: tab}">
                                {n}
                            </f:link.action>
                        </li>
                    </f:then>
                    <f:else>
                        <li class="active">
                            <a>{n}</a>
                        </li>
                    </f:else>
                </f:if>
            </f:for>

            <f:if condition="{xmTools:object.ArrayCheck(array:pageBrowser.pages,needle:pageBrowser.countPages,check:'NOT_IN_KEYS')}">
                <f:if condition="{xmTools:object.ArrayCheck(array:pageBrowser.pages,needle:pageBrowser.penUltimatePage,check:'NOT_IN_KEYS')}">
                    <li>
                        ...
                    </li>
                </f:if>
                <li>
                    <f:link.action arguments="{page: pageBrowser.countPages, tab: tab}">
                        {pageBrowser.countPages}
                    </f:link.action>
                </li>

            </f:if>

            <f:if condition="{pageBrowser.next}">
                <li>
                    <f:link.action arguments="{page: pageBrowser.next, tab: tab}" title="{dict.list_pager_next_page}">
                        <span class="icon icon-forward"></span>
                    </f:link.action>
                </li>
            </f:if>
        </ul>
    </nav>
</f:if>

Filter-List-Flow

A common use case of TYPO3 extensions is to list, filter and show data. The suggested pattern to do this repeated task described and support by xm_tools consists of two plugins: one for the query and one for the list. They share the query submitted by the user by using the session.

The query plugin

Create a query class that use the QueryTrait and add the properties you want to filter the data for (see here). Create a controller and a template for the filter you want to show to your user.

abstract class AbstractController
{
    /**
     * paginator
     *
     * @var \Xima\XmTools\Classes\Helper\Paginator
     * @inject
     */
    protected $paginator = null;

    /**
     * @param $className
     * @throws \TYPO3\CMS\Extbase\Mvc\Exception\NoSuchArgumentException
     */
    protected function initializeQuery($className)
    {
        //the filter object
        $query = $this->session->get('query');
        if (!is_a($query, $className)) {
            $query = $this->objectManager->get($className);
        }

        //paging
        if ($this->request->hasArgument('page')) {
            $query->setCurrentPage($this->request->getArgument('page'));
        }

        //limit
        $query->setLimit($this->settings ['flexform'] ['limit']);

        $this->query = $query;
    }
}

class BlogQueryController extends AbstractController
{

    /**
     * action new
     *
     * @return void
     */
    public function newAction()
    {
        $this->initializeQuery('Xima\BlogExampleExtension\Domain\Model\Query\BlogQuery');

        //example form data: get tags to allow for filtering on them
        $tags = $this->objectManager->get('Xima\BlogExampleExtension\Domain\Repository\TagRepository')->findAll();
        $this->view->assign('tags', $tags);

        $this->session->set('query', $this->query);
        $this->view->assign('query', $this->query);

    }

    /**
     * action create
     *
     * @param \Xima\BlogExampleExtension\Domain\Model\Query\BlogQuery $newBlogQuery
     * @return void
     */
    public function createAction(\Xima\XmDwiDb\Domain\Model\Query\BlogQuery $newBlogQuery)
    {
        $this->session->set('query', $newBlogQuery);
        $this->redirect('new');
    }

}

The list plugin

In your list action, retrieve the query from the session and let your entity repository filter your data:

class BlogController extends AbstractController
{

    ...

    /**
     * action list
     *
     * @return void
     */
    public function listAction()
    {
        $this->initializeQuery('Xima\BlogExampleExtension\Domain\Model\Query\BlogQuery');

        $items = $this->repository->findAllByQuery($query);
        $this->view->assign('items', $items);
    }
}

Note: The repository function findAllByQuery is so far only implemented for the ApiRepository class (see ApiRepository::findAllByQuery).

I10n

Translating extensions is often needed for frontend plugins. One challenge is to offer those translations for Javascript functions, another is to share translations between extensions, so that common labels are stored in one place. The Localization class tries to accomplish all those goals, while additionally offering a quick access to translations from fluid templates. Translations are to be stored in the common xliff files. If you want to share a global dictionary with all your extensions, rename any Resources\Private\Language\locallang.xlf.dist file to *.xlf and/or add more languages.

Retrieve a dictionary and assign it to the view

By default, the returns all translations from your current extension. You can pass an array of additional extension names you want to have included in your dictionary:

In your controller action:
...
$this->view->assign('dict', Localization::getDictionary());
...

In your fluid template:
...
{dict.subject}: {post.subject}
...

Use the dictionary in Javascript

If you want to have your translations available in Javascript as well, enable it by configuring xm_tools to do so (see Configuration). This will generate a Javascript file and make it load. Your translations are available in Javascript through:

...
xmTools.getTranslation('subject');
...

Use the dictionary in PHP

Of course you can also access your translations of the dictionary by using the Dictionary::getTranslations method or by accessing the translations directly via the magic Dictionary::__call method, e.g.:

...
$dictionary = Localization::getDictionary();
echo $dictionary->subject();
...

Parameters

The meaning of parameters in this extension is the availability of some configuration or other information to all extensions. Imagine you want all your dates formatted in the same way, being it called from PHP, Fluid or Javascript. Just copy parameters.yml.dist from xm_tools’ root folder to parameters.yml and fill your data you want to have available anywhere. Add any configuration in YAML style.

The Servcies class looks for an existing parameters.yml, parses (and caches it) and makes it available. If you want to have the parameters available in Javascript, enable it by configuring xm_tools to do so (see Configuration). This will generate a Javascript file and make it load. Your translations are available in Javascript through:

...
xmTools.getParameter('dateFormat');
...

Caching

The CacheManager offers a simple way to write and restore data to the file system. Currently, a cache counts as valid when it’s not older than 24 hours. See CacheManager::write and CacheManager::get.

Extension manager

The xm_tools extension offers to access other extensions, meaning their settings, configuration, assets or translations. Get an extension by injecting the ExtensionManager and calling it’s method ExtensionManager::getExtensionByName:

class BlogController
{
    /**
     * @var \Xima\XmTools\Classes\Typo3\Extension\ExtensionManager
     * @inject
     */
    protected $extensionManager;

    public function exampleAction()
    {
        $someOtherExtension = $this->extensionManager->getExtensionByName('SomeOtherExtensionName');
    }
}

This will give you a Extension object.

Services

The Services class can be seen as a facade that you can inject to your controller (or by extending the AbstractController). Once initialized, there is a collection of usefull functions you can use:

For a full list see the API documentation.

REST-API Connector

This section describes how to create a TYPO3 extension that retrieves data from and sends data to an API, while acting as an Extbase extension on the TYPO3 side.

Given there is an API that publishes data. We want our extension to have entity classes mapping to the structure the API returns. By using the tool Extbaser, you can set up your new extension’s skeleton, the file ExtensionBuilder.json. Using this and the TYPO3 backend extension Extension Builder you can create your extension.

The following image describes the way to create your API extension and how data flows.

_images/api_workflow.png

Enabling the new entities’ repositories to retrieve data from the API requires the following steps:

  • configure your API connection in the Typoscript template of your plugin or in the extension settings (ext_conf_template, then leave the settings out):
...
settings {
       api {
        # cat=plugin.tx_yourextension/api/001; type=string; label=Api-URL
        url =
        # cat=plugin.tx_yourextension/api/002; type=string; label=Api-Key
        key =
        # cat=plugin.tx_yourextension/api/003; type=string; label=Api-Schema (available placeholders: [Api-URL], [Api-Route], [Api-Key])
        schema = #e.g.  "[Api-URL]/[Api-Route]?api_key=[Api-Key]"
        # cat=plugin.tx_yourextension/api/004; type=string; label=Route for finding one result by id (available placeholders: [Target])
        routeFindById =
        # cat=plugin.tx_yourextension/api/005; type=string; label=Route for finding by query (available placeholders: [Target])
        routeFindByQuery =
        # cat=plugin.tx_yourextension/api/006; type=string; label=Route for creating entities (available placeholders: [Target])
        routeCreate =
        # cat=plugin.tx_yourextension/api/007; type=string; label=Route for updating entities (available placeholders: [Target])
        routeUpdate =
        # cat=plugin.tx_yourextension/api/008; type=boolean; label=Use Api-Cache
        isCacheEnabled =
    }
...
  • make your model classes extend \Xima\XmTools\Classes\API\REST\Model\AbstractEntity
  • make your repositories extend \Xima\XmTools\Classes\API\REST\Repository\ApiRepository and implement \Xima\XmTools\Classes\Typo3\Extension\ExtensionAwareInterface

You can then use your new extension’s repositories just the same way as native Extbase repositories, e.g.:

...
$repository = $objectManager->get('\Xima\BlogExampleExtension\Domain\Repository\BlogRepository');
$blogs = $repository->findAll();
...

This wil retrieve all country entites from the API (considering the API offers a corresponding route, meaning the demanded entity’s name e.g. http://your.api/country/query?&api_key=your_key). The retrieved data gets mapped to your extension’s entity class, e.g. XimaBlogExampleExtensionDomainModelCountry.

ErrorHandler and ProductionExceptionHandler

TYPO3 allows to change handlers for Errors and Exceptions. Configure the following handlers and set up your email address to receive mails about these events:

  1. Go to Install Tool, switch to All configurations
  2. Set [SYS][errorHandler]=\Xima\XmTools\Typo3\Handler\ErrorHandler
  3. Set [SYS][productionExceptionHandler]=\Xima\XmTools\Typo3\Handler\ProductionExceptionHandler
  4. Set the recipient e-mail address in TypoScript (xmTools.errorHandler.recipient = ) or in Constant Editor (Multiple mail addresses possible as CSV)

Extensions

Some relief for some extensions...

Contents:

More tools

  • Helper functions in API documentation: Helper

  • Logger: The Logger class uses the \TYPO3CMS\Core\Log\LogManager to log data. It must be enabled, see Configuration.

    $logger = $objectManager->get('Xima\XmTools\Classes\Typo3\Logger');
    /* @var $logger \Xima\XmTools\Classes\Typo3\Logger */
    $logger->log('Usefull log information...');
    
  • FalHelper: ...

  • FEUser: ...

  • FlexFormHelper: ...

ViewHelper

Control

  • ForViewHelper

    The ForViewHelper represents the for loop (e.g. For loops in PHP). You can name the loop variable if you want to access it in your template.

  • IfViewHelper

    The IfViewHelper can be used to render a template part depending on multiple conditions that can be AND or OR joined.

Form

  • AdvancedSelectViewHelper: ...

Object

  • ArrayCheckViewHelper

    The ArrayCheckViewHelper offers checks on arrays, such as in_array(), empty(). Possible conditions are ‘IN’,’NOT_IN’, ‘NOT_FIRST’, ‘NOT_LAST’, ‘EMPTY’, ‘NOT_EMPTY’, ‘IS_ARRAY’, ‘IN_KEYS’, ‘NOT_IN_KEYS’. Usage example:

    {namespace xmTools = Xima\XmTools\Classes\ViewHelpers}
    <f:if condition="{xmTools:object.ArrayCheck(array:yourArray,needle:1,check:'IN')}">
        ...
    </f:if>
    
  • ArrayExplodeViewHelper

    The ArrayExplodeViewHelper encapsulates PHP’s explode() function. Usage example:

    {namespace xmTools = Xima\XmTools\Classes\ViewHelpers}
    <f:for each="{xmTools:object.ArrayExplode(delimiter:',',string:someString)}" as="item">
        ...do something with {item}
    </f:for>
    
  • ArrayImplodeViewHelper

    The ArrayImplodeViewHelper encapsulates PHP’s implode() function and displays the output. You can specify a key if the array is an array or a property or function if the array is an array of objects. Usage example:

    {namespace xmTools = Xima\XmTools\Classes\ViewHelpers}
    <xmTools:object.ArrayImplode glue=", " array="{someArray}">
    
  • StrReplaceViewHelper

    The StrReplaceViewHelper encapsulates PHP’s str-replace() function.

  • StrtolowerViewHelper

    The StrtolowerViewHelper encapsulates PHP’s strtolower() function.

  • StrtoupperViewHelper

    The StrtoupperViewHelper encapsulates PHP’s strtoupper() function.

Templates

Pake-Link (workaround for internal and external Links in TYPO3 7 < 7.6.3)

see https://forge.typo3.org/issues/72818

The Partial “Resources/Private/Partials/Link.html” can be used as a workaround to correctly render internal and external links:

<v:render.template file="EXT:xm_tools/Resources/Private/Partials/Link.html" variables="{parameter: data.link, linkHtml: linkHtml, class: 'some-classt', title: data.linkText}" />

API documentation