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