Merge branch 'develop' into 'master'
State after Workshop at TYPO3 Camp Rhein Ruhr 2018 See merge request internal/events/trainings/typo3-extension-workshop!1
This commit is contained in:
commit
a99c42257c
40 changed files with 6554 additions and 0 deletions
4
CodeExamples/.gitignore
vendored
Normal file
4
CodeExamples/.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
/public
|
||||||
|
/vendor
|
||||||
|
/app/*
|
||||||
|
!/app/typo3conf/sites/default/config.yaml
|
39
CodeExamples/app/typo3conf/sites/default/config.yaml
Normal file
39
CodeExamples/app/typo3conf/sites/default/config.yaml
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
rootPageId: 1
|
||||||
|
base: /
|
||||||
|
baseVariants: {}
|
||||||
|
languages:
|
||||||
|
-
|
||||||
|
title: English
|
||||||
|
enabled: true
|
||||||
|
languageId: '0'
|
||||||
|
base: /
|
||||||
|
typo3Language: default
|
||||||
|
locale: en_US.utf8
|
||||||
|
iso-639-1: en
|
||||||
|
navigationTitle: English
|
||||||
|
hreflang: en-US
|
||||||
|
direction: ''
|
||||||
|
flag: global
|
||||||
|
errorHandling: {}
|
||||||
|
routes: {}
|
||||||
|
routeEnhancers:
|
||||||
|
ExamplePlugin:
|
||||||
|
type: Extbase
|
||||||
|
extension: ExampleExtension
|
||||||
|
plugin: Address
|
||||||
|
defaultController: 'Address::index'
|
||||||
|
routes:
|
||||||
|
-
|
||||||
|
routePath: '/edit/{address}'
|
||||||
|
_controller: 'Address::edit'
|
||||||
|
_arguments:
|
||||||
|
'address': 'address'
|
||||||
|
-
|
||||||
|
routePath: '/update'
|
||||||
|
_controller: 'Address::update'
|
||||||
|
aspects:
|
||||||
|
address:
|
||||||
|
type: PersistedPatternMapper
|
||||||
|
tableName: 'tx_exampleextension_domain_model_address'
|
||||||
|
routeFieldPattern: '^(?P<company_name>.+)-(?P<uid>\d+)$'
|
||||||
|
routeFieldResult: '{company_name}-{uid}'
|
44
CodeExamples/composer.json
Normal file
44
CodeExamples/composer.json
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
{
|
||||||
|
"repositories": [
|
||||||
|
{
|
||||||
|
"type": "path",
|
||||||
|
"url": "localPackages/*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "composer",
|
||||||
|
"url": "https://composer.typo3.org/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "website/typo3-extension-workshop",
|
||||||
|
"description": "Example TYPO3 installation for workshop",
|
||||||
|
"license": "GPL-2.0-or-later",
|
||||||
|
"require": {
|
||||||
|
"helhum/typo3-console": "^5.5.5",
|
||||||
|
"helhum/typo3-secure-web": "^0.2.7",
|
||||||
|
"typo3-console/composer-auto-commands": "^0.2.0",
|
||||||
|
"typo3/cms-core": "^9.5.0",
|
||||||
|
"typo3/cms-about": "*",
|
||||||
|
"typo3/cms-belog": "*",
|
||||||
|
"typo3/cms-beuser": "*",
|
||||||
|
"typo3/cms-fluid-styled-content": "*",
|
||||||
|
"typo3/cms-info": "*",
|
||||||
|
"typo3/cms-info-pagetsconfig": "*",
|
||||||
|
"typo3/cms-rte-ckeditor": "*",
|
||||||
|
"typo3/cms-setup": "*",
|
||||||
|
"typo3/cms-t3editor": "*",
|
||||||
|
"typo3/cms-tstemplate": "*",
|
||||||
|
"workshop/example-extension": "@dev"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"typo3/cms-lowlevel": "*"
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"typo3/cms": {
|
||||||
|
"app-dir": "app",
|
||||||
|
"root-dir": "app",
|
||||||
|
"web-dir": "public"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minimum-stability": "dev",
|
||||||
|
"prefer-stable": true
|
||||||
|
}
|
4082
CodeExamples/composer.lock
generated
Normal file
4082
CodeExamples/composer.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,67 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Workshop\ExampleExtension\Controller;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Daniel Siepmann <coding@daniel-siepmann.de>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||||
|
* 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
|
||||||
|
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
|
||||||
|
use Workshop\ExampleExtension\Domain\Model\Address;
|
||||||
|
use Workshop\ExampleExtension\Domain\Repository\AddressRepository;
|
||||||
|
|
||||||
|
class AddressController extends ActionController
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var AddressRepository
|
||||||
|
*/
|
||||||
|
protected $addressRepository;
|
||||||
|
|
||||||
|
public function __construct(AddressRepository $addressRepository)
|
||||||
|
{
|
||||||
|
$this->addressRepository = $addressRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function indexAction()
|
||||||
|
{
|
||||||
|
$this->view->assign('addresses', $this->addressRepository->findAll());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ignorevalidation $address
|
||||||
|
*/
|
||||||
|
public function editAction(Address $address)
|
||||||
|
{
|
||||||
|
$this->view->assign('address', $address);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateAction(Address $address)
|
||||||
|
{
|
||||||
|
$this->addressRepository->update($address);
|
||||||
|
|
||||||
|
$this->addFlashMessage(
|
||||||
|
LocalizationUtility::translate('flashSuccess', 'ExampleExtension', [
|
||||||
|
'companyName' => $address->getCompanyName(),
|
||||||
|
'street' => $address->getStreet(),
|
||||||
|
]),
|
||||||
|
'Update successfully'
|
||||||
|
);
|
||||||
|
$this->redirect('index');
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Workshop\ExampleExtension\Controller;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Daniel Siepmann <coding@daniel-siepmann.de>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||||
|
* 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
|
||||||
|
|
||||||
|
class ExampleController extends ActionController
|
||||||
|
{
|
||||||
|
public function exampleAction()
|
||||||
|
{
|
||||||
|
// Use the code below, to output the string.
|
||||||
|
// Comment the code out, to use fluid template from
|
||||||
|
// "Resources/Private/Templates/Example/Example.html"
|
||||||
|
|
||||||
|
return 'Hello world!';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,119 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Workshop\ExampleExtension\Domain\Model;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Daniel Siepmann <coding@daniel-siepmann.de>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||||
|
* 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
|
||||||
|
|
||||||
|
class Address extends AbstractEntity
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
* @validate NotEmpty
|
||||||
|
*/
|
||||||
|
protected $companyName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $street;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $houseNumber;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
* @validate RegularExpression(regularExpression = '/^[0-9]{5}$/')
|
||||||
|
*/
|
||||||
|
protected $zip;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $city;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $country;
|
||||||
|
|
||||||
|
public function setCompanyName(string $companyName)
|
||||||
|
{
|
||||||
|
$this->companyName = $companyName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCompanyName(): string
|
||||||
|
{
|
||||||
|
return $this->companyName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setStreet(string $street)
|
||||||
|
{
|
||||||
|
$this->street = $street;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getStreet(): string
|
||||||
|
{
|
||||||
|
return $this->street;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setHouseNumber(string $houseNumber)
|
||||||
|
{
|
||||||
|
$this->houseNumber = $houseNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getHouseNumber(): string
|
||||||
|
{
|
||||||
|
return $this->houseNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setZip(string $zip)
|
||||||
|
{
|
||||||
|
$this->zip = $zip;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getZip(): string
|
||||||
|
{
|
||||||
|
return $this->zip;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCity(string $city)
|
||||||
|
{
|
||||||
|
$this->city = $city;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCity(): string
|
||||||
|
{
|
||||||
|
return $this->city;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCountry(string $country)
|
||||||
|
{
|
||||||
|
$this->country = $country;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCountry(): string
|
||||||
|
{
|
||||||
|
return $this->country;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Workshop\ExampleExtension\Domain\Repository;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Daniel Siepmann <coding@daniel-siepmann.de>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||||
|
* 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use TYPO3\CMS\Extbase\Persistence\Repository;
|
||||||
|
|
||||||
|
class AddressRepository extends Repository
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
\TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin(
|
||||||
|
'Workshop.ExampleExtension',
|
||||||
|
'pluginkey',
|
||||||
|
'Example Plugin'
|
||||||
|
);
|
||||||
|
|
||||||
|
\TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin(
|
||||||
|
'Workshop.ExampleExtension',
|
||||||
|
'Address',
|
||||||
|
'Address Plugin'
|
||||||
|
);
|
||||||
|
})();
|
|
@ -0,0 +1,101 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return (function (
|
||||||
|
$extensionKey = 'example_extension',
|
||||||
|
$tableName = 'tx_workshopexampleextension_domain_model_address'
|
||||||
|
) {
|
||||||
|
$extensionLanguagePrefix = 'LLL:EXT:example_extension/Resources/Private/Language/locallang_tca.xlf:';
|
||||||
|
$coreLanguagePrefix = 'LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:';
|
||||||
|
|
||||||
|
return [
|
||||||
|
'ctrl' => [
|
||||||
|
'label' => 'company_name',
|
||||||
|
'default_sortby' => 'company_name',
|
||||||
|
'tstamp' => 'tstamp',
|
||||||
|
'crdate' => 'crdate',
|
||||||
|
'cruser_id' => 'cruser_id',
|
||||||
|
'title' => $extensionLanguagePrefix . 'address',
|
||||||
|
'delete' => 'deleted',
|
||||||
|
'enablecolumns' => [
|
||||||
|
'disabled' => 'hidden',
|
||||||
|
'starttime' => 'starttime',
|
||||||
|
'endtime' => 'endtime'
|
||||||
|
],
|
||||||
|
'searchFields' => 'company_name, street, city'
|
||||||
|
],
|
||||||
|
'interface' => [
|
||||||
|
'showRecordFieldList' => 'company_name, street, house_number, zip, city, country'
|
||||||
|
],
|
||||||
|
'palettes' => [
|
||||||
|
'address' => [
|
||||||
|
'showitem' => implode(',', [
|
||||||
|
'street, house_number',
|
||||||
|
'--linebreak--',
|
||||||
|
'zip, city',
|
||||||
|
'--linebreak--',
|
||||||
|
'country',
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'types' => [
|
||||||
|
'0' => [
|
||||||
|
'showitem' => implode(',', [
|
||||||
|
'--div--;' . $coreLanguagePrefix . 'general',
|
||||||
|
'company_name;;address',
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'columns' => [
|
||||||
|
'company_name' => [
|
||||||
|
'label' => $extensionLanguagePrefix . 'company_name',
|
||||||
|
'config' => [
|
||||||
|
'type' => 'input',
|
||||||
|
'max' => 255,
|
||||||
|
'eval' => 'trim,required',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'street' => [
|
||||||
|
'label' => $extensionLanguagePrefix . 'street',
|
||||||
|
'config' => [
|
||||||
|
'type' => 'input',
|
||||||
|
'max' => 255,
|
||||||
|
'eval' => 'trim,required',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'house_number' => [
|
||||||
|
'label' => $extensionLanguagePrefix . 'house_number',
|
||||||
|
'config' => [
|
||||||
|
'type' => 'input',
|
||||||
|
'size' => 10,
|
||||||
|
'max' => 255,
|
||||||
|
'eval' => 'trim,required',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'zip' => [
|
||||||
|
'label' => $extensionLanguagePrefix . 'zip',
|
||||||
|
'config' => [
|
||||||
|
'type' => 'input',
|
||||||
|
'size' => 10,
|
||||||
|
'max' => 255,
|
||||||
|
'eval' => 'trim,required',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'city' => [
|
||||||
|
'label' => $extensionLanguagePrefix . 'city',
|
||||||
|
'config' => [
|
||||||
|
'type' => 'input',
|
||||||
|
'max' => 255,
|
||||||
|
'eval' => 'trim,required',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'country' => [
|
||||||
|
'label' => $extensionLanguagePrefix . 'country',
|
||||||
|
'config' => [
|
||||||
|
'type' => 'input',
|
||||||
|
'max' => 255,
|
||||||
|
'eval' => 'trim,required',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
})();
|
|
@ -0,0 +1,39 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<file source-language="en" datatype="plaintext" original="messages" date="2018-10-01T11:16:33Z" product-name="CodeExamples">
|
||||||
|
<header/>
|
||||||
|
<body>
|
||||||
|
<!-- Form Labels -->
|
||||||
|
<trans-unit id="labels.companyName" xml:space="preserve">
|
||||||
|
<source>Company name</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="labels.street" xml:space="preserve">
|
||||||
|
<source>Street</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="labels.houseNumber" xml:space="preserve">
|
||||||
|
<source>House number</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="labels.zip" xml:space="preserve">
|
||||||
|
<source>Zip</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="labels.city" xml:space="preserve">
|
||||||
|
<source>City</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="labels.country" xml:space="preserve">
|
||||||
|
<source>Country</source>
|
||||||
|
</trans-unit>
|
||||||
|
|
||||||
|
<!-- Form Validation -->
|
||||||
|
<trans-unit id="error.address.companyName.1221560718" xml:space="preserve">
|
||||||
|
<source>Please provide a company name.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="error.address.zip.1221565130" xml:space="preserve">
|
||||||
|
<source>Please provide a valid ZIP consisting of 5 digits.</source>
|
||||||
|
</trans-unit>
|
||||||
|
|
||||||
|
<trans-unit id="flashSuccess" xml:space="preserve">
|
||||||
|
<source>Update des Datensatzes %1$s war Erfolgreich.</source>
|
||||||
|
</trans-unit>
|
||||||
|
</body>
|
||||||
|
</file>
|
||||||
|
</xliff>
|
|
@ -0,0 +1,29 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<file source-language="en" datatype="plaintext" original="messages" date="2018-09-05T08:51:01Z" product-name="CodeExamples">
|
||||||
|
<header/>
|
||||||
|
<body>
|
||||||
|
<trans-unit id="address" xml:space="preserve">
|
||||||
|
<source>Address</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="company_name" xml:space="preserve">
|
||||||
|
<source>Company name</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="street" xml:space="preserve">
|
||||||
|
<source>Street</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="house_number" xml:space="preserve">
|
||||||
|
<source>House number</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="zip" xml:space="preserve">
|
||||||
|
<source>Zip</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="city" xml:space="preserve">
|
||||||
|
<source>City</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="country" xml:space="preserve">
|
||||||
|
<source>Country</source>
|
||||||
|
</trans-unit>
|
||||||
|
</body>
|
||||||
|
</file>
|
||||||
|
</xliff>
|
|
@ -0,0 +1,41 @@
|
||||||
|
<h3>Editing: {address.companyName}</h3>
|
||||||
|
|
||||||
|
<f:form action="update" object="{address}" name="address">
|
||||||
|
<f:for each="{
|
||||||
|
0: 'companyName',
|
||||||
|
1: 'street',
|
||||||
|
2: 'houseNumber',
|
||||||
|
3: 'zip',
|
||||||
|
4: 'city',
|
||||||
|
5: 'country'
|
||||||
|
}" as="propertyName">
|
||||||
|
{f:render(section: 'Field', arguments: {
|
||||||
|
propertyName: propertyName
|
||||||
|
})}
|
||||||
|
</f:for>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
<f:form.submit value="Update" />
|
||||||
|
</f:form>
|
||||||
|
|
||||||
|
<f:section name="Field">
|
||||||
|
<p>
|
||||||
|
<label for="{propertyName}">{f:translate(id: 'labels.{propertyName}')}</label>
|
||||||
|
<br>
|
||||||
|
<f:form.textfield property="{propertyName}" id="{propertyName}" />
|
||||||
|
{f:render(section: 'FieldErrors', arguments: {
|
||||||
|
propertyPath: 'address.{propertyName}'
|
||||||
|
})}
|
||||||
|
<br>
|
||||||
|
</p>
|
||||||
|
</f:section>
|
||||||
|
|
||||||
|
<f:section name="FieldErrors">
|
||||||
|
<f:form.validationResults for="{propertyPath}">
|
||||||
|
<f:for each="{validationResults.flattenedErrors}" as="errors">
|
||||||
|
<f:for each="{errors}" as="error">
|
||||||
|
{f:translate(id: 'error.{propertyPath}.{error.code}', default: error.code)}
|
||||||
|
</f:for>
|
||||||
|
</f:for>
|
||||||
|
</f:form.validationResults>
|
||||||
|
</f:section>
|
|
@ -0,0 +1,11 @@
|
||||||
|
<f:flashMessages />
|
||||||
|
<f:for each="{addresses}" as="address">
|
||||||
|
<h3>{address.companyName}</h3>
|
||||||
|
<address>
|
||||||
|
{address.street} {address.houseNumber}
|
||||||
|
{address.zip} {address.city}
|
||||||
|
{address.country}
|
||||||
|
</address>
|
||||||
|
|
||||||
|
<f:link.action action="edit" arguments="{address: address}">Edit</f:link.action>
|
||||||
|
</f:for>
|
|
@ -0,0 +1 @@
|
||||||
|
Hello World!
|
21
CodeExamples/localPackages/example_extension/composer.json
Normal file
21
CodeExamples/localPackages/example_extension/composer.json
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"name": "workshop/example-extension",
|
||||||
|
"description": "Example for TYPO3 Extension Workshop.",
|
||||||
|
"type": "typo3-cms-extension",
|
||||||
|
"license": "GPL-2.0-or-later",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Daniel Siepmann",
|
||||||
|
"email": "coding@daniel-siepmann.de"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Workshop\\ExampleExtension\\": "Classes"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.2.0",
|
||||||
|
"typo3/cms-core": "*"
|
||||||
|
}
|
||||||
|
}
|
23
CodeExamples/localPackages/example_extension/ext_emconf.php
Normal file
23
CodeExamples/localPackages/example_extension/ext_emconf.php
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$EM_CONF['example_extension'] = [
|
||||||
|
'title' => 'Example extension',
|
||||||
|
'description' => 'Example for TYPO3 Extension Workshop.',
|
||||||
|
'category' => 'example',
|
||||||
|
'version' => '1.0.0',
|
||||||
|
'state' => 'stable',
|
||||||
|
'author' => 'Daniel Siepmann',
|
||||||
|
'author_email' => 'coding@daniel-siepmann.de',
|
||||||
|
'author_company' => 'Codappix',
|
||||||
|
'constraints' => [
|
||||||
|
'depends' => [
|
||||||
|
'php' => '7.2.0-7.2.999',
|
||||||
|
'typo3' => '8.7.0-9.5.999',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'autoload' => [
|
||||||
|
'psr-4' => [
|
||||||
|
'Workshop\\ExampleExtension\\' => 'Classes',
|
||||||
|
]
|
||||||
|
],
|
||||||
|
];
|
|
@ -0,0 +1,46 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
\TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin(
|
||||||
|
'Workshop.ExampleExtension',
|
||||||
|
'pluginkey',
|
||||||
|
[
|
||||||
|
'Example' => 'example'
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
\TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin(
|
||||||
|
'Workshop.ExampleExtension',
|
||||||
|
'Address',
|
||||||
|
[
|
||||||
|
'Address' => 'index, edit, update'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'Address' => 'update'
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addPageTSConfig('
|
||||||
|
mod {
|
||||||
|
wizards {
|
||||||
|
newContentElement {
|
||||||
|
wizardItems {
|
||||||
|
plugins {
|
||||||
|
elements {
|
||||||
|
exampleElement {
|
||||||
|
iconIdentifier = content-coffee
|
||||||
|
title = Example title
|
||||||
|
description = Example Description
|
||||||
|
tt_content_defValues {
|
||||||
|
CType = list
|
||||||
|
list_type = exampleextension_pluginkey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
');
|
||||||
|
})();
|
|
@ -0,0 +1,8 @@
|
||||||
|
CREATE TABLE tx_exampleextension_domain_model_address (
|
||||||
|
company_name varchar(255) DEFAULT '' NOT NULL,
|
||||||
|
street varchar(255) DEFAULT '' NOT NULL,
|
||||||
|
house_number varchar(255) DEFAULT '' NOT NULL,
|
||||||
|
zip varchar(255) DEFAULT '' NOT NULL,
|
||||||
|
city varchar(255) DEFAULT '' NOT NULL,
|
||||||
|
country varchar(255) DEFAULT '' NOT NULL,
|
||||||
|
);
|
2
Documentation/requirements.txt
Normal file
2
Documentation/requirements.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Sphinx>=1.3.0,<1.4.0
|
||||||
|
guzzle_sphinx_theme>=0.7.0,<0.8.0
|
111
Documentation/source/AddFirstPlugin.rst
Normal file
111
Documentation/source/AddFirstPlugin.rst
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
.. _add-first-plugin:
|
||||||
|
|
||||||
|
Add first Plugin
|
||||||
|
================
|
||||||
|
|
||||||
|
Let's get dirty and start with some functionality.
|
||||||
|
|
||||||
|
Most extensions provide a *Plugin*, this is a functionality provided as content
|
||||||
|
element for TYPO3 CMS Frontend. Also *Modules* are available, that is functionality
|
||||||
|
in TYPO3 CMS Backend.
|
||||||
|
|
||||||
|
Also many more parts like *Signals and Slots* or *Hooks* are available. You can also
|
||||||
|
provide *HashAlgorithms* some *Logger* or *CacheBackends*, etc. TYPO3 CMS can be
|
||||||
|
extended in many areas.
|
||||||
|
|
||||||
|
Still Plugins are a very typical thing to bring in features to your website.
|
||||||
|
|
||||||
|
The Plugin
|
||||||
|
----------
|
||||||
|
|
||||||
|
As mentioned, we will start with a simple example plugin to display "Hello World".
|
||||||
|
|
||||||
|
Register Plugin in Backend
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
.. admonition:: Task
|
||||||
|
|
||||||
|
Register a plugin in TYPO3 backend.
|
||||||
|
|
||||||
|
We first need to register the Plugin in the backend. This way it will become
|
||||||
|
available as a new option for the content element *Insert Plugin*.
|
||||||
|
This is done with the following code in file
|
||||||
|
:file:`Configuration/TCA/Overrides/tt_content.php`:
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
\TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin(
|
||||||
|
'Workshop.ExampleExtension',
|
||||||
|
'pluginkey',
|
||||||
|
'Example Plugin'
|
||||||
|
);
|
||||||
|
})();
|
||||||
|
|
||||||
|
Configure Plugin for Frontend
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
.. admonition:: Task
|
||||||
|
|
||||||
|
Configure a plugin for TYPO3 frontend.
|
||||||
|
|
||||||
|
To actually call some PHP Code when the content element is rendered, we need to
|
||||||
|
configure the plugin in :file:`ext_localconf.php`:
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
\TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin(
|
||||||
|
'Workshop.ExampleExtension',
|
||||||
|
'pluginkey',
|
||||||
|
[
|
||||||
|
'Example' => 'example'
|
||||||
|
]
|
||||||
|
);
|
||||||
|
})();
|
||||||
|
|
||||||
|
|
||||||
|
Write necessary Code
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
.. admonition:: Task
|
||||||
|
|
||||||
|
Remove all PHP Errors, step by step.
|
||||||
|
|
||||||
|
If we insert the plugin as content element and open the site, we should see an error
|
||||||
|
message. This message is not helpful, so we will switch to development context within
|
||||||
|
the installation / maintenance tool. Also we will add the following TypoScript setup
|
||||||
|
for our local development instance:
|
||||||
|
|
||||||
|
.. code-block:: typoscript
|
||||||
|
|
||||||
|
config.contentObjectExceptionHandler = 0
|
||||||
|
|
||||||
|
Afterwards we should see the following error message:
|
||||||
|
|
||||||
|
Could not analyse class: "Workshop\\ExampleExtension\\Controller\\ExampleController" maybe not loaded or no autoloader? Class Workshop\\ExampleExtension\\Controller\\ExampleController does not exist
|
||||||
|
|
||||||
|
This tells us that everything so far has worked as expected. TYPO3 tries to call our
|
||||||
|
*ExampleController*, which just does not exist yet.
|
||||||
|
|
||||||
|
So let's create the controller with the following code in
|
||||||
|
:file:`Classes/Controller/ExampleController.php`:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../CodeExamples/localPackages/example_extension/Classes/Controller/ExampleController.php
|
||||||
|
:language: php
|
||||||
|
:lines: 1-27,36
|
||||||
|
|
||||||
|
The error message should change to:
|
||||||
|
|
||||||
|
An action "exampleAction" does not exist in controller "Workshop\\ExampleExtension\\Controller\\ExampleController".
|
||||||
|
|
||||||
|
Yeah, we fixed the error to get the next one. Even if our class exists, the
|
||||||
|
configured default action does not exist yet, so let's create it.
|
||||||
|
|
||||||
|
.. literalinclude:: ../../CodeExamples/localPackages/example_extension/Classes/Controller/ExampleController.php
|
||||||
|
:language: php
|
||||||
|
:lines: 26-29,34-
|
||||||
|
|
||||||
|
We now should see "Hello world!" in our frontend.
|
||||||
|
|
||||||
|
We just created our first plugin.
|
231
Documentation/source/Configuration.rst
Normal file
231
Documentation/source/Configuration.rst
Normal file
|
@ -0,0 +1,231 @@
|
||||||
|
.. _configuration:
|
||||||
|
|
||||||
|
Configuration
|
||||||
|
=============
|
||||||
|
|
||||||
|
There are many different ways and places in TYPO3 for configuration. We will cover
|
||||||
|
the different places and types of configuration, including when to use a certain way.
|
||||||
|
|
||||||
|
By following the API you also make sure modern approaches like configuration loaders
|
||||||
|
will work with your extensions.
|
||||||
|
|
||||||
|
Places of configuration
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
The following places exist to configure a TYPO3 Extensions:
|
||||||
|
|
||||||
|
PHP
|
||||||
|
^^^
|
||||||
|
|
||||||
|
PHP via :file:`LocalConfiguration.php` and :file:`AdditionalConfiguration.php`.
|
||||||
|
|
||||||
|
Some extensions allow to place configuration in custom files, e.g. EXT:realurl. I
|
||||||
|
would call that a bad practice as you have arbitrary places to check for certain
|
||||||
|
configurations.
|
||||||
|
|
||||||
|
Instead use existing places like the two mentioned files above. This is done by
|
||||||
|
either using:
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
return [
|
||||||
|
'EXTCONF' => [
|
||||||
|
'extkey' => [
|
||||||
|
// options
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
This way you can access the configuration via ``$GLOBALS['EXTCONF']['extkey']``.
|
||||||
|
|
||||||
|
Or by providing a :file:`ext_conf_template.txt` in the root of your Extension.
|
||||||
|
The content is TypoScript as documented at :ref:`t3coreapi:extension-options`.
|
||||||
|
Afterwards you can access the options through an API.
|
||||||
|
|
||||||
|
TypoScript
|
||||||
|
^^^^^^^^^^
|
||||||
|
|
||||||
|
TypoScript and Flexforms are merged by Extbase, so you do not have to do any
|
||||||
|
additional work and combine both. They are available as Variable ``{settings}`` in
|
||||||
|
all templates. Also inside the Controller they are available as array in
|
||||||
|
``$this->settings`` out of the box.
|
||||||
|
|
||||||
|
.. admonition:: Task
|
||||||
|
|
||||||
|
Add some settings and print them in template and controller.
|
||||||
|
|
||||||
|
The configuration via TypoScript has to be located at a specific path:
|
||||||
|
|
||||||
|
.. code-block:: typoscript
|
||||||
|
:linenos:
|
||||||
|
|
||||||
|
// For all frontend plugins of the extension
|
||||||
|
plugin {
|
||||||
|
tx_exampleextension {
|
||||||
|
settings {
|
||||||
|
// The configuration goes here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For a specific frontend plugin of the extension
|
||||||
|
plugin {
|
||||||
|
tx_exampleextension_pluginkey {
|
||||||
|
settings {
|
||||||
|
// The configuration goes here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For Backend Modules
|
||||||
|
module {
|
||||||
|
tx_exampleextension {
|
||||||
|
settings {
|
||||||
|
// The configuration goes here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Extbase itself already has some configuration options available via TypoScript, some
|
||||||
|
are mentioned at :ref:`t3extbasebook:typoscript_configuration` section of Extbase
|
||||||
|
book.
|
||||||
|
|
||||||
|
.. tip::
|
||||||
|
|
||||||
|
The whole ``settings`` array is passed into all templates, layouts and partials.
|
||||||
|
This way it's possible for integrators to provide arbitary information.
|
||||||
|
|
||||||
|
E.g. introduce a new namespace ``codappix`` with project specific settings::
|
||||||
|
|
||||||
|
plugin {
|
||||||
|
tx_exampleextension {
|
||||||
|
settings {
|
||||||
|
codappix {
|
||||||
|
pageUids {
|
||||||
|
detail = {$pageUids.exampleextension.detail}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Also it's possible to insert a plugin via TypoScript. In that case the settings can
|
||||||
|
be provided only for that instance:
|
||||||
|
|
||||||
|
.. code-block:: typoscript
|
||||||
|
:linenos:
|
||||||
|
|
||||||
|
lib.instance = USER
|
||||||
|
lib.instance {
|
||||||
|
userFunc = TYPO3\CMS\Extbase\Core\Bootstrap->run
|
||||||
|
extensionName = ExampleExtension
|
||||||
|
pluginName = pluginkey
|
||||||
|
vendorName = Workshop
|
||||||
|
settings {
|
||||||
|
testKey = testValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Flexforms
|
||||||
|
^^^^^^^^^
|
||||||
|
|
||||||
|
Flexforms are like TCA, which will be covered at :ref:`custom-records-tca` section of
|
||||||
|
:ref:`custom-records`. The format is XML instead of PHP and saved inside the database
|
||||||
|
field ``pi_flexform`` of the ``tt_content`` record. This way editors are able to
|
||||||
|
adjust provided settings within a plugin record.
|
||||||
|
|
||||||
|
Custom
|
||||||
|
^^^^^^
|
||||||
|
|
||||||
|
Do whatever you want, e.g. use yaml or TypoScript by calling the parser for contents
|
||||||
|
from anywhere.
|
||||||
|
|
||||||
|
When to use which
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
The Flexform approach provides the best UX as it uses the known UI of TYPO3 inside a
|
||||||
|
record. It should be used if the setting is plugin instance related.
|
||||||
|
|
||||||
|
The TypoScript provided the best UX when integrators have to deploy configuration or
|
||||||
|
configuration is necessary on multiple pages. Also if the plugin is inserted directly
|
||||||
|
via TypoScript.
|
||||||
|
|
||||||
|
The PHP approach is best suited for instance wide configuration, which nearly never
|
||||||
|
exists. Things like API Keys might depend on the current Domain or Website, and there
|
||||||
|
can be multiple in a single TYPO3 instance.
|
||||||
|
|
||||||
|
.. _configuration-content-wizard:
|
||||||
|
|
||||||
|
Adding Content Wizard for our Plugin
|
||||||
|
------------------------------------
|
||||||
|
|
||||||
|
So far we do not have any configuration. But we can use PageTSConfig to make live
|
||||||
|
easier for editors.
|
||||||
|
|
||||||
|
Right now an editor has to insert a new "Insert Plugin" content record and choose our
|
||||||
|
plugin. We can provide a Configuration to make our Plugin available via the "Create
|
||||||
|
new content element" wizard.
|
||||||
|
|
||||||
|
.. admonition:: Task
|
||||||
|
|
||||||
|
Add the new Plugin to content element wizard.
|
||||||
|
|
||||||
|
Within :file:`ext_localconf.php` we add the following::
|
||||||
|
|
||||||
|
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addPageTSConfig('
|
||||||
|
mod {
|
||||||
|
wizards {
|
||||||
|
newContentElement {
|
||||||
|
wizardItems {
|
||||||
|
plugins {
|
||||||
|
elements {
|
||||||
|
exampleElement {
|
||||||
|
iconIdentifier = content-coffee
|
||||||
|
title = Example title
|
||||||
|
description = Example Description
|
||||||
|
tt_content_defValues {
|
||||||
|
CType = list
|
||||||
|
list_type = exampleextension_pluginkey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
');
|
||||||
|
|
||||||
|
This will add the given PageTS as default to global configuration. Within the TS we
|
||||||
|
define a new element ``exampleElement`` with icon, title and description. The important
|
||||||
|
part is, that we can define default values (``defValues``) for the creation. This way
|
||||||
|
we can pre select the plugin.
|
||||||
|
|
||||||
|
See: https://docs.typo3.org/typo3cms/TSconfigReference/PageTsconfig/Mod.html#wizards
|
||||||
|
|
||||||
|
Available TYPO3 Icons can be found here: https://typo3.github.io/TYPO3.Icons/
|
||||||
|
|
||||||
|
.. _configuration-view-paths:
|
||||||
|
|
||||||
|
Adjusting view paths
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
In :ref:`views` we covered the conventions for paths. However sometimes you need to
|
||||||
|
change these paths. E.g. if you exchange a Partial or template.
|
||||||
|
|
||||||
|
This can be done via TypoScript, the same way as for ``FLUIDTEMPLATE`` cObject:
|
||||||
|
|
||||||
|
.. code-block:: typoscript
|
||||||
|
:linenos:
|
||||||
|
|
||||||
|
plugin {
|
||||||
|
tx_exampleextension {
|
||||||
|
view {
|
||||||
|
templateRootPaths {
|
||||||
|
10 = EXT:sitepackage/Resources/Plugins/ExampleExtension/Templates/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
See: https://docs.typo3.org/typo3cms/TyposcriptReference/ContentObjects/Fluidtemplate/Index.html
|
248
Documentation/source/CustomRecords.rst
Normal file
248
Documentation/source/CustomRecords.rst
Normal file
|
@ -0,0 +1,248 @@
|
||||||
|
.. _custom-records:
|
||||||
|
|
||||||
|
Custom records
|
||||||
|
==============
|
||||||
|
|
||||||
|
The basics are behind us, now let's get deeper into the system and create a new
|
||||||
|
record type, like ``tt_address`` which can be displayed through our plugin.
|
||||||
|
|
||||||
|
.. _custom-records-tca:
|
||||||
|
|
||||||
|
TCA
|
||||||
|
---
|
||||||
|
|
||||||
|
.. admonition:: Task
|
||||||
|
|
||||||
|
Create necessary TCA for our new record.
|
||||||
|
|
||||||
|
Before we can do anything with Extbase, we need to configure TYPO3. The TCA (=Table
|
||||||
|
Configuration Array) contains configuration for each database table. TYPO3 will
|
||||||
|
generate the list view and edit forms within the Backend from this configuration.
|
||||||
|
|
||||||
|
Extbase uses this configuration for mapping and database queries, together with
|
||||||
|
relation handling.
|
||||||
|
|
||||||
|
TYPO3 provides a rich documentation about the TCA at
|
||||||
|
https://docs.typo3.org/typo3cms/TCAReference/. That's why this section is empty, all
|
||||||
|
information are available there.
|
||||||
|
|
||||||
|
One thing to notice is that Extbase uses "Convention over Configuration". While we
|
||||||
|
can configure Extbase to map a Model to a specific database table, we can auto match
|
||||||
|
them. For a Model ``\Workshop\ExampleExtension\Domain\Model\Address``, the database
|
||||||
|
table would be ``tx_exampleextension_domain_model_address``. So this will be
|
||||||
|
our database table name for our example. Also TYPO3 uses convention over
|
||||||
|
configuration, so the TCA for this table is placed within
|
||||||
|
:file:`Configuration/TCA/tx_exampleextension_domain_model_address.php` within our
|
||||||
|
Extension.
|
||||||
|
|
||||||
|
Also each property within the model is written lowerCamelCase, while the database
|
||||||
|
columns are written snake_case.
|
||||||
|
|
||||||
|
Our new record will be an address record with the following fields:
|
||||||
|
|
||||||
|
* Company Name
|
||||||
|
|
||||||
|
* Street
|
||||||
|
|
||||||
|
* House number
|
||||||
|
|
||||||
|
* Zip
|
||||||
|
|
||||||
|
* City
|
||||||
|
|
||||||
|
* Country
|
||||||
|
|
||||||
|
Once we finished the TCA, we already can create new records. Only saving them does
|
||||||
|
not work, as we didn't setup the database yet.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
By default new records are only allowed on pages of type "Folder".
|
||||||
|
|
||||||
|
ext_tables.sql
|
||||||
|
--------------
|
||||||
|
|
||||||
|
.. admonition:: Task
|
||||||
|
|
||||||
|
Create necessary sql for our new record.
|
||||||
|
|
||||||
|
.. admonition:: Task
|
||||||
|
|
||||||
|
Create some records, edit them, play around.
|
||||||
|
|
||||||
|
Once the TCA is provided, we need to create the table in our Database.
|
||||||
|
Each extension can provide a :file:`ext_tables.sql` in the root directory. Within the
|
||||||
|
admin tools and TYPO3 Console, you can update the database schema to match the
|
||||||
|
current necessary structure of all extensions.
|
||||||
|
|
||||||
|
If multiple extensions adjust the same field, the last one in load order is used.
|
||||||
|
|
||||||
|
The example :file:`ext_tables.sql` is:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../CodeExamples/localPackages/example_extension/ext_tables.sql
|
||||||
|
:language: sql
|
||||||
|
:linenos:
|
||||||
|
|
||||||
|
All further TYPO3 specific fields, like ``uid`` and ``pid`` are added by TYPO3 CMS since v9.
|
||||||
|
|
||||||
|
Before v9, the file would look like:
|
||||||
|
|
||||||
|
.. code-block:: sql
|
||||||
|
:linenos:
|
||||||
|
|
||||||
|
CREATE TABLE tx_exampleextension_domain_model_address (
|
||||||
|
uid int(11) unsigned NOT NULL auto_increment,
|
||||||
|
pid int(11) unsigned DEFAULT '0' NOT NULL,
|
||||||
|
|
||||||
|
crdate int(11) unsigned DEFAULT '0' NOT NULL,
|
||||||
|
cruser_id int(11) unsigned DEFAULT '0' NOT NULL,
|
||||||
|
tstamp int(11) unsigned DEFAULT '0' NOT NULL,
|
||||||
|
hidden tinyint(3) unsigned DEFAULT '0' NOT NULL,
|
||||||
|
deleted tinyint(3) unsigned DEFAULT '0' NOT NULL,
|
||||||
|
starttime int(11) unsigned DEFAULT '0' NOT NULL,
|
||||||
|
endtime int(11) unsigned DEFAULT '0' NOT NULL,
|
||||||
|
|
||||||
|
company_name varchar(255) DEFAULT '' NOT NULL,
|
||||||
|
street varchar(255) DEFAULT '' NOT NULL,
|
||||||
|
house_number varchar(255) DEFAULT '' NOT NULL,
|
||||||
|
zip varchar(255) DEFAULT '' NOT NULL,
|
||||||
|
city varchar(255) DEFAULT '' NOT NULL,
|
||||||
|
country varchar(255) DEFAULT '' NOT NULL,
|
||||||
|
|
||||||
|
PRIMARY KEY (uid),
|
||||||
|
KEY parent (pid)
|
||||||
|
);
|
||||||
|
|
||||||
|
We should now be able to create and save new records within the TYPO3 backend. Also
|
||||||
|
existing records should be listed, searchable and editable.
|
||||||
|
|
||||||
|
Model
|
||||||
|
-----
|
||||||
|
|
||||||
|
.. admonition:: Task
|
||||||
|
|
||||||
|
Create Model representing our records.
|
||||||
|
|
||||||
|
Once we are able to create and edit records in TYPO3 backend, we are ready to go with
|
||||||
|
Extbase. First we need a representation of our Data. This is done with a Model, in
|
||||||
|
our case this has to match the table name and is called
|
||||||
|
``Workshop\ExampleExtension\Domain\Model\Address`` and located at
|
||||||
|
:file:`Classes/Domain/Model/Address.php`.
|
||||||
|
|
||||||
|
Each model is a PHP class, structured like:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../CodeExamples/localPackages/example_extension/Classes/Domain/Model/Address.php
|
||||||
|
:language: php
|
||||||
|
:linenos:
|
||||||
|
:lines: 1-4,24-29,31-32,64-68,119
|
||||||
|
|
||||||
|
Repository
|
||||||
|
----------
|
||||||
|
|
||||||
|
.. admonition:: Task
|
||||||
|
|
||||||
|
Create Repository to access records.
|
||||||
|
|
||||||
|
In order to get, update or delete our records, we need a repository. This will return
|
||||||
|
the models for us. The repository is another class which can be completely empty:
|
||||||
|
|
||||||
|
The file is located at :file:`Classes/Domain/Repository/AddressRepository.php`:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../CodeExamples/localPackages/example_extension/Classes/Domain/Repository/AddressRepository.php
|
||||||
|
:language: php
|
||||||
|
:linenos:
|
||||||
|
:lines: 1-4, 24-
|
||||||
|
|
||||||
|
The parent class already provides all necessary methods for daily use cases.
|
||||||
|
|
||||||
|
Controller
|
||||||
|
----------
|
||||||
|
|
||||||
|
.. admonition:: Task
|
||||||
|
|
||||||
|
Provide available records to template.
|
||||||
|
|
||||||
|
In order to provide records in form of models to our template, we first need an
|
||||||
|
instance of our repository:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../CodeExamples/localPackages/example_extension/Classes/Controller/AddressController.php
|
||||||
|
:language: php
|
||||||
|
:linenos:
|
||||||
|
:lines: 1-4, 24-38,66
|
||||||
|
|
||||||
|
With the above code we only can create instances of the controller if an instance of
|
||||||
|
the Repository is provided.
|
||||||
|
|
||||||
|
Extbase itself will analyse dependencies inside ``__construct`` and will provide
|
||||||
|
instances. This is called Dependency Injection and works in three different ways with
|
||||||
|
Extbase. The above one is the preferred as this is not Extbase specific but will also
|
||||||
|
work in other PHP Frameworks and without any Dependency Injection at all.
|
||||||
|
|
||||||
|
We then can call the accordingly method to fetch records, which then can be assigned
|
||||||
|
to the view:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../CodeExamples/localPackages/example_extension/Classes/Controller/AddressController.php
|
||||||
|
:language: php
|
||||||
|
:linenos:
|
||||||
|
:lines: 28-29,40-43,66
|
||||||
|
|
||||||
|
The ``AddressRepository`` extends the base ``Repository`` class and inherits some
|
||||||
|
methods, e.g. ``findAll()``.
|
||||||
|
|
||||||
|
Template
|
||||||
|
--------
|
||||||
|
|
||||||
|
With our records in our template, we can iterate over them to display them.
|
||||||
|
|
||||||
|
:file:`Resources/Private/Templates/Address/Index.html`:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../CodeExamples/localPackages/example_extension/Resources/Private/Templates/Address/Index.html
|
||||||
|
:language: html
|
||||||
|
:linenos:
|
||||||
|
:lines: 1-7,10
|
||||||
|
|
||||||
|
Configure storage pid
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
We should not see any addresses yet, that’s due to the generated database query by
|
||||||
|
Extbase. If no storage pid is configured, Extbase will fetch records from pid 0.
|
||||||
|
|
||||||
|
Within the content element we can select arbitrary "Record Storage Page" entries to
|
||||||
|
use for records.
|
||||||
|
|
||||||
|
We could also configure the pid via TypoScript:
|
||||||
|
|
||||||
|
.. code-block:: typoscript
|
||||||
|
:linenos:
|
||||||
|
|
||||||
|
plugin {
|
||||||
|
tx_exampleextension {
|
||||||
|
persistence {
|
||||||
|
storagePid = 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Add new plugin
|
||||||
|
--------------
|
||||||
|
|
||||||
|
You might have noticed that the above controller is not the same as in our first
|
||||||
|
example. We therefore can add the controller to the existing plugin or add a new
|
||||||
|
plugin for this controller.
|
||||||
|
|
||||||
|
I would recommend to create a new plugin, to separate things. The process is not
|
||||||
|
explained again. If you struggle, take a look at :ref:`add-first-plugin` again.
|
||||||
|
|
||||||
|
Check everything
|
||||||
|
----------------
|
||||||
|
|
||||||
|
Once everything is set up, the following should be possible:
|
||||||
|
|
||||||
|
* Create, edit, search and delete records within TYPO3 Backend.
|
||||||
|
|
||||||
|
* List available records via Frontend Plugin.
|
||||||
|
|
||||||
|
Sounds like a lot of work for a small benefit? Right. If all you have to achieve is
|
||||||
|
this, you should not use Extbase but "pure" TYPO3. But we will extend the Extension
|
||||||
|
in the next step. Also this is about a first simple Extbase Extension, not how to use
|
||||||
|
TYPO3 the right way.
|
74
Documentation/source/Extension.rst
Normal file
74
Documentation/source/Extension.rst
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
Extension
|
||||||
|
=========
|
||||||
|
|
||||||
|
First of all we have to understand what an extension is, in the context of TYPO3 CMS.
|
||||||
|
|
||||||
|
What is an extension?
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
See :ref:`t3coreapi:extension-architecture` in TYPO3 Core API Reference.
|
||||||
|
|
||||||
|
TYPO3 is built only with extensions, there is no framework below. All features are
|
||||||
|
assigned to a specific extension. This way it's possible to build the TYPO3 that fits
|
||||||
|
the project needs.
|
||||||
|
|
||||||
|
An extension is something like ``frontend`` or ``backend``, which provides the TYPO3
|
||||||
|
frontend or backend. It can also be ``extbase`` which works as an Framework
|
||||||
|
to build further extensions or ``fluid`` an template engine.
|
||||||
|
|
||||||
|
Nowadays most installations also have a ``site_`` or ``sitepackage`` extensions, which
|
||||||
|
encapsulates the systems configuration and resources like assets and templates. Thus
|
||||||
|
an TYPO3 extension is the same as an composer package.
|
||||||
|
|
||||||
|
In this workshop we will concentrate on a "typical" extension that will provide a
|
||||||
|
plugin and custom record types. This can be installed into any compatible TYPO3
|
||||||
|
installation. A new record type will be added, which can be edited in the TYPO3
|
||||||
|
backend. Also a new plugin will be added which can be added as a content element and
|
||||||
|
displayed in frontend.
|
||||||
|
|
||||||
|
Structure of an extension
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
.. code-block:: plain
|
||||||
|
|
||||||
|
extension_key
|
||||||
|
├── Classes
|
||||||
|
│ ├── Command
|
||||||
|
│ │ └── ExampleCommandController.php
|
||||||
|
│ ├── Controller
|
||||||
|
│ │ └── ExampleController.php
|
||||||
|
│ └── Domain
|
||||||
|
│ └── Model
|
||||||
|
│ └── Example.php
|
||||||
|
├── composer.json
|
||||||
|
├── Configuration
|
||||||
|
│ ├── TCA
|
||||||
|
│ │ └── Overrides
|
||||||
|
│ │ └── tt_content.php
|
||||||
|
│ └── TypoScript
|
||||||
|
│ ├── constants.typoscript
|
||||||
|
│ └── setup.typoscript
|
||||||
|
├── Documentation
|
||||||
|
├── ext_conf_template.txt
|
||||||
|
├── ext_emconf.php
|
||||||
|
├── ext_localconf.php
|
||||||
|
├── ext_tables.php
|
||||||
|
├── readme.rst
|
||||||
|
└── Resources
|
||||||
|
└── Private
|
||||||
|
└── Templates
|
||||||
|
└── Search
|
||||||
|
└── Search.html
|
||||||
|
|
||||||
|
See :ref:`t3coreapi:extension-files-locations` in TYPO3 Core API Reference.
|
||||||
|
|
||||||
|
Further resources
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
* https://extensions.typo3.org/about-extension-repository/what-are-extensions/
|
||||||
|
|
||||||
|
* :ref:`t3coreapi:extension-architecture` in TYPO3 Core API Reference.
|
||||||
|
|
||||||
|
* :ref:`t3coreapi:extension-files-locations` in TYPO3 Core API Reference.
|
||||||
|
|
||||||
|
* https://docs.typo3.org/typo3cms/ExtbaseFluidBook/Index.html
|
17
Documentation/source/Outlook.rst
Normal file
17
Documentation/source/Outlook.rst
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
Outlook
|
||||||
|
=======
|
||||||
|
|
||||||
|
If we have some time left, we can take a deeper look at some of these topics.
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
:numbered:
|
||||||
|
|
||||||
|
Outlook/Routing
|
||||||
|
Outlook/Dependency-Injection
|
||||||
|
Outlook/Automated-Testing
|
||||||
|
Outlook/Property-Mapper
|
||||||
|
Outlook/Command-Controllers
|
||||||
|
Outlook/Backend-Modules
|
||||||
|
Outlook/Fluid/Custom-ViewHelper
|
||||||
|
Outlook/Configuration/Mapping-DB
|
6
Documentation/source/Outlook/Automated-Testing.rst
Normal file
6
Documentation/source/Outlook/Automated-Testing.rst
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
Automated Testing
|
||||||
|
=================
|
||||||
|
|
||||||
|
See:
|
||||||
|
|
||||||
|
* https://github.com/DanielSiepmann/testing-talk/
|
6
Documentation/source/Outlook/Backend-Modules.rst
Normal file
6
Documentation/source/Outlook/Backend-Modules.rst
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
Backend Modules
|
||||||
|
===============
|
||||||
|
|
||||||
|
See:
|
||||||
|
|
||||||
|
* https://docs.typo3.org/typo3cms/ExtbaseFluidBook/10-Outlook/2-Backend-modules.html
|
6
Documentation/source/Outlook/Command-Controllers.rst
Normal file
6
Documentation/source/Outlook/Command-Controllers.rst
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
Command Controllers
|
||||||
|
===================
|
||||||
|
|
||||||
|
See:
|
||||||
|
|
||||||
|
* https://docs.typo3.org/typo3cms/ExtbaseFluidBook/10-Outlook/3-Command-controllers.html
|
|
@ -0,0 +1,6 @@
|
||||||
|
Configuration Mapping DB
|
||||||
|
========================
|
||||||
|
|
||||||
|
See:
|
||||||
|
|
||||||
|
* https://docs.typo3.org/typo3cms/ExtbaseFluidBook/b-ExtbaseReference/Index.html#persistence
|
8
Documentation/source/Outlook/Dependency-Injection.rst
Normal file
8
Documentation/source/Outlook/Dependency-Injection.rst
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
Dependency Injection
|
||||||
|
====================
|
||||||
|
|
||||||
|
See:
|
||||||
|
|
||||||
|
* https://daniel-siepmann.de/Posts/2017/2017-08-17-typo3-injection.html
|
||||||
|
|
||||||
|
* https://daniel-siepmann.de/Posts/Migrated/2015-10-20-extbase-inject-settings.html
|
6
Documentation/source/Outlook/Fluid/Custom-ViewHelper.rst
Normal file
6
Documentation/source/Outlook/Fluid/Custom-ViewHelper.rst
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
Fluid - Custom ViewHelper
|
||||||
|
=========================
|
||||||
|
|
||||||
|
See:
|
||||||
|
|
||||||
|
* https://docs.typo3.org/typo3cms/ExtbaseFluidBook/8-Fluid/8-developing-a-custom-viewhelper.html
|
8
Documentation/source/Outlook/Property-Mapper.rst
Normal file
8
Documentation/source/Outlook/Property-Mapper.rst
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
Property Mapper
|
||||||
|
===============
|
||||||
|
|
||||||
|
See:
|
||||||
|
|
||||||
|
* https://docs.typo3.org/typo3cms/ExtbaseFluidBook/10-Outlook/4-Property-mapping.html
|
||||||
|
|
||||||
|
* https://github.com/DanielSiepmann/extbase-the-good-parts/blob/master/Tests/Unit/PropertyMappingTest.php
|
38
Documentation/source/Outlook/Routing.rst
Normal file
38
Documentation/source/Outlook/Routing.rst
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
Routing
|
||||||
|
=======
|
||||||
|
|
||||||
|
This section is related to TYPO3 v9.5.0 LTS, which introduces routing into the TYPO3
|
||||||
|
core.
|
||||||
|
|
||||||
|
See: https://docs.typo3.org/typo3cms/extensions/core/latest/Changelog/9.5/Feature-86365-RoutingEnhancersAndAspects.html
|
||||||
|
|
||||||
|
An example for the example extension looks like:
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
:linenos:
|
||||||
|
|
||||||
|
routeEnhancers:
|
||||||
|
ExamplePlugin:
|
||||||
|
type: Extbase
|
||||||
|
extension: ExampleExtension
|
||||||
|
plugin: Address
|
||||||
|
defaultController: 'Address::index'
|
||||||
|
routes:
|
||||||
|
-
|
||||||
|
routePath: '/edit/{address}'
|
||||||
|
_controller: 'Address::edit'
|
||||||
|
_arguments:
|
||||||
|
'address': 'address'
|
||||||
|
-
|
||||||
|
routePath: '/update'
|
||||||
|
_controller: 'Address::update'
|
||||||
|
aspects:
|
||||||
|
address:
|
||||||
|
type: PersistedPatternMapper
|
||||||
|
tableName: 'tx_exampleextension_domain_model_address'
|
||||||
|
routeFieldPattern: '^(?P<company_name>.+)-(?P<uid>\d+)$'
|
||||||
|
routeFieldResult: '{company_name}-{uid}'
|
||||||
|
|
||||||
|
This defines two routed, one for edit and one for update. Also there is no cHash in
|
||||||
|
URls due to the configuration. The address is replaced by company name and uid within
|
||||||
|
the url.
|
85
Documentation/source/StartNewExtension.rst
Normal file
85
Documentation/source/StartNewExtension.rst
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
Start new extension
|
||||||
|
===================
|
||||||
|
|
||||||
|
We will start with the simplest example ``Hello World``. Once we understood the
|
||||||
|
basics, we will create an "address" extension to manage a custom record.
|
||||||
|
|
||||||
|
Necessary files
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Extensions consists of a folder and the single necessary file, which is
|
||||||
|
:file:`ext_emconf.php`. This configures the *Extension Manager*. Without this file,
|
||||||
|
the Extension Manager would not recognize the extension and would prevent
|
||||||
|
installation.
|
||||||
|
|
||||||
|
.. literalinclude:: ../../CodeExamples/localPackages/example_extension/ext_emconf.php
|
||||||
|
:language: php
|
||||||
|
|
||||||
|
See :ref:`t3coreapi:extension-declaration` in TYPO3 Core API Reference.
|
||||||
|
|
||||||
|
.. admonition:: Task
|
||||||
|
|
||||||
|
So let's create a new folder and add the file within the folder.
|
||||||
|
|
||||||
|
In this example I'll use :file:`example_extension` as folder name.
|
||||||
|
|
||||||
|
Install extension
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
Once we have created the first extension, we need to install the extension. There are
|
||||||
|
two ways for a local extension. Either placing the extension inside the installation,
|
||||||
|
or via composer.
|
||||||
|
|
||||||
|
.. admonition:: Task
|
||||||
|
|
||||||
|
Install the new extension.
|
||||||
|
|
||||||
|
Oldschool
|
||||||
|
^^^^^^^^^
|
||||||
|
|
||||||
|
Copy the extension to :file:`typo3conf/ext/`, and head over to *Extension Manager* to
|
||||||
|
activate the extension.
|
||||||
|
|
||||||
|
Via Composer
|
||||||
|
^^^^^^^^^^^^
|
||||||
|
|
||||||
|
The following project setup is suggested:
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
.
|
||||||
|
├── composer.json
|
||||||
|
└── localPackages
|
||||||
|
└── example_extension
|
||||||
|
|
||||||
|
:file:`composer.json`:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../CodeExamples/composer.json
|
||||||
|
:language: json
|
||||||
|
|
||||||
|
In this case, we also need a :file:`composer.json` inside our extension, to make the
|
||||||
|
extension an composer package and allow the installation:
|
||||||
|
|
||||||
|
:file:`composer.json`:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../CodeExamples/localPackages/example_extension/composer.json
|
||||||
|
:language: json
|
||||||
|
|
||||||
|
Thanks due ``typo3-console/composer-auto-commands`` our extension is activated already.
|
||||||
|
|
||||||
|
Autoloading
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Using composer, TYPO3 does not do any special. The autoloading is provided by
|
||||||
|
composer and can be configured as documented by composer.
|
||||||
|
|
||||||
|
If you are not using composer, you should provide autoloading information inside
|
||||||
|
:file:`ext_emconf.php`:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../CodeExamples/localPackages/example_extension/ext_emconf.php
|
||||||
|
:language: php
|
||||||
|
:lines: 1-3,18-
|
||||||
|
|
||||||
|
There you can follow the composer autoloading configuration.
|
||||||
|
|
||||||
|
You can find the composer documentation about autoloading at https://getcomposer.org/doc/04-schema.md#autoload .
|
54
Documentation/source/UnderstandTypo3Plugins.rst
Normal file
54
Documentation/source/UnderstandTypo3Plugins.rst
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
Understand TYPO3 Plugins
|
||||||
|
========================
|
||||||
|
|
||||||
|
What happened until now?
|
||||||
|
|
||||||
|
We created an extension and a very basic plugin.
|
||||||
|
|
||||||
|
The plugin is created with two API calls and a Controller class with a single action
|
||||||
|
method.
|
||||||
|
|
||||||
|
What exactly are the API calls doing? And what does the PHP code in our Controller do
|
||||||
|
so far? Let's understand TYPO3!
|
||||||
|
|
||||||
|
TYPO3 Backend
|
||||||
|
-------------
|
||||||
|
|
||||||
|
The TYPO3 Backend needs to provide a new Plugin to the editor, this is done within
|
||||||
|
:file:`Configuration/TCA/Overrides/tt_content.php`.
|
||||||
|
|
||||||
|
The API configures the TCA (=Table Configuration Array) for ``tt_content``. And adds
|
||||||
|
the new plugin as ``list_type``.
|
||||||
|
|
||||||
|
We can go further on the content element during the Topic :ref:`configuration`,
|
||||||
|
especially :ref:`configuration-content-wizard`.
|
||||||
|
|
||||||
|
TYPO3 Frontend rendering
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
Also we need to configure the handling of our plugin during the rendering of TYPO3
|
||||||
|
frontend. This is done within :file:`ext_localconf.php`.
|
||||||
|
|
||||||
|
The API configures TypoScript ``tt_content.list.20.<pluginSignature>`` to define
|
||||||
|
rendering of the new registered ``list_type``.
|
||||||
|
|
||||||
|
Extbase bootstrap will be started with extension and plugin name.
|
||||||
|
Also the configuration of callable controller actions and caching is stored in
|
||||||
|
``$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['extensions']``.
|
||||||
|
|
||||||
|
The controller
|
||||||
|
--------------
|
||||||
|
|
||||||
|
TypoScript will call Extbase, which will figure out to call the ``exampleAction``
|
||||||
|
method of our ``ExampleController``, thanks to our frontend rendering configuration.
|
||||||
|
|
||||||
|
The default action is always the first action of the first controller in the
|
||||||
|
configuration. Multiple actions will be shown later.
|
||||||
|
|
||||||
|
The Controller should extend the ``ActionController`` to work as expected out of the
|
||||||
|
box. Also all "actions" must have the suffix ``Action`` and need to be public.
|
||||||
|
|
||||||
|
As soon as an action returns a string, this will be the output. If nothing is
|
||||||
|
returned, which is ``null`` Extbase will try to find and render a template matching
|
||||||
|
our current controller and action. This is done at
|
||||||
|
``\TYPO3\CMS\Extbase\Mvc\Controller\ActionController::callActionMethod``.
|
300
Documentation/source/UpdatingRecords.rst
Normal file
300
Documentation/source/UpdatingRecords.rst
Normal file
|
@ -0,0 +1,300 @@
|
||||||
|
.. highlight:: php
|
||||||
|
|
||||||
|
Updating records
|
||||||
|
================
|
||||||
|
|
||||||
|
For everything we have done so far, you do not need a plugin at all. Custom records
|
||||||
|
only need :file:`ext_tables.sql` the TCA and TypoScript for rendering.
|
||||||
|
|
||||||
|
Extbase is needed if you provide interaction to the user, e.g. updating or adding
|
||||||
|
records.
|
||||||
|
|
||||||
|
Even that can nowadays be achieved using the system extension "Form". Still we will
|
||||||
|
cover how to update a record next.
|
||||||
|
|
||||||
|
We need a form where we can adjust the values of the record, e.g. change the
|
||||||
|
company name.
|
||||||
|
|
||||||
|
Therefore we will add a new ``editAction()``. This will receive a single ``Address``
|
||||||
|
for editing and provides the form. We also add an ``updateAction()`` which receives a
|
||||||
|
single ``Address``. This action is the target of the submit form and will update the
|
||||||
|
database record.
|
||||||
|
|
||||||
|
To start editing an address, we will add an link from :file:`index.html` to
|
||||||
|
``editAction()``.
|
||||||
|
|
||||||
|
Link to another action
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
.. admonition:: Task
|
||||||
|
|
||||||
|
Create a link to the ``editAction()`` providing the "current" Address record.
|
||||||
|
|
||||||
|
TYPO3 provides ViewHelpers to create links to different actions. To insert a Link to
|
||||||
|
edit a record, you could use the following:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../CodeExamples/localPackages/example_extension/Resources/Private/Templates/Address/Index.html
|
||||||
|
:language: html
|
||||||
|
:linenos:
|
||||||
|
:dedent: 4
|
||||||
|
:lines: 9
|
||||||
|
|
||||||
|
The ViewHelper generates a link, thanks to the current plugin context, all arguments
|
||||||
|
are prefixed with the plugin namespace.
|
||||||
|
|
||||||
|
Creating Forms
|
||||||
|
--------------
|
||||||
|
|
||||||
|
.. admonition:: Task
|
||||||
|
|
||||||
|
Create a form which will show the current values of a single ``Address`` to the
|
||||||
|
user.
|
||||||
|
|
||||||
|
TYPO3 also provides ViewHelpers to create forms. Of course you could create forms with
|
||||||
|
pure HTML, but TYPO3 / Fluid adds some security aspects and makes things easier.
|
||||||
|
|
||||||
|
E.g. a proper validation that forms were not manipulated are added out of the box.
|
||||||
|
Also you do not need to take care of proper names of the inputs to allow Extbase to
|
||||||
|
map incoming inputs.
|
||||||
|
|
||||||
|
A basic form looks like:
|
||||||
|
|
||||||
|
.. code-block:: html
|
||||||
|
:linenos:
|
||||||
|
|
||||||
|
<f:form action="update" object="{address}" name="address">
|
||||||
|
<f:form.textfield property="companyName" />
|
||||||
|
<f:form.submit value="Update" />
|
||||||
|
</f:form>
|
||||||
|
|
||||||
|
Persistence
|
||||||
|
-----------
|
||||||
|
|
||||||
|
.. admonition:: Task
|
||||||
|
|
||||||
|
Save changes to database.
|
||||||
|
|
||||||
|
Once the user submits the form, the ``updateAction()`` method within our controller is
|
||||||
|
called. We therefore have to implement this method and to persist the changes that
|
||||||
|
were submitted.
|
||||||
|
|
||||||
|
We already have an instance of the accordingly repository within our controller. We
|
||||||
|
also receive the modified object as an argument within our action. All we have to do
|
||||||
|
is to update the record within the repository::
|
||||||
|
|
||||||
|
public function updateAction(Address $address)
|
||||||
|
{
|
||||||
|
$this->addressRepository->update($address);
|
||||||
|
}
|
||||||
|
|
||||||
|
At the end of the "request", Extbase will cleanup everything and persist the updates
|
||||||
|
to the backend, which in our case is MySQL.
|
||||||
|
|
||||||
|
Redirect
|
||||||
|
--------
|
||||||
|
|
||||||
|
.. admonition:: Task
|
||||||
|
|
||||||
|
Redirect back to index.
|
||||||
|
|
||||||
|
All our changes are already saved to database, but the user receives an error that no
|
||||||
|
template could be found. We actually do not need any template for our
|
||||||
|
``updateAction()``. Instead we will redirect the user back to the ``indexAction()``.
|
||||||
|
This way he can check whether the change has effected the output and works as
|
||||||
|
expected.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Following REST, an update returns the updated resource, which is the uri to the
|
||||||
|
resource, in our case the redirect.
|
||||||
|
|
||||||
|
As Browsers do not support ``PATCH``, which would be the request method, we use
|
||||||
|
``POST``, see: https://en.wikipedia.org/wiki/Representational_state_transfer#Relationship_between_URL_and_HTTP_methods
|
||||||
|
|
||||||
|
As we extend ``ActionController`` we can use the following line to redirect to
|
||||||
|
another action::
|
||||||
|
|
||||||
|
$this->redirect('index');
|
||||||
|
|
||||||
|
Cache clearing
|
||||||
|
--------------
|
||||||
|
|
||||||
|
Even if the user is redirected, he does not see any difference. That's due to TYPO3
|
||||||
|
caching.
|
||||||
|
|
||||||
|
.. admonition:: Task
|
||||||
|
|
||||||
|
Make changes visible in index action.
|
||||||
|
|
||||||
|
Extbase, by default, clears the cache for all updated records. Therefore the page
|
||||||
|
cache for the pages holding the records is cleared. As our plugin resists on a
|
||||||
|
different page, we have to configure TYPO3.
|
||||||
|
|
||||||
|
Same is true for plain TYPO3 Backend. As soon as a record is edited, the page cache
|
||||||
|
is cleared. If this record is displayed on another page, the caching has to be
|
||||||
|
configured, so this is not Extbase specific.
|
||||||
|
|
||||||
|
The caching can be configured using Page TS Config:
|
||||||
|
|
||||||
|
.. code-block:: typoscript
|
||||||
|
|
||||||
|
TCEMAIN {
|
||||||
|
clearCacheCmd = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
See: https://docs.typo3.org/typo3cms/TSconfigReference/PageTsconfig/TceMain.html#clearcachecmd
|
||||||
|
|
||||||
|
Flash message
|
||||||
|
-------------
|
||||||
|
|
||||||
|
.. admonition:: Task
|
||||||
|
|
||||||
|
Inform user about what happened.
|
||||||
|
|
||||||
|
We now have a fully working process. Still in a long list of records, the user might
|
||||||
|
not notice a difference. Also if he leaves the computer and comes back, he will not
|
||||||
|
know what was done before.
|
||||||
|
|
||||||
|
Extbase has a feature called "Flashmessages" which are also used within TYPO3
|
||||||
|
Backend. They inform a user on next page about some thing that happened during
|
||||||
|
the last request. We could use that to add a message about which record was updated.
|
||||||
|
This is also just one line within a controller::
|
||||||
|
|
||||||
|
$this->addFlashMessage(
|
||||||
|
$address->getCompanyName() . ' was updated.',
|
||||||
|
'Update successfully'
|
||||||
|
);
|
||||||
|
|
||||||
|
Adding alone would not work, so we have to display thus messages. This is done within
|
||||||
|
the View with an ViewHelper:
|
||||||
|
|
||||||
|
.. code-block:: html
|
||||||
|
|
||||||
|
<f:flashMessages />
|
||||||
|
|
||||||
|
Validation
|
||||||
|
----------
|
||||||
|
|
||||||
|
.. admonition:: Task
|
||||||
|
|
||||||
|
Prevent invalid data.
|
||||||
|
|
||||||
|
Up till now, the user could provide any text into any property. There was no
|
||||||
|
validation whether a zip is actual valid.
|
||||||
|
|
||||||
|
Adding this is done within either the model, or the controller.
|
||||||
|
|
||||||
|
Within Controller
|
||||||
|
^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Sparely used, this makes sense if you do not use models at all or use the same model
|
||||||
|
with different validation rules.
|
||||||
|
|
||||||
|
The process is the same as within a model, just the annotations are added to the
|
||||||
|
PHPDoc of the corresponding action.
|
||||||
|
|
||||||
|
We will not cover this, instead we use validation within model.
|
||||||
|
|
||||||
|
Within Model
|
||||||
|
^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Each property already has a type annotation::
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
|
||||||
|
By adding one, or multiple, ``@validate`` annotations, these properties get
|
||||||
|
validated::
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
* @validate NotEmpty
|
||||||
|
*/
|
||||||
|
|
||||||
|
Extbase provides some validators our of the box, all available within ``typo3/sysext/extbase/Classes/Validation/Validator``:
|
||||||
|
|
||||||
|
* AlphanumericValidator
|
||||||
|
* EmailAddressValidator
|
||||||
|
* NotEmptyValidator
|
||||||
|
* NumberRangeValidator
|
||||||
|
* RawValidator
|
||||||
|
* RegularExpressionValidator
|
||||||
|
* StringLengthValidator
|
||||||
|
* TextValidator
|
||||||
|
|
||||||
|
Also validators for PHP Type like ``String`` or ``DateTime`` are provided which are
|
||||||
|
auto added based on ``@var`` annotation.
|
||||||
|
|
||||||
|
Let's say a zip only consists of integers and is exactly 5 integers long, like in
|
||||||
|
Germany. One or more leading 0 are allowed, we therefore will not use the PHP type
|
||||||
|
``integer`` but ``string``. A possible validation might look like::
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
* @validate RegularExpression(regularExpression = '/^[0-9]{5}$/')
|
||||||
|
*/
|
||||||
|
protected $zip;
|
||||||
|
|
||||||
|
Also see: https://docs.typo3.org/typo3cms/extensions/core/Changelog/9.3/Feature-83167-ReplaceValidateWithTYPO3CMSExtbaseAnnotationValidate.html
|
||||||
|
|
||||||
|
Display validation errors
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
Nearly finished, we can no longer save invalid records. Still the user does not get
|
||||||
|
any information about what's wrong. Fluid by default will add the css class
|
||||||
|
``f3-form-error`` to all inputs with an error. So one could style this css class:
|
||||||
|
|
||||||
|
.. code-block:: css
|
||||||
|
|
||||||
|
.f3-form-error {
|
||||||
|
border: solid 5px #cd2323;
|
||||||
|
}
|
||||||
|
|
||||||
|
This way at least it's clear which fields fail, but not why. We therefore use another
|
||||||
|
ViewHelper to add the validation errors to each field. As we have to add the same
|
||||||
|
markup for each field, we will put it into a section for re-use. On larger projects
|
||||||
|
this might be a Partial:
|
||||||
|
|
||||||
|
.. code-block:: html
|
||||||
|
:linenos:
|
||||||
|
|
||||||
|
<f:section name="FieldErrors">
|
||||||
|
<f:form.validationResults for="{propertyPath}">
|
||||||
|
<f:for each="{validationResults.flattenedErrors}" as="errors">
|
||||||
|
<f:for each="{errors}" as="error">
|
||||||
|
<li>{error.code}: {error}</li>
|
||||||
|
</f:for>
|
||||||
|
</f:for>
|
||||||
|
</f:form.validationResults>
|
||||||
|
</f:section>
|
||||||
|
|
||||||
|
This section can be used like:
|
||||||
|
|
||||||
|
.. code-block:: html
|
||||||
|
:linenos:
|
||||||
|
|
||||||
|
<f:form.textfield property="companyName" />
|
||||||
|
{f:render(section: 'FieldErrors', arguments: {
|
||||||
|
propertyPath: 'address.companyName'
|
||||||
|
})}
|
||||||
|
|
||||||
|
Handling existing invalid records
|
||||||
|
---------------------------------
|
||||||
|
|
||||||
|
In some circumstances your system might have an invalid record. Right now it's not
|
||||||
|
possible to edit this record with ``editAction()`` as Extbase will validate the
|
||||||
|
record.
|
||||||
|
|
||||||
|
Therefore the ``@ignorevalidation`` annotation can be added to the action::
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ignorevalidation $address
|
||||||
|
*/
|
||||||
|
public function editAction(Address $address)
|
||||||
|
{
|
||||||
|
$this->view->assign('address', $address);
|
||||||
|
}
|
||||||
|
|
||||||
|
This way Extbase will ignore raised validation issues and we are ready to go to edit
|
||||||
|
the record.
|
223
Documentation/source/Views.rst
Normal file
223
Documentation/source/Views.rst
Normal file
|
@ -0,0 +1,223 @@
|
||||||
|
.. _views:
|
||||||
|
|
||||||
|
Views
|
||||||
|
=====
|
||||||
|
|
||||||
|
So far we only have our "Hello world!" output hardcoded in our PHP Code.
|
||||||
|
That's not helpful, so let's introduce Views with Fluid.
|
||||||
|
|
||||||
|
Fluid
|
||||||
|
-----
|
||||||
|
|
||||||
|
Fluid is the TYPO3 Template engine. Nowadays it's standalone, but was developed for
|
||||||
|
and by TYPO3.
|
||||||
|
|
||||||
|
It follows XML / HTML which should make it easier to get started.
|
||||||
|
|
||||||
|
Fluid templates are valid HTML and custom tags, called ViewHelpers, are introduced to
|
||||||
|
bring in logic.
|
||||||
|
|
||||||
|
Convention over configuration
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
Extbase follows the principle "Convention over configuration", which we already saw
|
||||||
|
with our Controller. We didn't configure the path or class name, it just follows a
|
||||||
|
convention.
|
||||||
|
|
||||||
|
Same is true for the output of plugins. If "something" is returned, this will be the
|
||||||
|
output. If nothing is returned, Extbase will call Fluid to render a Fluid template.
|
||||||
|
|
||||||
|
Paths
|
||||||
|
-----
|
||||||
|
|
||||||
|
The path to the template of an controller action is
|
||||||
|
:file:`example_extension/Resources/Private/Templates/ControllerName/ActionName.html`,
|
||||||
|
which in our example would be: :file:`example_extension/Resources/Private/Templates/Example/Example.html`,
|
||||||
|
|
||||||
|
.. admonition:: Task
|
||||||
|
|
||||||
|
Move the output to a fluid template, following Extbase conventions.
|
||||||
|
|
||||||
|
So let's create the file and move the "Hello world!" to this file. We should make a
|
||||||
|
small change, otherwise we will not see whether our change has worked. E.g. make the
|
||||||
|
"w" uppercase "W".
|
||||||
|
|
||||||
|
Do not forget to remote the ``return 'Hello world!';`` from our controller.
|
||||||
|
|
||||||
|
We should now see our "Hello World!".
|
||||||
|
|
||||||
|
Configuration
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Awesome, we now do no longer need to touch PHP code to change the output, we can use
|
||||||
|
Fluid and an Integrator is able to change something.
|
||||||
|
|
||||||
|
But they should be able to change templates in their own extension, e.g. a
|
||||||
|
"sitepackage". We will see how to do this in next chapter :ref:`configuration`,
|
||||||
|
especially :ref:`configuration-view-paths`.
|
||||||
|
|
||||||
|
Sections
|
||||||
|
--------
|
||||||
|
|
||||||
|
If templates grow in size, we need to add some structure. One way is to use sections
|
||||||
|
inside a single template. A section is like a PHP method or function and can be
|
||||||
|
called with arguments:
|
||||||
|
|
||||||
|
.. code-block:: html
|
||||||
|
:linenos:
|
||||||
|
|
||||||
|
Normal output
|
||||||
|
|
||||||
|
{f:render(
|
||||||
|
section: 'FirstSection',
|
||||||
|
arguments: {
|
||||||
|
arg1: var1,
|
||||||
|
arg2: var2,
|
||||||
|
arg3: 'string'
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
|
||||||
|
<f:section name="FirstSection">
|
||||||
|
Some output + {arg1}.
|
||||||
|
</f:section>
|
||||||
|
|
||||||
|
We have our default output "Normal output" and call a ViewHelper ``f:render`` with
|
||||||
|
some arguments to render a specific section with some arguments. The ViewHelper will
|
||||||
|
be replaced with the rendered result of the section.
|
||||||
|
|
||||||
|
This way it's possible to structure templates like Controllers. They control the
|
||||||
|
output flow and call different sections with arguments where more specific logic
|
||||||
|
happens.
|
||||||
|
|
||||||
|
Variables
|
||||||
|
---------
|
||||||
|
|
||||||
|
Variables are assigned via PHP:
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
:linenos:
|
||||||
|
|
||||||
|
// Inside a controller action do:
|
||||||
|
$this->view->assign('var1', $variable1);
|
||||||
|
$this->view->assign('var2', $variable2);
|
||||||
|
|
||||||
|
// Or to assign multiple variables at once:
|
||||||
|
$this->view->assignMultiple([
|
||||||
|
'var1' => $variable1
|
||||||
|
'var2' => $variable2
|
||||||
|
]);
|
||||||
|
|
||||||
|
Assigned variables can be accessed inside Fluid with curly braces:
|
||||||
|
|
||||||
|
.. code-block:: html
|
||||||
|
:linenos:
|
||||||
|
|
||||||
|
Hello {userInput}!
|
||||||
|
|
||||||
|
ViewHelper
|
||||||
|
----------
|
||||||
|
|
||||||
|
To make templates more flexible, ViewHelpers are available. They are custom HTML-Tags
|
||||||
|
available inside the template engine.
|
||||||
|
TYPO3 and Fluid already ship some ViewHelpers, but you can provide own ViewHelpers.
|
||||||
|
|
||||||
|
ViewHelpers always live in a Namespace, e.g. ``\TYPO3\CMS\Fluid\ViewHelpers`` or
|
||||||
|
``\Workshop\ExampleExtension\ViewHelpers``.
|
||||||
|
|
||||||
|
You can either register these namespaces globally, or inside the templates via
|
||||||
|
``{namespace wee=Workshop\ExampleExtension\ViewHelpers}``.
|
||||||
|
The ``f`` namespace for ``Fluid`` is always registered globally.
|
||||||
|
|
||||||
|
Once ViewHelpers are available available, you can use them:
|
||||||
|
|
||||||
|
.. code-block:: html
|
||||||
|
|
||||||
|
<f:format.crop maxCharacters="5">Hello World!</f:format.crop>
|
||||||
|
|
||||||
|
The above should output "Hello ...", as the string is cropped to 5 characters, the
|
||||||
|
"..." can be configured via another argument of the ViewHelper:
|
||||||
|
|
||||||
|
.. code-block:: html
|
||||||
|
|
||||||
|
<f:format.crop maxCharacters="5" append="">Hello World!</f:format.crop>
|
||||||
|
|
||||||
|
Beside the tag based kind of inserting ViewHelpers, you can also use the "inline
|
||||||
|
notation":
|
||||||
|
|
||||||
|
.. code-block:: html
|
||||||
|
:linenos:
|
||||||
|
|
||||||
|
{f:format.date(date: 'midnight')}
|
||||||
|
|
||||||
|
It's also possible to chain ViewHelpers in both ways:
|
||||||
|
|
||||||
|
.. code-block:: html
|
||||||
|
:linenos:
|
||||||
|
|
||||||
|
{f:format.date(date: 'midnight') -> f:format.raw()}
|
||||||
|
|
||||||
|
<f:format.raw>
|
||||||
|
{f:format.date(date: 'midnight')}
|
||||||
|
</f:format.raw>
|
||||||
|
|
||||||
|
<f:format.raw>
|
||||||
|
<f:format.date date="midnight" />
|
||||||
|
</f:format.raw>
|
||||||
|
|
||||||
|
<f:format.raw>
|
||||||
|
<f:format.date>midnight</f:format.date>
|
||||||
|
</f:format.raw>
|
||||||
|
|
||||||
|
Partials and Layouts
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
We already saw sections to make a single template easier to manage.
|
||||||
|
For re-using parts between multiple templates there are Partials.
|
||||||
|
|
||||||
|
Partials are like templates and can be rendered via:
|
||||||
|
|
||||||
|
.. code-block:: html
|
||||||
|
:linenos:
|
||||||
|
|
||||||
|
Normal output
|
||||||
|
|
||||||
|
{f:render(
|
||||||
|
partial: 'Path/To/Partial',
|
||||||
|
arguments: {
|
||||||
|
arg1: var1,
|
||||||
|
arg2: var2,
|
||||||
|
arg3: 'string'
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
|
||||||
|
|
||||||
|
Also each template can be embedded into a Layout via:
|
||||||
|
|
||||||
|
.. code-block:: html
|
||||||
|
:linenos:
|
||||||
|
|
||||||
|
<f:layout name="Layout/Path/AndName" />
|
||||||
|
|
||||||
|
This way wrapping code, e.g. for HTML E-Mails or content elements can be moved to a
|
||||||
|
layout and all templates can inherit this layout.
|
||||||
|
|
||||||
|
Further resources
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
.. hint::
|
||||||
|
|
||||||
|
Use ViewHelpers for output logic, not to get data into your View.
|
||||||
|
|
||||||
|
Use Controller and DataProcessing to prepare data.
|
||||||
|
|
||||||
|
* Available ViewHelpers can be found at:
|
||||||
|
|
||||||
|
* :file:`typo3/sysext/fluid/Classes/ViewHelpers/`
|
||||||
|
|
||||||
|
* :file:`vendor/typo3fluid/src/ViewHelpers/`
|
||||||
|
|
||||||
|
* https://github.com/TYPO3/Fluid
|
||||||
|
|
||||||
|
* https://docs.typo3.org/typo3cms/TyposcriptReference/ContentObjects/Fluidtemplate/Index.html
|
||||||
|
|
||||||
|
* https://docs.typo3.org/typo3cms/ExtbaseFluidBook/Index.html
|
3
Documentation/source/_static/css/custom.css
Normal file
3
Documentation/source/_static/css/custom.css
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
.admonition-task {
|
||||||
|
border-left-color: #ad7526;
|
||||||
|
}
|
323
Documentation/source/conf.py
Normal file
323
Documentation/source/conf.py
Normal file
|
@ -0,0 +1,323 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# TYPO3 Extension Workshop documentation build configuration file, created by
|
||||||
|
# sphinx-quickstart on Mon Aug 13 12:04:20 2018.
|
||||||
|
#
|
||||||
|
# This file is execfile()d with the current directory set to its
|
||||||
|
# containing dir.
|
||||||
|
#
|
||||||
|
# Note that not all possible configuration values are present in this
|
||||||
|
# autogenerated file.
|
||||||
|
#
|
||||||
|
# All configuration values have a default; values that are commented out
|
||||||
|
# serve to show the default.
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
# If extensions (or modules to document with autodoc) are in another directory,
|
||||||
|
# add these directories to sys.path here. If the directory is relative to the
|
||||||
|
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||||
|
#sys.path.insert(0, os.path.abspath('.'))
|
||||||
|
|
||||||
|
# -- General configuration ------------------------------------------------
|
||||||
|
|
||||||
|
# If your documentation needs a minimal Sphinx version, state it here.
|
||||||
|
#needs_sphinx = '1.0'
|
||||||
|
|
||||||
|
# Add any Sphinx extension module names here, as strings. They can be
|
||||||
|
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||||
|
# ones.
|
||||||
|
extensions = [
|
||||||
|
'sphinx.ext.intersphinx',
|
||||||
|
'sphinx.ext.todo',
|
||||||
|
'sphinx.ext.githubpages',
|
||||||
|
]
|
||||||
|
|
||||||
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
|
templates_path = ['_templates']
|
||||||
|
|
||||||
|
# The suffix(es) of source filenames.
|
||||||
|
# You can specify multiple suffix as a list of string:
|
||||||
|
# source_suffix = ['.rst', '.md']
|
||||||
|
source_suffix = '.rst'
|
||||||
|
|
||||||
|
# The encoding of source files.
|
||||||
|
#source_encoding = 'utf-8-sig'
|
||||||
|
|
||||||
|
# The master toctree document.
|
||||||
|
master_doc = 'index'
|
||||||
|
|
||||||
|
# General information about the project.
|
||||||
|
project = u'TYPO3 Extension Workshop'
|
||||||
|
copyright = u'2018, Daniel Siepmann'
|
||||||
|
author = u'Daniel Siepmann'
|
||||||
|
|
||||||
|
# The version info for the project you're documenting, acts as replacement for
|
||||||
|
# |version| and |release|, also used in various other places throughout the
|
||||||
|
# built documents.
|
||||||
|
#
|
||||||
|
# The short X.Y version.
|
||||||
|
version = u'1.0.0'
|
||||||
|
# The full version, including alpha/beta/rc tags.
|
||||||
|
release = u'1.0.0'
|
||||||
|
|
||||||
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||||
|
# for a list of supported languages.
|
||||||
|
#
|
||||||
|
# This is also used if you do content translation via gettext catalogs.
|
||||||
|
# Usually you set "language" from the command line for these cases.
|
||||||
|
language = None
|
||||||
|
|
||||||
|
# There are two options for replacing |today|: either, you set today to some
|
||||||
|
# non-false value, then it is used:
|
||||||
|
#today = ''
|
||||||
|
# Else, today_fmt is used as the format for a strftime call.
|
||||||
|
#today_fmt = '%B %d, %Y'
|
||||||
|
|
||||||
|
# List of patterns, relative to source directory, that match files and
|
||||||
|
# directories to ignore when looking for source files.
|
||||||
|
# This patterns also effect to html_static_path and html_extra_path
|
||||||
|
exclude_patterns = []
|
||||||
|
|
||||||
|
# The reST default role (used for this markup: `text`) to use for all
|
||||||
|
# documents.
|
||||||
|
#default_role = None
|
||||||
|
|
||||||
|
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||||
|
#add_function_parentheses = True
|
||||||
|
|
||||||
|
# If true, the current module name will be prepended to all description
|
||||||
|
# unit titles (such as .. function::).
|
||||||
|
#add_module_names = True
|
||||||
|
|
||||||
|
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||||
|
# output. They are ignored by default.
|
||||||
|
#show_authors = False
|
||||||
|
|
||||||
|
# The name of the Pygments (syntax highlighting) style to use.
|
||||||
|
pygments_style = 'sphinx'
|
||||||
|
|
||||||
|
# A list of ignored prefixes for module index sorting.
|
||||||
|
#modindex_common_prefix = []
|
||||||
|
|
||||||
|
# If true, keep warnings as "system message" paragraphs in the built documents.
|
||||||
|
#keep_warnings = False
|
||||||
|
|
||||||
|
# If true, `todo` and `todoList` produce output, else they produce nothing.
|
||||||
|
todo_include_todos = True
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for HTML output ----------------------------------------------
|
||||||
|
|
||||||
|
import guzzle_sphinx_theme
|
||||||
|
|
||||||
|
html_theme_path = guzzle_sphinx_theme.html_theme_path()
|
||||||
|
html_theme = 'guzzle_sphinx_theme'
|
||||||
|
|
||||||
|
# Register the theme as an extension to generate a sitemap.xml
|
||||||
|
extensions.append("guzzle_sphinx_theme")
|
||||||
|
|
||||||
|
|
||||||
|
# Guzzle theme options (see theme.conf for more information)
|
||||||
|
html_theme_options = {
|
||||||
|
# Set the name of the project to appear in the sidebar
|
||||||
|
"project_nav_name": "TYPO3 Extension Workshop",
|
||||||
|
}
|
||||||
|
|
||||||
|
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||||
|
# a list of builtin themes.
|
||||||
|
# html_theme = 'alabaster'
|
||||||
|
|
||||||
|
# Theme options are theme-specific and customize the look and feel of a theme
|
||||||
|
# further. For a list of options available for each theme, see the
|
||||||
|
# documentation.
|
||||||
|
#html_theme_options = {}
|
||||||
|
|
||||||
|
# Add any paths that contain custom themes here, relative to this directory.
|
||||||
|
#html_theme_path = []
|
||||||
|
|
||||||
|
# The name for this set of Sphinx documents.
|
||||||
|
# "<project> v<release> documentation" by default.
|
||||||
|
#html_title = u'TYPO3 Extension Workshop v1.0.0'
|
||||||
|
|
||||||
|
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||||
|
#html_short_title = None
|
||||||
|
|
||||||
|
# The name of an image file (relative to this directory) to place at the top
|
||||||
|
# of the sidebar.
|
||||||
|
#html_logo = None
|
||||||
|
|
||||||
|
# The name of an image file (relative to this directory) to use as a favicon of
|
||||||
|
# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||||
|
# pixels large.
|
||||||
|
#html_favicon = None
|
||||||
|
|
||||||
|
# Add any paths that contain custom static files (such as style sheets) here,
|
||||||
|
# relative to this directory. They are copied after the builtin static files,
|
||||||
|
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||||
|
html_static_path = ['_static']
|
||||||
|
|
||||||
|
# Add any extra paths that contain custom files (such as robots.txt or
|
||||||
|
# .htaccess) here, relative to this directory. These files are copied
|
||||||
|
# directly to the root of the documentation.
|
||||||
|
#html_extra_path = []
|
||||||
|
|
||||||
|
# If not None, a 'Last updated on:' timestamp is inserted at every page
|
||||||
|
# bottom, using the given strftime format.
|
||||||
|
# The empty string is equivalent to '%b %d, %Y'.
|
||||||
|
#html_last_updated_fmt = None
|
||||||
|
|
||||||
|
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||||
|
# typographically correct entities.
|
||||||
|
#html_use_smartypants = True
|
||||||
|
|
||||||
|
# Custom sidebar templates, maps document names to template names.
|
||||||
|
#html_sidebars = {}
|
||||||
|
|
||||||
|
# Additional templates that should be rendered to pages, maps page names to
|
||||||
|
# template names.
|
||||||
|
#html_additional_pages = {}
|
||||||
|
|
||||||
|
# If false, no module index is generated.
|
||||||
|
#html_domain_indices = True
|
||||||
|
|
||||||
|
# If false, no index is generated.
|
||||||
|
#html_use_index = True
|
||||||
|
|
||||||
|
# If true, the index is split into individual pages for each letter.
|
||||||
|
#html_split_index = False
|
||||||
|
|
||||||
|
# If true, links to the reST sources are added to the pages.
|
||||||
|
#html_show_sourcelink = True
|
||||||
|
|
||||||
|
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||||
|
#html_show_sphinx = True
|
||||||
|
|
||||||
|
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||||
|
#html_show_copyright = True
|
||||||
|
|
||||||
|
# If true, an OpenSearch description file will be output, and all pages will
|
||||||
|
# contain a <link> tag referring to it. The value of this option must be the
|
||||||
|
# base URL from which the finished HTML is served.
|
||||||
|
#html_use_opensearch = ''
|
||||||
|
|
||||||
|
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||||
|
#html_file_suffix = None
|
||||||
|
|
||||||
|
# Language to be used for generating the HTML full-text search index.
|
||||||
|
# Sphinx supports the following languages:
|
||||||
|
# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'
|
||||||
|
# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh'
|
||||||
|
#html_search_language = 'en'
|
||||||
|
|
||||||
|
# A dictionary with options for the search language support, empty by default.
|
||||||
|
# 'ja' uses this config value.
|
||||||
|
# 'zh' user can custom change `jieba` dictionary path.
|
||||||
|
#html_search_options = {'type': 'default'}
|
||||||
|
|
||||||
|
# The name of a javascript file (relative to the configuration directory) that
|
||||||
|
# implements a search results scorer. If empty, the default will be used.
|
||||||
|
#html_search_scorer = 'scorer.js'
|
||||||
|
|
||||||
|
# Output file base name for HTML help builder.
|
||||||
|
htmlhelp_basename = 'TYPO3ExtensionWorkshopdoc'
|
||||||
|
|
||||||
|
# -- Options for LaTeX output ---------------------------------------------
|
||||||
|
|
||||||
|
latex_elements = {
|
||||||
|
# The paper size ('letterpaper' or 'a4paper').
|
||||||
|
#'papersize': 'letterpaper',
|
||||||
|
|
||||||
|
# The font size ('10pt', '11pt' or '12pt').
|
||||||
|
#'pointsize': '10pt',
|
||||||
|
|
||||||
|
# Additional stuff for the LaTeX preamble.
|
||||||
|
#'preamble': '',
|
||||||
|
|
||||||
|
# Latex figure (float) alignment
|
||||||
|
#'figure_align': 'htbp',
|
||||||
|
}
|
||||||
|
|
||||||
|
# Grouping the document tree into LaTeX files. List of tuples
|
||||||
|
# (source start file, target name, title,
|
||||||
|
# author, documentclass [howto, manual, or own class]).
|
||||||
|
latex_documents = [
|
||||||
|
(master_doc, 'TYPO3ExtensionWorkshop.tex', u'TYPO3 Extension Workshop Documentation',
|
||||||
|
u'Daniel Siepmann', 'manual'),
|
||||||
|
]
|
||||||
|
|
||||||
|
# The name of an image file (relative to this directory) to place at the top of
|
||||||
|
# the title page.
|
||||||
|
#latex_logo = None
|
||||||
|
|
||||||
|
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||||
|
# not chapters.
|
||||||
|
#latex_use_parts = False
|
||||||
|
|
||||||
|
# If true, show page references after internal links.
|
||||||
|
#latex_show_pagerefs = False
|
||||||
|
|
||||||
|
# If true, show URL addresses after external links.
|
||||||
|
#latex_show_urls = False
|
||||||
|
|
||||||
|
# Documents to append as an appendix to all manuals.
|
||||||
|
#latex_appendices = []
|
||||||
|
|
||||||
|
# If false, no module index is generated.
|
||||||
|
#latex_domain_indices = True
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for manual page output ---------------------------------------
|
||||||
|
|
||||||
|
# One entry per manual page. List of tuples
|
||||||
|
# (source start file, name, description, authors, manual section).
|
||||||
|
man_pages = [
|
||||||
|
(master_doc, 'typo3extensionworkshop', u'TYPO3 Extension Workshop Documentation',
|
||||||
|
[author], 1)
|
||||||
|
]
|
||||||
|
|
||||||
|
# If true, show URL addresses after external links.
|
||||||
|
#man_show_urls = False
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for Texinfo output -------------------------------------------
|
||||||
|
|
||||||
|
# Grouping the document tree into Texinfo files. List of tuples
|
||||||
|
# (source start file, target name, title, author,
|
||||||
|
# dir menu entry, description, category)
|
||||||
|
texinfo_documents = [
|
||||||
|
(master_doc, 'TYPO3ExtensionWorkshop', u'TYPO3 Extension Workshop Documentation',
|
||||||
|
author, 'TYPO3ExtensionWorkshop', 'One line description of project.',
|
||||||
|
'Miscellaneous'),
|
||||||
|
]
|
||||||
|
|
||||||
|
# Documents to append as an appendix to all manuals.
|
||||||
|
#texinfo_appendices = []
|
||||||
|
|
||||||
|
# If false, no module index is generated.
|
||||||
|
#texinfo_domain_indices = True
|
||||||
|
|
||||||
|
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||||
|
#texinfo_show_urls = 'footnote'
|
||||||
|
|
||||||
|
# If true, do not generate a @detailmenu in the "Top" node's menu.
|
||||||
|
#texinfo_no_detailmenu = False
|
||||||
|
|
||||||
|
|
||||||
|
# Example configuration for intersphinx: refer to the Python standard library.
|
||||||
|
intersphinx_mapping = {
|
||||||
|
't3coreapi': ('https://docs.typo3.org/typo3cms/CoreApiReference/', None),
|
||||||
|
't3extbasebook': ('https://docs.typo3.org/typo3cms/ExtbaseFluidBook/', None),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Allow inline PHP highlighting
|
||||||
|
# See: https://github.com/guzzle/guzzle/blob/master/docs/conf.py
|
||||||
|
|
||||||
|
from sphinx.highlighting import lexers
|
||||||
|
from pygments.lexers.web import PhpLexer
|
||||||
|
lexers['php'] = PhpLexer(startinline=True, linenos=1)
|
||||||
|
|
||||||
|
# Add custom css
|
||||||
|
def setup(app):
|
||||||
|
app.add_stylesheet('css/custom.css')
|
44
Documentation/source/index.rst
Normal file
44
Documentation/source/index.rst
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
Welcome to TYPO3 Extension Workshop
|
||||||
|
===================================
|
||||||
|
|
||||||
|
This workshop is about the basics of TYPO3 extensions, using Extbase and Fluid.
|
||||||
|
|
||||||
|
Some "rules" for the workshop
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
* You will write all the code yourself. The repository is available though, if you need
|
||||||
|
help or are to lazy… .
|
||||||
|
|
||||||
|
* We will not call APIs without checking out their code. Always understand what your
|
||||||
|
own code does.
|
||||||
|
|
||||||
|
* I'm using the latest TYPO3 CMS 9 LTS, most of the parts are so basic, they should
|
||||||
|
work from 4.5 onwards.
|
||||||
|
|
||||||
|
* Ask questions as soon as possible. This way we have the context.
|
||||||
|
|
||||||
|
* Tell me if you want less details.
|
||||||
|
|
||||||
|
* All materials are available on Github: https://github.com/DanielSiepmann/typo3-extension-workshop
|
||||||
|
|
||||||
|
* When should we make breaks? Any Smokers here?
|
||||||
|
|
||||||
|
Topics
|
||||||
|
------
|
||||||
|
|
||||||
|
We will cover the following topics during the workshop, to build a foundation to
|
||||||
|
build custom extensions:
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
:numbered:
|
||||||
|
|
||||||
|
Extension
|
||||||
|
StartNewExtension
|
||||||
|
AddFirstPlugin
|
||||||
|
UnderstandTypo3Plugins
|
||||||
|
Views
|
||||||
|
Configuration
|
||||||
|
CustomRecords
|
||||||
|
UpdatingRecords
|
||||||
|
Outlook
|
Loading…
Reference in a new issue