Create a backend module

Setup, customize and more

Register the module

First we need ro register our module. For that we create or edit the ext_tables.php of our extension. Then we can use the registerModule() method from ExtensionUtility.

 

<?php
defined('TYPO3_MODE') || die('Access denied.');

call_user_func(
    function ($extKey) {
        if (TYPO3_MODE === 'BE') {
            \TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerModule(
                '<vendorName>.<ExtensionName>',
                'tools', // Make module a submodule of 'tools'
                'mybemodule', // Submodule key
                '', // Position
                [
                    'MyController' => 'list, show'
                ],
                [
                    'access' => 'user,group',
                    'icon' => 'EXT:' . $extKey . '/Resources/Public/Icons/user_mod_mybemodule.svg',
                    'labels' => 'LLL:EXT:' . $extKey . '/Resources/Private/Language/locallang_mybemodule.xlf',
                ]
            );
        }
    },
    $_EXTKEY
);

 

Take a look into the documentation, if you don´t know what the vendor name or extension name is.

Documentation

My module is a "tool", so I decided to put it into the "tools" section. Other sections are "admin tools", "file", "help", "system", "user" and "web". You can also add your own sections if you want.

Define the template paths for fluid

For a better structure it is advisable to place the fluid templates of the backend modules in a separate directory. To define this, you can use the following 2 methods from the ExtensionManagementUtility. We call these methods via the ext_localconf.php of our extension.

 

\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTypoScriptSetup(
    'module.tx_kronova {
        view {
            templateRootPaths {
                0 = EXT:kronova/Resources/Private/Backend/Templates/
                1 = {$module.tx_kronova.view.templateRootPath}
            }
            partialRootPaths {
                0 = EXT:kronova/Resources/Private/Backend/Partials/
                1 = {$module.tx_kronova.view.partialRootPath}
            }
            layoutRootPaths {
                0 = EXT:kronova/Resources/Private/Backend/Layouts/
                1 = {$module.tx_kronova.view.layoutRootPath}
            }
        }
        persistence < plugin.tx_kronova.persistence
    }'
);
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTypoScriptConstants(
    'module.tx_kronova {
        view {
            # cat=module.tx_kronova/file; type=string; label=Path to template root (BE)
            templateRootPath = EXT:kronova/Resources/Private/Backend/Templates/
            # cat=module.tx_kronova/file; type=string; label=Path to template partials (BE)
            partialRootPath = EXT:kronova/Resources/Private/Backend/Partials/
            # cat=module.tx_kronova/file; type=string; label=Path to template layouts (BE)
            layoutRootPath = EXT:kronova/Resources/Private/Backend/Layouts/
        }
        persistence {
            # cat=module.tx_kronova//a; type=string; label=Default storage PID
            storagePid =
        }
    }'
);

 

 

Generate an action menu inside the controller

Since TYPO3 8 we can easily generate a menu with Extbase:

 

/**
 * Generates and adds the menu to the docheader
 *
 * @return void
 */
protected function generateMenu()
{
    $menu = $this->view->getModuleTemplate()->getDocHeaderComponent()->getMenuRegistry()->makeMenu();
    $menu->setIdentifier('BackendChatMenu');
    // ['controllerName' => ['actionName1', 'actionName2']]
    $menuItems = ['BackendChat' => ['show', 'management']];
    foreach ($menuItems as $controller => $actions) {
        $underscoredControllerName = GeneralUtility::camelCaseToLowerCaseUnderscored($controller);
        foreach ($actions as $action) {
            $menuItem = $menu->makeMenuItem();
            $menuItem->setTitle(
                LocalizationUtility::translate(
                    'LLL:EXT:chat/Resources/Private/Language/locallang.xlf:menu.' .
                    $underscoredControllerName . '.' . GeneralUtility::camelCaseToLowerCaseUnderscored($action),
                    ''
                )
            );
            if ($this->request->getControllerName() === $controller) {
                $isActive = $this->request->getControllerActionName() === $action ? true : false;
            } else {
                $isActive = false;
            }
            $menuItem->setActive($isActive);
            $menuItem->setHref($this->getHref($controller, $action));
            $menu->addMenuItem($menuItem);
        }
    }
    $this->view->getModuleTemplate()->getDocHeaderComponent()->getMenuRegistry()->addMenu($menu);
}

/**
 * Creates the URI for a backend action
 *
 * @param string $controller
 * @param string $action
 * @param array $parameters
 * @return string
 */
protected function getHref($controller, $action, $parameters = [])
{
    $uriBuilder = $this->objectManager->get(UriBuilder::class);
    $uriBuilder->setRequest($this->request);
    return $uriBuilder->reset()->uriFor($action, $parameters, $controller);
}