Daniel Siepmann
b396925dda
As the feed contains the whole posts. But mirror in fediverse should only contain title.
7413 lines
426 KiB
XML
7413 lines
426 KiB
XML
<?xml version="1.0" encoding="UTF-8" ?>
|
||
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
||
<channel>
|
||
|
||
<title>Daniel Siepmann - Coding is Art - Blog Posts typo3</title>
|
||
<description>List of typo3 blog posts at daniel-siepmann.de</description>
|
||
<link>https://daniel-siepmann.de/filtered-blog-posts/topic/typo3.html</link>
|
||
<atom:link href="https://daniel-siepmann.de/rss-feed/blog-posts/typo3.xml" rel="self" type="application/rss+xml" />
|
||
|
||
<lastBuildDate>Tue, 05 Mar 2024 19:00:04 +0100</lastBuildDate>
|
||
<ttl>1800</ttl>
|
||
|
||
|
||
|
||
|
||
<item>
|
||
<title>Recap Web Camp Venlo 2024</title>
|
||
<description><![CDATA[I attend this year's Web Camp Venlo. And usually I don't publish recaps any more. But this year felt special to me and there was so much going on that I need to write that down and share the experience with everyone out there.
|
||
|
||
|
||
|
||
<a name="c672"></a>
|
||
<a name="history"></a>
|
||
|
||
<h2>History <small><a href="#history">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>The Web Camp Venlo started as a TYPO3 Camp back in 2015. I still have the T-shirt, and it was my very first international TYPO3 Camp. I'm living next to the Dutch border and wanted to get in touch with none German people of the community. I learned friendly people from the Netherlands and other countries and even sponsored the camp in the beginning, so it could happen.</p><p>But things go on and the camp opened by changing its name, now known as “Web Camp Venlo” instead of “TYPO3 Camp Venlo”.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c673"></a>
|
||
<a name="humans"></a>
|
||
|
||
<h2>Humans <small><a href="#humans">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Why covering the history you might ask? Well, because the shift from TYPO3 to Web feels very much like what this year's camp felt to me. It was not about TYPO3 or anything specific. It was about something bigger, it was about humans.</p><p>The Camp started with the Keynote “Keynote - The human touch in software development” but Carlijn Compen from Canon, the printer company. There was a talk “Defend FOSS: From innovation to world-wide positive change” by Jeffrey A. "jam" McGuire and Mathias Bolt Lesniak. As well as a Talk “Domain-Driven Design: The Basics” by Stefan Koopmanschap. I also attended “The latest computer virus is called burnout and only motivated people can get it” from Jeroen Baten and “Community collaboration” by Jaap Van Otterdijk as well as his Talk “Documentation”.</p><p>All these talks have one thing in common, they are about humans, about community and therefore social aspects. I am a Backend software developer working with TYPO3 all day, building websites and allowing editors to publish content to the world. I am building websites for visitors and do so by using a free open source software content management system where humans can be creative. They can share there thoughts, just like I am doing right now typing these words into my TYPO3 installation, serving the content to you as a reader.</p><p>There were also workshops provided a day before the usual camp days. I attend the workshop “Advanced Application Architecture” by Matthias Noback. Which again was about humans. The technical part as about encapsulation of domain and system architecture. But the concepts behind that were about humans as well.</p><p>It is all about Humans, about interactions. Domain Driven Design (=DDD) is about reflecting human beings and their processes and interactions within software code. We write documentation for humans in order to understand and use something, like a CMS, build by humans.</p><p>Some might be afraid of meeting people with well known names, but they are all human. They all have been where starters are today. They attend events like this because they share the same passion. We all want to exchange knowledge. We want to become better and gain new ideas and learn from each other.</p><p>Also, the camp is organized by a small group of nice people. There are human speakers sharing their ideas, opinions, and experiences to other humans.</p><p>Everything we do influences other humans.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c674"></a>
|
||
<a name="acks"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>I want to thank the Organizers of the camp, the speakers, the sponsors and all the attendees for making events like this web camp happen. I had a great time, as every other year in the past. And I'm looking forward to joining the camp next year.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c675"></a>
|
||
<a name="reading"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<ul><li>You can find the live streams at YouTube: <a href="https://www.youtube.com/@typo3campvenlo">https://www.youtube.com/@typo3campvenlo</a></li><li>You can find the program of 2024 online: <a href="https://www.webcampvenlo.nl/program-2024">https://www.webcampvenlo.nl/program-2024</a></li><li>You can find photos made by great Jonathan IROULIN at: <a href="https://u.pcloud.link/publink/show?code=kZp3EJ0ZCb5USzM8YjkAO6eGYClctFihAR7V">https://u.pcloud.link/publink/show?code=kZp3EJ0ZCb5USzM8YjkAO6eGYClctFihAR7V</a></li><li>And next year's camp will be from 13 - 15 Feb 2025</li></ul>
|
||
|
||
|
||
]]></description>
|
||
<link>https://daniel-siepmann.de/recap-web-camp-venlo-2024.html</link>
|
||
<pubDate>Sun, 03 Mar 2024 17:00:00 +0100</pubDate>
|
||
<guid isPermaLink="true">https://daniel-siepmann.de/recap-web-camp-venlo-2024.html</guid>
|
||
</item>
|
||
|
||
|
||
|
||
|
||
|
||
<item>
|
||
<title>TYPO3 Composer Best Practices</title>
|
||
<description><![CDATA[We always use composer to manage our TYPO3 (and other) PHP projects. I'll share our own best practices that come along by using composer for TYPO3 projects.
|
||
|
||
|
||
|
||
<a name="c661"></a>
|
||
<a name="fileStructure"></a>
|
||
|
||
<h2>File structure <small><a href="#fileStructure">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>We store one composer.json which is project specific inside the root folder of a project. We also have a folder holding all project specific composer packages, e.g. specific extensions. The file directory looks like this:</p><pre><code>.
|
||
├── <span class="selector-tag">composer</span><span class="selector-class">.json</span>
|
||
├── <span class="selector-tag">composer</span><span class="selector-class">.lock</span>
|
||
└── <span class="selector-tag">localPackages</span>
|
||
├── <span class="selector-tag">client_one</span>
|
||
├── <span class="selector-tag">client_two</span>
|
||
├── <span class="selector-tag">client_three</span>
|
||
├── <span class="selector-tag">css_styled_content</span>
|
||
├── …
|
||
├── <span class="selector-tag">e2_survey</span>
|
||
└── <span class="selector-tag">typo3_scripts</span></code></pre><p>Each local composer package has its own composer.json file. Composer can look up the local packages by adding the folder as repository within the project root composer.json:</p><pre><code><span class="string">"repositories"</span>: [
|
||
{
|
||
<span class="string">"type"</span>: <span class="string">"path"</span>,
|
||
<span class="string">"url"</span>: <span class="string">"localPackages/*"</span>
|
||
}
|
||
]</code></pre>
|
||
|
||
|
||
|
||
|
||
|
||
<aside class="admonition-hint">
|
||
|
||
<a name="c665"></a>
|
||
<a name="hint"></a>
|
||
|
||
<h2>Hint <small><a href="#hint">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Wondering about client_one to client_three? Check out <a href="/create-git-mono-repository.html">the blog post about mono repositories</a> to find out how to use the same project for multiple installations. Or watch Patrick Broens presentation at Web Camp Venlo on <a href="https://www.youtube.com/watch?v=gx5AQjMBuxs">YouTube</a>.</p>
|
||
</aside>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c662"></a>
|
||
<a name="composerjsonOfProjectSpecificPackages"></a>
|
||
|
||
<h2>composer.json of project specific packages <small><a href="#composerjsonOfProjectSpecificPackages">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Each composer.json of our local packages looks like this:</p><pre><code>{
|
||
<span class="attr">"name"</span>: <span class="string">"vendor/client-one"</span>,
|
||
<span class="attr">"description"</span>: <span class="string">"Client one specific system adjustments."</span>,
|
||
<span class="attr">"type"</span>: <span class="string">"typo3-cms-extension"</span>,
|
||
<span class="attr">"license"</span>: [
|
||
<span class="string">"GPL-2.0-or-later"</span>
|
||
],
|
||
<span class="attr">"version"</span>: <span class="string">"v12.0.0"</span>,
|
||
<span class="attr">"autoload"</span>: {
|
||
<span class="attr">"psr-4"</span>: {
|
||
<span class="attr">"Vendor\\ExtName\\"</span>: <span class="string">"Classes/"</span>
|
||
}
|
||
},
|
||
<span class="attr">"extra"</span>: {
|
||
<span class="attr">"typo3/cms"</span>: {
|
||
<span class="attr">"extension-key"</span>: <span class="string">"ext_name"</span>
|
||
}
|
||
}
|
||
}</code></pre><p>We add a version number as this reduces the amount of changes within composer.lock files. It also doesn't force allowing unstable versions. We use the major version of the corresponding TYPO3 version for TYPO3 extensions. But hat is only a convention without real impact so far.</p><p>Those files also can contain dependencies. This is important in order to know which parts of a project actually need a dependency. But we always use the asterisk as version constraint. The project root will repeat the dependency with a proper version constraint. That eases maintenance when updating versions as there is only one place to touch. Still we have the info which part introduces a dependency and the TYPO3 composer installer can set up a proper loading order of TYPO3 extensions.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c660"></a>
|
||
<a name="validatingComposerjsonFiles"></a>
|
||
|
||
<h2>Validating composer.json files <small><a href="#validatingComposerjsonFiles">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>We always make use of a CI (=Continuous Integration) for all our projects. That ensures that we keep our minimum quality, even for small tasks. One task within our CI ensures that our <code>composer.json</code> files are okay. We use the following two lines to validate the project root file as well as files for local packages:</p><pre><code><span class="string">"find localPackages/ -name 'composer.json' -print0 | xargs -0 -n 1 -P 4 composer validate --no-check-publish --no-check-all --no-check-version --strict"</span>
|
||
composer validate --no-check-publish --no-check-all --strict</code></pre><p>The first lines will search all composer.json files within the folder <code>localPackages/</code>. This is the folder where we store our local composer packages. The <code>-P 4</code> in <code>xargs</code> defined to parallelize the checks in 4 threads. We add the <code>--no-check-version</code> option as we hardcode a version number within the composer.json files for internal packages.<br>The second line validates in project root folder where we store our project composer.json file.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c663"></a>
|
||
<a name="acks"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Thanks to <a href="https://www.kaarna.de/" title="His work related website">Gerald Rintisch</a> for motivating me to create and publish this post.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c664"></a>
|
||
<a name="furtherReading"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<ul><li>Composer documentation describing <a href="https://getcomposer.org/doc/05-repositories.md#path">the local path repository</a></li><li><a href="https://getcomposer.org/doc/">Composer documentation</a></li><li><a href="https://docs.typo3.org/m/typo3/reference-coreapi/12.4/en-us/ExtensionArchitecture/FileStructure/ComposerJson.html">TYPO3 documentation regarding composer.json files</a></li></ul>
|
||
|
||
|
||
]]></description>
|
||
<link>https://daniel-siepmann.de/typo3-composer-best-practices.html</link>
|
||
<pubDate>Wed, 20 Dec 2023 08:31:00 +0100</pubDate>
|
||
<guid isPermaLink="true">https://daniel-siepmann.de/typo3-composer-best-practices.html</guid>
|
||
</item>
|
||
|
||
|
||
|
||
|
||
|
||
<item>
|
||
<title>TypeScript with Modules lacking Types</title>
|
||
<description><![CDATA[We have a custom TYPO3 Backend module which is a small web application. We wrote the web application using TypeScript. But we need to make use of some TYPO3 JavaScript APIs which do not expose types for TypeScript. I'll be going to explain how to set up TypeScript for modules not providing those types. This is a general approach, not specific to TYPO3.
|
||
|
||
|
||
<aside class="admonition-note">
|
||
|
||
<a name="c652"></a>
|
||
<a name="note"></a>
|
||
|
||
<h2>Note <small><a href="#note">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>I'm not a frontend developer, but a backend developer. I have not enough experience in order to explain everything here or provide proper pointers. But the following is working for me in one of our projects.</p>
|
||
</aside>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c649"></a>
|
||
<a name="theConsumingCode"></a>
|
||
|
||
<h2>The consuming code <small><a href="#theConsumingCode">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Let's start with our actual TypeScript code consuming ES6 modules not providing types.</p><pre><code><span class="keyword">import</span> Modal <span class="keyword">from</span> <span class="string">'@typo3/backend/modal.js'</span>;
|
||
<span class="keyword">import</span> { SeverityEnum } <span class="keyword">from</span> <span class="string">'@typo3/backend/enum/severity.js'</span>;</code></pre>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c651"></a>
|
||
<a name="theError"></a>
|
||
|
||
<h2>The error <small><a href="#theError">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>The above code will result in the following error output, which will lead to missing compiled JavaScript.</p><pre><code>Src/Libs/Export/Ui.ts(<span class="number">4</span>,<span class="number">19</span>): error TS7016: Could not find a declaration file <span class="keyword">for</span> <span class="built_in">module</span> <span class="string">'@typo3/backend/modal.js'</span>. <span class="string">'vendor/typo3/cms-backend/Resources/Public/JavaScript/modal.js'</span> implicitly has an <span class="string">'any'</span> type.
|
||
Src/Libs/Export/Ui.ts(<span class="number">6</span>,<span class="number">30</span>): error TS7016: Could not find a declaration file <span class="keyword">for</span> <span class="built_in">module</span> <span class="string">'@typo3/backend/enum/severity.js'</span>. <span class="string">'vendor/typo3/cms-backend/Resources/Public/JavaScript/enum/severity.js'</span> implicitly has an <span class="string">'any'</span> type.
|
||
TypeScript: <span class="number">4</span> semantic errors
|
||
<span class="attr">TypeScript</span>: emit succeeded (<span class="keyword">with</span> errors)</code></pre>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c650"></a>
|
||
<a name="addingTheNecessarySetup"></a>
|
||
|
||
<h2>Adding the necessary setup <small><a href="#addingTheNecessarySetup">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Now let's continue with the necessary adjustments in order to get rid of the errors.</p><p>We need to adjust the existing <code>tsconfig.json</code> to provide information where to find type information. Add the following entry and adjust the path to a custom folder:</p><pre><code>{
|
||
<span class="attr">"compilerOptions"</span>: {
|
||
<span class="attr">"typeRoots"</span>: [
|
||
<span class="string">"./typescript_types"</span>
|
||
]
|
||
}
|
||
}</code></pre><p>The folder structure looks like this (again, adjust to match your own folder names):</p><pre><code><span class="selector-tag">typescript_types</span>
|
||
└── <span class="selector-tag">TYPO3</span>
|
||
└── <span class="selector-tag">index</span><span class="selector-class">.d</span><span class="selector-class">.ts</span></code></pre><p>And the content of the file looks like this:</p><pre><code>declare <span class="built_in">module</span> <span class="string">'@typo3/backend/enum/severity.js'</span>;
|
||
declare <span class="built_in">module</span> <span class="string">'@typo3/backend/modal.js'</span>;</code></pre>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c653"></a>
|
||
<a name="acks"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>The solution is copied from the TYPO3 Extension <a href="https://github.com/quellenform/t3x-iconpack/">https://github.com/quellenform/t3x-iconpack/</a>.</p>
|
||
|
||
|
||
]]></description>
|
||
<link>https://daniel-siepmann.de/typescript-with-modules-lacking-types.html</link>
|
||
<pubDate>Wed, 15 Nov 2023 09:19:00 +0100</pubDate>
|
||
<guid isPermaLink="true">https://daniel-siepmann.de/typescript-with-modules-lacking-types.html</guid>
|
||
</item>
|
||
|
||
|
||
|
||
|
||
|
||
<item>
|
||
<title>Import Data within TYPO3</title>
|
||
<description><![CDATA[TYPO3 developers are often faced with the same requirement: Import data into TYPO3. TYPO3 itself offers many ways to import data into the system. The DataHandler (TCE) or Extbase might come to your mind, as well as plain Doctrine DBAL queries.
|
||
|
||
This post will explain my views on each of the options and their pros and cons. I don't consider any of them the general solution, but the pros and cons might help you find the one suiting your current use case best.
|
||
|
||
|
||
|
||
<a name="c625"></a>
|
||
<a name="datahandler"></a>
|
||
|
||
<h2>DataHandler <small><a href="#datahandler">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>I always would suggest DataHandler first for the following reasons:</p><ul><li>API is pretty simple, build a single array and submit.</li><li>It handles everything you can think of, and everything you don't remember:</li><li>History</li><li>Logging</li><li>Relations</li><li>Permissions</li><li>Hooks (indexing in solr, or whatever your system uses hooks for)</li><li>Cache flushing</li><li>… (things I forgot)</li></ul><p>But there are some downsides:</p><ul><li>It is slow (You can tweak the DataHandler instance to skip some things and be more performant)</li><li>It might do more than you want, e.g. history, logging, relations, permissions, hooks, cache flushing, ….</li></ul>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c626"></a>
|
||
<a name="doctrineDbal"></a>
|
||
|
||
<h2>Doctrine DBAL <small><a href="#doctrineDbal">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>I than would recommend Doctrine DBAL for the following reasons:</p><ul><li>You are in full control. You can do exactly what you want the way you want, and can optimize for memory and performance.</li></ul><p>But it also has downsides:</p><ul><li>You need to remember all necessary steps and rebuild integrations, e.g. indexing into solr, triggering cache flushes, etc.</li></ul>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c627"></a>
|
||
<a name="extbase"></a>
|
||
|
||
<h2>Extbase <small><a href="#extbase">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>And then there is Extbase for the following reasons:</p><ul><li>You stay in OOP</li><li>Most developers are probably more familiar with Extbase than the other APIs</li></ul><p>With downsides:</p><ul><li>It was not build for that task.</li><li>You need to add workarounds to keep performance okay.</li><li>You still need to do extra work like triggering indexing, cache flushing, etc.</li></ul>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c630"></a>
|
||
<a name="itDepends"></a>
|
||
|
||
<h2>It depends <small><a href="#itDepends">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>I would say it depends on:</p><ul><li>What do you need from the system, do you need hooks, logging (history) and cache flushing?</li><li>Do you know what you do and want to have full control over every aspect?</li><li>How much data do you have, how much memory and time do you have?</li><li>Do you want to keep OOP?</li></ul>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c628"></a>
|
||
<a name="acks"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>This is a recurring topic, but Eric Harrer finally made me write this blog post by bringing up the topic once more at the Fediverse: <a href="https://phpc.social/@ErHaWeb/111096230843826340">https://phpc.social/@ErHaWeb/111096230843826340</a>.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c629"></a>
|
||
<a name="furtherReading"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Read more about:</p><ul><li>TCE (TYPO3 Core engine) & DataHandler: <a href="https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/Typo3CoreEngine/Index.html">https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/Typo3CoreEngine/Index.html</a></li><li>Doctrine DBAL: <a href="https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/Database/Index.html">https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/Database/Index.html</a></li><li>Example Extension importing JSON data mapped to Objects using DataHandler in the end: <a href="https://git.daniel-siepmann.de/Customers/thuecat">https://git.daniel-siepmann.de/Customers/thuecat</a> / <a href="https://github.com/werkraum-media/thuecat">https://github.com/werkraum-media/thuecat</a></li><li>Example Extension importing JSON data mapped to Extbase Entities using mostly Extbase for importing: <a href="https://git.daniel-siepmann.de/Customers/events">https://git.daniel-siepmann.de/Customers/events</a> / <a href="https://github.com/werkraum-media/events">https://github.com/werkraum-media/events</a></li></ul>
|
||
|
||
|
||
]]></description>
|
||
<link>https://daniel-siepmann.de/import-data-within-typo3.html</link>
|
||
<pubDate>Wed, 20 Sep 2023 11:19:00 +0200</pubDate>
|
||
<guid isPermaLink="true">https://daniel-siepmann.de/import-data-within-typo3.html</guid>
|
||
</item>
|
||
|
||
|
||
|
||
|
||
|
||
<item>
|
||
<title>My TYPO3 Journey</title>
|
||
<description><![CDATA[I'm a professional web developer doing PHP with TYPO3 since 2012. This post covers my journey from the beginning, how I've become a web developer and how I've become a TYPO3 developer and integrator. I'll also cover my experience back in the old days as a beginner and my current experience, covering my own history as well as TYPO3 history.
|
||
|
||
I hope this will be useful for you, e.g. providing insights in how to become a web developer, how to get started with TYPO3 and how our job and technologies have changed over the years.
|
||
|
||
|
||
|
||
<a name="c622"></a>
|
||
<a name="intro"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>I can't cover everything as that would be way too much. I won't cover my time as JavaScript developer when jQuery was available and made JavaScript finally available to everyone. I won't cover my time with Internet Explorer 6 and its unusable developer tools. I won't cover the time before Google Chrome when Mozilla Firefox with Firebug was the only way to debug websites. And I won't cover my journey through all different kind of Editors and IDEs. I'll also not cover my journey from Windows 3.11 over Windows XP, Windows 7, different Mac versions, Gentoo and Ubuntu up to NixOS. I'll focus on the relevant parts of becoming a web developer and TYPO3.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c617"></a>
|
||
<a name="today"></a>
|
||
|
||
<h2>Today <small><a href="#today">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>I'm currently employed at <a href="https://www.codappix.com/">Codappix GmbH</a> a small web manufacturer in my home town, that I've founded with some friends and old fellow workers, see the <a href="/about-me.html#c34">corresponding section</a> on the about me page. I'm mostly focusing on <a href="https://typo3.org/">TYPO3 CMS</a> as backend developer and integrator right now, mostly doing <a href="https://www.php.net/">PHP</a> and a bit of TypoScript. TypoScript is a specific configuration language developed for TYPO3 itself.</p><p>The idea behind TypoScript is that a developer doesn't need to know any PHP or programming. The developer should still be able to build a full-blown website with CSS, JavaScript and HTML. The whole rendering is defined by TypoScript that glues everything together. I won't go into much detail here, but the rendering of a page can look like this, so you can get an idea:</p><pre><code>page = PAGE
|
||
page {
|
||
config.htmlTag.attributes.class = no-js
|
||
includeCSS.styles = EXT:site_package/Resources/Public/Css/styles.css
|
||
10 = FLUIDTEMPLATE
|
||
10 {
|
||
file = EXT:site_package/Resources/Private/Templates/Page/Default.html
|
||
variables {
|
||
content < styles.content.get
|
||
}
|
||
}
|
||
}</code></pre><p>My main goal is to develop maintainable feature rich websites solving the customer needs and making editing content as smooth as possible. TYPO3 itself is a great piece of software, most of the time, helping me to achieve the goal. It is written in PHP and open sourced, licensed under <a href="https://typo3.org/project/licenses">GNU GPL 2 +</a>. It was developed by a single person back at the end of 90s. The history of the project is available at <a href="https://typo3.org/project/history">https://typo3.org/project/history</a>. The old history is one of the main drawbacks of the system. It still has code dating back to the ancient time. But that proves the code worked for more than 20 years already. Still one wouldn't write the code the same with todays knowledge and language possibilities. One needs to remember that PHP 3 was just released, there were no OOP (=object oriented programming) nor namespaces or anything related. We didn't have composer or any other dependency manager. TYPO3 has features dating back to the beginning that are still state of the art (and were way ahead of time back than). A small list of those features:</p><ul><li>Extendability. The system works much like todays Laravel, Symfony, Ruby on Rails, etc. But it had the extendability back in the 90s. The system itself is build by a set of extensions (Symfony calls it bundles). Developers can create and share own Extensions. TYPO3 itself has an Extension Manager and a central <a href="https://extensions.typo3.org/">Extension Repository</a>, the TER (=TYPO3 Extension Repository). TYPO3 also supports Composer, allowing you to draw any composer package. Packages with a dedicated type are considered to be TYPO3 extensions and handled in a special way.</li><li>Configurability. Most parts of the system can be configured. There is most often no need to build an Extension, one can configure stuff, hide inputs, alter existing inputs, change behaviour, etc.</li><li>Versioning. TYPO3 is a CMS and has versioning of content built into it. Every change made by any user within the TYPO3 Backend is tracked. Users are able to check the history of every single record and can revert changes.</li><li>Workspaces. TYPO3 provides different workspaces, allowing users to create content in their own workspace. It supports workflows, allowing other users to review the content and publishing content. A preview URL can be created for content within a workspace, allowing to share a draft with foreign visitors, customers, etc.</li><li>Multilanguage. TYPO3 allows to maintain content in any language, content can be translated, maintained independent of different languages. It offers overviews what parts of a site are lacking a translation, etc.</li><li>Multisite. TYPO3 has multisite built in. It is possible to manage hundreds of websites within a single installation, sharing users, permissions, and content.</li><li>Permissions. TYPO3 has a fine grained permission system of users and groups with permissions to different record types, content types, mount points, languages, and way more.</li><li>Robustness. I only list this cause I've heard from other projects that this issue exists. But TYPO3 has a smooth upgrade path. It is possible to update from an old TYPO3 version 4 to an current TYPO3 version 12 LTS (=Long Term Supported). TYPO3 always provides so-called update wizards that will handle the migration of data to stay compatible. Only rendering is constantly changing, so one might have its own rendering (templates) anyway and might need to do some small adjustments.</li><li>Image processing. Another one I took for granted, but it looks like many systems don't have it out of the box. TYPO3 allows using any server side supported file formats and will process them. Users can define an area within an image to be rendered on the website, they can crop and resize images.</li><li>FAL (=File Abstraction Layer). TYPO3 abstracts the file system, which allows to add other drivers like Amazon, Google Drive, Dropbox, etc. It is not necessary to store all files, e.g. images or PDFs, on the same server.</li><li>TCA (=Table Configuration Array). TYPO3 is like a big improved version of a database tool like Sequel Pro, Sequel Ace, PHPMyadmin, etc. It has a backend with many types of inputs and the TCA is the configuration that TYPO3 reads in order to render the different forms for users to edit content. It allows to rapidly create an interface familiar to users and focus on actual business logic or rendering and features instead of building forms and validation for adding content.</li></ul><p>A more complete list is available at <a href="https://typo3.org/cms/features">https://typo3.org/cms/features</a>.</p><p>I'm really happy that we have customers like <a href="https://werkraum-media.de/">https://werkraum-media.de/</a> that live open source the same way we and TYPO3 do. That's why we have some open source TYPO3 extensions available: <a href="https://packagist.org/packages/werkraummedia/">https://packagist.org/packages/werkraummedia/</a>.</p><p>I'm also nowadays very active within the TYPO3 community, since some years. I've listed all my activities at the <a href="/about-me.html#c37">TYPO3 section</a> on the about me page.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c618"></a>
|
||
<a name="theMeantime"></a>
|
||
|
||
<h2>The meantime <small><a href="#theMeantime">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>I've already worked at two agencies and for one freelancer before I've joined the efforts of my friends to start our own company. One reason we started our own company and that we don't call it an agency is that we hate how most agencies seem to work in web development here in Germany. There is a lot of stress, time pressure, bad decisions, bad leadership and foremost bad technical decisions. There is no time to do things right and think about your work. We had to spend extra hours fixing stuff, altering implementations to follow ever-changing requirements. It is a nightmare. Still in Germany everyone talks about “Fachkräftemangel”, we lack well-educated people on our business. But most companies don't provide an environment that allows to work as expected. There are so many companies, do your self a favour if you can and don't stop looking for the right company. Settle down once you find it and be happy. Or become a freelancer or found your own, as we did.</p><p>We still always tried to become better. We checked solutions like <a href="https://www.selenium.dev/">https://www.selenium.dev/</a> to automate end to end website tests. Furthermore, we started to use automated deployments and continues integrations including static code analysis, code style checks and a lot more. We always tried to get used to testing. Things that we take for granted nowadays, but it was a hard time back then. The first deployment I established was more an accident. It was during my apprenticeship. We wanted to relaunch a huge website and had multiple different staging systems in order to test different features in parallel. I still used windows and wanted to test out shell scripting. I installed Cygwin on Windows and tried very small scripts. I then had the job to update all the different staging systems. Furthermore, I needed to sync content between them and update the code. That's when I started to build my first deployment via shell scripts on Windows. We then had a day when our boss asked when we could go productive and how the procedure would look like. I said that the script should work fine for production as well, and we had a deployment. The bad thing was that it was running on my machine, and a colleague and I always hat to set up and test it on his machine every time I went on holidays. They moved the deployment into a virtual machine when I left the company.</p><p>That was the time when I got deeper knowledge of TYPO3. I was only a backend developer back then, now knowing much about integration or features provided by TYPO3 except for developing extensions. But not knowing the features of TYPO3 itself, or the interface is a big drawback. You don't know how users will actually use TYPO3 and can't integrate solutions into the system. You most likely, like I did, always start from scratch and build your own stuff that you will add to the system, not playing nice with the existing system. E.g. you will build custom modules for the TYPO3 backend to get things done, instead of using existing modules with a bit of configuration and small modifications.</p><p>That was also the time when I wanted to give something back. I understood that this system, being open source, offered with no fees, was great and enabled me to work and earn money. I joined TYPO3 Bar Camps and User groups. Check the <a href="https://en.wikipedia.org/wiki/BarCamp">Wikipedia page</a> if you don't know what a Bar Camp is. That way I encountered the great community. It was hard for me at the beginning. I'm not that extrovert at the beginning in foreign environments with unknown people. I visited different sessions and listened, but didn't talk to anyone. Nowadays, the community feels like a big family for me. There were stories where people help a community member to build a ramp because she is now bound to a wheelchair. People ask whether everything is okay and everyone is happy if we meet. It is way more than exchanging experience, knowledge. It is about the same mindset, being open source, sharing ideas and concepts, becoming motivated.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c619"></a>
|
||
<a name="theBeginning"></a>
|
||
|
||
<h2>The beginning <small><a href="#theBeginning">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>My dad was a programming for banks doing Cobol all the time. He taught me the dual system when I was pretty young and forced me to watch movies in English. I didn't like anything of that. But I also was able to buy his old PC when I was in ground school, it was a Dos 6.22 with Windows 3.11. I only used it to play around, using everything that was installed on the PC. There were games like Settlers and software like Word for Windows, a tool to draw diagrams and another tool to build any kind of prints, like business cards. I also bought his old printer. That way I already had a business card when I wasn't even able to write “computer” but wrote “compjuter” on it. I never thought I would become a programmer. But my Dad also was curious about the Internet. We had ISDN and I got a 56k Modem. My dad had his own website <a href="https://web.archive.org/web/20010402045707/http://www.srui.de/">www.srui.de</a>. This was when I was around 13 years old. I read his book about HTML 4 which I still own. I used some editor and build my first websites which I couldn't host anywhere but could view them via Netscape on my own local PC. Still I had no idea what I would become. I thought I'd work in a library or book store, where I did my first two internships each two weeks.</p><p>We than had IT at school in class 10, and I asked the teacher whether I could do the teaching about HTML and JavaScript. That was the time when JavaScript was used to do stupid animations like eyes following the mouse, stupid alerts and scrolling status bars (Yes browser once had a status bar with a loading bar and infos about links, etc.). Still I didn't think about a job as programmer. I didn't finish school that good, because I was way too lazy. I ended in another school after class 10, there I were additional 3 years becoming a “Staatlich geprüfter kaufmännischer Assistent mit Fachrichtung Informationsverarbeitung”. That means I, at least in theory, know the business side and could support a CEO. But the focus was on IT again. And we learend about database normalization, programming and debugging. All using Windows tools like Excel and Visual Basic as well as Microsoft Access. The school forced us to do two internships, one six and another four weeks long. I travelled to our industrial area and walked from door to door looking for a business where I could do my internship. I visited a company doing lightning and sound for events, and many IT companies. I settled for the one where a tall man with long hair was sitting in front of two monitors, reminding me of Neo from Matrix. This company allowed me to do an internship without any further questions. All other companies wanted to know details, I should write a letter, provide certificates, etc. I was lazy and did a six-week internship at that web agency, mostly doing image processing within Photoshop. Cause I thought I was too dumb for programming, but I could repeat what someone will show me within an image processing program. But one frontend guy also introduced me into HTML and CSS, nice technics how to build layouts with div Tags and floats, how to use a 1px image with repeat as background.</p><p>I finished off the school and knew I won't do anything related to business. I had some months until my social service started, and I asked the old company whether it would be possible to help them out for five months, again mostly doing image processing within Photoshop. Once finished with social services I still had no plan, so I asked whether I could work at that company again. But my parents wanted me to do another education and that's why I did another three years education within the company. It was a hard decision, but I decided that I won't do image processing but would like to finally learn programming. I settled, and the company didn't do a great job. My first task was a very long-running project without much help and way ahead of what I could do. I needed to program a web interface allowing citizens of our home town to apply for becoming a company. Including the frontend forms, the editing interface for the city, all data crypted within the database. The city already had a TYPO3, but everyone told me to just program, and they would integrate the result into the website. I really thought I would abort the education at least three or five times within the first one and a half year. I constantly got bad feedback but way to less help. And I constantly needed to do the same things, building web forms to insert data that would be stored within a database and representing it to website visitors.</p><p>I then thought that this TYPO3 CMS we were using must be good for something and took a first look into the docs. I discovered the TCA (=Table Configuration Array) which would allow me to configure the forms. That was the first time I got positive feedback and other employees asked me for help, because no one ever had a look at the features and docs of the software. That was the turning point.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c621"></a>
|
||
<a name="acks"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>This blog post was made for <a href="https://fosstodon.org/@array">@array@fosstodon.org</a> as he requested the post at <a href="https://fosstodon.org/@array/110701165059012368">https://fosstodon.org/@array/110701165059012368</a>. I hope you liked the post. Feel free to <a href="/about-me.html#c484">contact me</a> and request other blog post.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c620"></a>
|
||
<a name="furtherReading"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>You might like those sources:</p><ul><li><a href="https://typo3.org/">https://typo3.org/</a> The official website of the TYPO3 CMS project.</li><li><a href="https://typo3.org/cms/features">https://typo3.org/cms/features</a> The features of TYPO3</li><li><a href="https://en.wikipedia.org/wiki/BarCamp">https://en.wikipedia.org/wiki/BarCamp</a> Wikipedia explaining Bar Camps.</li><li><a href="https://git.daniel-siepmann.de/Customers">https://git.daniel-siepmann.de/Customers</a> a list of open source projects I am or were working on for customers.</li><li><a href="https://docs.typo3.org/">https://docs.typo3.org/</a> The official docs of TYPO3 CMS.</li><li><a href="https://extensions.typo3.org/">https://extensions.typo3.org/</a> The extension repository of TYPO3.</li><li><a href="https://packagist.org/?type=typo3-cms-extension">https://packagist.org/?type=typo3-cms-extension</a> TYPO3 Extensions on Packagist.</li></ul><p>Check out my other blog posts:</p><ul><li><a href="/short-introduction-into-typo3-what-is-content.html">Video: Short introduction into TYPO3 - What is Content?</a></li><li><a href="/posts/2017/using-php-codesniffer-for-automated-code-migrations.html">Using PHP_CodeSniffer for automated code migrations</a>, when rector wasn't a thing.</li><li>I still plan to create a curated list of my TYPO3 talks on YouTube, but I'm too lazy, still I've collected some from TYPO3 and vim: <a href="https://www.youtube.com/playlist?list=PLDXiDr43HScExqOdpcQbCTmxfdBtIn63O">https://www.youtube.com/playlist?list=PLDXiDr43HScExqOdpcQbCTmxfdBtIn63O</a>.</li></ul>
|
||
|
||
|
||
]]></description>
|
||
<link>https://daniel-siepmann.de/my-typo3-journey.html</link>
|
||
<pubDate>Wed, 12 Jul 2023 17:34:00 +0200</pubDate>
|
||
<guid isPermaLink="true">https://daniel-siepmann.de/my-typo3-journey.html</guid>
|
||
</item>
|
||
|
||
|
||
|
||
|
||
|
||
<item>
|
||
<title>Auto migrate PHP code via configuration</title>
|
||
<description><![CDATA[We need to update software regularly. E.g. because our framework released a new major version containing breaking changes to APIs. It is a cumber stone and hard and boring task, not meant to be done by humans. Luckily we have computers and software we can use to automate boring tasks.
|
||
|
||
Our example is about TYPO3 and Aimeos updates. TYPO3 is an open source CMS while Aimeos is an open source shop framework. I'll share my experience how to automate the boring tasks during one update and how to use the automation while upgrading further installations.
|
||
|
||
We will check out rector and show how you can use existing rules with custom configuration to auto mate your code migrations.
|
||
|
||
|
||
|
||
<a name="c589"></a>
|
||
<a name="meetRector"></a>
|
||
|
||
<h2>Meet rector <small><a href="#meetRector">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>rector is an open source PHP framework that allows to auto migrate source code. The project is available at <a href="https://getrector.com/">https://getrector.com/</a> and <a href="https://github.com/rectorphp/rector/.">https://github.com/rectorphp/rector/.</a> At its core it provides so-called rectors that check source code and adjust the source code. Rector itself delivers some rectors already for certain tasks, a full list is available at <a href="https://getrector.com/documentation/rules-overview.">https://getrector.com/documentation/rules-overview.</a> It includes rules (=rectors) to migrate based on PHP version switches, Symfony, PHPUnit, etc. It is possible to write custom rules (rectors) <a href="https://getrector.com/documentation/custom-rule">https://getrector.com/documentation/custom-rule</a> covered with tests.</p><p>It is also possible to re-use existing rectors with different configuration. E.g. there is one rector to rename methods. This rector can be configured to migrate method calls for example if a framework switches from <code>getView()</code> to <code>view()</code>.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c590"></a>
|
||
<a name="rectorForTypo3"></a>
|
||
|
||
<h2>Rector for TYPO3 <small><a href="#rectorForTypo3">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>TYPO3 developers have an easy job, there is already a community project maintaining specific rectors for upgrading TYPO3 versions. You can check the project at <a href="https://www.typo3-rector.com/">https://www.typo3-rector.com/</a> as well as <a href="https://github.com/sabbelasichon/typo3-rector.">https://github.com/sabbelasichon/typo3-rector.</a> It comes with a documentation and explains how to copy the template configuration and how to execute the update. I'll not go into much detail about that.</p><p>It is possible to extend the boilerplate configuration. That way TYPO3 extensions can provide their own rectors that can be added to your project setup, in order to not only auto migrate source code related to TYPO3 CMS itself, but also to TYPO3 Extensions provided by 3rd Parties.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c591"></a>
|
||
<a name="rectorForAimeos"></a>
|
||
|
||
<h2>Rector for Aimeos <small><a href="#rectorForAimeos">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Aimeos doesn't have anything regarding rector out of the box. But one can use that as an opportunity to get used to rector. We had one project where we had to upgrade from TYPO3 v8 to v11 and Aimeos 2018 to 2022. We first started with migrations by hand until we realized how much work it is for the concrete code base in that project. So I've started to add rector rules. The result looks like this:</p><pre><code><span class="keyword">use</span> <span class="title">Rector</span>\<span class="title">Renaming</span>\<span class="title">Rector</span>\<span class="title">ClassConstFetch</span>\<span class="title">RenameClassConstFetchRector</span>;
|
||
<span class="keyword">use</span> <span class="title">Rector</span>\<span class="title">Renaming</span>\<span class="title">Rector</span>\<span class="title">MethodCall</span>\<span class="title">RenameMethodRector</span>;
|
||
<span class="keyword">use</span> <span class="title">Rector</span>\<span class="title">Renaming</span>\<span class="title">Rector</span>\<span class="title">Name</span>\<span class="title">RenameClassRector</span>;
|
||
<span class="keyword">use</span> <span class="title">Rector</span>\<span class="title">Renaming</span>\<span class="title">Rector</span>\<span class="title">StaticCall</span>\<span class="title">RenameStaticMethodRector</span>;
|
||
<span class="keyword">use</span> <span class="title">Rector</span>\<span class="title">Renaming</span>\<span class="title">ValueObject</span>\<span class="title">MethodCallRename</span>;
|
||
<span class="keyword">use</span> <span class="title">Rector</span>\<span class="title">Renaming</span>\<span class="title">ValueObject</span>\<span class="title">RenameClassAndConstFetch</span>;
|
||
<span class="keyword">use</span> <span class="title">Rector</span>\<span class="title">Renaming</span>\<span class="title">ValueObject</span>\<span class="title">RenameStaticMethod</span>;
|
||
|
||
<span class="comment">// Custom Aimeos migration</span>
|
||
$rectorConfig->ruleWithConfiguration(RenameClassRector::class, [
|
||
<span class="string">'Aimeos\MShop\Context\Item\Iface'</span> => <span class="string">'Aimeos\MShop\ContextIface'</span>,
|
||
]);
|
||
|
||
$rectorConfig->ruleWithConfiguration(RenameMethodRector::class, [
|
||
<span class="keyword">new</span> MethodCallRename(<span class="string">'Aimeos\Admin\JQAdm\Base'</span>, <span class="string">'getContext'</span>, <span class="string">'context'</span>),
|
||
<span class="keyword">new</span> MethodCallRename(<span class="string">'Aimeos\Admin\JQAdm\Base'</span>, <span class="string">'getView'</span>, <span class="string">'view'</span>),
|
||
<span class="keyword">new</span> MethodCallRename(<span class="string">'Aimeos\Base\Mail\Message\Iface'</span>, <span class="string">'setSubject'</span>, <span class="string">'subject'</span>),
|
||
<span class="keyword">new</span> MethodCallRename(<span class="string">'Aimeos\MShop\Common\Manager\Iface'</span>, <span class="string">'createItem'</span>, <span class="string">'create'</span>),
|
||
<span class="keyword">new</span> MethodCallRename(<span class="string">'Aimeos\MShop\Common\Manager\Iface'</span>, <span class="string">'createSearch'</span>, <span class="string">'filter'</span>),
|
||
<span class="keyword">new</span> MethodCallRename(<span class="string">'Aimeos\MShop\Common\Manager\Iface'</span>, <span class="string">'deleteItems'</span>, <span class="string">'delete'</span>),
|
||
<span class="keyword">new</span> MethodCallRename(<span class="string">'Aimeos\MShop\Common\Manager\Iface'</span>, <span class="string">'findItem'</span>, <span class="string">'find'</span>),
|
||
<span class="keyword">new</span> MethodCallRename(<span class="string">'Aimeos\MShop\Common\Manager\Iface'</span>, <span class="string">'saveItem'</span>, <span class="string">'save'</span>),
|
||
<span class="keyword">new</span> MethodCallRename(<span class="string">'Aimeos\MShop\Common\Manager\Iface'</span>, <span class="string">'searchItems'</span>, <span class="string">'search'</span>),
|
||
<span class="keyword">new</span> MethodCallRename(<span class="string">'Aimeos\MShop\ContextIface'</span>, <span class="string">'getCache'</span>, <span class="string">'cache'</span>),
|
||
<span class="keyword">new</span> MethodCallRename(<span class="string">'Aimeos\MShop\ContextIface'</span>, <span class="string">'getConfig'</span>, <span class="string">'config'</span>),
|
||
<span class="keyword">new</span> MethodCallRename(<span class="string">'Aimeos\MShop\ContextIface'</span>, <span class="string">'getLocale'</span>, <span class="string">'locale'</span>),
|
||
<span class="keyword">new</span> MethodCallRename(<span class="string">'Aimeos\MShop\ContextIface'</span>, <span class="string">'getLogger'</span>, <span class="string">'logger'</span>),
|
||
]);
|
||
|
||
$rectorConfig->ruleWithConfiguration(RenameStaticMethodRector::class, [
|
||
<span class="keyword">new</span> RenameStaticMethod(<span class="string">'Aimeos\MShop\Factory'</span>, <span class="string">'createManager'</span>, <span class="string">'Aimeos\MShop'</span>, <span class="string">'create'</span>),
|
||
]);
|
||
|
||
$rectorConfig->ruleWithConfiguration(RenameClassConstFetchRector::class, [
|
||
<span class="keyword">new</span> RenameClassAndConstFetch(<span class="string">'Aimeos\MShop\Common\Item\Address\Base'</span>, <span class="string">'SALUTATION_MISS'</span>, <span class="string">'Aimeos\MShop\Common\Item\Address\Base'</span>, <span class="string">'SALUTATION_MS'</span>),
|
||
<span class="keyword">new</span> RenameClassAndConstFetch(<span class="string">'Aimeos\MShop\Common\Item\Address\Base'</span>, <span class="string">'SALUTATION_MRS'</span>, <span class="string">'Aimeos\MShop\Common\Item\Address\Base'</span>, <span class="string">'SALUTATION_MS'</span>),
|
||
<span class="keyword">new</span> RenameClassAndConstFetch(<span class="string">'Aimeos\MW\Logger\Base'</span>, <span class="string">'ALERT'</span>, <span class="string">'Aimeos\Base\Logger\Iface'</span>, <span class="string">'ALERT'</span>),
|
||
<span class="keyword">new</span> RenameClassAndConstFetch(<span class="string">'Aimeos\MW\Logger\Base'</span>, <span class="string">'CRIT'</span>, <span class="string">'Aimeos\Base\Logger\Iface'</span>, <span class="string">'CRIT'</span>),
|
||
<span class="keyword">new</span> RenameClassAndConstFetch(<span class="string">'Aimeos\MW\Logger\Base'</span>, <span class="string">'DEBUG'</span>, <span class="string">'Aimeos\Base\Logger\Iface'</span>, <span class="string">'DEBUG'</span>),
|
||
<span class="keyword">new</span> RenameClassAndConstFetch(<span class="string">'Aimeos\MW\Logger\Base'</span>, <span class="string">'EMERG'</span>, <span class="string">'Aimeos\Base\Logger\Iface'</span>, <span class="string">'EMERG'</span>),
|
||
<span class="keyword">new</span> RenameClassAndConstFetch(<span class="string">'Aimeos\MW\Logger\Base'</span>, <span class="string">'ERR'</span>, <span class="string">'Aimeos\Base\Logger\Iface'</span>, <span class="string">'ERR'</span>),
|
||
<span class="keyword">new</span> RenameClassAndConstFetch(<span class="string">'Aimeos\MW\Logger\Base'</span>, <span class="string">'INFO'</span>, <span class="string">'Aimeos\Base\Logger\Iface'</span>, <span class="string">'INFO'</span>),
|
||
<span class="keyword">new</span> RenameClassAndConstFetch(<span class="string">'Aimeos\MW\Logger\Base'</span>, <span class="string">'NOTICE'</span>, <span class="string">'Aimeos\Base\Logger\Iface'</span>, <span class="string">'NOTICE'</span>),
|
||
<span class="keyword">new</span> RenameClassAndConstFetch(<span class="string">'Aimeos\MW\Logger\Base'</span>, <span class="string">'WARN'</span>, <span class="string">'Aimeos\Base\Logger\Iface'</span>, <span class="string">'WARN'</span>),
|
||
]);</code></pre><p>That way we don't have to write actual code but configure rector to migrate method calls, classes, constants, etc. Thanks to rector generic rules.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c593"></a>
|
||
<a name="thanks"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Thanks to <a href="https://werkraum-media.de/">werkraum_media</a> who allowed me to do my first Aimeos upgrade in order to get used to configure generic rector rules.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c592"></a>
|
||
<a name="furtherReading"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Read more about rector:</p><ul><li>Project website: <a href="https://getrector.com/">https://getrector.com/</a></li><li>Project repository on GitHub: <a href="https://github.com/rectorphp/rector/">https://github.com/rectorphp/rector/</a></li><li>Adding custom rules: <a href="https://getrector.com/documentation/custom-rule">https://getrector.com/documentation/custom-rule</a></li><li>Overview of existing rules: <a href="https://github.com/rectorphp/rector/blob/main/docs/rector_rules_overview.md">https://github.com/rectorphp/rector/blob/main/docs/rector_rules_overview.md</a></li></ul><p>Read more about TYPO3 rector:</p><ul><li>Project website: <a href="https://www.typo3-rector.com/">https://www.typo3-rector.com/</a></li><li>Project repository on GitHub: <a href="https://github.com/sabbelasichon/typo3-rector">https://github.com/sabbelasichon/typo3-rector</a></li></ul>
|
||
|
||
|
||
]]></description>
|
||
<link>https://daniel-siepmann.de/auto-migrate-php-code-via-configuration.html</link>
|
||
<pubDate>Wed, 28 Jun 2023 14:17:00 +0200</pubDate>
|
||
<guid isPermaLink="true">https://daniel-siepmann.de/auto-migrate-php-code-via-configuration.html</guid>
|
||
</item>
|
||
|
||
|
||
|
||
|
||
|
||
<item>
|
||
<title>Missing Motivation to Blog</title>
|
||
<description><![CDATA[I find motivation crucial. It is the most important energy that drives us. Everyone has different motivations, but it is essential to every one of us. I currently lack motivation to write new blog posts. And I decided to write down my thoughts why I lack motivation and what actually motivates me. Maybe that will help some of you to learn from it regarding your own motivation.
|
||
|
||
|
||
|
||
<a name="c585"></a>
|
||
<a name="whyIBlog"></a>
|
||
|
||
<h2>Why I blog <small><a href="#whyIBlog">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>I wouldn't blog if I'm not motivated. I currently believe you either need motivation or pressure in order to do things. Some might feel the urgent pressure to rescue our planet, others might feel the pressure to finish something in time. But some might find it motivating to help other people. I'd say motivation is the better reason to do things. And I don't have any pressure to publish blog post.</p><p>I blog because I feel motivated. I'm motivated to blog because it is a way to share my knowledge with others. It allows me to demonstrate things I've done and am proud of. That's the same as being at a user group or on a Bar Camp. All three allow me to share my knowledge with others.</p><p>But it wouldn't motivate me if it would be a one way. It motivates me because of the feedback. The feedback regarding blogging is very small. I can see the number of page views, but I don't know if it is actually useful for visitors. Unless someone contacts me and says thanks. That doesn't happen very frequently, leading to the feeling my blog posts aren't helpful, leading to the feeling this is wasted time, leading to lack of motivation.</p><p>The feedback during camps and user groups on the other hand is much more. At least one visitor of a session always says thanks or ask questions. Questions are an indicator that they find the session interesting. And some people also say thanks for one or the other blog posts, but they wouldn't write me, its just that they see me and think they will share their gratitude. That's very motivating.</p><p>Camps and user groups also allowed me to share what I've done since the last camp. Most of the time I couldn't wait until I could share the latest ideas and concepts and their outcome. Some people could follow my ideas and found them useful, which ended up in me writing a blog post to share with everyone else.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c586"></a>
|
||
<a name="whyILackMotivation"></a>
|
||
|
||
<h2>Why I lack motivation <small><a href="#whyILackMotivation">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>I've now covered what motivates me and why I blog. I'll now share why I currently lack the motivation.</p><p>I don't visit many bar camps any more and I don't visit any user group at all. That happened during the COVID-19 pandemic, for obvious reasons. But I also no longer visit them after the pandemic. I'm too lazy, the habits have changed. I'm now in a relationship, and it is more important to have time with my partner than to invest time in visiting camps or user groups. That leads to missing motivation, as most feedback came from personal talks. That also leads to missing exchange of knowledge and I no longer share what I've done.</p><p>I also didn't share my ideas any more. I noticed that during the TYPO3 Developer Days 2022 when Benni asked me what I've done in recent years. I couldn't answer that question, it felt like I've done boring daily business. But of course I've done the same daily business as before the pandemic, always questioning existing solutions and looking for something better. Developing new concepts and ideas and trying them out. There would have been so much to talk about and to share and blog. But my mood has changed due to not sharing my excitement with others for some years.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c587"></a>
|
||
<a name="whatYouCouldLearn"></a>
|
||
|
||
<h2>What you could learn <small><a href="#whatYouCouldLearn">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Every one of us needs motivation to pass the days, weeks, months, and years. Motivation is the positive influence that's keeping us alive. That's why I believe every one of us should find what motivates us and focus that in our lives. Motivation can change though so can our focus. We shouldn't feel bad because of that. Instead, we should become aware of our shift of motivation or focus and adjust our live.</p><p>My focus has shifted towards my current relationship and becoming more friendly to our planet. I can't use that as focus or motivation for my job, but for my live. It is currently a bit complicated to keep up the same level of motivation for my job. But the issue might not be the lack of motivation but the lack of self awareness of motivation. Sometimes we don't feel motivated because we are not aware of certain things. Things might become commonplace, like your job or relationship. But that doesn't mean they become less motivation, just you might not notice any more. We can train our self awareness for those kinds of things and become motivated again. That's what I'll try to do next. You might notice the outcome based on the upcoming or missing new blog posts.</p><p>I hope this blog post helps some of you to become motivated again. Feel free to contact me. And feel free to contact every one who motivates you or who helps you. People from your private live, family, partners, friends. As well as within your jobs like your boss or colleagues. And don't forget people like open source maintainers, writers, video producer people in your library, in your supermarket, and everyone else who makes your live better. I believe it doesn't harm our world and society to say “thank you” more often.</p><p>Thank you for reading this blog post.</p>
|
||
|
||
|
||
]]></description>
|
||
<link>https://daniel-siepmann.de/missing-motivation-to-blog.html</link>
|
||
<pubDate>Fri, 23 Jun 2023 09:43:00 +0200</pubDate>
|
||
<guid isPermaLink="true">https://daniel-siepmann.de/missing-motivation-to-blog.html</guid>
|
||
</item>
|
||
|
||
|
||
|
||
|
||
|
||
<item>
|
||
<title>TYPO3 Update</title>
|
||
<description><![CDATA[I've just made two updates from TYPO3 v8 to 11 LTS. As well as some updates from 11 LTS to 12 LTS. This post will be an explanation of how I did those updates in order to share my experience with you.
|
||
|
||
|
||
|
||
<a name="c657"></a>
|
||
<a name="updateVsUpgrade"></a>
|
||
|
||
<h2>Update vs Upgrade <small><a href="#updateVsUpgrade">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>People might have different definitions for “Upgrade” as well as “Update”. Here is my definition, in order to align expectation for this blog post. Both mean to update a software. E.g. update a TYPO3 installation from any version to any higher version. The goal is to keep the system running with all expected features and only minor necessary adjustments. The <strong>difference</strong> between an upgrade and an update is to also make use of new features and migrate existing code and features. E.g. make use of TYPO3 dashboard when updating to v10 or higher. Or migrate to use the new TYPO3 queuing when updating to TYPO3 v12 or later. This is mostly useful for very long running and feature heavy installations.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c583"></a>
|
||
<a name="properSetup"></a>
|
||
|
||
<h2>Proper Setup <small><a href="#properSetup">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>I first start with a proper setup. Some installations were maintained by other agencies, some might be set up some years ago. I always start with migrating the existing setup into our own current best practice setup.</p><ol><li>Create or migrate Git. Some installations might not provide a repository already.</li><li>Migrate foreign dependencies (e.g. from infrastructure of old agency) into mono repository, see my blog post <a href="/create-git-mono-repository.html">Create Git mono repository</a>.<br>This is done for all dependencies that are no longer maintained somewhere else, but crucial for the installation.</li><li>Get rid of foreign dependencies. Some setups might fetch sources from the infrastructure of the old agency. This could be circumvented already by step 2.</li><li>Ensure you have a locally running system. That way you can switch back and forth and compare the update with the old system.</li><li>Migrate to composer. Remove version constraints from project specific packages. Add a <code class>*</code> as version constraint. That way we don't need to adjust the constraints for upgrades over and over again. Still we define dependencies and loading order. See blog post <a href="/typo3-composer-best-practices.html">TYPO3 Composer Best Practices</a>.</li><li>Set up proper CI (=Continuous Integration).<br>This makes it easier to catch issues by applying static code analysis and prevent unnecessary merge conflicts by applying a CGL (=Coding Guide Line). Add proper linters within CI.</li><li>Add a CGL with proper tooling.</li><li>Add PHPStan with baseline.</li><li>Add linter for: TypoScript, XLIFF (Translation files), Yaml, PHP.</li></ol><p>That's the proper setup I need prior starting with the actual update.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c584"></a>
|
||
<a name="theUpdate"></a>
|
||
|
||
<h2>The update <small><a href="#theUpdate">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>I'm doing the following steps to update a TYPO3 installation:</p><ol><li>Install the target version.<ol><li>Therefore, I remove all installed files (e.g. typo3/sysext, typo3conf/ext, vendor, …) and <code class>composer.lock</code> file.</li><li>I then manually update <code class>composer.json</code>. It will include the target versions of all packages, TYPO3 system extensions and 3rd Party Extensions.</li></ol></li><li>Add <a href="https://packagist.org/packages/ssch/typo3-rector">ssch/typo3-rector</a> package and configure it.</li><li>Execute migration via rector.</li><li>Replace auto generated config.persistence.classes TypoScript configuration. Rector is unable to split this up, so we will do in next step.</li><li>Execute cache flush from command line and fix all issues by hand until this succeeds. We then should have a running TYPO3 install tool.</li><li>Remove <code class>extensionScannerIgnoreLine</code> and <code class>extensionScannerIgnoreFile</code> leftovers from previous upgrades. False positives from previous upgrades might be real matches for the new upgrade.</li><li>Open TYPO3 install tool and execute the various checks within the Upgrade module. Fix remaining by hand.</li><li>Create site configuration. (If updating from pre site handling versions)</li><li>Open backend and frontend and fix all remaining issues by hand.<br>Only open the backend and startpage, not all pages.<br>That gives a good feeling, and allows other team members to work in parallel.</li><li>Use automated tools to find and fix issues:<ul><li>Execute and investigate / fix new PHPStan issues</li><li>Execute and fix Unit Tests</li><li>Execute and fix Functional Tests</li></ul></li><li>Configure routing within site configuration to match original URLs.</li><li>Configure EXT:seo to create sitemaps for all detail views. (Not necessary, more like an upgrade)</li><li>Crawl original setup prior update for all URLs. You can read my basic approach in <a href="/check-urls-for-error-codes.html">a separate blog post</a>.</li><li>Crawl new setup after update for all URLs.</li><li>Compare output of both crawls and fix all remaining issues. I also wrote <a href="/basic-sourcecode-comparison-of-website.html">a separate blog post</a> describing my basic approach.</li><li>Manually check backend features. (Or cover with end to end tests, to benefit in the future)</li></ol>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c594"></a>
|
||
<a name="phpstanRector"></a>
|
||
|
||
<h2>PHPStan + rector <small><a href="#phpstanRector">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Why do we use PHPStan within all our projects? It allows us to prevent many stupid bugs. It forces us to write clean understandable PHP Code. It eases updates as rector uses PHPStan under the hood and only is as good as the understanding of the code by PHPStan. Furthermore, there is a huge ecosystem around PHPStan that can improve the benefits of PHPStan.</p><p>rector is used as we save a lot of time. Rector can do the following modifications to our PHP sourcecode:</p><ul><li>Migrate PHP source code to newer PHP versions, e.g. update our code base from PHP 7.4 to 8.2.</li><li>Migrate TYPO3 source code to newer TYPO3 versions, e.g. from TYPO3 8.7 to TYPO3 12.</li><li>Migrate code related to 3rd Party libraries, e.g. PHPUnit tests.</li><li>We often can configure additional rules to save further time (and share them upstream).</li></ul><p>We most often use the following packages related to updates:</p><ul><li><a href="https://packagist.org/packages/ssch/typo3-rector" title="Packagist page of the package">ssch/typo3-rector</a></li><li><a href="https://packagist.org/packages/phpstan/phpstan" title="Packagist page of the package">phpstan/phpstan</a></li><li><a href="https://packagist.org/packages/saschaegerer/phpstan-typo3" title="Packagist page of the package">saschaegerer/phpstan-typo3</a></li><li><a href="https://packagist.org/packages/phpstan/phpstan-phpunit" title="Packagist page of the package">phpstan/phpstan-phpunit</a></li></ul><p>I'll also share some learnings from using rector for updates:</p><ul><li>Do not use <code class>UP_TO_PHP_82</code> sets. Those include all the rules from the lowest version up to the defined version and will slow things down.<br>Instead use dedicated Versions, e.g. <code class>PHP_80</code>, <code class>PHP_81</code> and <code class>PHP_82</code> when updating from PHP 7.4 to 8.2. This will be much faster.<br>Same is true for TYPO3. And I highly recmmend that, as TYPO3 does not include all lower versions. E.g. using <code class>UP_TO_TYPO3_12</code> will only include V11 and V12, not earlier versions.</li><li>Use a separate commit to where you execute rector. That eases rebases as you can remove the previous result and re-run rector instead.</li><li>Write and share your own rules if useful, e.g. we created and shared some of our rules here: <a href="https://github.com/sabbelasichon/typo3-rector/issues/3936">https://github.com/sabbelasichon/typo3-rector/issues/3936</a> That also means you should probably check open issues before you start configuring own rules.</li></ul>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c658"></a>
|
||
<a name="easeUpdatesInTheFuture"></a>
|
||
|
||
<h2>Ease updates in the future <small><a href="#easeUpdatesInTheFuture">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>You sometimes make changes where you know they need adjustments once you update the system. E.g. you backport a certain feature and can remove the backport. There are some ways to make this easier. We did not settle for any of them yet but still play around with them:</p><p>Maintain a proper <strong>documentation</strong>. We did this for some of our public extensions. You can see an example at <a href="https://docs.typo3.org/p/werkraummedia/thuecat/2.1/en-us/Maintenance.html">https://docs.typo3.org/p/werkraummedia/thuecat/2.1/en-us/Maintenance.html</a>. There we document the changes which makes it easy to check the documentation during each update and process the information. Another way is to document inline <strong>within the code</strong>. You need a proper rule how to document in order to properly search code for those comments.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c659"></a>
|
||
<a name="acks"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Thanks to some people:</p><ul><li><a href="https://phpc.social/@cybersmog" title="His Mastodon profile">Peter Kraume</a> for reviewing the post</li><li>Justus Moroni for reviewing the post</li><li><a href="https://wue.social/@Xitnelat" title="His Mastodon profile">Julian Hofman</a> for noticing a wrong statement about UP_TO Constants regarding TYPO3, this was fixed. One can also use the <code class>TYPO3_12</code> instead of the <code class>UP_TO_</code> version.</li></ul><p>And thanks to companies I've done updates in that way:</p><ul><li><a href="https://jobs.reuter.de/" title="Their job portal">reuter.de</a> where I'm working on the same instance from v7 up to v12 (at time of writing)</li><li><a href="https://werkraum-media.de/" title="Their company website">werkraum_media</a> where I could do a lot of TYPO3 update on various versions.</li></ul>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c623"></a>
|
||
<a name="furtherReading"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<ul><li>Official TYPO3 Documentation on upgrading/updating: <a href="https://docs.typo3.org/Home/UpgradingTYPO3.html">https://docs.typo3.org/Home/UpgradingTYPO3.html</a></li><li>Official TYPO3 Documentation on Extension Scanner: <a href="https://docs.typo3.org/m/typo3/reference-coreapi/12.4/en-us/ExtensionArchitecture/HowTo/UpdateExtensions/ExtensionScanner.html#extension-scanner">https://docs.typo3.org/m/typo3/reference-coreapi/12.4/en-us/ExtensionArchitecture/HowTo/UpdateExtensions/ExtensionScanner.html#extension-scanner</a></li><li>Community driven rector extension for TYPO3 updates: <a href="https://packagist.org/packages/ssch/typo3-rector">https://packagist.org/packages/ssch/typo3-rector</a></li><li>Blog post on <a href="/create-git-mono-repository.html">how to create a monorepository</a></li><li>Blog post on <a href="/basic-sourcecode-comparison-of-website.html">how to compare output of TYPO3 frontend</a>. Alternative: <a href="https://sitediff.io/">https://sitediff.io</a></li><li>Blog post regarding <a href="/typo3-composer-best-practices.html">TYPO3 Composer Best Practices</a></li><li>PHPStan static code analyzer: <a href="https://phpstan.org/">https://phpstan.org</a></li></ul>
|
||
|
||
|
||
]]></description>
|
||
<link>https://daniel-siepmann.de/typo3-update.html</link>
|
||
<pubDate>Mon, 05 Jun 2023 11:02:00 +0200</pubDate>
|
||
<guid isPermaLink="true">https://daniel-siepmann.de/typo3-update.html</guid>
|
||
</item>
|
||
|
||
|
||
|
||
|
||
|
||
<item>
|
||
<title>TYPO3 show additional information in dropdown</title>
|
||
<description><![CDATA[TYPO3 provides an "Application Information" dropdown to show some information.
|
||
This dropdown can be extended with custom information, e.g. some API endpoints or currently deployed Git Commit Hash. This can ease debugging as you can easily check whether the system runs the newest version with all changes applied.
|
||
|
||
I did that for one of our customers and wanted to share how easy it is to add such information.
|
||
|
||
|
||
|
||
<a name="c574"></a>
|
||
<a name="ourGoal"></a>
|
||
|
||
<h2>Our Goal <small><a href="#ourGoal">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>The goal is to add a custom entry to the drop down as shown in i56. It will show the Git SVG Icon, the title “Git Commit” and the actual Git commit.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c577"></a>
|
||
<a name="image"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<figure id="i56">
|
||
<a href="/fileadmin/Blog_Post_Content/TYPO3_show_additional_information_in_dropdown/application-context.png" target="_blank"><img src="/fileadmin/Blog_Post_Content/TYPO3_show_additional_information_in_dropdown/application-context.png" width="431" height="494" alt="" /></a>
|
||
<figcaption>Figure i56: The application context dropdown of a production System. It has a new entry "Git Commit" with an Git SVG Icon in front and a Git commit hash as value.</figcaption>
|
||
</figure>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c575"></a>
|
||
<a name="addingEventlistener"></a>
|
||
|
||
<h2>Adding EventListener <small><a href="#addingEventlistener">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>We will need to register a new event listener via <code>Services.yaml</code> of our Extension:</p><pre><code>services:
|
||
Vendor\ExtName\EventHandler\SystemInformationToolbarCollectorHandler:
|
||
tags:
|
||
- name: <span class="string">'event.listener'</span>
|
||
<span class="attr">event</span>: <span class="string">'TYPO3\CMS\Backend\Backend\Event\SystemInformationToolbarCollectorEvent'</span></code></pre><p>That will ensure our own class <code>Vendor\ExtName\EventHandler\SystemInformationToolbarCollectorHandler </code>is called for the corresponding event.</p><p>The class itself needs to implement the PHP magic method <code>__invoke()</code>. That method is called for the event, as we didn't provide a dedicated method when registering our event listener. The class itself can look like:</p><pre><code><span class="php"><span class="meta"><?php</span>
|
||
|
||
<span class="keyword">namespace</span> <span class="title">Vendor</span>\<span class="title">ExtName</span>\<span class="title">EventHandler</span>;
|
||
|
||
<span class="keyword">use</span> <span class="title">TYPO3</span>\<span class="title">CMS</span>\<span class="title">Backend</span>\<span class="title">Backend</span>\<span class="title">Event</span>\<span class="title">SystemInformationToolbarCollectorEvent</span>;
|
||
<span class="keyword">use</span> <span class="title">TYPO3</span>\<span class="title">CMS</span>\<span class="title">Backend</span>\<span class="title">Backend</span>\<span class="title">ToolbarItems</span>\<span class="title">SystemInformationToolbarItem</span>;
|
||
<span class="keyword">use</span> <span class="title">TYPO3</span>\<span class="title">CMS</span>\<span class="title">Backend</span>\<span class="title">Toolbar</span>\<span class="title">Enumeration</span>\<span class="title">InformationStatus</span>;
|
||
<span class="keyword">use</span> <span class="title">TYPO3</span>\<span class="title">CMS</span>\<span class="title">Core</span>\<span class="title">Core</span>\<span class="title">Environment</span>;
|
||
|
||
<span class="class"><span class="keyword">class</span> <span class="title">SystemInformationToolbarCollectorHandler</span>
|
||
</span>{
|
||
<span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">__invoke</span><span class="params">(SystemInformationToolbarCollectorEvent $event)</span>: <span class="title">void</span>
|
||
</span>{
|
||
$status = <span class="string">'unknown'</span>;
|
||
$content = file_get_contents(Environment::getProjectPath() . DIRECTORY_SEPARATOR . <span class="string">'REVISION'</span>);
|
||
<span class="keyword">if</span> (is_string($content)) {
|
||
$status = mb_substr($content, <span class="number">0</span>, <span class="number">7</span>);
|
||
}
|
||
|
||
$event->getToolbarItem()->addSystemInformation(
|
||
<span class="string">'Git Commit'</span>,
|
||
$status,
|
||
<span class="string">'actions-brand-git'</span>,
|
||
$status === <span class="string">'unknown'</span> ? InformationStatus::STATUS_WARNING : InformationStatus::STATUS_NOTICE
|
||
);
|
||
}
|
||
}</span></code></pre><p>It will read the contents of the file REVISION from project (not web) root. That way the deployment can create the file and add the commit hash as content. We will strip down to first 7 characters of the hash. That's usually enough to be unique within a project.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c576"></a>
|
||
<a name="furtherReading"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<ul><li>Documentation of Event: <a href="https://docs.typo3.org/m/typo3/reference-coreapi/11.5/en-us/ApiOverview/Events/Events/Backend/SystemInformationToolbarCollectorEvent.html">https://docs.typo3.org/m/typo3/reference-coreapi/11.5/en-us/ApiOverview/Events/Events/Backend/SystemInformationToolbarCollectorEvent.html</a></li><li>Documentation of PSR-14 (Event Dispatcher): <a href="https://docs.typo3.org/m/typo3/reference-coreapi/11.5/en-us/ApiOverview/Events/EventDispatcher/Index.html">https://docs.typo3.org/m/typo3/reference-coreapi/11.5/en-us/ApiOverview/Events/EventDispatcher/Index.html</a></li><li>Documentation of PHP magic method <code class>__invoke()</code>: <a href="https://www.php.net/manual/en/language.oop5.magic.php#object.invoke">https://www.php.net/manual/en/language.oop5.magic.php#object.invoke</a></li></ul>
|
||
|
||
|
||
]]></description>
|
||
<link>https://daniel-siepmann.de/typo3-show-additional-information-in-dropdown.html</link>
|
||
<pubDate>Tue, 17 Jan 2023 08:08:00 +0100</pubDate>
|
||
<guid isPermaLink="true">https://daniel-siepmann.de/typo3-show-additional-information-in-dropdown.html</guid>
|
||
</item>
|
||
|
||
|
||
|
||
|
||
|
||
<item>
|
||
<title>TYPO3 Fediverse Accounts</title>
|
||
<description><![CDATA[I'm a user of the Fediverse. But TYPO3 is not yet there. I've created multiple accounts that mirror feeds to you can follow them within the Fediverse.
|
||
|
||
|
||
|
||
<a name="c569"></a>
|
||
<a name="usingTheAccounts"></a>
|
||
|
||
<h2>Using the accounts <small><a href="#usingTheAccounts">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>You can either follow the different accounts with your own Fediverse account. Each account also is available as an Atom Feed which you can add to your feed reader. You can also check each accounts contacts and add the RSS feeds they mirror to your reader.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c568"></a>
|
||
<a name="typo3NewsAccount"></a>
|
||
|
||
<h2>TYPO3 News Account <small><a href="#typo3NewsAccount">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>The first and probably most important account is TYPO3 News which is available at <a href="https://friendica.daniel-siepmann.de/profile/typo3news/.">https://friendica.daniel-siepmann.de/profile/typo3news</a>. You can see the mirrored feeds within the contacts page at <a href="https://friendica.daniel-siepmann.de/profile/typo3news/contacts">https://friendica.daniel-siepmann.de/profile/typo3news/contacts</a>.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c578"></a>
|
||
<a name="typo3SecurityAdvisoriesAccount"></a>
|
||
|
||
<h2>TYPO3 Security Advisories Account <small><a href="#typo3SecurityAdvisoriesAccount">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>TYPO3 Security Advisories is available at <a href="https://friendica.daniel-siepmann.de/profile/typo3securityadvisories/.">https://friendica.daniel-siepmann.de/profile/typo3securityadvisories</a>. You can see the mirrored feeds within the contacts page at <a href="https://friendica.daniel-siepmann.de/profile/typo3securityadvisories/contacts">https://friendica.daniel-siepmann.de/profile/typo3securityadvisories/contacts</a>. It will mirror the RSS feed of the official TYPO3 Security Advisories.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c570"></a>
|
||
<a name="typo3CommitsAccount"></a>
|
||
|
||
<h2>TYPO3 Commits Account <small><a href="#typo3CommitsAccount">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Another accounts mirrors each TYPO3 commit made to the main branch. The account is available here: <a href="https://friendica.daniel-siepmann.de/profile/typo3commits">https://friendica.daniel-siepmann.de/profile/typo3commits</a>.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c624"></a>
|
||
<a name="typo3Typo3IssuesAccount"></a>
|
||
|
||
<h2>TYPO3 TYPO3 Issues Account <small><a href="#typo3Typo3IssuesAccount">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Another accounts mirrors each TYPO3 issue created at forge.typo3.org. The account is available here: <a href="https://friendica.daniel-siepmann.de/profile/typo3issues">https://friendica.daniel-siepmann.de/profile/typo3issues</a>.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c571"></a>
|
||
<a name="typo3BlogsAccount"></a>
|
||
|
||
<h2>TYPO3 Blogs Account <small><a href="#typo3BlogsAccount">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>And there is an account mirror different community blog feeds. The account is available at <a href="https://friendica.daniel-siepmann.de/profile/typo3blogs">https://friendica.daniel-siepmann.de/profile/typo3blogs</a> and you can check all the blog feeds at <a href="https://friendica.daniel-siepmann.de/profile/typo3blogs/contacts">https://friendica.daniel-siepmann.de/profile/typo3blogs/contacts</a>.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c572"></a>
|
||
<a name="typo3VideosAccount"></a>
|
||
|
||
<h2>TYPO3 Videos Account <small><a href="#typo3VideosAccount">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>There is also an account mirroring feeds with Videos, e.g. TYPO3 YouTube Channel. The account is available here: <a href="https://friendica.daniel-siepmann.de/profile/typo3videos">https://friendica.daniel-siepmann.de/profile/typo3videos</a>.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c573"></a>
|
||
<a name="typo3DecisionsAndTalkAccounts"></a>
|
||
|
||
<h2>TYPO3 Decisions and Talk Accounts <small><a href="#typo3DecisionsAndTalkAccounts">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>One account mirrors each new TYPO3 decisions topic from <a href="https://decisions.typo3.org/latest">https://decisions.typo3.org/latest</a> and is available at <a href="https://friendica.daniel-siepmann.de/profile/typo3decisions/.">https://friendica.daniel-siepmann.de/profile/typo3decisions/.</a></p><p>The other mirrors each new TYPO3 talk topic from <a href="https://talk.typo3.org/">https://talk.typo3.org/</a> and is available at <a href="https://friendica.daniel-siepmann.de/profile/typo3talk/">https://friendica.daniel-siepmann.de/profile/typo3talk/</a>.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c595"></a>
|
||
<a name="typo3LemmyForum"></a>
|
||
|
||
<h2>TYPO3 (lemmy) Forum <small><a href="#typo3LemmyForum">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>This one isn't provided by myself, but <a href="https://lemmy.ml/u/matengor">Matengor</a>. A forum is more or less the same concept as Reddit has. You can find the forum at <a href="https://lemmy.ml/c/typo3">https://lemmy.ml/c/typo3</a>. Thanks to Matengor for providing the forum. You don't need an account there, as lemmy is part of the Fediverse.You can follow the forum as you can follow other user accounts. And you can post to the Forum as you can post to a user.</p>
|
||
|
||
|
||
]]></description>
|
||
<link>https://daniel-siepmann.de/typo3-fediverse-news-accounts.html</link>
|
||
<pubDate>Sun, 08 Jan 2023 16:17:00 +0100</pubDate>
|
||
<guid isPermaLink="true">https://daniel-siepmann.de/typo3-fediverse-news-accounts.html</guid>
|
||
</item>
|
||
|
||
|
||
|
||
|
||
|
||
<item>
|
||
<title>TYPO3 RTE for Input Fields</title>
|
||
<description><![CDATA[Sometimes a headline needs to use line breaks or formatting like italic words. Some integrators define placeholders like [BR] which get substituted. We use TYPO3 native RTE functionality with stripped down configuration. This small post describes how we set it up. The goal is to have a small field and features like limited number of characters and reduced formatting possibilities.
|
||
|
||
|
||
|
||
<a name="c551"></a>
|
||
<a name="theProblem"></a>
|
||
|
||
<h2>The Problem <small><a href="#theProblem">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Sometimes editors need to insert line breaks <code><br></code> or special characters like ⓒ and a reduced set of formatting like <sup>superscript</sup> or <em>italic</em> within headlines or other input fields of TYPO3. This might be necessary because a headline references a product or company.</p><p>But that's not possible with TYPO3 native input fields. Editors might not be aware of system features to insert special characters and even then they won't be able to insert line breaks or formatting within those fields.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c552"></a>
|
||
<a name="existingApproaches"></a>
|
||
|
||
<h2>Existing Approaches <small><a href="#existingApproaches">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Some approach we've seen so far:</p><p>Provide a documentation with special characters for copy and paste.<br> We don't like this approach as editors need to switch between TYPO3 and another system holding the documentation for copy and paste which itself is not a nice workflow.</p><p>Provide special sequences like <code>[BR]</code> (good old BB-Code style 😉). Those become replaced via TypoScript.<br> We don't like this as well as you need to document those sequences. And they are more or less HTML in the end, just different braces. One big goal of most CMS is to remove the need for editors to write HTML.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c553"></a>
|
||
<a name="ourApproach"></a>
|
||
|
||
<h2>Our Approach <small><a href="#ourApproach">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>We thought we could use a very reduced RTE configuration for those fields. The goal would be to have a small input field instead of a huge text area. Furthermore, only the necessary formatting should be provided, resulting in a very small toolbar. That would allow an already known way to format text and insert special characters. Editors won't need to learn something new.</p><p>TYPO3 offers the option <code>rows</code> for <code><textarea></code> inputs to define how large an input should be. Furthermore, TYPO3 provides a <code>max</code> option for <code>input</code> to limit the number of characters. That's important to not lose input after persisting to database. Both should be respected as well. Allowing integrators and developers to use existing approaches and documented options.</p><p>TYPO3 by default processes the input when storing and retrieving from database. It for example will wrap lines with <code><p></code> tags. We don't want that, as it should be handled like a normal input field.</p><p>We use a dedicated RTE configuration and a small EventListener. The configuration will provide the wordcount plugin, adjust the processing of the input and define which formatting options should be available. The Listener should connect the TCA configuration for <code>rows</code> and <code>max</code> to the RTE, by adjusting the configuration.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c554"></a>
|
||
<a name="theConfiguration"></a>
|
||
|
||
<h2>The Configuration <small><a href="#theConfiguration">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>The configuration of <a href="/typo3-rte-for-input-fields.html#i52">figure i52</a> looks like this:</p><pre><code>imports:
|
||
- {resource: <span class="string">"EXT:rte_ckeditor/Configuration/RTE/Editor/Base.yaml"</span>}
|
||
- {resource: <span class="string">"EXT:e2_core/Configuration/RTE/Editor/SpecialChars.yaml"</span>}
|
||
|
||
editor:
|
||
config:
|
||
allowedContent: <span class="keyword">true</span>
|
||
enableContextMenu: <span class="keyword">false</span>
|
||
forcePasteAsPlainText: <span class="keyword">true</span>
|
||
clipboard_defaultContentType: <span class="string">'text'</span>
|
||
|
||
toolbar:
|
||
- [<span class="string">'Subscript'</span>, <span class="string">'Superscript'</span>, <span class="string">'-'</span>, <span class="string">'SpecialChar'</span>]
|
||
|
||
wordcount:
|
||
showRemaining: <span class="keyword">true</span>
|
||
showParagraphs: <span class="keyword">false</span>
|
||
showWordCount: <span class="keyword">false</span>
|
||
showCharCount: <span class="keyword">true</span>
|
||
<span class="comment"># Filled by Event based on TCA configuration</span>
|
||
<span class="comment"># maxCharCount: 255</span>
|
||
|
||
autoParagraph: <span class="keyword">false</span>
|
||
enterMode: <span class="number">2</span> <span class="comment"># <br> instead of <p></span>
|
||
|
||
extraPlugins:
|
||
- wordcount
|
||
- specialchar
|
||
|
||
removePlugins:
|
||
- resize
|
||
- autogrow
|
||
|
||
processing:
|
||
overruleMode: nothing
|
||
allowTags:
|
||
- sub
|
||
- sup
|
||
- br</code></pre><p>This removes some unwanted features like autogrow, manual resize and context menu.</p><p>It also only allows the expected formatting and tags. Furthermore, it configures that line breaks instead of paragraphs should be inserted.</p><p>It also imports a global configuration defining the actual special characters needed by editors in this system. TYPO3 v12 will update to CKEditor 5 where special characters UI becomes event better, see: <a href="https://ckeditor.com/docs/ckeditor5/latest/features/special-characters.html">https://ckeditor.com/docs/ckeditor5/latest/features/special-characters.html</a>.</p><p>Wordcount is also pre-configured. It will behave like max of input fields and prevent further characters exceeding the maximum count. The count itself is not defined but will be inserted via EventListener based on TCA configuration. One could pre-configure a value within the configuration which can be overwritten via TCA.</p><p>The <code>overruleMode: nothing</code> blocks TYPO3 default processing of HTML while inserting and reading from database. This blocks wrapping within <code><p></code> tags.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c555"></a>
|
||
<a name="theEventListener"></a>
|
||
|
||
<h2>The Event Listener <small><a href="#theEventListener">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>The Listener looks like this:</p><pre><code><span class="php"><span class="meta"><?php</span>
|
||
|
||
<span class="keyword">declare</span>(strict_types=<span class="number">1</span>);
|
||
|
||
<span class="keyword">namespace</span> <span class="title">E2</span>\<span class="title">E2Core</span>\<span class="title">EventListener</span>;
|
||
|
||
<span class="keyword">use</span> <span class="title">TYPO3</span>\<span class="title">CMS</span>\<span class="title">RteCKEditor</span>\<span class="title">Form</span>\<span class="title">Element</span>\<span class="title">Event</span>\<span class="title">AfterPrepareConfigurationForEditorEvent</span>;
|
||
|
||
<span class="comment">/**
|
||
* Transports some of the TCA features into RTE.
|
||
*
|
||
* RTE can limit input based on a count, just like TCA 'max' property.
|
||
*/</span>
|
||
<span class="class"><span class="keyword">class</span> <span class="title">TcaToRteConfiguration</span>
|
||
</span>{
|
||
<span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">__invoke</span><span class="params">(AfterPrepareConfigurationForEditorEvent $event)</span>: <span class="title">void</span>
|
||
</span>{
|
||
$rteConfiguration = $event->getConfiguration();
|
||
$fieldConfiguration = $event->getData()[<span class="string">'parameterArray'</span>][<span class="string">'fieldConf'</span>][<span class="string">'config'</span>] ?? [];
|
||
|
||
$rteConfiguration = <span class="keyword">$this</span>->setRows($rteConfiguration, $fieldConfiguration);
|
||
$rteConfiguration = <span class="keyword">$this</span>->setMax($rteConfiguration, $fieldConfiguration);
|
||
|
||
$event->setConfiguration($rteConfiguration);
|
||
}
|
||
|
||
<span class="keyword">private</span> <span class="function"><span class="keyword">function</span> <span class="title">setRows</span><span class="params">(array $rteConfiguration, array $fieldConfiguration)</span>: <span class="title">array</span>
|
||
</span>{
|
||
$rows = $fieldConfiguration[<span class="string">'rows'</span>] ?? <span class="number">0</span>;
|
||
|
||
<span class="keyword">if</span> ($rows > <span class="number">0</span>) {
|
||
$rteConfiguration[<span class="string">'height'</span>] = ($rows * <span class="number">6</span>) . <span class="string">'rem'</span>;
|
||
}
|
||
|
||
<span class="keyword">return</span> $rteConfiguration;
|
||
}
|
||
|
||
<span class="keyword">private</span> <span class="function"><span class="keyword">function</span> <span class="title">setMax</span><span class="params">(array $rteConfiguration, array $fieldConfiguration)</span>: <span class="title">array</span>
|
||
</span>{
|
||
$max = $fieldConfiguration[<span class="string">'max'</span>] ?? <span class="number">0</span>;
|
||
|
||
<span class="keyword">if</span> ($max > <span class="number">0</span>) {
|
||
$rteConfiguration[<span class="string">'wordcount'</span>][<span class="string">'maxCharCount'</span>] = (int) $max;
|
||
}
|
||
|
||
<span class="keyword">return</span> $rteConfiguration;
|
||
}
|
||
}</span></code></pre><p>And is registered like this (within <code>Services.yaml</code> of the extension):</p><pre><code>services:
|
||
_defaults:
|
||
autowire: <span class="keyword">true</span>
|
||
autoconfigure: <span class="keyword">true</span>
|
||
<span class="keyword">public</span>: <span class="keyword">false</span>
|
||
|
||
E2\E2Core\:
|
||
resource: <span class="string">'../Classes/*'</span>
|
||
|
||
E2\E2Core\EventListener\TcaToRteConfiguration:
|
||
tags:
|
||
- name: <span class="string">'event.listener'</span>
|
||
event: <span class="string">'TYPO3\CMS\RteCKEditor\Form\Element\Event\AfterPrepareConfigurationForEditorEvent'</span></code></pre>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c557"></a>
|
||
<a name="combiningThePieces"></a>
|
||
|
||
<h2>Combining the pieces <small><a href="#combiningThePieces">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>We need to properly configure our TCA column to use the dedicated configuration. The column looks like this:</p><pre><code><span class="string">'tx_e2core_link_label_label'</span> => [
|
||
<span class="string">'label'</span> => $languagePath . <span class="string">'tx_e2core_link_label_label'</span>,
|
||
<span class="string">'config'</span> => [
|
||
<span class="string">'type'</span> => <span class="string">'text'</span>,
|
||
<span class="string">'eval'</span> => <span class="string">'required,trim'</span>,
|
||
<span class="string">'max'</span> => <span class="number">255</span>,
|
||
<span class="string">'rows'</span> => <span class="number">1</span>,
|
||
<span class="string">'enableRichtext'</span> => <span class="keyword">true</span>,
|
||
<span class="string">'richtextConfiguration'</span> => <span class="string">'minimal-input-field'</span>,
|
||
],
|
||
],</code></pre><p>This converts our input to a text field. It is still required and gets trimmed. The <code>max</code> and <code>rows</code> are respected via above EventListener. The last two turn the field into an RTE with our dedicated configuration.</p><p>The RTE configuration itself is registered within <code>ext_localconf.php</code>:</p><pre><code>\TYPO3\CMS\Core\Utility\ArrayUtility::mergeRecursiveWithOverrule(
|
||
$GLOBALS[<span class="string">'TYPO3_CONF_VARS'</span>],
|
||
[
|
||
<span class="string">'RTE'</span> => [
|
||
<span class="string">'Presets'</span> => [
|
||
<span class="string">'default'</span> => <span class="string">'EXT:e2_core/Configuration/RTE/Editor/Custom.yaml'</span>,
|
||
<span class="string">'minimal-input-field'</span> => <span class="string">'EXT:e2_core/Configuration/RTE/Editor/MinimalInputField.yaml'</span>,
|
||
],
|
||
],
|
||
]
|
||
);</code></pre><p>We prefer the merge way as we don't have different ways to configure the <code>$GLOBALS['TYPO3_CONF_VARS']</code>. It is always this way within <code>AdditionalConfiguration.php</code>, <code>LocalConfiguration.php</code> and <code>ext_localconf.php</code>. That allows to copy and paste without adjusting the code.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c556"></a>
|
||
<a name="ourTakeaway"></a>
|
||
|
||
<h2>Our Takeaway <small><a href="#ourTakeaway">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>We first thought there must be an extension or we should build an extension. That one should provide a new <code>renderType</code> for TCA <code>input</code> columns. We knew many others have the same issue and one of them should have already done that.<br> But we didn't find an extension and thought there should be an easier way and came up with our solution. That one is so flexible and easy to integrate that it doesn't make sense to provide an extension but a blog post. Every installation and even some fields have very special needs which now can be covered with different configurations.</p><p>We love flexible and simple solutions as they are the best solutions most of the time. To us, it is important to overthink solutions and approaches from the past. It is also critical to not get driven by technology hype or by the feeling to release another extension. Find a proper solution for the actual problem, ignoring other influences from the outside. It is still possible to share those solutions. Either as blog post, video or a small gist or some other way.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c549"></a>
|
||
<a name="acks"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Thanks to my co-worker <a href="https://justusmoroni.com">Justus Moroni</a> who once more helped to provide a good solution for a problem.</p><p>Thanks to our customer <a href="https://www.reuter.de/">reuter.de</a> who allows us to re-think existing solutions in order to improve editor UX.</p><p>Thanks to <a href="https://www.codappix.com/">Codappix GmbH</a> for allowing me to share our solutions on my own blog during working hours.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c550"></a>
|
||
<a name="furtherReading"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<ul> <li><a href="https://docs.typo3.org/c/typo3/cms-rte-ckeditor/main/en-us/Configuration/ConfigureTypo3.html">https://docs.typo3.org/c/typo3/cms-rte-ckeditor/main/en-us/Configuration/ConfigureTypo3.html</a> - General CKEditor documentation for TYPO3</li> <li><a href="https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_config.html">https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_config.html</a> - All configuration options of CKEditor</li> <li><a href="https://github.com/w8tcha/CKEditor-WordCount-Plugin">https://github.com/w8tcha/CKEditor-WordCount-Plugin</a> - Configuration of WordCount plugin for CKEditor</li> <li><a href="https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/Events/EventDispatcher/Index.html">https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/Events/EventDispatcher/Index.html</a> - Event Dispatcher / PSR-14 Events documentation for TYPO3</li> </ul>
|
||
|
||
|
||
]]></description>
|
||
<link>https://daniel-siepmann.de/typo3-rte-for-input-fields.html</link>
|
||
<pubDate>Tue, 08 Nov 2022 00:00:00 +0100</pubDate>
|
||
<guid isPermaLink="true">https://daniel-siepmann.de/typo3-rte-for-input-fields.html</guid>
|
||
</item>
|
||
|
||
|
||
|
||
|
||
|
||
<item>
|
||
<title>Use supported PHP Versions</title>
|
||
<description><![CDATA[I thought it is fine to use older no longer supported PHP versions as long as they are supported by the OS (e.g. Debian, Ubuntu, etc.). I've changed my mind after reading blog posts and noticing a change within PHP ecosystem.
|
||
|
||
Let me explain why I changed my mind and why you should prefer official supported versions instead. This not only is true for PHP but any software.
|
||
|
||
|
||
|
||
<a name="c538"></a>
|
||
<a name="howIUsedToThink"></a>
|
||
|
||
<h2>How I used to think <small><a href="#howIUsedToThink">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>I always thought it is fine to use outdated versions as long as they are covered by my operating system like Ubuntu. I thought the operating system will invest time to keep those versions secure. I didn't mind to run web projects on older versions and to provide TYPO3 extensions or composer packages for outdated versions.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c539"></a>
|
||
<a name="sourcesThatProofMeWrong"></a>
|
||
|
||
<h2>Sources that proof me wrong <small><a href="#sourcesThatProofMeWrong">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>I then discovered those sources:</p><ul> <li><a href="https://unixsheikh.com/articles/the-delusions-of-debian.html">https://unixsheikh.com/articles/the-delusions-of-debian.html</a></li> <li><a href="https://www.unixsheikh.com/articles/linux-distribution-long-term-support-might-not-be-what-you-think-it-is.html">https://www.unixsheikh.com/articles/linux-distribution-long-term-support-might-not-be-what-you-think-it-is.html</a></li> <li><a href="https://repology.org/repositories/statistics">https://repology.org/repositories/statistics</a></li> <li><a href="https://wiki.debian.org/PHP#Notes_on_PHP_and_security">https://wiki.debian.org/PHP#Notes_on_PHP_and_security</a></li> </ul><p>It also feels like the PHP ecosystem is changing. Libraries and tools are only covering official supported PHP versions as listed here: <a href="https://www.php.net/supported-versions.php">https://www.php.net/supported-versions.php</a>.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c540"></a>
|
||
<a name="myOutcome"></a>
|
||
|
||
<h2>My outcome <small><a href="#myOutcome">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>This leads to issues if you stick to older PHP versions. You might lack security updates as they take some extra time. You might not be able to use some new library or latest versions of libraries including features and fixes.</p><p>Furthermore, you create technical debt as you might need to update to newer PHP versions anyway, but with larger code bases in case you keep adding lines of code.</p><p>There are tools like rector (<a href="https://getrector.org/">https://getrector.org/</a>) which allow you to upgrade your code base to support newer PHP versions nowadays. Also tools like PHPStan (<a href="https://phpstan.org/">https://phpstan.org/</a>) allow you to check compatibility with PHP versions. It is not as hard to update as it was some years ago.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c541"></a>
|
||
<a name="furtherReading"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>I recommend checking my Reddit post regarding this blog post, so you get further insights and opinions: <a href="https://www.reddit.com/r/PHP/comments/wumtvm/why_legacy_php_versions_maintained_by_os_might/.">https://www.reddit.com/r/PHP/comments/wumtvm/why_legacy_php_versions_maintained_by_os_might/</a>.</p><p>You might also be interested in the following blog post: <a href="https://php.watch/articles/extend-lifetime-legacy-php">https://php.watch/articles/extend-lifetime-legacy-php</a>.</p>
|
||
|
||
|
||
]]></description>
|
||
<link>https://daniel-siepmann.de/use-supported-php-versions.html</link>
|
||
<pubDate>Mon, 22 Aug 2022 00:00:00 +0200</pubDate>
|
||
<guid isPermaLink="true">https://daniel-siepmann.de/use-supported-php-versions.html</guid>
|
||
</item>
|
||
|
||
|
||
|
||
|
||
|
||
<item>
|
||
<title>Mock Guzzle Requests in Functional Tests</title>
|
||
<description><![CDATA[Functional tests won't mock single implementations. But code often interacts with external systems by sending requests, e.g. via Guzzle. Guzzle is the default library used within TYPO3, so you might need to mock the requests within functional tests.
|
||
|
||
This blog post explains our solution which is based on Guzzle Docs and Susis Blog post on how to mock Guzzle Client implementation within Unit Tests.
|
||
|
||
|
||
|
||
<a name="c537"></a>
|
||
<a name="theIdea"></a>
|
||
|
||
<h2>The idea <small><a href="#theIdea">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Connect the existing pieces. Susis <a href="https://susi.dev/mock-http-api-responses-with-guzzle-psr-18-psr-7/">blog post</a> explains how to mock responses already. We only need to adapt for functional tests where we can't easily replace the concrete instance of the Guzzle Client.</p><p>This would work if TYPO3 would allow to load a different <code>Services.yaml</code> for Testing context, just like Symfony. But TYPO3 allows us to add handlers to Guzzle via configuration of <code>$GLOBALS['TYPO3_CONF_VARS']['HTTP']['handler']</code>.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c535"></a>
|
||
<a name="theImplementation"></a>
|
||
|
||
<h2>The implementation <small><a href="#theImplementation">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>We created a new PHP Class within the Test namespace. This class provides the API to register itself, to clean things up and to mock the responses:</p><pre><code><span class="php"><span class="meta"><?php</span>
|
||
|
||
<span class="keyword">declare</span>(strict_types=<span class="number">1</span>);
|
||
|
||
<span class="keyword">namespace</span> <span class="title">Codappix</span>\<span class="title">ExampleExtension</span>\<span class="title">Tests</span>\<span class="title">Functional</span>;
|
||
|
||
<span class="keyword">use</span> <span class="title">GuzzleHttp</span>\<span class="title">Handler</span>\<span class="title">MockHandler</span>;
|
||
<span class="keyword">use</span> <span class="title">GuzzleHttp</span>\<span class="title">Psr7</span>\<span class="title">Response</span>;
|
||
<span class="keyword">use</span> <span class="title">Symfony</span>\<span class="title">Component</span>\<span class="title">HttpFoundation</span>\<span class="title">Response</span> <span class="title">as</span> <span class="title">SymfonyResponse</span>;
|
||
|
||
<span class="class"><span class="keyword">class</span> <span class="title">GuzzleClientFaker</span>
|
||
</span>{
|
||
<span class="comment">/**
|
||
* <span class="doctag">@var</span> MockHandler
|
||
*/</span>
|
||
<span class="keyword">private</span> <span class="keyword">static</span> $mockHandler;
|
||
|
||
<span class="keyword">public</span> <span class="keyword">static</span> <span class="function"><span class="keyword">function</span> <span class="title">registerClient</span><span class="params">()</span>: <span class="title">void</span>
|
||
</span>{
|
||
$GLOBALS[<span class="string">'TYPO3_CONF_VARS'</span>][<span class="string">'HTTP'</span>][<span class="string">'handler'</span>][<span class="string">'faker'</span>] = <span class="function"><span class="keyword">function</span> <span class="params">(callable $handler)</span> </span>{
|
||
<span class="keyword">return</span> <span class="keyword">self</span>::getMockHandler();
|
||
};
|
||
|
||
}
|
||
|
||
<span class="comment">/**
|
||
* Cleans things up, call it in tests tearDown() method.
|
||
*/</span>
|
||
<span class="keyword">public</span> <span class="keyword">static</span> <span class="function"><span class="keyword">function</span> <span class="title">tearDown</span><span class="params">()</span>: <span class="title">void</span>
|
||
</span>{
|
||
<span class="keyword">unset</span>($GLOBALS[<span class="string">'TYPO3_CONF_VARS'</span>][<span class="string">'HTTP'</span>][<span class="string">'handler'</span>][<span class="string">'faker'</span>]);
|
||
}
|
||
|
||
<span class="comment">/**
|
||
* Adds a new response to the stack with defaults, returning the file contents of given file.
|
||
*/</span>
|
||
<span class="keyword">public</span> <span class="keyword">static</span> <span class="function"><span class="keyword">function</span> <span class="title">appendResponseFromFile</span><span class="params">(string $fileName)</span>: <span class="title">void</span>
|
||
</span>{
|
||
$fileContent = file_get_contents($fileName);
|
||
<span class="keyword">if</span> ($fileContent === <span class="keyword">false</span>) {
|
||
<span class="keyword">throw</span> <span class="keyword">new</span> \<span class="keyword">Exception</span>(<span class="string">'Could not load file: '</span> . $fileName, <span class="number">1656485162</span>);
|
||
}
|
||
|
||
<span class="keyword">self</span>::appendResponseFromContent($fileContent);
|
||
}
|
||
|
||
<span class="keyword">private</span> <span class="keyword">static</span> <span class="function"><span class="keyword">function</span> <span class="title">appendResponseFromContent</span><span class="params">(string $content)</span>: <span class="title">void</span>
|
||
</span>{
|
||
<span class="keyword">self</span>::appendResponse(<span class="keyword">new</span> Response(
|
||
SymfonyResponse::HTTP_OK,
|
||
[],
|
||
$content
|
||
));
|
||
}
|
||
|
||
<span class="keyword">private</span> <span class="keyword">static</span> <span class="function"><span class="keyword">function</span> <span class="title">getMockHandler</span><span class="params">()</span>: <span class="title">MockHandler</span>
|
||
</span>{
|
||
<span class="keyword">if</span> (!<span class="keyword">self</span>::$mockHandler <span class="keyword">instanceof</span> MockHandler) {
|
||
<span class="keyword">self</span>::$mockHandler = <span class="keyword">new</span> MockHandler();
|
||
}
|
||
|
||
<span class="keyword">return</span> <span class="keyword">self</span>::$mockHandler;
|
||
}
|
||
|
||
<span class="keyword">private</span> <span class="keyword">static</span> <span class="function"><span class="keyword">function</span> <span class="title">appendResponse</span><span class="params">(Response $response)</span>: <span class="title">void</span>
|
||
</span>{
|
||
<span class="keyword">self</span>::getMockHandler()->append($response);
|
||
}
|
||
}</span></code></pre>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c536"></a>
|
||
<a name="howToUse"></a>
|
||
|
||
<h2>How to use <small><a href="#howToUse">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>The new class can be used within tests:</p><pre><code><span class="meta"><?php</span>
|
||
|
||
<span class="keyword">namespace</span> <span class="title">Codappix</span>\<span class="title">ExampleExtension</span>\<span class="title">Tests</span>\<span class="title">Functional</span>;
|
||
|
||
<span class="keyword">use</span> <span class="title">Codappix</span>\<span class="title">ExampleExtension</span>\<span class="title">Command</span>\<span class="title">Import</span>;
|
||
<span class="keyword">use</span> <span class="title">Codappix</span>\<span class="title">ExampleExtension</span>\<span class="title">Extension</span>;
|
||
<span class="keyword">use</span> <span class="title">Symfony</span>\<span class="title">Component</span>\<span class="title">Console</span>\<span class="title">Tester</span>\<span class="title">CommandTester</span>;
|
||
<span class="keyword">use</span> <span class="title">TYPO3</span>\<span class="title">TestingFramework</span>\<span class="title">Core</span>\<span class="title">Functional</span>\<span class="title">FunctionalTestCase</span> <span class="title">as</span> <span class="title">TestCase</span>;
|
||
|
||
<span class="class"><span class="keyword">class</span> <span class="title">ImportTest</span> <span class="keyword">extends</span> <span class="title">TestCase</span>
|
||
</span>{
|
||
<span class="keyword">protected</span> <span class="function"><span class="keyword">function</span> <span class="title">setUp</span><span class="params">()</span>: <span class="title">void</span>
|
||
</span>{
|
||
<span class="keyword">parent</span>::setUp();
|
||
<span class="comment">// Ensure the fake guzzle client is used when executing tests.</span>
|
||
GuzzleClientFaker::registerClient();
|
||
}
|
||
|
||
<span class="keyword">protected</span> <span class="function"><span class="keyword">function</span> <span class="title">tearDown</span><span class="params">()</span>: <span class="title">void</span>
|
||
</span>{
|
||
<span class="comment">// Ensure the guzzle client is cleaning up all leftovers</span>
|
||
GuzzleClientFaker::tearDown();
|
||
<span class="keyword">parent</span>::tearDown();
|
||
}
|
||
|
||
<span class="comment">/**
|
||
* <span class="doctag">@test</span>
|
||
*/</span>
|
||
<span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">importIsFilteredByCompanyName</span><span class="params">()</span>: <span class="title">void</span>
|
||
</span>{
|
||
<span class="comment">// Register two responses with the content of those files.</span>
|
||
<span class="comment">// The files only provide the content of the response, no header or status codes.</span>
|
||
GuzzleClientFaker::appendResponseFromFile(<span class="keyword">__DIR__</span> . <span class="string">'/ImportFixtures/Guzzle/example.com/api/oauth/token.json'</span>);
|
||
GuzzleClientFaker::appendResponseFromFile(<span class="keyword">__DIR__</span> . <span class="string">'/ImportFixtures/GuzzleImportIsFilteredByCompanyName/example.com/api/v1/job_market/jobs/GET.json'</span>);
|
||
|
||
<span class="comment">// Execute the actual tests, that code will trigger two requests.</span>
|
||
<span class="comment">// The actual code doesn't matter for this example.</span>
|
||
<span class="keyword">$this</span>->importDataSet(<span class="string">'EXT:example_extension/Tests/Functional/ImportFixtures/FilteredJobsByCompanyName.xml'</span>);
|
||
$importer = <span class="keyword">$this</span>->getContainer()->get(Import::class);
|
||
$commandTester = <span class="keyword">new</span> CommandTester($importer);
|
||
$commandTester->execute([
|
||
<span class="string">'storagePid'</span> => <span class="string">'2'</span>,
|
||
]);
|
||
<span class="keyword">$this</span>->assertCSVDataSet(<span class="string">'EXT:example_extension/Tests/Functional/ImportAssertions/FilteredJobsByCompanyName.csv'</span>);
|
||
}
|
||
}</code></pre><p>We register the fake class within the <code>setUp()</code> method. We also ensure it cleans up itself within the <code>tearDown()</code> method.</p><p>The class can now be used from within the Test. We currently offer one public method which allows to set the response via <code>appendResponseFromFile()</code>.</p><p>Other methods could easily be exposed. E.g. the private method <code>appendResponseFromContent()</code> in order to dynamically build the response content. One also could create the whole response including status code within the test and pass it to a new method of the class.</p><p>And one could integrate the history as mentioned in Susis blog post in order to assert that certain requests were made.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c534"></a>
|
||
<a name="acks"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Thanks to <a href="https://stadtwerk.org/">stadt.werk GmbH</a> who paid us to implement the mocking within one of their extensions.</p><p>Thanks to Susi (<a href="https://twitter.com/sasunegomo">https://twitter.com/sasunegomo</a>) for providing an inspiring blog post on how to mock the Client itself.</p><p>Thanks to <a href="https://github.com/mbrodala" title="GitHub User Profile">Mathias</a> for motivating me to write the blog post.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c533"></a>
|
||
<a name="furtherReading"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<ul> <li>Read Susis blog post regarding mocking of HTTP api Responses with Guzzle: <a href="https://susi.dev/mock-http-api-responses-with-guzzle-psr-18-psr-7/">https://susi.dev/mock-http-api-responses-with-guzzle-psr-18-psr-7/</a></li> <li>Read Guzzle documentation on testing and mocking of Guzzle: <a href="https://docs.guzzlephp.org/en/stable/testing.html">https://docs.guzzlephp.org/en/stable/testing.html</a> and Handlers and Middleware: <a href="https://docs.guzzlephp.org/en/stable/handlers-and-middleware.html">https://docs.guzzlephp.org/en/stable/handlers-and-middleware.html</a></li> </ul>
|
||
|
||
|
||
]]></description>
|
||
<link>https://daniel-siepmann.de/mock-guzzle-requests-in-functional-tests.html</link>
|
||
<pubDate>Fri, 08 Jul 2022 00:00:00 +0200</pubDate>
|
||
<guid isPermaLink="true">https://daniel-siepmann.de/mock-guzzle-requests-in-functional-tests.html</guid>
|
||
</item>
|
||
|
||
|
||
|
||
|
||
|
||
<item>
|
||
<title>TYPO3 Content with Solr Usable by Editors</title>
|
||
<description><![CDATA[I know many editors and people complaining about TYPO3 for various reasons. In this video I'll show an actual customer project. I demonstrate how performant a website build with TYPO3 and EXT:solr can be. And I demonstrate how pleasant the TYPO3 backend can be.
|
||
|
||
|
||
|
||
<a name="c517"></a>
|
||
<a name="context"></a>
|
||
|
||
<h2>Context <small><a href="#context">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>The customer switched the agency because the old agency created a very slow website. The website used EXT:ke_search. That itself wasn't a problem. The agency extended the search and destroyed the performance. An initial page load of search site, without actually searching, took over 1 minute.</p><p>This new website was build by <a href="https://werkraum-media.de/">https://werkraum-media.de/</a> one of our customers. We did the integration and programming part. Our main goal was to use as few dependencies as possible and stick to TYPO3. Still the website should be feature rich.</p><p>We decided to integrate EXT:solr as one of the few dependencies. The website has a lot of content and needs a proper search. Furthermore, EXT:solr allows rendering content lists with additional features. I've already published a post back in 2016 regarding this topic, make sure <a href="/posts/migrated/everything-is-content-that-can-be-served-via-solr.html">to read it</a> to get the idea.</p><p>We also decided to work on editor experience for TYPO3 backend. Editors were overwhelmed by the complexity of the old TYPO3 installation. That installation used EXT:news as many other installations out there. And as very often, EXT:news was extended for events and other kind of contents. The setup was a mess. We decided to use TYPO3 pages instead of EXT:news. Rendering of lists will be done via EXT:solr instead, detail view is done by opening the actual TYPO3 page.</p><p>We added different page types as there are still many types from articles over calls for papers and events. Also, the page structure matches the different areas like book reviews, interviews and such things. Editors will find the same structure as visitors that actual matches the types of content.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c516"></a>
|
||
<a name="video"></a>
|
||
|
||
<h2>Video <small><a href="#video">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<figure>
|
||
<video controls poster="/fileadmin/Videos/TYPO3/TYPO3-Content-With-Solr-Usable-By-Editors.png">
|
||
<source src="/fileadmin/Videos/TYPO3/TYPO3-Content-With-Solr-Usable-By-Editors.mp4" type="video/mp4">
|
||
<p>
|
||
Sorry, your browser doesn't support embedded videos.<br>
|
||
You can find the raw video file here: <a href="/fileadmin/Videos/TYPO3/TYPO3-Content-With-Solr-Usable-By-Editors.mp4">/fileadmin/Videos/TYPO3/TYPO3-Content-With-Solr-Usable-By-Editors.mp4</a>.
|
||
</p>
|
||
</video>
|
||
<figcaption>
|
||
<p>
|
||
Video v41: TYPO3 Content with Solr usable by Editors<br>
|
||
Size: 95.10 MB
|
||
|
||
<br>
|
||
YouTube: <a href="https://www.youtube.com/watch?v=w8z5gsU1z5g" target="_blank" rel="noreferrer">w8z5gsU1z5g</a>
|
||
|
||
</p>
|
||
<p>Demonstration on how to create high performant websites using TYPO3 and EXT:solr.<br />
|
||
Also demonstrating how Editors create content within this installation.</p>
|
||
</figcaption>
|
||
</figure>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c518"></a>
|
||
<a name="acks"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Thanks to the customer <a href="https://www.soziopolis.de/">https://www.soziopolis.de/</a> who trusted us and allowed us to build such a great website.</p><p>Thanks to our direct customer, the TYPO3 agency <a href="https://werkraum-media.de/">https://werkraum-media.de/</a> for trusting us for many years already. It is always a pleasure to work with them.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c519"></a>
|
||
<a name="reading"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>You might be interested in the following sources:</p><ul> <li> <p>Blog post <a href="/posts/migrated/everything-is-content-that-can-be-served-via-solr.html">Everything is Content, that can be served via Solr</a> promoting the idea back in 2016</p> </li> <li> <p>Blog post <a href="/posts/2019/typo3-plugins-as-content-elements.html">TYPO3 Plugins as Content Elements</a> promoting the idea of content elements instead of complicated technical plugins for editors</p> </li> <li>TYPO3 extension solr: <a href="https://extensions.typo3.org/extension/solr/">https://extensions.typo3.org/extension/solr/</a></li> </ul>
|
||
|
||
|
||
]]></description>
|
||
<link>https://daniel-siepmann.de/typo3-content-with-solr-usable-by-editors.html</link>
|
||
<pubDate>Fri, 01 Apr 2022 00:00:00 +0200</pubDate>
|
||
<guid isPermaLink="true">https://daniel-siepmann.de/typo3-content-with-solr-usable-by-editors.html</guid>
|
||
</item>
|
||
|
||
|
||
|
||
|
||
|
||
<item>
|
||
<title>TypoScript outside of Frontend context</title>
|
||
<description><![CDATA[TypoScript by concept is meant for frontend context only. There is TSconfig for backend as well. Extbase introduced a way to also configure backend modules via TypoScript. Nowadays TypoScript is also used in other context, e.g. scheduler tasks or commands.
|
||
|
||
I'll explain how the actual TypoScript is loaded, so you understand that part and can use it.
|
||
|
||
|
||
|
||
<a name="c469"></a>
|
||
<a name="theIssue"></a>
|
||
|
||
<h2>The issue <small><a href="#theIssue">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Let me explain the issue first. Hopefully that will ease understanding of the following sections.</p><p>TypoScript by concept is meant for Frontend. That concept didn't change, even if TypoScript now is also used in other context. Therefore, TypoScript is still bound to a concrete page, as each page could load different TypoScript. Even contexts without the concept of a page still need to determine the page to load TypoScript.</p><p>That's not an issue for backend modules with page tree component. Whenever a page is selected, that one will be passed as an argument to the URL of the module. That way TYPO3 already has a page to use. But that's not the case if no page is selected, or in context of a command where no page is available at all.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c471"></a>
|
||
<a name="determiningThePage"></a>
|
||
|
||
<h2>Determining the page <small><a href="#determiningThePage">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>TYPO3 needs to determine the page that defines TypoScript to load. TYPO3 in that context means Extbase. ViewHelpers and other components relying on TypoScript often use Extbase <code>TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface</code> to load TypoScript. I'll explain exactly that implementation:</p><ol> <li><code>ConfigurationManager</code> use the concrete configuration manager based on context, which is <code>TYPO3\CMS\Extbase\Configuratio\BackendConfigurationManager</code> for all contexts except frontend.</li> <li><code>BackendConfigurationManager</code> first determine the page before loading TypoScript.</li> </ol><p>The logic to determine the page can be fetched from its <code>getCurrentPageId()</code> method:</p><ol> <li>Use <code>id</code> parameter of either GET or POST.</li> <li>Use the first root page. A root page is determined by the following: <code>is_siteroot</code> has to be set, it has to be in default language and should not be within a workspace. "First" means the first one based on sorting.<br> Most probably a page was found at this position and is used.<br> <br> Humans are not able to determine that in case of nested page trees with root pages on different levels. Those scenarios always force to check the database. That sorting can change at any time, as sorting is based on the actual level. So never relay on that.</li> <li>Determine first TypoScript record which is defined as root. "First" means the first one created.</li> <li>Use <code>0</code> as page uid.</li> </ol>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c473"></a>
|
||
<a name="howToOvercomeTheLimitation"></a>
|
||
|
||
<h2>How to overcome the limitation <small><a href="#howToOvercomeTheLimitation">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>I never found a proper way, there is no API. The best suggestion:</p><p>Don't use TypoScript in such contexts. If you need TypoScript, for whatever reason, keep it to a minimum, always include it and don't use any features like conditions.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c477"></a>
|
||
<a name="moduleVsPlugin"></a>
|
||
|
||
<h2>module. vs plugin. <small><a href="#moduleVsPlugin">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Extbase populates <code>$this->settings</code> within controllers. Extbase uses <code>plugin.</code> in frontend and <code>module.</code> in backend context. Some extensions add <code>module.tx_extname < plugin.tx_extname</code> in order to have the same settings available in both contexts.</p><p>That's specific to extension / plugin settings fetched by the API.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c475"></a>
|
||
<a name="furtherReading"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<ul> <li><a href="/concrete-typo3-dependency-injection-examples.html#c370">Injecting TypoScript Settings</a> with Dependency Injection since V10.</li> <li><a href="/posts/migrated/inject-typoscript-settings.html">Injecting TypoScript Settings</a> with Extbase <code>ObjectManager</code> prior v10.</li> </ul>
|
||
|
||
|
||
]]></description>
|
||
<link>https://daniel-siepmann.de/typoscript-outside-of-frontend-context.html</link>
|
||
<pubDate>Wed, 24 Mar 2021 00:00:00 +0100</pubDate>
|
||
<guid isPermaLink="true">https://daniel-siepmann.de/typoscript-outside-of-frontend-context.html</guid>
|
||
</item>
|
||
|
||
|
||
|
||
|
||
|
||
<item>
|
||
<title>Concrete TYPO3 Dependency Injection examples</title>
|
||
<description><![CDATA[Some real world examples on how to use the Symfony Dependency Injection in TYPO3. E.g. how to inject TypoScript settings or database query builder.
|
||
|
||
|
||
|
||
<a name="c467"></a>
|
||
<a name="intro"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Don't expect to many explanations here. These are already available, see links at bottom. Instead, this blog post contains concrete examples as reference and copy & paste.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c372"></a>
|
||
<a name="injectDbConnectionAndQuerybuilder"></a>
|
||
|
||
<h2>Inject DB Connection and QueryBuilder <small><a href="#injectDbConnectionAndQuerybuilder">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Given the new Dependency Injection, one can inject a concrete <code>QueryBuilder</code> instance, or Database Connection. As TYPO3 allows configuring multiple different database servers, developers need to fetch a proper instance based on the concrete database table. This involves some unnecessary code, which can be removed thanks to the Dependency Injection.</p><pre><code>services:
|
||
_defaults:
|
||
autowire: true
|
||
autoconfigure: true
|
||
public: false
|
||
|
||
DanielSiepmann\Tracking\:
|
||
resource: '../Classes/*'
|
||
|
||
dbconnection.tx_tracking_pageview:
|
||
class: 'TYPO3\CMS\Core\Database\Connection'
|
||
factory:
|
||
- '@TYPO3\CMS\Core\Database\ConnectionPool'
|
||
- 'getConnectionForTable'
|
||
arguments:
|
||
- 'tx_tracking_pageview'
|
||
|
||
querybuilder.tx_tracking_pageview:
|
||
class: 'TYPO3\CMS\Core\Database\Query\QueryBuilder'
|
||
factory:
|
||
- '@TYPO3\CMS\Core\Database\ConnectionPool'
|
||
- 'getQueryBuilderForTable'
|
||
arguments:
|
||
- 'tx_tracking_pageview'
|
||
|
||
DanielSiepmann\Tracking\Domain\Repository\Pageview:
|
||
public: true
|
||
arguments:
|
||
- '@dbconnection.tx_tracking_pageview'</code></pre><p>The corresponding PHP could look like:</p><pre><code><span class="php"><span class="meta"><?php</span>
|
||
|
||
<span class="keyword">namespace</span> <span class="title">DanielSiepmann</span>\<span class="title">Tracking</span>\<span class="title">Domain</span>\<span class="title">Repository</span>;
|
||
|
||
<span class="keyword">use</span> <span class="title">TYPO3</span>\<span class="title">CMS</span>\<span class="title">Core</span>\<span class="title">Database</span>\<span class="title">Connection</span>;
|
||
|
||
<span class="class"><span class="keyword">class</span> <span class="title">Pageview</span>
|
||
</span>{
|
||
<span class="comment">/**
|
||
* <span class="doctag">@var</span> Connection
|
||
*/</span>
|
||
<span class="keyword">private</span> $connection;
|
||
|
||
<span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">__construct</span><span class="params">(
|
||
Connection $connection
|
||
)</span> </span>{
|
||
<span class="keyword">$this</span>->connection = $connection;
|
||
}
|
||
}</span></code></pre>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c370"></a>
|
||
<a name="injectTyposcriptSettings"></a>
|
||
|
||
<h2>Inject TypoScript Settings <small><a href="#injectTyposcriptSettings">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Back in the old days, you could inject the TypoScript Settings into Extbase classes, see <a href="/posts/migrated/inject-typoscript-settings.html">my old blog post</a>. Given the new Dependency Injection, those can be injected into all classes, with no additional code inside the class:</p><pre><code>services:
|
||
_defaults:
|
||
autowire: true
|
||
autoconfigure: true
|
||
public: false
|
||
|
||
Vendor\ExtName\:
|
||
resource: '../Classes/*'
|
||
|
||
extbaseSettings.ExtName.PluginName:
|
||
class: 'array'
|
||
factory:
|
||
- '@TYPO3\CMS\Extbase\Configuration\ConfigurationManager'
|
||
- 'getConfiguration'
|
||
arguments:
|
||
$configurationType: 'Settings'
|
||
$extensionName: 'ExtName'
|
||
$pluginName: 'PluginName'
|
||
|
||
Vendor\ExtName\Domain\PeriodCreation\DataHandler:
|
||
arguments:
|
||
$settings: '@extbaseSettings.ExtName.PluginName'</code></pre><p>The TypoScript settings are injected as plain PHP array into the constructor argument <code>$settings</code>.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c482"></a>
|
||
<a name="injectExtensionConfiguration"></a>
|
||
|
||
<h2>Inject Extension Configuration <small><a href="#injectExtensionConfiguration">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>TYPO3 offers a new API to retrieve extension configuration. This can be used as a factory to provide options via dependency injection. This might be handy under some circumstances, but there might be reasons to inject the API itself.</p><pre><code>services:
|
||
_defaults:
|
||
autowire: true
|
||
autoconfigure: true
|
||
public: false
|
||
|
||
Vendor\ExtName\:
|
||
resource: '../Classes/*'
|
||
|
||
extensionconfiguration.ext_key.title:
|
||
class: 'string'
|
||
factory:
|
||
- '@TYPO3\CMS\Core\Configuration\ExtensionConfiguration'
|
||
- 'get'
|
||
arguments:
|
||
- 'ext_key'
|
||
- 'title'
|
||
|
||
Vendor\ExtName\Namespace\Class:
|
||
public: true
|
||
arguments:
|
||
$config: '@extensionconfiguration.ext_key.title'</code></pre><p>This example injects a single property from extension configuration. Note that property <code>class</code> needs to be set to the appropriate type. The <code>$config</code> argument of the constructor needs to have the same type hint.</p><p>Another example would be to inject the whole configuration array. That would then be of type <code>array</code> and remove the 2nd argument from factory:</p><pre><code>services:
|
||
_defaults:
|
||
autowire: true
|
||
autoconfigure: true
|
||
public: false
|
||
|
||
Vendor\ExtName\:
|
||
resource: '../Classes/*'
|
||
|
||
extensionconfiguration.ext_key:
|
||
class: 'array'
|
||
factory:
|
||
- '@TYPO3\CMS\Core\Configuration\ExtensionConfiguration'
|
||
- 'get'
|
||
arguments:
|
||
- 'ext_key'
|
||
|
||
Vendor\ExtName\Namespace\Class:
|
||
public: true
|
||
arguments:
|
||
$config: '@extensionconfiguration.ext_key'</code></pre>
|
||
|
||
|
||
|
||
|
||
|
||
<aside class="admonition-note">
|
||
|
||
<a name="c483"></a>
|
||
<a name="note"></a>
|
||
|
||
<h2>Note <small><a href="#note">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>The <code>TYPO3\CMS\Core\Configuration\ExtensionConfiguration->get()</code> might throw exceptions. That's one reason why using this API as factory might be a bad idea. It is possible to inject the service and call the method within code. That would allow to catch and handle the exception.</p>
|
||
</aside>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c486"></a>
|
||
<a name="ack"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Thanks to <a href="https://www.nikita-hovratov.de/" target="_blank" title="Official Website of Nikita Hovratov" rel="noreferrer">Nikita Hovratov</a> for providing feedback regarding 2nd example of <a href="/concrete-typo3-dependency-injection-examples.html#c482">Inject Extension Configuration</a>. It was wrong as it contained <code>extensionconfiguration.ext_key.title</code> instead of <code>extensionconfiguration.ext_key</code>. It was updated 2021-08-17.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c465"></a>
|
||
<a name="furtherReading"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>That are some concrete examples. For further information, check:</p><ul> <li><a href="https://docs.typo3.org/m/typo3/reference-coreapi/master/en-us/ApiOverview/DependencyInjection/Index.html">https://docs.typo3.org/m/typo3/reference-coreapi/master/en-us/ApiOverview/DependencyInjection/Index.html</a></li> <li><a href="https://usetypo3.com/di-and-events-example.html">https://usetypo3.com/di-and-events-example.html</a></li> <li><a href="https://usetypo3.com/dependency-injection.html">https://usetypo3.com/dependency-injection.html</a></li> </ul>
|
||
|
||
|
||
]]></description>
|
||
<link>https://daniel-siepmann.de/concrete-typo3-dependency-injection-examples.html</link>
|
||
<pubDate>Wed, 24 Feb 2021 00:00:00 +0100</pubDate>
|
||
<guid isPermaLink="true">https://daniel-siepmann.de/concrete-typo3-dependency-injection-examples.html</guid>
|
||
</item>
|
||
|
||
|
||
|
||
|
||
|
||
<item>
|
||
<title>Prepare legacy code for upcoming TYPO3 versions</title>
|
||
<description><![CDATA[Some of you, like one of our customers, are still on TYPO3 v8 or 9. We all dream of a bright future where we get shiny new features like new event handling by PSR-14, and dependency injection by PSR-11. Still we can write code that actually follows the new features and provides benefits today. Updates will become very smooth thanks to rector which could auto migrate everything necessary.
|
||
|
||
Read the following sections to get concrete examples on how we write code in 8.7 that will be auto migrated to 10.4 (11.x) via rector and how we profit today.
|
||
|
||
|
||
|
||
<a name="c422"></a>
|
||
<a name="usingDependencyInjection"></a>
|
||
|
||
<h2>Using Dependency Injection <small><a href="#usingDependencyInjection">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>The BIG change with 10.4 is dependency injection through the whole code base and usage of Symfony container, and therefore configuration of dependency injection.</p><p>Actually older versions already have dependency injection which you can use. And that can be migrated via <a href="https://github.com/sabbelasichon/typo3-rector" title="GitHub project of TYPO3 rector">TYPO3 rector</a>, once you update. Let me explain a bit more, before showing a concrete example, because there are two things to understand, which you also need to understand for 10.4 anyway.</p><p>1. TYPO3 has some "entry points" into your Code, such as hooks, user functions. Those entry points always use TYPO3 API like <code>GeneralUtility::makeInstance</code> to create instances of your classes. You don't have dependency injection within any TYPO3 version in such cases. In order to get dependency injection within 10.4, you need to explicitly mark those classes to be <code>public</code>. Find out more at <a href="https://docs.typo3.org/m/typo3/reference-coreapi/10.4/en-us/ApiOverview/DependencyInjection/Index.html#knowing-what-to-make-public">docs.typo3.org</a>.</p><p>2. TYPO3 has deprecated usage of Extbase <code>ObjectManager</code>, which provides Dependency Injection. One might think adding usages of that class will complicate updates, but <em>TYPO3 rector</em> will do the job for you. Don't fear that change in case you follow this post.<br> Once you understood those two points about TYPO3 10.4, you should have all you need to prepare older code base for an update.</p><p>Let's have a look at two concrete examples, all stripped down to what matters.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c430"></a>
|
||
<a name="firstConcreteExampleForDi"></a>
|
||
|
||
<h2>First concrete example for DI <small><a href="#firstConcreteExampleForDi">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>First, let's take a look at a concrete user function, which is an entry point from TYPO3 to our code base.</p><pre><code><span class="php"><span class="meta"><?php</span>
|
||
|
||
<span class="keyword">namespace</span> <span class="title">E2</span>\<span class="title">E2Core</span>\<span class="title">Export</span>\<span class="title">UserFunction</span>;
|
||
|
||
<span class="keyword">use</span> <span class="title">E2</span>\<span class="title">E2Core</span>\<span class="title">Export</span>\<span class="title">JsonSerializer</span>\<span class="title">JsonEncode</span>;
|
||
<span class="keyword">use</span> <span class="title">TYPO3</span>\<span class="title">CMS</span>\<span class="title">Core</span>\<span class="title">Utility</span>\<span class="title">GeneralUtility</span>;
|
||
<span class="keyword">use</span> <span class="title">TYPO3</span>\<span class="title">CMS</span>\<span class="title">Extbase</span>\<span class="title">Object</span>\<span class="title">ObjectManager</span>;
|
||
|
||
<span class="class"><span class="keyword">class</span> <span class="title">Convert</span>
|
||
</span>{
|
||
<span class="comment">/**
|
||
* <span class="doctag">@var</span> JsonEncode
|
||
*/</span>
|
||
<span class="keyword">private</span> $jsonEncoder;
|
||
|
||
<span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">__construct</span><span class="params">(
|
||
JsonEncode $jsonEncoder = null
|
||
)</span> </span>{
|
||
<span class="keyword">$this</span>->jsonEncoder = $jsonEncoder ?? GeneralUtility::makeInstance(ObjectManager::class)->get(JsonEncode::class);
|
||
}
|
||
|
||
<span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">typo3LinkReference</span><span class="params">(string $link)</span>: <span class="title">string</span>
|
||
</span>{
|
||
<span class="keyword">return</span> json_encode(<span class="keyword">$this</span>->jsonEncoder->normalizeTypo3Link($link));
|
||
}
|
||
|
||
<span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">typo3FilePath</span><span class="params">(string $filePath)</span>: <span class="title">string</span>
|
||
</span>{
|
||
<span class="keyword">return</span> <span class="keyword">$this</span>->jsonEncoder->normalizeFilePath($filePath);
|
||
}
|
||
}</span></code></pre><p>This class offers two user functions and already is using dependency injection. It expects an <code>JsonEncode</code> instance on creation. The little change compared to TYPO3 10.4? This dependency is marked optional by using <code>= null</code>. That's necessary as older TYPO3 versions don't resolve dependency for us. Therefore, we use <code>GeneralUtility::makeInstance(ObjectManager::class)->get(JsonEncode::class);</code> to fetch the dependency.</p><p>The usage of <code>ObjectManager</code> is part of our second concrete example.</p><p>Following this concrete example, our code is already prepared for 10.4 native dependency injection. We also can write our tests and inject dependencies. No need to change those tests for 10.4.</p><p>Once we update to 10.4, we will use TYPO3 rector to remove the <code>= null</code> and <code>?? GeneralUtility …</code> call. No manual work is involved in updating this code.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c432"></a>
|
||
<a name="secondConcreteExampleForDi"></a>
|
||
|
||
<h2>Second concrete example for DI <small><a href="#secondConcreteExampleForDi">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>The second example is code from within our code base. We expect that this class is already injected, or at least fetched via Extbase <code>ObjectManager</code>.</p><pre><code><span class="php"><span class="meta"><?php</span>
|
||
|
||
<span class="keyword">namespace</span> <span class="title">E2</span>\<span class="title">E2Core</span>\<span class="title">Export</span>\<span class="title">JsonSerializer</span>;
|
||
|
||
<span class="keyword">use</span> <span class="title">E2</span>\<span class="title">E2Core</span>\<span class="title">Service</span>\<span class="title">ImageServerService</span>;
|
||
<span class="keyword">use</span> <span class="title">E2</span>\<span class="title">E2Core</span>\<span class="title">Service</span>\<span class="title">TypolinkService</span>;
|
||
|
||
<span class="class"><span class="keyword">class</span> <span class="title">JsonEncode</span>
|
||
</span>{
|
||
<span class="comment">/**
|
||
* <span class="doctag">@var</span> ImageServerService
|
||
*/</span>
|
||
<span class="keyword">private</span> $imageService;
|
||
|
||
<span class="comment">/**
|
||
* <span class="doctag">@var</span> TypolinkService
|
||
*/</span>
|
||
<span class="keyword">private</span> $typolinkService;
|
||
|
||
<span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">__construct</span><span class="params">(
|
||
ImageServerService $imageService,
|
||
TypolinkService $typolinkService
|
||
)</span> </span>{
|
||
<span class="keyword">$this</span>->imageService = $imageService;
|
||
<span class="keyword">$this</span>->typolinkService = $typolinkService;
|
||
}
|
||
|
||
<span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">normalizeTypo3Link</span><span class="params">($value)</span>
|
||
</span>{
|
||
<span class="keyword">if</span> (is_string($value) === <span class="keyword">false</span>) {
|
||
<span class="keyword">return</span> $value;
|
||
}
|
||
|
||
$result = <span class="keyword">$this</span>->typolinkService->process($value);
|
||
<span class="keyword">return</span> $result ?? $value;
|
||
}
|
||
|
||
<span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">normalizeFilePath</span><span class="params">(string $value)</span>: <span class="title">string</span>
|
||
</span>{
|
||
<span class="keyword">return</span> <span class="keyword">$this</span>->imageService->generateFileSrc($value);
|
||
}
|
||
}</span></code></pre><p>That's the class which is used by the first example. As instances are always injected, or fetched by <code>ObjectManager</code>, there is no need for the more complex code from first example. This code will not change in 10.4 and just work.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c424"></a>
|
||
<a name="usingPsr14Events"></a>
|
||
|
||
<h2>Using PSR-14 events <small><a href="#usingPsr14Events">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>TYPO3 10.4 introduced the PSR-14 events. Until then, there were hooks and signal / slots from Extbase. The events will replace those solutions step by step.</p><p>Some concepts can already be used within older TYPO3 code base, easing actual code in older versions and reducing amount of work during updates.</p><p>One can follow the new TYPO3 documentation regarding events, and already create events. Those events encapsulate information and provide APIs. (Let's not talk about using technical implementations for foreign concepts here). Events are simple classes that are passed through the dispatcher. The same is already possible in older versions. Only the dispatcher needs to be replaced during an update to 10.4, keeping the rest as is.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c434"></a>
|
||
<a name="concreteExampleForEvents"></a>
|
||
|
||
<h2>Concrete example for events <small><a href="#concreteExampleForEvents">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Let's have a look at a concrete example on how to implement an event in 8.7. I'll not go into detail here, because you can find information at <a href="https://docs.typo3.org/m/typo3/reference-coreapi/10.4/en-us/ApiOverview/Hooks/EventDispatcher/Index.html">docs.typo3.org for 10.4</a> already. This example should only show how to use it in older versions.</p><pre><code><span class="keyword">private</span> <span class="function"><span class="keyword">function</span> <span class="title">jsonEncode</span><span class="params">($object)</span>: <span class="title">string</span>
|
||
</span>{
|
||
$normalizer = <span class="keyword">new</span> ObjectNormalizer();
|
||
|
||
$event = <span class="keyword">new</span> InitializeJsonEncode($object);
|
||
<span class="keyword">$this</span>->dispatcher->dispatch(<span class="keyword">__CLASS__</span>, <span class="string">'jsonEncodeNormalizer'</span>, [$event]);
|
||
$normalizer->setCallbacks($event->getCallbacks());
|
||
$normalizer->setIgnoredAttributes($event->getAttributesToIgnore());
|
||
|
||
$serializer = <span class="keyword">new</span> Serializer(
|
||
[$normalizer],
|
||
[<span class="keyword">new</span> JsonEncoder()]
|
||
);
|
||
|
||
<span class="keyword">return</span> $serializer->serialize($object, <span class="string">'json'</span>);
|
||
}</code></pre><p>The above method creates a new instance of an event. The event is dispatched by Extbase dispatcher. Once you update, replace the dispatcher and the line dispatching the event. Everything else can stay.</p><p>Let's see the counterpart, registering and using the event.</p><pre><code><span class="keyword">public</span> <span class="keyword">static</span> <span class="function"><span class="keyword">function</span> <span class="title">register</span><span class="params">()</span>
|
||
</span>{
|
||
$dispatcher = GeneralUtility::makeInstance(Dispatcher::class);
|
||
|
||
$dispatcher->connect(
|
||
JsonSerializer::class,
|
||
<span class="string">'jsonEncodeNormalizer'</span>,
|
||
JsonEncode::class,
|
||
<span class="string">'initializeNormalizer'</span>
|
||
);
|
||
}
|
||
|
||
<span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">initializeNormalizer</span><span class="params">(InitializeJsonEncode $event)</span>
|
||
</span>{
|
||
$event->addCallback(<span class="string">'meta'</span>, [<span class="keyword">$this</span>, <span class="string">'normalizeFeUserGroups'</span>]);
|
||
|
||
$object = $event->getObject();
|
||
|
||
<span class="keyword">if</span> ($object <span class="keyword">instanceof</span> HasExtbaseFileReferences) {
|
||
<span class="keyword">$this</span>->initializeCallbackForAttributes(
|
||
$event,
|
||
<span class="string">'fileReferenceProperties'</span>,
|
||
<span class="string">'normalizeExtbaseFileReference'</span>
|
||
);
|
||
}
|
||
<span class="keyword">if</span> ($object <span class="keyword">instanceof</span> HasTypo3LinkReferences) {
|
||
<span class="keyword">$this</span>->initializeCallbackForAttributes(
|
||
$event,
|
||
<span class="string">'linkProperties'</span>,
|
||
<span class="string">'normalizeTypo3Link'</span>
|
||
);
|
||
}
|
||
}</code></pre><p>The first method is called from ext_localconf.php to connect the listener to the dispatcher. This will be removed with 10.4. Instead, the registration will be done via configuration inside Services.yaml. The public method which receives the event will stay when updating to 10.4, no change will be necessary.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c436"></a>
|
||
<a name="conclusion"></a>
|
||
|
||
<h2>Conclusion <small><a href="#conclusion">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>If you follow recent development, e.g. by reading the changelog, you can understand new concepts and ideas. By following these new concepts within older projects, updates will become smoother. You can already learn new concepts, which will make working with recent versions easier. Beside all of that, your code already follows those concepts, which should make it "better".</p><p>In some cases updates become even easier, thanks to preparation and usage of TYPO3 rector.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c426"></a>
|
||
<a name="ack"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Thanks to <a href="https://twitter.com/Naderio" title="Twitter profile of Naderio">Naderio</a> for pushing me to write this blog post.</p><p>Thanks to <a href="https://github.com/sabbelasichon" title="GitHub profile of Sebastian Schreiber">Sebastian Schreiber</a> for creating, maintaining and developing TYPO3 rector.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c428"></a>
|
||
<a name="furtherReading"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>You might also be interested in the following sources, which touch the same topics:</p><ul> <li><a href="/typo3-v10-feature-outlook.html">TYPO3 v10 feature outlook</a></li> <li><a href="https://github.com/sabbelasichon/typo3-rector">TYPO3 rector</a> — auto migrate your TYPO3 code base</li> <li><a href="https://docs.typo3.org/m/typo3/reference-coreapi/10.4/en-us/ApiOverview/DependencyInjection/Index.html">https://docs.typo3.org/m/typo3/reference-coreapi/10.4/en-us/ApiOverview/DependencyInjection/Index.html</a></li> <li><a href="https://docs.typo3.org/m/typo3/reference-coreapi/10.4/en-us/ApiOverview/Hooks/EventDispatcher/Index.html">https://docs.typo3.org/m/typo3/reference-coreapi/10.4/en-us/ApiOverview/Hooks/EventDispatcher/Index.html</a></li> <li><a href="https://www.php-fig.org/psr/psr-11/">PSR-11: Container interface</a></li> <li><a href="https://www.php-fig.org/psr/psr-14/">PSR-14: Event Dispatcher</a></li> </ul>
|
||
|
||
|
||
]]></description>
|
||
<link>https://daniel-siepmann.de/prepare-legacy-code-for-upcoming-typo3-versions.html</link>
|
||
<pubDate>Tue, 26 Jan 2021 00:00:00 +0100</pubDate>
|
||
<guid isPermaLink="true">https://daniel-siepmann.de/prepare-legacy-code-for-upcoming-typo3-versions.html</guid>
|
||
</item>
|
||
|
||
|
||
|
||
|
||
|
||
<item>
|
||
<title>Reuse existing Extbase controller</title>
|
||
<description><![CDATA[Since TYPO3 10.0 it is possible, actually necessary, to use FQCN (=Fully Qualified Class Names) for controllers when configuring plugins and modules. This provides a new benefit in terms of reusing and sharing code between different extensions.
|
||
|
||
This blog post will explain the benefit and provide an outlook in a possible future of TYPO3 Extbase extensions.
|
||
|
||
|
||
|
||
<a name="c374"></a>
|
||
<a name="theTechnicalChange"></a>
|
||
|
||
<h2>The technical change <small><a href="#theTechnicalChange">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>The new feature was introduced in TYPO3 10.0 as an actual deprecation of the old way to register plugins and modules when working with Extbase. Refer to <a href="https://docs.typo3.org/c/typo3/cms-core/master/en-us/Changelog/10.0/Deprecation-87550-UseControllerClassesWhenRegisteringPluginsmodules.html">TYPO3 Changelog 10.0 Deprecation #87550</a> to have a detailed explanation of the change.</p><p>Before 10.0 one registered the vendor independent from the actual controller name. Controller class names were build by Extbase, based on their provided alias and the vendor. Since 10.0 there is no more vendor but fully qualified class names (=FQCN). An example configuration of a plugin looks like this:</p><pre><code>\TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin(
|
||
$extensionName,
|
||
$pluginName,
|
||
[
|
||
\DanielSiepmann\ExtensionName\Controller\Frontend\CalendarController::class => implode(<span class="string">','</span>, [
|
||
<span class="string">'month'</span>,
|
||
<span class="string">'week'</span>,
|
||
<span class="string">'day'</span>,
|
||
]),
|
||
]
|
||
);</code></pre>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c376"></a>
|
||
<a name="theBenefits"></a>
|
||
|
||
<h2>The benefits <small><a href="#theBenefits">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>This allows to use controllers from all possible namespaces. Therefore a plugin can be configured to use a controller from a foreign Extension. That Extension should be required in <code>ext_emconf.php</code> and <code>composer.json</code>.</p><p>Why is that actual a benefit? This small change allows to reuse existing controllers. The example above contains a calendar controller to view a month, week and day. The extension also has some models which are part of the controller actions. Another extension which might need a calendar could now define a dependency and configure their own plugins to reuse this controller and models.</p><p>That way the extension does not need to re implement logic to build up data structure for calendars. Instead it can focus on their own parts.</p><p>That was a very technical benefit. But there are some more improving the experience of integrators and developers:</p><p>Each plugin has its own namespace. Combining units of the installation into a single plugin will assign a single new namespace to that feature set. That way configuring routing and building links becomes way easier. Same goes for TypoScript configuration.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c382"></a>
|
||
<a name="theConclusion"></a>
|
||
|
||
<h2>The conclusion <small><a href="#theConclusion">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>In order to make all that work, controllers and models should be selfincluded. They should "just work" and solve a single issue. Otherwise one will easily mix up things and create bloated plugins.</p><p>As templates belong to the extension namespace configuring the plugin, the new extension can provide custom templates with rendering for month, week and day. Each of them can contain links back to other actions, e.g. of controllers provided by the second Extension.</p><p>Also in case the calendar Extension makes it possible to easily attach custom records to these models, they would be available in the templates already.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c380"></a>
|
||
<a name="possibleOutlook"></a>
|
||
|
||
<h2>Possible Outlook <small><a href="#possibleOutlook">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>I'm not investing time in developing Extbase and therefore my outlook might not match the actual future. But in my opinion Extbase is changing and so should developers. It looks like most Extensions are built without thinking in a Unix / composer way. Instead of putting everything into an Extension, it would make more sense to focus on one thing and reuse existing components, either TYPO3 Extensions or composer packages.</p><p>That way issues should be solved way faster. Developers would focus on the important things.</p><p>Imagine an calendar Extension that would do all the calendar stuff. Another extension would provide data and logic to buy tickets for events. Combine both with a third extension, or add support for the calendar Extension within the ticket Extension. Without further overhead, all would have less code with more features. That would make Extensions easier to maintain.</p><p>Maybe some "Extensions" no longer will be Extensions, but pure composer packages providing some specific logic that can be reused by TYPO3 Extbase Extensions, as well as other systems.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c655"></a>
|
||
<a name="acks"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Thanks to <a href="https://github.com/Starkmann" title="GitHub user profile">Eike Starkmann</a> for mentioning further benefits.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c378"></a>
|
||
<a name="furtherReading"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<ul> <li>Changelog entry regarding the change: <a href="https://docs.typo3.org/c/typo3/cms-core/master/en-us/Changelog/10.0/Deprecation-87550-UseControllerClassesWhenRegisteringPluginsmodules.html">https://docs.typo3.org/c/typo3/cms-core/master/en-us/Changelog/10.0/Deprecation-87550-UseControllerClassesWhenRegisteringPluginsmodules.html</a></li> <li>Tweet mentioning the idea to write this blog post: <a href="https://twitter.com/daniel_siepmann/status/1319900536683040768">https://twitter.com/daniel_siepmann/status/1319900536683040768</a></li> </ul>
|
||
|
||
|
||
]]></description>
|
||
<link>https://daniel-siepmann.de/reuse-existing-extbase-controller.html</link>
|
||
<pubDate>Sat, 24 Oct 2020 00:00:00 +0200</pubDate>
|
||
<guid isPermaLink="true">https://daniel-siepmann.de/reuse-existing-extbase-controller.html</guid>
|
||
</item>
|
||
|
||
|
||
|
||
|
||
|
||
<item>
|
||
<title>feedit bugfix 10.0.2 released</title>
|
||
<description><![CDATA[A new bugfix version of TYPO3 extension feedit was released.
|
||
This one actually allows to use the extension again, and also fixes an issue that prevented proper initialization.
|
||
|
||
|
||
|
||
<a name="c359"></a>
|
||
<a name="moreInfoAboutFixes"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Older versions of the extension did not properly initialize TYPO3 backend, e.g. when trying to edit an record. This could lead to an unusable state.</p><p>This version should fix that and always allow to edit records.</p><p>The issue is fixed for buttons in content, as well as the admin panel.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c357"></a>
|
||
<a name="containedBugfixes"></a>
|
||
|
||
<h2>Contained bugfixes <small><a href="#containedBugfixes">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>The following two bugfixes are part of the release:</p><ul> <li><a href="https://github.com/FriendsOfTYPO3/feedit/commit/3450ed69189b94d09d52ddb9fe76b5653be80083" title="Commit on GitHub">3450ed6</a> Fix implementation of RequestEnricherInterface</li> <li><a href="https://github.com/FriendsOfTYPO3/feedit/commit/3b3f15606a681658f7073d3982611cde9aa13257" title="Commit on GitHub">3b3f156</a> Fix none working backend</li> </ul>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c358"></a>
|
||
<a name="updateNow"></a>
|
||
|
||
<h2>Update now <small><a href="#updateNow">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>The current version can be installed via composer:</p><pre><code>composer req friendsoftypo3/feedit:^10.0.2</code></pre><p>It can be downloaded from GitHub: <a href="https://github.com/FriendsOfTYPO3/feedit/releases/tag/v10.0.2">https://github.com/FriendsOfTYPO3/feedit/releases/tag/v10.0.2</a>.</p>
|
||
|
||
|
||
]]></description>
|
||
<link>https://daniel-siepmann.de/feedit-bugfix-1002-released.html</link>
|
||
<pubDate>Fri, 25 Sep 2020 00:00:00 +0200</pubDate>
|
||
<guid isPermaLink="true">https://daniel-siepmann.de/feedit-bugfix-1002-released.html</guid>
|
||
</item>
|
||
|
||
|
||
|
||
|
||
|
||
<item>
|
||
<title>TYPO3 tracking extension</title>
|
||
<description><![CDATA[I've created a small tracking extension for TYPO3. This should basically demonstrate what developers can achieve with newest APIs inside of TYPO3 v10.
|
||
|
||
It also provides widgets and works as a showcase for the new EXT:dashboard.
|
||
|
||
Two of our customers requested us to extend the extension for their TYPO3 installations. Therefore we added some more features and are now releasing the extension for public use.
|
||
|
||
|
||
|
||
<a name="c311"></a>
|
||
<a name="features"></a>
|
||
|
||
<h2>Features <small><a href="#features">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Right now everyone can use the extension. Either require from composer, or download the extension from GitHub and place it inside of <code>typo3conf/ext</code> folder.</p><p>The extension provides server side tracking <strong>without any JavaScript or Cookies</strong>. Still it lacks many of the powerful features provided by solutions like Google Analytics.</p><p>The extension allows to <strong>track visits of pages</strong>, as well of specific <strong>records</strong>. The statistics will be displayed via widgets and new ext:dashboard.</p><p>Integrators can fine control which requests should be tracked. E.g. by default visits with active backend user login are not tracked.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c315"></a>
|
||
<a name="image"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<figure id="i20">
|
||
<a href="/fileadmin/Projects/TYPO3_Extension__tracking/Widgets.png" target="_blank"><img src="/fileadmin/Projects/TYPO3_Extension__tracking/Widgets.png" width="1252" height="478" alt="Screenshot of TYPO3 dashbaord widgets, displaying pageview records." title="Screenshot of TYPO3 dashbaord widgets, displaying pageview records." /></a>
|
||
<figcaption>Figure i20: Demonstrates how collected pageviews can be visualized via EXT:dashboard.</figcaption>
|
||
</figure>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c312"></a>
|
||
<a name="development"></a>
|
||
|
||
<h2>Development <small><a href="#development">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>The development happens on GitHub (<a href="https://github.com/danielsiepmann/tracking">https://github.com/danielsiepmann/tracking</a>) and is fully transparent and open source. All changes are made via pull requests. Also we have 100% code coverage (linewise) using Unit and Functional tests.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c313"></a>
|
||
<a name="contribution"></a>
|
||
|
||
<h2>Contribution <small><a href="#contribution">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>In case you wanna contribute, you can create issues or send in pull requests. Also we do paid development for the extension, in case you miss a specific feature.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c316"></a>
|
||
<a name="thanksToSponsors"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Thanks to our sponsors so far which are web-to-date: <a href="https://www.web-to-date.com/">https://www.web-to-date.com/</a> as well as werkraum_ media: <a href="https://www.werkraum-media.de/">https://www.werkraum-media.de/</a>. They requested and paid me to extend and finish the extension for public usage.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c314"></a>
|
||
<a name="furtherReading"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>I've initially published information about the extension on <a href="/projects/typo3-extension-tracking.html">a subpage</a>.</p><p>You can also find the official documentation at <a href="https://docs.typo3.org/p/danielsiepmann/tracking/1.0/en-us/" title="Extension documentation at docs.typo3.org">docs.typo3.org</a>.</p><p>And source code, pull requests and issues can be found on GitHub: <a href="https://github.com/danielsiepmann/tracking">https://github.com/danielsiepmann/tracking</a></p><p>The composer package is available at: <a href="https://packagist.org/packages/danielsiepmann/tracking">https://packagist.org/packages/danielsiepmann/tracking</a>.</p>
|
||
|
||
|
||
]]></description>
|
||
<link>https://daniel-siepmann.de/typo3-tracking-extension.html</link>
|
||
<pubDate>Thu, 17 Sep 2020 00:00:00 +0200</pubDate>
|
||
<guid isPermaLink="true">https://daniel-siepmann.de/typo3-tracking-extension.html</guid>
|
||
</item>
|
||
|
||
|
||
|
||
|
||
|
||
<item>
|
||
<title>Composer dependency checker</title>
|
||
<description><![CDATA[This post will provide a small introduction into composer package "maglnet/composer-require-checker".
|
||
Some hints regarding usage for TYPO3 extensions is given, while there is not to much to know.
|
||
|
||
The composer package allows to check missing dependencies within composer.json. Its purpose is to run locally or in a CI to verify that all necessary dependencies are added to the composer.json file.
|
||
|
||
|
||
|
||
<a name="c328"></a>
|
||
<a name="requirements"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>The blog posts expects a working composer environment. It will not explain how to migrate or setup a project using composer.</p><p>The blog post will follow a concrete example of an TYPO3 extension that is delivered to the public. The steps can be adapted to other PHP and TYPO3 projects.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c329"></a>
|
||
<a name="installation"></a>
|
||
|
||
<h2>Installation <small><a href="#installation">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>The package can be installed in different ways. Those are all mentioned in projects readme at <a href="https://packagist.org/packages/maglnet/composer-require-checker">https://packagist.org/packages/maglnet/composer-require-checker</a> as well as on <a href="https://github.com/maglnet/ComposerRequireChecker">https://github.com/maglnet/ComposerRequireChecker</a>. I'll add the package as development dependency to the extension:</p><pre><code>composer req --dev maglnet/composer-<span class="built_in">require</span>-checker</code></pre><p>Depending on existing dependencies an older version might be necessary. E.g. <code>"maglnet/composer-require-checker:2.0.*"</code> which supports older versions of <code>symfony/console</code>. In order to ensure the package detects well known PHP native extensions and function, add PHP as dependency:</p><pre><code>composer req <span class="string">"php:*"</span></code></pre><p>Adjust the statement in case only specific PHP versions are supported.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c330"></a>
|
||
<a name="addingConfigurationFile"></a>
|
||
|
||
<h2>Adding configuration file <small><a href="#addingConfigurationFile">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>This step is optional, but suggested for TYPO3 extensions.</p><p>The package by default will check all files configured via composer autoloading. As TYPO3 extensions provide further files, e.g. configuration files, those need to be configured. This can happen with a custom JSON configuration file. No specific location is defined, it can be located in a subfolder or the projects root.</p><p>In the following example the file will be located in the extensions root folder and named <code>dependency-checker.json</code>. The contents of the file differ from project to project but might look like the following:</p><pre><code>{
|
||
<span class="attr">"scan-files"</span> : [
|
||
<span class="string">"*.php"</span>,
|
||
<span class="string">"Configuration/TCA/*.php"</span>,
|
||
<span class="string">"Configuration/TCA/Overrides/*.php"</span>
|
||
]
|
||
}</code></pre><p>An example can be found inside the projects repository: <a href="https://github.com/maglnet/ComposerRequireChecker/blob/2.1.0/data/config.dist.json">https://github.com/maglnet/ComposerRequireChecker/blob/2.1.0/data/config.dist.json</a>. The above example will configure the tool to scan further files. All PHP files in project root, as well as some PHP files configuring TCA.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c331"></a>
|
||
<a name="executing"></a>
|
||
|
||
<h2>Executing <small><a href="#executing">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Once everything is setup, one can execute the tool, this is done via:</p><pre><code>./vendor/bin/composer-<span class="built_in">require</span>-checker</code></pre><p>As a configuration file should be read, the full execution looks like:</p><pre><code>./vendor/bin/composer-<span class="built_in">require</span>-checker check --config-file dependency-checker.json</code></pre>
|
||
|
||
|
||
|
||
|
||
|
||
<aside class="admonition-note">
|
||
|
||
<a name="c332"></a>
|
||
<a name="note"></a>
|
||
|
||
<h2>Note <small><a href="#note">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>In order to allow the package to find all dependencies, execute the following composer command:</p><pre><code>composer install --no-plugins</code></pre><p>Otherwise plugins will kick in while installing dependencies. In case of TYPO3 they move dependencies out of vendor folder into TYPO3 project structure. This will prevent the tool from finding them.</p>
|
||
</aside>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c335"></a>
|
||
<a name="lastHint"></a>
|
||
|
||
<h2>Last hint <small><a href="#lastHint">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>The project aims to find missing dependencies in production code. It will not be extended or support <code>require-dev</code>, <code>suggest</code> or <code>autoload-dev</code>.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c334"></a>
|
||
<a name="furtherReading"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>The package can be found at:</p><ul> <li>Packagist: <a href="https://packagist.org/packages/maglnet/composer-require-checker">https://packagist.org/packages/maglnet/composer-require-checker</a></li> <li>GitHub: <a href="https://github.com/maglnet/ComposerRequireChecker">https://github.com/maglnet/ComposerRequireChecker</a></li> </ul><p>The following TYPO3 Extension uses the package and can be used as an example:</p><ul> <li>Example TYPO3 Extension: <a href="https://github.com/DanielSiepmann/tracking">https://github.com/DanielSiepmann/tracking</a></li> </ul>
|
||
|
||
|
||
]]></description>
|
||
<link>https://daniel-siepmann.de/composer-dependency-checker.html</link>
|
||
<pubDate>Wed, 16 Sep 2020 00:00:00 +0200</pubDate>
|
||
<guid isPermaLink="true">https://daniel-siepmann.de/composer-dependency-checker.html</guid>
|
||
</item>
|
||
|
||
|
||
|
||
|
||
|
||
<item>
|
||
<title>Hidden TYPO3 gem EXT:feedit</title>
|
||
<description><![CDATA[TYPO3 has hidden gems. One of them is the system extension feedit which adds frontend editing to TYPO3. It adds some options to the admin panel in TYPO3 frontend, and allows integrators to add more fine grained edit experience right into the content.
|
||
|
||
This post will cover most of the provided features, with screenshots and how to configure the extension. I'll also explain why I prefer the approach of EXT:feedit over extensions like EXT:frontend_editing.
|
||
|
||
|
||
|
||
<a name="c243"></a>
|
||
<a name="conceptsOfFrontendEditing"></a>
|
||
|
||
<h2>Concepts of frontend editing <small><a href="#conceptsOfFrontendEditing">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>First of all let me explain two different concepts how to edit frontend in TYPO3.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c244"></a>
|
||
<a name="firstConceptInlineEditing"></a>
|
||
|
||
<h3>First Concept: Inline Editing <small><a href="#firstConceptInlineEditing">¶</a></small></h3>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>The first concept is provided by <a href="https://extensions.typo3.org/extension/frontend_editing" title="TYPO3 Extension frontend_editing in TER">EXT:frontend_editing</a>. Some of you might know this concept from other systems like <a href="https://www.neos.io/" title="Content Management System Neos">Neos</a>. I would call this concept "live editing" or "inline editing". The basic idea is to wrap your existing frontend with all the tools you need. E.g. add a page tree to the left, an inspector to the right. This is more or less the same concept as word processors like LibreOffice or Microsoft Word use. You have a real live view of your content and edit it inline. Every change is visible in the context of your rendered website in current view port.</p><p>This approach is very intuitive for editors that didn't work with any older CMS. There is no system to learn. You log into the system and see your website. Go ahead and edit whatever you want to change. For more fine grained adjustments there is the inspector, which let's you edit the ratio of an image and other specific stuff.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c245"></a>
|
||
<a name="secondConceptStructuredEditing"></a>
|
||
|
||
<h3>Second Concept: Structured Editing <small><a href="#secondConceptStructuredEditing">¶</a></small></h3>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>One strength of TYPO3 is the structured content. In TYPO3 you have different types of content elements, all with their configured backend form. More or less all content information are saved in their own context and database fields. In order to keep this structure and still provide frontend editing, you would need to add information to your view and keep it in sync.</p><p>EXT:feedit still can provide a benefit, by not providing the same UX (=User Experience) to the editor as the concept above. Instead it will place little icons to interact with existing TYPO3 functionality. These icons are added to the admin panel and before or after all content elements and records. These Icons allow editors to hide or unhide, to sort and add elements, as well as opening an edit form for a content element, record or the page.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c246"></a>
|
||
<a name="howExtfeeditWorks"></a>
|
||
|
||
<h2>How EXT:feedit works <small><a href="#howExtfeeditWorks">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Now that we are aware of different concepts and the features of EXT:feedit, how does it work?</p><p>Frontend editing is provided in three different areas. The first one is the admin panel, which allows editors to work with the current page. The admin panel allows to open the current page in TYPO3 backend, edit the page, move the page, open list view or add new pages.</p><p>Also the admin panel allows to check two options. The first option is called "Display edit icons", the second one is called "Display edit panels". Both of them will be explained in more detail below, as they need to be configured by an TYPO3 integrator.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c249"></a>
|
||
<a name="featuresOfFeedit"></a>
|
||
|
||
<h2>Features of feedit <small><a href="#featuresOfFeedit">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Some features were already mentioned. Let's dig deeper into the features provided by EXT:feedit.</p><p>For an editor there is a single feature available by default: He can edit the current page via the admin panel.</p><p>All other features need to be configured by an integrator. The configuration is pretty easy, and TYPO3 EXT:fluid_styled_content is already pre configured.</p><p>The screenshots below will provide a better overview of the provided features.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c250"></a>
|
||
<a name="screenshotsOfExtfeedit"></a>
|
||
|
||
<h3>Screenshots of EXT:feedit <small><a href="#screenshotsOfExtfeedit">¶</a></small></h3>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<figure id="i15">
|
||
<a href="/fileadmin/Blog_Post_Content/Hidden_TYPO3_gem_EXT_feedit/AdminPanel.png" target="_blank"><img src="/fileadmin/Blog_Post_Content/Hidden_TYPO3_gem_EXT_feedit/AdminPanel.png" width="410" height="297" alt="EXT:feedit feature in admin panel. Allows to activate further features and edit current page." title="EXT:feedit feature in admin panel. Allows to activate further features and edit current page." /></a>
|
||
<figcaption>Figure i15: Displays the features in admin panel.
|
||
Allows to activate or deactivate further features configured by integrators.
|
||
Also allows to edit the current page.</figcaption>
|
||
</figure>
|
||
|
||
|
||
|
||
|
||
<figure id="i16">
|
||
<a href="/fileadmin/Blog_Post_Content/Hidden_TYPO3_gem_EXT_feedit/EditPanelAndIcon.png" target="_blank"><img src="/fileadmin/Blog_Post_Content/Hidden_TYPO3_gem_EXT_feedit/EditPanelAndIcon.png" width="1115" height="261" alt="Screenshot of editPanel and editIcon in page context " title="Screenshot of editPanel and editIcon in page context " /></a>
|
||
<figcaption>Figure i16: A screenshot of custom styled editPanel and editIcon.
|
||
The panel allows to edit, move, hide and delete the record.</figcaption>
|
||
</figure>
|
||
|
||
|
||
|
||
|
||
<figure id="i22">
|
||
<a href="/fileadmin/Blog_Post_Content/Hidden_TYPO3_gem_EXT_feedit/FrontendEditingExample.png" target="_blank"><img src="/fileadmin/Blog_Post_Content/Hidden_TYPO3_gem_EXT_feedit/FrontendEditingExample.png" width="2559" height="1414" alt="Example of how frontend editing can be used with EXT:feedit" title="Example of how frontend editing can be used with EXT:feedit" /></a>
|
||
<figcaption>Figure i22: Screenshot of this website. There are two browser windows. The left one shows the website with active backend login and enabled icons by EXT:feedit.
|
||
The right window shows the editing mask, opened by the icon. using the view button refreshes the left window, which allows for fast editing while seeing results instantly.</figcaption>
|
||
</figure>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c247"></a>
|
||
<a name="theEditpanel"></a>
|
||
|
||
<h3>The editPanel <small><a href="#theEditpanel">¶</a></small></h3>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Example is from this website:</p><pre><code>lib.contentElement = FLUIDTEMPLATE
|
||
lib.contentElement {
|
||
stdWrap {
|
||
editPanel = <span class="number">1</span>
|
||
editPanel {
|
||
printBeforeContent = <span class="number">1</span>
|
||
allow = edit, <span class="keyword">new</span>, <span class="keyword">delete</span>, move, hide
|
||
}
|
||
}
|
||
}</code></pre><p>That's one of the simplest approaches. The integrator enables the <code><a href="https://docs.typo3.org/m/typo3/reference-typoscript/master/en-us/Functions/Stdwrap.html#editpanel" title="TYPO3 TypoScript Reference entry for editPanel">editPanel</a></code> and configures it. Per default the panel will be displayed after content elements. I switch the behavior using <code>printBeforeContent = 1</code>.</p><p>Also there are different actions one can allow for the panel:</p><ul> <li>edit<br> Opens the edit form. This is the same form as when opened from TYPO3 backend.</li> <li>new<br> Opens the default content element (first item in drop down) in editing mode.<br> This could be improved in future, to use the "New Content Element" wizard.</li> <li>delete<br> Asks for confirmation before deleting the record.</li> <li>move<br> Allows editors to move the record, the same way as in list view of TYPO3 backend.</li> <li>hide<br> Asks for confirmation before hiding the record. If hidden records are displayed (which you can activate through admin panel), one can also enable the record again. There is no need for confirmation to enable a record.</li> </ul><p>The confirmations can be turned off, this is configurable via TSConfig <code><a href="https://docs.typo3.org/m/typo3/reference-tsconfig/master/en-us/UserTsconfig/Options.html#alertpopups" title="alertPopups Option at docs.typo3.org">options.alertPopups</a></code>.</p><p>There are also options to configure the <code>editPanel</code> for custom records, e.g. news. As I follow the "<a href="/posts/migrated/everything-is-content-that-can-be-served-via-solr.html">pages approach</a>", I don't have an example from this website yet.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c248"></a>
|
||
<a name="theEditicons"></a>
|
||
|
||
<h3>The editIcons <small><a href="#theEditicons">¶</a></small></h3>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Beside <code>editPanel</code> there is also <code><a href="https://docs.typo3.org/m/typo3/reference-typoscript/master/en-us/Functions/Stdwrap.html#editicons" title="TYPO3 TypoScript Reference entry for editIcons">editIcons</a></code>. While the panel adds a full blown toolbar, the icon is a single icon that allows editing of the record.</p><p>The icon not only prevents sorting, adding, deletion, etc. It also allows integrator to fine grain configure which fields to show. An example from this website looks like this:</p><pre><code>lib.contentElement = FLUIDTEMPLATE
|
||
lib.contentElement {
|
||
stdWrap {
|
||
editIcons = tt_content:
|
||
editIcons {
|
||
beforeLastTag = -1
|
||
iconTitle = Edit specific fields of content element
|
||
}
|
||
}
|
||
}
|
||
|
||
tt_content.text =<span class="tag">< <span class="attr">lib.contentElement</span>
|
||
<span class="attr">tt_content.text</span> {
|
||
<span class="attr">stdWrap</span> {
|
||
<span class="attr">editIcons</span> <span class="attr">:</span>= <span class="string">appendString(header,</span> <span class="attr">header_layout</span>, <span class="attr">layout</span>, <span class="attr">bodytext</span>)
|
||
}
|
||
}</span></code></pre><p>First of all editIcons are enabled for all content elements. I also configure to add the icon before the content element and provide a title. As every content element might have other fields that need to be edited, these are configured on a per content element level. In above example only the following fields are shown and editable for text content elements: <code>header, header_layout, layout, bodytext</code>.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c251"></a>
|
||
<a name="stateOfTheExtension"></a>
|
||
|
||
<h2>State of the extension <small><a href="#stateOfTheExtension">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>For a very long time the extension was part of TYPO3 core. Since v10 the extension was moved to FriendsOfTYPO3 GitHub account and is available at <a href="https://github.com/FriendsOfTYPO3/feedit">https://github.com/FriendsOfTYPO3/feedit</a>. The extension is also available as a composer package <a href="https://packagist.org/packages/friendsoftypo3/feedit" title="Composer package at packagist.org">friendsoftypo3/feedit</a>.</p><p>I've invested some hours to make it compatible with current TYPO3 master (v10) and to add two smaller features.</p><p>As the extension doesn't need much code and interacts in a clean way with TYPO3, it is very likely to stay for future versions.</p><p>I would highly appreciate more love to this hidden gem of TYPO3, which in my opinion improves the editing experience. It allows fast fixes to content and even provide a nice experience when creating new blog posts. All of that without heavy configuration.</p><p>This blog post was written using the extension.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c280"></a>
|
||
<a name="furtherReading"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<ul> <li>TypoScript cObject <code>EDITPANEL</code>: <a href="https://docs.typo3.org/m/typo3/reference-typoscript/master/en-us/ContentObjects/Editpanel/Index.html">https://docs.typo3.org/m/typo3/reference-typoscript/master/en-us/ContentObjects/Editpanel/Index.html</a></li> <li>TypoScript <code>stdWrap.editPanel</code>: <a href="https://docs.typo3.org/m/typo3/reference-typoscript/master/en-us/Functions/Stdwrap.html#editpanel">https://docs.typo3.org/m/typo3/reference-typoscript/master/en-us/Functions/Stdwrap.html#editpanel</a></li> <li>TypoScript <code>stdWrap.editIcons</code>: <a href="https://docs.typo3.org/m/typo3/reference-typoscript/master/en-us/Functions/Stdwrap.html#editicons">https://docs.typo3.org/m/typo3/reference-typoscript/master/en-us/Functions/Stdwrap.html#editicons</a></li> <li>TSconfig <code>options.alertPopups</code>: <a href="https://docs.typo3.org/m/typo3/reference-tsconfig/master/en-us/UserTsconfig/Options.html#alertpopups">https://docs.typo3.org/m/typo3/reference-tsconfig/master/en-us/UserTsconfig/Options.html#alertpopups</a></li> <li>Extension in TER: <a href="https://extensions.typo3.org/extension/feedit">https://extensions.typo3.org/extension/feedit</a></li> <li>Extension on packagist.org: <a href="https://packagist.org/packages/friendsoftypo3/feedit">https://packagist.org/packages/friendsoftypo3/feedit</a></li> <li>Extension on GitHub: <a href="https://github.com/FriendsOfTYPO3/feedit">https://github.com/FriendsOfTYPO3/feedit</a></li> <li><a href="/typo3-extension-feedit.html">TYPO3 Extension: feedit</a> on my site.</li> </ul>
|
||
|
||
|
||
]]></description>
|
||
<link>https://daniel-siepmann.de/hidden-typo3-gem-extfeedit.html</link>
|
||
<pubDate>Fri, 20 Mar 2020 00:00:00 +0100</pubDate>
|
||
<guid isPermaLink="true">https://daniel-siepmann.de/hidden-typo3-gem-extfeedit.html</guid>
|
||
</item>
|
||
|
||
|
||
|
||
|
||
|
||
<item>
|
||
<title>TYPO3 v10 feature outlook</title>
|
||
<description><![CDATA[With TYPO3 version 10 around the corner, it is time to have a deeper look at new features. I often hear something like "those features are only targeting developers". Therefore I try to promote opportunities for agencies and users, provided by the new more technical features.
|
||
|
||
Expect a round up of ext:dashboard, new notification API, PSR-14 Events, and some more.
|
||
|
||
|
||
|
||
<a name="c288"></a>
|
||
<a name="buildingBlocks"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>This block post should prove that TYPO3 sticks to his roots ans strengths. The biggest strength of TYPO3 is the basic idea of extensions. Do not build a single monolith, instead follow Unix philosophy. Each component should do a single thing very well. By adding clean APIs like PSR-15 or PSR-14 you can combine all those building blocks and create the system your client needs.</p><p>Also TYPO3 is going in a bright future. With all new standards and features, you can build awesome things. It is easier then ever to integrate stuff, leaving more time for other important parts, e.g. contribution to TYPO3.</p><p>What are you going to do with all the new awesome technical features? Which benefits will you create out of them for your customers or your own company?</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c282"></a>
|
||
<a name="relationToOfficialNews"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>TYPO3 version 10 has a new topic. Accordingly to the official news named "X Marks the Spot - TYPO3 v10.0 is here", "TYPO3 Version 10.1 - On the High Seas" and "TYPO3 Version 10.2 — Treasure Hunting!", the focus should be moved to everyone. Its not about the TYPO3 core team or any other team, its about the whole community. Everyone is involved and everyone can mark his own spot, sail on the high seas and find treasures.</p><p>This blog post should motivate you to find treasures and mark your own spot. Which feature will you find useful and what do you gonna do with it?</p><p>Did you know this website is running TYPO3 CMS v10.3 already?</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c283"></a>
|
||
<a name="psr14EventsAkaServerSideTracking"></a>
|
||
|
||
<h2>PSR-14 Events aka. server side tracking <small><a href="#psr14EventsAkaServerSideTracking">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>It was never easier to attach code to events. This opens possibilities e.g. for easier logging or tracking. Wanna know how many people submitted your contact form? Or how many orders where placed? With GDPR and foreign services, this is the time to get back to server side tracking. With a very simple API, you could just attach any internal Event to the API and record what happens in your system. It should also be possible to attach any Event to your API. That could also open possibilities for error reporting. Improve your exceptions with information from past events. That way you always know what was going on.</p><p>Back in the old days people just copied and modified 3rd Party Extensions. With ease of PSR-14, it is even easier to add and use Events. Wanna add a new registered user to a 3rd Party Service, e.g. newsletter system? Just use EXT:form to register a user, use hooks or form finisher and submit the information to another system.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c284"></a>
|
||
<a name="extdashboard"></a>
|
||
|
||
<h2>EXT:dashboard <small><a href="#extdashboard">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Agencies have a lot of customers. Those customers open issues, requesting features or reporting bugs. Some customers might have a service level agreement with some hours per month. Ever wanted to communicate all those information to your customers? Use the new dashboard. Create your own dashboards, connect your issue tracker and invoicing tool. Display how many issues are open, how many hours are used in the current month. Display open and payed invoices to your customers, right within TYPO3.</p><p>Combined with the mentioned server side tracking, or existing solutions like Google Analytics, you could also add important facts right there. No more need for your customer to log into multiple systems. TYPO3 can become the single system he needs to care about. Just integrate all necessary information. E.g. delivered newsletters, current solr index queue, etc.</p><p>Add some buttons to your widgets to allow users to take action. E.g. rank specific pages higher in a solr index queue. Or go into more detail in the system log, regarding current raised system errors shown in a dashboard.</p><p>The following screenshots demonstrate the current state of the dashboard.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c290"></a>
|
||
<a name="dashboardImages"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<figure id="i18">
|
||
<a href="/fileadmin/Blog_Post_Content/TYPO3_v10_feature_outlook/Dashboard-Initial.png" target="_blank"><img src="/fileadmin/Blog_Post_Content/TYPO3_v10_feature_outlook/Dashboard-Initial.png" width="1644" height="548" alt="Initial Dashboard created for all users" title="Initial Dashboard created for all users" /></a>
|
||
<figcaption>Figure i18: Screenshot of initial Dashboard (might change in future versions).
|
||
It has three widgets, where system information and RSS feed of typo3.org are the most important ones.</figcaption>
|
||
</figure>
|
||
|
||
|
||
|
||
|
||
<figure id="i17">
|
||
<a href="/fileadmin/Blog_Post_Content/TYPO3_v10_feature_outlook/Dashboard-Graph-Widgets.png" target="_blank"><img src="/fileadmin/Blog_Post_Content/TYPO3_v10_feature_outlook/Dashboard-Graph-Widgets.png" width="1644" height="548" alt="Some graph widgets provided by EXT:dashboard" title="Some graph widgets provided by EXT:dashboard" /></a>
|
||
<figcaption>Figure i17: Provides different graph types, which are used by a history of system log error entries over days, and number of normal and admin users.</figcaption>
|
||
</figure>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c287"></a>
|
||
<a name="psr15Middlewares"></a>
|
||
|
||
<h2>PSR-15 Middlewares <small><a href="#psr15Middlewares">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Sure, this one was delivered with TYPO3 CMS v9 LTS already, still this might be new to some of you, therefore I'll add my thoughts about that feature, too.</p><p>Middlewares are a stack of Code that is executed for each incoming request and outgoing response. That makes them more or less the heart of each web application. In combination with PSR-7 request and response, they allow developers to integrate more or less every clean system. For example TYPO3 can be run as a proxy before your shop system. Add some configuration and forward all incoming requests to your shop application. That way you can have a single entry point, and TYPO3 does not only handle multiple languages and websites, but also multiple applications.</p><p>The same reasons also allow the developers to integrate foreign frameworks, e.g. Slim or any other PHP framework. You need a website with an area where you build a web app, but maybe still need the layout and content of TYPO3? You could use a middleware and attach a php framework to integrate content. You could also use another framework to deliver an RESTful API or something else. Add some logic to a single middleware to detect whether requests should go to TYPO3 or your rest api integration.</p><p>Beside that awesome possibilities, middlewares also ease to adjust your request and response. E.g. adding specific header or resolving data. Let's say your TYPO3 is running behind a Proxy, e.g. Cloudflare or Varnish. The middleware can add further information that can be used by your Proxy. They can also resolve information added by the Proxy.</p><p>That were all very technical features, that would allow to integrate further systems and approaches. Yet there is another more basic example. In "PSR-14 Events aka. server side tracking" we already talked about server side tracking. A middleware is a perfect place to add basic page view tracking to your system. Just add an DB Entry for each request, with all necessary information. Adding Symfony Expression Language and you already have a black list configuration at your hand. E.g. disable tracking for active backend logins or crawler.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c285"></a>
|
||
<a name="notificationApiWithActions"></a>
|
||
|
||
<h2>Notification API with actions <small><a href="#notificationApiWithActions">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>The notification API was extended. Some might know the new feature from iOS or other operating systems. Each notification can now have attached actions.</p><p>Imagine an error in your TYPO3 installation. Instead of showing an exception, show a nice error page and raise a notification. Attach an "open issue" action and let the user create an issue in your issue tracker. Combine that one with ext:dashboard to display all those open issues and their current state.</p><p>It also allows you to make users decisions, without loosing context. One of our customers has a backend module that exports TYPO3 content to filesystem. There might be situation where things go wrong. Right now we display a modal with some actions. In future we could use a smoother user experience. Instead of a disturbing modal interrupting the current flow, we could display an notificitation which has actions attached.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c291"></a>
|
||
<a name="notificationApiImages"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<figure id="i19">
|
||
<a href="/fileadmin/Blog_Post_Content/TYPO3_v10_feature_outlook/Notification_API.png" target="_blank"><img src="/fileadmin/Blog_Post_Content/TYPO3_v10_feature_outlook/Notification_API.png" width="664" height="224" alt="Example of a warning with action via Notification API" title="Example of a warning with action via Notification API" /></a>
|
||
<figcaption>Figure i19: Screenshot displays the result of showing a warning notification with an action button.</figcaption>
|
||
</figure>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c286"></a>
|
||
<a name="fluidBasedPagelayoutview"></a>
|
||
|
||
<h2>Fluid based PageLayoutView <small><a href="#fluidBasedPagelayoutview">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>There was some love in a specific PHP Class, the PageLayoutView. This class generates the output of the "Page" backend module. I guess that module is the most used module within TYPO3. More or less all developers, integrators and editors work with this module. It is one of the biggest benefits for Editors. The module displays a mock like version of your frontend and allows to place content exactly where you wanna have it.</p><p>Still it was very old and integrators had a hard time to add custom preview for more complex content elements. Also extensions like Flux and Gridelements adjusted the module to make user experience better and to add nested content elements. Some people like that, some don't. There are different approaches to the user needs.</p><p>With the upcoming LTS v10, this situation was changed. After a refactoring improving the code base, a rewrite was done. Beside the original implementation, a new feature toggle allows to switch the implementation. Integrators can now change every single part through Fluid, as the whole module is now generated through Fluid. Adding a custom preview is just a matter of adding the Fluidtemplate and configure template paths, as done for the Frontend. Also each existing template can be replaced. Custom renderings can be added in addition, in order to allow developers to add custom PHP code upfront to prepare data, e.g. add information from an API.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c293"></a>
|
||
<a name="nativeNestedContentElements"></a>
|
||
|
||
<h2>Native nested content elements <small><a href="#nativeNestedContentElements">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Beside all that awesomeness, which will make live of integrators easier, and improve user experience of editors, that might come with another feature: Native nested content elements.</p>
|
||
|
||
|
||
|
||
|
||
|
||
<aside class="admonition-note">
|
||
|
||
<a name="c294"></a>
|
||
<a name="note"></a>
|
||
|
||
<h2>Note <small><a href="#note">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>That one is still work in progress and might make it into the final release. If it will be part of the LTS release, it will be a feature toggle and marked as experimental. People would need to opt in to this feature.</p>
|
||
</aside>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c296"></a>
|
||
<a name="nativeNestedContentElements2"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Thanks to the new code base, it is possible to configure layouts for content elements. That layout will be passed as structured information to the mentioned fluid templates. This allows native rendering of nested content elements inside the page module. A very long wished feature comes true for all developers, agencies and customers.</p><p>Some of you might ask: What about the structured content initiative? They still work on a big picture improving the overall editor experience for the upcoming versions. And they are in tight contact with developers working on that parts. Saying that, it is what makes TYPO3 great, a community effort.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c297"></a>
|
||
<a name="exampleExtensionTracking"></a>
|
||
|
||
<h2>Example extension "tracking" <small><a href="#exampleExtensionTracking">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>In order to test combinations of those features, I've created the extension "tracking". You can check the extension on GitHub: <a href="https://github.com/danielsiepmann/tracking">https://github.com/danielsiepmann/tracking</a> and find further information on a <a href="/projects/typo3-extension-tracking.html">separate site</a>.</p><p>The extension demonstrates how to collect data from requests, and displaying such data through new widgets. It already combines EXT:dashboard and PSR-15 middlewares. Also PSR-11 dependency injection is heavily used.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c295"></a>
|
||
<a name="acknowledgements"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Thanks to <a href="https://twitter.com/kevinappelt">Kevin Appelt</a>. The paragraph regarding "Nested content elements" was split up from "Fluid based PageLayoutView". Also a note was added that this feature is not yet released, together with further information.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c289"></a>
|
||
<a name="furtherReading"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Check the release news</p><ul> <li><a href="https://typo3.org/article/x-marks-the-spot-typo3-v10-0-is-here">TYPO3 News "X Marks the Spot - TYPO3 v10.0 is here"</a></li> <li><a href="https://typo3.org/article/typo3-v10-1-release-announcement">TYPO3 News "TYPO3 Version 10.1 - On the High Seas"</a></li> <li><a href="https://typo3.org/article/typo3-version-102-treasure-hunting">TYPO3 News "TYPO3 Version 10.2 — Treasure Hunting!"</a></li> <li><a href="https://typo3.org/article/typo3-version-103-almost-there">TYPO3 News "TYPO3 Version 10.3 — Almost There"</a></li> <li><a href="https://typo3.org/cms/roadmap/">Development Roadmap and release dates</a></li> </ul><p>And official documentation</p><ul> <li><a href="https://docs.typo3.org/c/typo3/cms-core/master/en-us/">TYPO3 ChangeLog</a></li> <li><a href="https://docs.typo3.org/m/typo3/reference-coreapi/master/en-us/ApiOverview/Hooks/EventDispatcher/Index.html">PSR-14 Events at docs.typo3.org</a></li> <li><a href="https://docs.typo3.org/m/typo3/reference-coreapi/master/en-us/ApiOverview/RequestHandling/Index.html">Request Handling / Middlewars PSR-15 at docs.typo3.org</a></li> <li><a href="https://docs.typo3.org/m/typo3/reference-coreapi/master/en-us/ApiOverview/FlashMessages/Index.html?highlight=notification#actions">Notification API at docs.typo3.org</a></li> </ul><p>And further resources</p><ul> <li>Example extension "tracking" <a href="https://github.com/danielsiepmann/tracking">on GitHub</a> and on <a href="/projects/typo3-extension-tracking.html">this site</a>.</li> </ul>
|
||
|
||
|
||
]]></description>
|
||
<link>https://daniel-siepmann.de/typo3-v10-feature-outlook.html</link>
|
||
<pubDate>Tue, 25 Feb 2020 00:00:00 +0100</pubDate>
|
||
<guid isPermaLink="true">https://daniel-siepmann.de/typo3-v10-feature-outlook.html</guid>
|
||
</item>
|
||
|
||
|
||
|
||
|
||
|
||
<item>
|
||
<title>Local mysqldump via SSH Tunnel</title>
|
||
<description><![CDATA[When using mysqldump, the tool has to be compatible with mysql server version. Some hosters might not provide such an environment. That's one reason when you wanna use mysqldump on your local system to create an mysql dump from a foreign database.
|
||
|
||
Most environments prevent remote access to the database server, which is a good thing. But this would prevent you from using mysqldump on your local environment. To overcome this issue, you can use ssh to create a temporary tunnel to the database server through another server, e.g. the production system.
|
||
|
||
|
||
|
||
<a name="c241"></a>
|
||
<a name="disclaimer"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>This one is rather a short blog post. It will not go into detail and explain the used tools and concepts. Instead it is more or less used to keep and share the knowledge. I find it especially necessary if you are working with german TYPO3 hoster mittwald.de.</p><p>Of course one can also use SSH and some database GUIs instead, which will do exactly the same. Just I like the CLI approach.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c240"></a>
|
||
<a name="creatingAndUsingTheTunnel"></a>
|
||
|
||
<h2>Creating and using the tunnel <small><a href="#creatingAndUsingTheTunnel">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<pre><code>ssh -L <span class="tag"><<span class="name">local-port</span>></span>:<span class="tag"><<span class="name">database-server</span>></span>:<span class="tag"><<span class="name">remote-port</span>></span> -N <span class="tag"><<span class="name">server-used-for-tunnel</span>></span></code></pre><p>So an example to create a tunnel to the database connection <code>db1234.dbserver.com</code> with Port <code>3306</code>, which is only available to <code>example.com</code> could look like this:</p><pre><code><span class="selector-tag">ssh</span> <span class="selector-tag">-L</span> 3307<span class="selector-pseudo">:db1234.dbservers.com</span><span class="selector-pseudo">:3306</span> <span class="selector-tag">-N</span> <span class="selector-tag">example</span><span class="selector-class">.com</span></code></pre><p>After the tunnel is created, the local port 3307 is passed through the tunnel. This enables the following mysqldump execution:</p><pre><code><span class="selector-tag">mysqldump</span> <span class="selector-tag">-u</span> <<span class="selector-tag">username</span>> <span class="selector-tag">-P</span> 3307 <span class="selector-tag">-h</span> 127<span class="selector-class">.0</span><span class="selector-class">.0</span><span class="selector-class">.1</span> <span class="selector-tag">-p</span> <<span class="selector-tag">dbname</span>> > <span class="selector-tag">dump</span><span class="selector-class">.sql</span>;</code></pre><p>The important part is to provide <code>-P 3307 -h 127.0.0.1</code>, in order to use the tunnel.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c242"></a>
|
||
<a name="furtherReading"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<ul> <li><a href="https://stackoverflow.com/questions/12031124/mysqldump-via-ssh-tunnel">https://stackoverflow.com/questions/12031124/mysqldump-via-ssh-tunnel</a></li> <li>SSH Man Page: <a href="https://linux.die.net/man/1/ssh">https://linux.die.net/man/1/ssh</a></li> </ul>
|
||
|
||
|
||
]]></description>
|
||
<link>https://daniel-siepmann.de/local-mysqldump-via-ssh-tunnel.html</link>
|
||
<pubDate>Thu, 30 Jan 2020 00:00:00 +0100</pubDate>
|
||
<guid isPermaLink="true">https://daniel-siepmann.de/local-mysqldump-via-ssh-tunnel.html</guid>
|
||
</item>
|
||
|
||
|
||
|
||
|
||
|
||
<item>
|
||
<title>TYPO3 Plugins as Content Elements</title>
|
||
<description><![CDATA[E.g. to add a simplified and project-specific News Extension Content element for backend editors.
|
||
|
||
You might think “I know what plugins, within TYPO3, are”. Maybe that’s true, maybe you will still learn something new.
|
||
|
||
This blog post will first explain what TYPO3 plugins are. But it will also explain how to define site specific plugins for existing installed 3rd party extensions, and why this might be useful.
|
||
|
||
|
||
|
||
<a name="c2"></a>
|
||
<a name="introduction"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>This will only cover Extbase plugins, as most extensions only provide Extbase nowadays. But it also works, partly, for pibase extensions. The basic idea dates back to 2018, when I first started to work on this. We now make use of this concept within an actual project, so this covers not only abstract concepts, but real world examples.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c3"></a>
|
||
<a name="targetAudience"></a>
|
||
|
||
<h2>Target audience <small><a href="#targetAudience">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>This blog post requires some TYPO3 knowledge in order to understand everything. This post targets integrators and developers, who already know how to write and use TypoScript, TSconfig, Fluid and TCA configuration. You should also know what <a href="https://docs.typo3.org/m/typo3/reference-coreapi/master/en-us/ApiOverview/FlexForms/Index.html" title=""FlexForms" in "TYPO3 Explained" master">FlexForms</a> are.</p><p>Also the official TYPO3 documentation section <a href="https://docs.typo3.org/m/typo3/reference-coreapi/master/en-us/ApiOverview/ContentElements/AddingYourOwnContentElements.html" title=""Custom Content Elements" in "TYPO3 Explained" current master">Adding your own content elements</a> is required in order to follow this blog post. This post will provide a complete example, but will not explain every taken step in order to create the new content element. Instead we will focus on the plugin becoming a regular content element.</p>
|
||
|
||
|
||
|
||
|
||
|
||
<aside class="admonition-hint">
|
||
|
||
<a name="c292"></a>
|
||
<a name="hint"></a>
|
||
|
||
<h2>Hint <small><a href="#hint">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>You might ended up here, because of the deprecation of "Switchable Controller Actions" in TYPO3 version 10.3. In that case, please check the following resources in addition to this blog post:</p><ul> <li><a href="https://docs.typo3.org/c/typo3/cms-core/master/en-us/Changelog/10.3/Deprecation-89463-SwitchableControllerActions.html">Official deprecation entry in ChangeLog</a></li> <li><a href="https://gist.github.com/alexanderschnitzler/c685218feea1a8956cc3f915f7a08d0b">Post on GitHub</a> explaining why "Switchable Controller Actions" were deprecated, by Alexander Schnitzler</li> <li>Dedicated TYPO3 Slack channel for this topic: <a href="https://typo3.slack.com/archives/CU2S0G2HX">#extbase-switchable-controller-actions</a></li> <li><a href="https://notes.typo3.org/p/switchable-controller-actions">notes.typo3.org document</a> to add pros and cons from your own perspective</li> </ul><p>Right now this blog post will explain one approach how to circumvent the need even before v10, so you can keep your created plugins. That is the same as solution number five from GitHub Post. Just I skipped how to disable original plugins, but would highly recommend that.</p><p>In the end this blog post was not intended as a solution to the deprecation, as it was written beforehand, it is just a nice side effect.</p>
|
||
</aside>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c4"></a>
|
||
<a name="whatIsATypo3Plugin"></a>
|
||
|
||
<h2>What is a TYPO3 plugin? <small><a href="#whatIsATypo3Plugin">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>To understand the whole blog post, one needs to understand the basics of plugins within TYPO3.</p><p>TYPO3 itself is nothing then a collection of so called “Extensions”. An extension is something that extends TYPO3 in any way. This can either be done by providing plugins and custom PHP code, or by providing CSS, JS, Fluid Templates, Hooks, or anything else. Within this post, we will only cover a specific aspect of plugins.</p><p>Plugins are a way to integrate custom PHP logic into TYPO3 for frontend websites. An editor is able to insert a new content element of type “Insert Plugin”, where he can select the specific plugin. This plugin can be something like “List news” or “List events”. A plugin can also be a search form or search result or some other kind of form. In the end, a plugin can be anything.</p><p>Most extensions provide plugins out of the box. Most likely you will have a single plugin per extension. The extension author allows the editor to select further configuration options through the content element, via so called FlexForms. E.g. the editor can select the “mode”, e.g. “list” or “detail” for something like news.</p><p>Within TYPO3 Extbase, a plugin consists of the following:</p><ul> <li>A title for the editor within the TYPO3 backend</li> <li>An optional icon within TYPO3 backend</li> <li>TypoScript defining the rendering of the plugin</li> <li>A combination of callable controllers and actions</li> <li>A combination of non cached callable controllers and actions</li> <li>An identifier, so called “plugin signature”</li> <li>An optional FlexForm for further configuration via editors</li> <li>An optional “New Content Element Wizard” entry for new content elements</li> </ul>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c5"></a>
|
||
<a name="whyAddingPluginsForExistingExtensions"></a>
|
||
|
||
<h2>Why adding plugins for existing extensions? <small><a href="#whyAddingPluginsForExistingExtensions">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>So extensions already provide plugins, why should one add further plugins to existing 3rd party extensions?</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c6"></a>
|
||
<a name="example1ExtsolrNews"></a>
|
||
|
||
<h3>Example 1 EXT:solr + news <small><a href="#example1ExtsolrNews">¶</a></small></h3>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Let’s assume there is a TYPO3 installation with a search and news. The search is provided by EXT:solr, and news are implemented using the “Custom Page Type approach™”, see (Blog Post: <a href="/posts/migrated/everything-is-content-that-can-be-served-via-solr.html">Everything is Content, that can be served via Solr</a> and TYPO3 Documentation <a href="https://docs.typo3.org/m/typo3/reference-coreapi/master/en-us/ApiOverview/PageTypes/Index.html#page-types" class="reference external" title="(in TYPO3 Explained vmaster (9-dev))">Page Types</a>). The news should be displayed by using EXT:solr, as there is no need for another extension, in this use case.</p><p>A typical use case would be to display a list of recent news, e.g. the five recent news on the startpage. Maybe also some pre filtered news should be displayed on sub pages, e.g. only news regarding new products or news regarding the company. All those use cases are solved by using EXT:solr.</p><p>Of course one could now add TypoScript to pages to configure EXT:solr to start in filter mode instead of search mode. Also filters can be added to only show news records from these categories. This is not that flexible. The editor is not able to add new “News listings” to further pages, as TypoScript is involved.</p><p>It would be better if the integrator can add a new plugin “news” within the “Sitepackage™” of the installation. This plugin duplicates the existing plugin, provided by EXT:solr.</p><p>Benefits of this approach would be:</p><ul class="simple"> <li>Instead of keeping the result action none cacheable, it can define that this action should be cacheable.</li> <li>Also a new plugin allows to add a different FlexForm to this plugin. This FlexForm can provide a drop down with possible categories, or allows an editor to define how many news should be displayed. Thanks to Extbase conventions, all options available within TypoScript settings section can be used within FlexForms. Due to a different plugin signature, the plugin can be configured differently via TypoScript.</li> </ul><p>This new Plugin speeds up the delivery of the page, as it’s fully cached. Also an editor can now add a “news” content element and select the specific category and number of news to display. He does not need to understand that solr is used.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c7"></a>
|
||
<a name="example2Extnews"></a>
|
||
|
||
<h3>Example 2 - EXT:news <small><a href="#example2Extnews">¶</a></small></h3>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>In case of EXT:news, one might want to add “recent news” to the pages. This might contain a configurable number of news entries and different layouts, like “list” or “slider”. This is another example where custom plugins for existing 3rd party extensions might be useful. One can create those content elements and plugins.</p><p>Another benefit of this example: One can add “recent news” on a news detail page without thinking about any limitations. Due to being another plugin with a different signature, no arguments might create trouble. Also links created between those plugins can make use of the Extbase setting:</p><pre><code>plugin.tx_news_recentnews {
|
||
features {
|
||
skipDefaultArguments = 1
|
||
}
|
||
}</code></pre><p>This can also be enabled for the whole extension:</p><pre><code>plugin.tx_news {
|
||
features {
|
||
skipDefaultArguments = 1
|
||
}
|
||
}</code></pre><p>Or the whole installation / page:</p><pre><code>config.tx_extbase {
|
||
features {
|
||
skipDefaultArguments = 1
|
||
}
|
||
}</code></pre><p>A link between those plugins can look like this, assuming to link from “Recent News” to “Detail News” custom plugin:</p><pre><code><span class="tag"><<span class="name">f:link.action</span> <span class="attr">pageUid</span>=<span class="string">"11"</span>
|
||
<span class="attr">pluginName</span>=<span class="string">"Details"</span>
|
||
<span class="attr">arguments</span>=<span class="string">"{news: news}"</span>
|
||
></span>
|
||
<span class="tag"><<span class="name">h4</span>></span>{news.title}<span class="tag"></<span class="name">h4</span>></span>
|
||
<span class="tag"></<span class="name">f:link.action</span>></span></code></pre><p>As each plugin has its own default Controller-Action-Combination, there is no need to add them to the URL generation. Also thanks to the configuration of skipDefaultArguments, these will not be added to the url, resulting in an URL like this with CMS v9:</p><pre><code>/?news_details%5Bnews%5D=1785&cHash=1f740d5404dddcf84b2c8bebc985deb9</code></pre>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c8"></a>
|
||
<a name="howToAddANewTypo3Plugin"></a>
|
||
|
||
<h2>How to add a new TYPO3 plugin <small><a href="#howToAddANewTypo3Plugin">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>To add a new plugin, first of one API call is necessary. After this was done, the plugin is already available to the frontend. Next, the content element can be created in the preferred way, which depends on the agency and developer.</p><p>Afterwards the optional FlexForm and TypoScript configuration can be added.</p><p>For further information, take a look at <a href="/posts/2019/typo3-plugins-as-content-elements.html#c10">Real world example</a>.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c9"></a>
|
||
<a name="conclusionForExtbaseController"></a>
|
||
|
||
<h2>Conclusion for Extbase controller <small><a href="#conclusionForExtbaseController">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Each controller within an Extbase extension consists of actions, which should only do a single task each. By providing fine grained actions for single tasks, the Integrator is able to configure installation specific plugins, with new combinations of existing controllers and actions.</p><p>A contrary example was developed by myself and our team during my training. There we created a single controller with nearly 10 actions, all doing the same. The reason for those actions was to provide 10 different template variants. Today one could use ten custom plugins. Or even better use a setting like the <em>layout</em> field within the content element, together with an <em>f:render</em> call within Fluid to switch the rendering. But this will not be covered here. Just make sure, actions and controllers are written in a clean, reusable way.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c10"></a>
|
||
<a name="realWorldExample"></a>
|
||
|
||
<h2>Real world example <small><a href="#realWorldExample">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>The following example demonstrates the concept based on EXT:news and a new content element to display recent news. The editor can configure how many news should be displayed.</p><p>Register plugin within ext_localconf.php:</p><pre><code>\TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin(
|
||
<span class="string">'GeorgRinger.news'</span>,
|
||
<span class="string">'Recent'</span>,
|
||
[
|
||
<span class="string">'News'</span> => <span class="string">'list'</span>,
|
||
],
|
||
[],
|
||
\TYPO3\CMS\Extbase\Utility\ExtensionUtility::PLUGIN_TYPE_CONTENT_ELEMENT
|
||
);</code></pre><p>Configure TCA for content element within <code>Configuration/TCA/Overrides/tt_content_news_recent.php</code>:</p><pre><code>(<span class="function"><span class="keyword">function</span> <span class="params">($tablename = <span class="string">'tt_content'</span>, $contentType = <span class="string">'news_recent'</span>)</span> </span>{
|
||
\TYPO3\CMS\Core\Utility\ArrayUtility::mergeRecursiveWithOverrule($GLOBALS[<span class="string">'TCA'</span>][$tablename], [
|
||
<span class="string">'ctrl'</span> => [
|
||
<span class="string">'typeicon_classes'</span> => [
|
||
$contentType => <span class="string">'content-recent-news'</span>,
|
||
],
|
||
],
|
||
<span class="string">'types'</span> => [
|
||
$contentType => [
|
||
<span class="string">'showitem'</span> => implode(<span class="string">','</span>, [
|
||
<span class="string">'--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:general'</span>,
|
||
<span class="string">'--palette--;;general'</span>,
|
||
<span class="string">'pi_flexform'</span>,
|
||
<span class="string">'--div--;LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:tabs.appearance,--palette--;;frames,--palette--;;appearanceLinks,'</span>,
|
||
<span class="string">'--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:language,--palette--;;language,'</span>,
|
||
<span class="string">'--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:access,
|
||
--palette--;;hidden,
|
||
--palette--;;access,
|
||
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:categories,
|
||
categories,
|
||
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:notes,
|
||
rowDescription,
|
||
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:extended,'</span>
|
||
]),
|
||
],
|
||
],
|
||
<span class="string">'columns'</span> => [
|
||
<span class="string">'pi_flexform'</span> => [
|
||
<span class="string">'config'</span> => [
|
||
<span class="string">'ds'</span> => [
|
||
<span class="string">'*,'</span> . $contentType => <span class="string">'FILE:EXT:sitepackage/Configuration/FlexForms/ContentElements/RecentNews.xml'</span>,
|
||
],
|
||
],
|
||
],
|
||
],
|
||
]);
|
||
|
||
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTcaSelectItem(
|
||
$tablename,
|
||
<span class="string">'CType'</span>,
|
||
[
|
||
<span class="string">'Recent News'</span>,
|
||
$contentType,
|
||
<span class="string">'content-recent-news'</span>,
|
||
],
|
||
<span class="string">'textmedia'</span>,
|
||
<span class="string">'after'</span>
|
||
);
|
||
})();</code></pre><p>Optionally, add and register FlexForm.</p><p>Registration is happening in TCA, see above example, line 27-35. Please note that there must not be any whitespace in the array key when registering the FlexForm: <code>'*,' . $contentType</code>.</p><p>The FlexForm itself can look like the following <code>Configuration/FlexForms/ContentElements/RecentNews.xml</code>.:</p><pre><code><span class="tag"><<span class="name">T3DataStructure</span>></span>
|
||
<span class="tag"><<span class="name">sheets</span>></span>
|
||
<span class="tag"><<span class="name">sDEF</span>></span>
|
||
<span class="tag"><<span class="name">ROOT</span>></span>
|
||
<span class="tag"><<span class="name">TCEforms</span>></span>
|
||
<span class="tag"><<span class="name">sheetTitle</span>></span>LLL:EXT:news/Resources/Private/Language/locallang_be.xlf:flexforms_tab.settings<span class="tag"></<span class="name">sheetTitle</span>></span>
|
||
<span class="tag"></<span class="name">TCEforms</span>></span>
|
||
<span class="tag"><<span class="name">type</span>></span>array<span class="tag"></<span class="name">type</span>></span>
|
||
<span class="tag"><<span class="name">el</span>></span>
|
||
<span class="comment"><!-- Limit Start --></span>
|
||
<span class="tag"><<span class="name">settings.limit</span>></span>
|
||
<span class="tag"><<span class="name">TCEforms</span>></span>
|
||
<span class="tag"><<span class="name">label</span>></span>LLL:EXT:news/Resources/Private/Language/locallang_be.xlf:flexforms_additional.limit<span class="tag"></<span class="name">label</span>></span>
|
||
<span class="tag"><<span class="name">config</span>></span>
|
||
<span class="tag"><<span class="name">type</span>></span>input<span class="tag"></<span class="name">type</span>></span>
|
||
<span class="tag"><<span class="name">size</span>></span>5<span class="tag"></<span class="name">size</span>></span>
|
||
<span class="tag"><<span class="name">eval</span>></span>num<span class="tag"></<span class="name">eval</span>></span>
|
||
<span class="tag"></<span class="name">config</span>></span>
|
||
<span class="tag"></<span class="name">TCEforms</span>></span>
|
||
<span class="tag"></<span class="name">settings.limit</span>></span>
|
||
|
||
<span class="comment"><!-- Offset --></span>
|
||
<span class="tag"><<span class="name">settings.offset</span>></span>
|
||
<span class="tag"><<span class="name">TCEforms</span>></span>
|
||
<span class="tag"><<span class="name">label</span>></span>LLL:EXT:news/Resources/Private/Language/locallang_be.xlf:flexforms_additional.offset<span class="tag"></<span class="name">label</span>></span>
|
||
<span class="tag"><<span class="name">config</span>></span>
|
||
<span class="tag"><<span class="name">type</span>></span>input<span class="tag"></<span class="name">type</span>></span>
|
||
<span class="tag"><<span class="name">size</span>></span>5<span class="tag"></<span class="name">size</span>></span>
|
||
<span class="tag"><<span class="name">eval</span>></span>num<span class="tag"></<span class="name">eval</span>></span>
|
||
<span class="tag"></<span class="name">config</span>></span>
|
||
<span class="tag"></<span class="name">TCEforms</span>></span>
|
||
<span class="tag"></<span class="name">settings.offset</span>></span>
|
||
|
||
<span class="comment"><!-- Category Mode --></span>
|
||
<span class="tag"><<span class="name">settings.categoryConjunction</span>></span>
|
||
<span class="tag"><<span class="name">TCEforms</span>></span>
|
||
<span class="tag"><<span class="name">label</span>></span>LLL:EXT:news/Resources/Private/Language/locallang_be.xlf:flexforms_general.categoryConjunction<span class="tag"></<span class="name">label</span>></span>
|
||
<span class="tag"><<span class="name">config</span>></span>
|
||
<span class="tag"><<span class="name">type</span>></span>select<span class="tag"></<span class="name">type</span>></span>
|
||
<span class="tag"><<span class="name">renderType</span>></span>selectSingle<span class="tag"></<span class="name">renderType</span>></span>
|
||
<span class="tag"><<span class="name">items</span>></span>
|
||
<span class="tag"><<span class="name">numIndex</span> <span class="attr">index</span>=<span class="string">"0"</span> <span class="attr">type</span>=<span class="string">"array"</span>></span>
|
||
<span class="tag"><<span class="name">numIndex</span> <span class="attr">index</span>=<span class="string">"0"</span>></span>LLL:EXT:news/Resources/Private/Language/locallang_be.xlf:flexforms_general.categoryConjunction.all<span class="tag"></<span class="name">numIndex</span>></span>
|
||
<span class="tag"><<span class="name">numIndex</span> <span class="attr">index</span>=<span class="string">"1"</span>></span><span class="tag"></<span class="name">numIndex</span>></span>
|
||
<span class="tag"></<span class="name">numIndex</span>></span>
|
||
<span class="tag"><<span class="name">numIndex</span> <span class="attr">index</span>=<span class="string">"1"</span>></span>
|
||
<span class="tag"><<span class="name">numIndex</span> <span class="attr">index</span>=<span class="string">"0"</span>></span>LLL:EXT:news/Resources/Private/Language/locallang_be.xlf:flexforms_general.categoryConjunction.or<span class="tag"></<span class="name">numIndex</span>></span>
|
||
<span class="tag"><<span class="name">numIndex</span> <span class="attr">index</span>=<span class="string">"1"</span>></span>or<span class="tag"></<span class="name">numIndex</span>></span>
|
||
<span class="tag"></<span class="name">numIndex</span>></span>
|
||
<span class="tag"><<span class="name">numIndex</span> <span class="attr">index</span>=<span class="string">"2"</span>></span>
|
||
<span class="tag"><<span class="name">numIndex</span> <span class="attr">index</span>=<span class="string">"0"</span>></span>LLL:EXT:news/Resources/Private/Language/locallang_be.xlf:flexforms_general.categoryConjunction.and<span class="tag"></<span class="name">numIndex</span>></span>
|
||
<span class="tag"><<span class="name">numIndex</span> <span class="attr">index</span>=<span class="string">"1"</span>></span>and<span class="tag"></<span class="name">numIndex</span>></span>
|
||
<span class="tag"></<span class="name">numIndex</span>></span>
|
||
<span class="tag"><<span class="name">numIndex</span> <span class="attr">index</span>=<span class="string">"3"</span>></span>
|
||
<span class="tag"><<span class="name">numIndex</span> <span class="attr">index</span>=<span class="string">"0"</span>></span>LLL:EXT:news/Resources/Private/Language/locallang_be.xlf:flexforms_general.categoryConjunction.notor<span class="tag"></<span class="name">numIndex</span>></span>
|
||
<span class="tag"><<span class="name">numIndex</span> <span class="attr">index</span>=<span class="string">"1"</span>></span>notor<span class="tag"></<span class="name">numIndex</span>></span>
|
||
<span class="tag"></<span class="name">numIndex</span>></span>
|
||
<span class="tag"><<span class="name">numIndex</span> <span class="attr">index</span>=<span class="string">"4"</span>></span>
|
||
<span class="tag"><<span class="name">numIndex</span> <span class="attr">index</span>=<span class="string">"0"</span>></span>LLL:EXT:news/Resources/Private/Language/locallang_be.xlf:flexforms_general.categoryConjunction.notand<span class="tag"></<span class="name">numIndex</span>></span>
|
||
<span class="tag"><<span class="name">numIndex</span> <span class="attr">index</span>=<span class="string">"1"</span>></span>notand<span class="tag"></<span class="name">numIndex</span>></span>
|
||
<span class="tag"></<span class="name">numIndex</span>></span>
|
||
<span class="tag"></<span class="name">items</span>></span>
|
||
<span class="tag"></<span class="name">config</span>></span>
|
||
<span class="tag"></<span class="name">TCEforms</span>></span>
|
||
<span class="tag"></<span class="name">settings.categoryConjunction</span>></span>
|
||
|
||
<span class="comment"><!-- Category --></span>
|
||
<span class="tag"><<span class="name">settings.categories</span>></span>
|
||
<span class="tag"><<span class="name">TCEforms</span>></span>
|
||
<span class="tag"><<span class="name">label</span>></span>LLL:EXT:news/Resources/Private/Language/locallang_be.xlf:flexforms_general.categories<span class="tag"></<span class="name">label</span>></span>
|
||
<span class="tag"><<span class="name">config</span>></span>
|
||
<span class="tag"><<span class="name">type</span>></span>select<span class="tag"></<span class="name">type</span>></span>
|
||
<span class="tag"><<span class="name">renderMode</span>></span>tree<span class="tag"></<span class="name">renderMode</span>></span>
|
||
<span class="tag"><<span class="name">renderType</span>></span>selectTree<span class="tag"></<span class="name">renderType</span>></span>
|
||
<span class="tag"><<span class="name">treeConfig</span>></span>
|
||
<span class="tag"><<span class="name">dataProvider</span>></span>GeorgRinger\News\TreeProvider\DatabaseTreeDataProvider<span class="tag"></<span class="name">dataProvider</span>></span>
|
||
<span class="tag"><<span class="name">parentField</span>></span>parent<span class="tag"></<span class="name">parentField</span>></span>
|
||
<span class="tag"><<span class="name">appearance</span>></span>
|
||
<span class="tag"><<span class="name">maxLevels</span>></span>99<span class="tag"></<span class="name">maxLevels</span>></span>
|
||
<span class="tag"><<span class="name">expandAll</span>></span>TRUE<span class="tag"></<span class="name">expandAll</span>></span>
|
||
<span class="tag"><<span class="name">showHeader</span>></span>TRUE<span class="tag"></<span class="name">showHeader</span>></span>
|
||
<span class="tag"></<span class="name">appearance</span>></span>
|
||
<span class="tag"></<span class="name">treeConfig</span>></span>
|
||
<span class="tag"><<span class="name">foreign_table</span>></span>sys_category<span class="tag"></<span class="name">foreign_table</span>></span>
|
||
<span class="tag"><<span class="name">foreign_table_where</span>></span>AND (sys_category.sys_language_uid = 0 OR sys_category.l10n_parent = 0) ORDER BY sys_category.sorting<span class="tag"></<span class="name">foreign_table_where</span>></span>
|
||
<span class="tag"><<span class="name">size</span>></span>15<span class="tag"></<span class="name">size</span>></span>
|
||
<span class="tag"><<span class="name">minitems</span>></span>0<span class="tag"></<span class="name">minitems</span>></span>
|
||
<span class="tag"><<span class="name">maxitems</span>></span>99<span class="tag"></<span class="name">maxitems</span>></span>
|
||
<span class="tag"></<span class="name">config</span>></span>
|
||
<span class="tag"></<span class="name">TCEforms</span>></span>
|
||
<span class="tag"></<span class="name">settings.categories</span>></span>
|
||
|
||
<span class="comment"><!-- Include sub categories --></span>
|
||
<span class="tag"><<span class="name">settings.includeSubCategories</span>></span>
|
||
<span class="tag"><<span class="name">TCEforms</span>></span>
|
||
<span class="tag"><<span class="name">label</span>></span>LLL:EXT:news/Resources/Private/Language/locallang_be.xlf:flexforms_general.includeSubCategories<span class="tag"></<span class="name">label</span>></span>
|
||
<span class="tag"><<span class="name">config</span>></span>
|
||
<span class="tag"><<span class="name">type</span>></span>check<span class="tag"></<span class="name">type</span>></span>
|
||
<span class="tag"></<span class="name">config</span>></span>
|
||
<span class="tag"></<span class="name">TCEforms</span>></span>
|
||
<span class="tag"></<span class="name">settings.includeSubCategories</span>></span>
|
||
<span class="tag"></<span class="name">el</span>></span>
|
||
<span class="tag"></<span class="name">ROOT</span>></span>
|
||
<span class="tag"></<span class="name">sDEF</span>></span>
|
||
<span class="tag"></<span class="name">sheets</span>></span>
|
||
<span class="tag"></<span class="name">T3DataStructure</span>></span></code></pre><p>Configure PageTSconfig for the content element to add it to the new content element wizard:</p><pre><code>mod {
|
||
wizards.newContentElement.wizardItems.common {
|
||
elements {
|
||
news_recent {
|
||
iconIdentifier = content-recent-news
|
||
title = Recent News
|
||
description = Displayes recent news
|
||
tt_content_defValues {
|
||
CType = news_recent
|
||
pi_flexform (
|
||
<span class="meta"><?xml version="1.0" encoding="utf-8" standalone="yes" ?></span>
|
||
<span class="tag"><<span class="name">T3FlexForms</span>></span>
|
||
<span class="tag"><<span class="name">data</span>></span>
|
||
<span class="tag"><<span class="name">sheet</span> <span class="attr">index</span>=<span class="string">"sDEF"</span>></span>
|
||
<span class="tag"><<span class="name">language</span> <span class="attr">index</span>=<span class="string">"lDEF"</span>></span>
|
||
<span class="tag"><<span class="name">field</span> <span class="attr">index</span>=<span class="string">"settings.limit"</span>></span>
|
||
<span class="tag"><<span class="name">value</span> <span class="attr">index</span>=<span class="string">"vDEF"</span>></span>4<span class="tag"></<span class="name">value</span>></span>
|
||
<span class="tag"></<span class="name">field</span>></span>
|
||
<span class="tag"></<span class="name">language</span>></span>
|
||
<span class="tag"></<span class="name">sheet</span>></span>
|
||
<span class="tag"></<span class="name">data</span>></span>
|
||
<span class="tag"></<span class="name">T3FlexForms</span>></span>
|
||
)
|
||
}
|
||
}
|
||
}
|
||
show := addToList(news_recent)
|
||
}
|
||
web_layout.tt_content.preview.news_recent = EXT:sitepackage/Resources/Private/Templates/ContentElementsPreview/RecentNews.html
|
||
}</code></pre><p>Configure TypoScript for rendering of content element: (This example assumes EXT:fluid_styled_content is used)</p><pre><code>plugin.tx_news_recent {
|
||
settings {
|
||
orderBy = datetime
|
||
orderDirection = desc
|
||
}
|
||
view {
|
||
templateRootPaths {
|
||
<span class="number">10</span> = EXT:sitepackage/Resources/<span class="keyword">Private</span>/Templates/Plugins/News/RecentNews/
|
||
}
|
||
pluginNamespace = news_recent
|
||
}
|
||
}</code></pre><p>Add fluid template accordingly to configured paths.</p><p>Optionally, register Icon for content element within <code>ext_localconf.php</code>:</p><pre><code>$icons = [
|
||
<span class="string">'content-recent-news'</span> => <span class="string">'EXT:news/Resources/Public/Icons/Extension.svg'</span>,
|
||
];
|
||
$iconRegistry = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Core\Imaging\IconRegistry::class);
|
||
<span class="keyword">foreach</span> ($icons <span class="keyword">as</span> $identifier => $path) {
|
||
$iconRegistry->registerIcon(
|
||
$identifier,
|
||
\TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider::class,
|
||
[<span class="string">'source'</span> => $path]
|
||
);
|
||
}</code></pre>
|
||
|
||
|
||
|
||
|
||
|
||
<aside class="admonition-note">
|
||
|
||
<a name="c148"></a>
|
||
<a name="note"></a>
|
||
|
||
<h2>Note <small><a href="#note">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>For several reasons, don’t hardcode labels, instead use <code>LLL:EXT:sitepackage/Resources/Private/locallang.xlf</code> references. In order to keep the example code short, this rule is broken.</p>
|
||
</aside>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c23"></a>
|
||
<a name="acknowledgements"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Acknowledgements to <a href="https://www.ppw.de/" class="reference external">pietzpluswild GmbH</a> and <a href="https://km2.de/" class="reference external">KM2 >> GmbH</a> who allowed me to dive into the topic and to implement a solution for their customers.</p><p>Also thanks to <a href="https://twitter.com/jouschcom" class="reference external">Josef Glatz</a> for proof reading and contributing to the Blog post. He also motivated me to finish this post.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c24"></a>
|
||
<a name="furtherReading"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<ul> <li><a href="https://docs.typo3.org/m/typo3/reference-coreapi/master/en-us/ApiOverview/FlexForms/Index.html" title=""FlexForms" in "TYPO3 Explained" master">FlexForm data structure</a></li> <li><a href="https://docs.typo3.org/m/typo3/book-extbasefluid/master/en-us/4-FirstExtension/7-configuring-the-plugin.html#configuring-the-plugin" class="reference external" title="(in Developing TYPO3 Extensions with Extbase and Fluid vmaster)">Configuring The Plugin</a></li> <li><a href="https://docs.typo3.org/m/typo3/reference-tsconfig/master/en-us/PageTsconfig/Mod.html#pagenewcontentelementwizard" class="reference external" title="(in TSconfig Reference vmaster (9-dev))">newContentElement.wizardItems</a></li> <li><a href="https://docs.typo3.org/m/typo3/reference-tca/master/en-us/Index.html#start" class="reference external" title="(in TCA Reference vmaster (9-dev))">TCA Reference</a></li> <li><a href="https://docs.typo3.org/m/typo3/reference-coreapi/master/en-us/ApiOverview/Icon/Index.html#icon" class="reference external" title="(in TYPO3 Explained vmaster (9-dev))">Icon API</a></li> <li><a href="https://docs.typo3.org/m/typo3/reference-coreapi/master/en-us/ApiOverview/ContentElements/AddingYourOwnContentElements.html" title=""Custom Content Elements" in "TYPO3 Explained" master">Adding your own content elements</a></li> </ul>
|
||
|
||
|
||
]]></description>
|
||
<link>https://daniel-siepmann.de/posts/2019/typo3-plugins-as-content-elements.html</link>
|
||
<pubDate>Wed, 13 Nov 2019 22:31:00 +0100</pubDate>
|
||
<guid isPermaLink="true">https://daniel-siepmann.de/posts/2019/typo3-plugins-as-content-elements.html</guid>
|
||
</item>
|
||
|
||
|
||
|
||
|
||
|
||
<item>
|
||
<title>TYPO3 content caching</title>
|
||
<description><![CDATA[TYPO3 provides a comprehensive caching implementation. Built into all parts of TYPO3. By default TYPO3 tries hard to provide a working caching solution for Websites. This way all generated content is cached whenever possible and delivered right from cache without rendering. This leads to some issues if dynamic content is added. Some bypass this issue by deactivating caching partly or for whole pages.
|
||
|
||
This post explains how to configure TYPO3 to make caching work without deactivating it. One place where caching does not work out of the box is changing files via Filelist module, changes there will not be reflected in content elements using a file reference. This example is used to explain how caching works in TYPO3 and how to adjust caching.
|
||
|
||
|
||
|
||
<a name="c11"></a>
|
||
<a name="introduction"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>As caching is a huge topic, this post will only cover the mentioned small part. Do not expect a complete explanation of all parts. The caching framework itself, useful in custom PHP Code is already explained in <a href="https://docs.typo3.org/m/typo3/reference-coreapi/master/en-us/ApiOverview/CachingFramework/Architecture/Index.html#caching-architecture" class="reference external" title="(in TYPO3 Explained vmaster (9-dev))">Caching Framework Architecture</a>. Also Plugins are not part of this post. Instead this post will focuses on content elements and proper cache invalidation.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c12"></a>
|
||
<a name="theExample"></a>
|
||
|
||
<h2>The example <small><a href="#theExample">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>This post will use the following example:</p><p>A page using a file reference, e.g. an Textmedia or Uploads content element where an image is rendered. This image does not have any property set in the reference, e.g. no title or description. Also the original file which is referenced does not contain any information yet.</p><p>Still the template will render those information if available. Now the editor will add those information to the file via Filelist. Still the information is not displayed in frontend, until the cache is cleared.</p><p>This is default TYPO3 behaviour, which might be “fixed” in the future. Till now, this works as an example how to adjust TYPO3 to clear cache for all pages using this file, to display the new information immediately.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c16"></a>
|
||
<a name="understandingExistingCaching"></a>
|
||
|
||
<h2>Understanding existing caching <small><a href="#understandingExistingCaching">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>At the end of serving a rendered page, TYPO3 will cache the rendered markup. Some parts might be replaced with a marker, but those are not part of this post. If anything on the page is changed, e.g. the editor is saving a content element, TYPO3 will clear the cache for this page. This way the next visitor will trigger a new rendering and see the results. Also the new generated markup is added to the cache again.</p><p>How does TYPO3 invalidate, aka clear, the cache?</p><p>Two PHP Classes are involved here, one is the <code>\TYPO3\CMS\Core\DataHandling\DataHandler</code> which is <em>THE</em> API to change any data inside of TYPO3. The second class is <code>TYPO3\CMS\Core\Cache\CacheManager</code>, which is the API to all caches. The DataHandler receives the call to update a certain content element and will generate some Cache Tags out of that record. These tags are then send for clearing to the <code>CacheManager</code>.</p><p>TYPO3 already assigns necessary cache tags like <code>pageId_6</code> to each rendered page cache entry. This way it’s possible to clear cache entries for a single page by clearing the accordingly cache tag.</p><p><code>DataHandler</code> also generates these cache tag, e.g. for each record that is updated, the <code>DataHandler</code> will look up the page where the record is persisted, which is the pid column of each record. This cache tags are then cleared, leading to an invalidated generated page markup.</p><p>This way TYPO3 works out of the box. But not all circumstances are handled yet. E.g. the above example with file references does not work.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c17"></a>
|
||
<a name="concreteCacheClearingExample"></a>
|
||
|
||
<h2>Concrete cache clearing example <small><a href="#concreteCacheClearingExample">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Let’s follow a concrete example:</p><p>There is one page with uid <code>10</code> and a single Textmedia content element with uid <code>20</code> on page <code>10</code>. This content element displays some text and an image. The image is a <code>sys_file_reference</code> with uid <code>30</code> referencing <code>sys_file</code> with uid <code>40</code>.</p><p>Once the content element is updated, <code>DataHandler</code> will generate the following cache tags:</p><ul> <li>pageId_10</li> <li>tt_content</li> <li>tt_content_20</li> <li>sys_file_reference</li> <li>sys_file_reference_30</li> </ul><p>Everything is working as expected. But as an editor updates the file itself, the record <code>sys_file_metadata</code> with uid <code>40</code>, the following cache tags will be generated:</p><ul> <li>pageId_0</li> <li>sys_file_metadata</li> <li>sys_file_metadata_40</li> </ul><p>There is no connection to page with uid <code>10</code>, leading to a non updated page, not displaying the updated title, which is inherited by <code>sys_file_reference</code> with uid <code>30</code> if no title is defined there.</p><p>Some more notes about the tags for <code>sys_file_metadata</code>: Saving the file updates not the file itself, but only the associated <code>sys_file_metadata</code> which are relevant through out the system. The file itself will only be changed if it’s replaced, e.g. by new upload. Also <code>pageId_0</code> is generated, as most <code>sys_</code> records are stored on the non existing page <code>0</code>.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c18"></a>
|
||
<a name="possibleApproaches"></a>
|
||
|
||
<h2>Possible approaches <small><a href="#possibleApproaches">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>One now has to associate either cache tag <code>sys_file_metadata_40</code> when rendering the page, or to add <code>sys_file_reference_30</code> cache tag when updating the <code>sys_file_metadata_40</code>.</p><p>Both approaches are completely valid and the latter might look easier first. As we can use a Hook to extend the generated cache tags to clear, check for the already available cache tags, fetch all associated references, and add them.</p><p>Still, this approach will not work, as this cache tag is not associated to the stored page. The page does not know about the internals of rendered content elements. It does not know or understand that Textmedia fetches <code>sys_file_reference</code> with uid <code>30</code> which in turn is based on <code>sys_file_metadata</code> with uid <code>40</code>. These might be added through a <code>DataProcessor</code>, which does not add any cache tags to the rendered page. Maybe this might change in the future.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c19"></a>
|
||
<a name="addingFurtherCacheTagsToGeneratedPage"></a>
|
||
|
||
<h2>Adding further cache tags to generated page <small><a href="#addingFurtherCacheTagsToGeneratedPage">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Adding further cache tags to pages is possible via TypoScript, and PHP. This way one can collect information and attach further tags based on this information. TYPO3 itself will already generate necessary tags when records are updated, leading to auto clearing of necessary pages.</p><p>Those tags can be added via <a href="https://docs.typo3.org/m/typo3/reference-typoscript/master/en-us/Functions/Stdwrap.html#stdwrap-addpagecachetags" class="reference external" title="(in TypoScript Reference vmaster (9-dev))">addPageCacheTags</a> property of <a href="https://docs.typo3.org/m/typo3/reference-typoscript/master/en-us/Functions/Stdwrap.html#stdwrap" class="reference external" title="(in TypoScript Reference vmaster (9-dev))">stdWrap</a>.</p><p>This property can either add static strings, e.g.:</p><pre><code>addPageCacheTags = pagetag1, pagetag2, pagetag3</code></pre><p>This property also implements <a href="https://docs.typo3.org/m/typo3/reference-typoscript/master/en-us/Functions/Stdwrap.html#stdwrap" class="reference external" title="(in TypoScript Reference vmaster (9-dev))">stdWrap</a> again, adding the possibility to use a <a href="https://docs.typo3.org/m/typo3/reference-typoscript/master/en-us/Functions/Stdwrap.html#stdwrap-preuserfunc" class="reference external" title="(in TypoScript Reference vmaster (9-dev))">preUserFunc</a> or <a href="https://docs.typo3.org/m/typo3/reference-typoscript/master/en-us/Functions/Stdwrap.html#stdwrap-postuserfunc" class="reference external" title="(in TypoScript Reference vmaster (9-dev))">postUserFunc</a>:</p><pre><code>tt_content.uploads.stdWrap {
|
||
addPageCacheTags {
|
||
postUserFunc = Codappix\CdxSite\Caching\ContentElementCaching->generateTags
|
||
}
|
||
}</code></pre><p>Now it’s up to the PHP implementation to add further tags.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c20"></a>
|
||
<a name="examplePhpSolution"></a>
|
||
|
||
<h2>Example PHP solution <small><a href="#examplePhpSolution">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>The concrete implementation depends on the concrete use case. For the above example the following implementation would be one working solution:</p><ol> <li>Extend existing <code>FilesProcessor</code> to “save” provided file references.</li> <li>Make this processor a singleton. The processor is state less but becomes state full as all file references need to be remembered until cache tags are added to the page.</li> <li>Replace existing processor in TypoScript with custom one.</li> <li>Add custom processor also as <code>postUserFunc</code> via <code>stdWrap</code></li> </ol><p>The TypoScript might look like:</p><pre><code>tt_content.uploads {
|
||
dataProcessing {
|
||
<span class="number">10</span> = Codappix\CdxSite\Caching\FilesProcessorAddingCacheTagsToPage
|
||
}
|
||
stdWrap {
|
||
addPageCacheTags {
|
||
postUserFunc = Codappix\CdxSite\Caching\FilesProcessorAddingCacheTagsToPage->generateTags
|
||
}
|
||
}
|
||
}</code></pre><p>While <code>EXT:cdx_site/Classes/Caching/FilesProcessorAddingCacheTagsToPage.php</code> might look like:</p><pre><code><span class="php"><span class="meta"><?php</span>
|
||
|
||
<span class="keyword">namespace</span> <span class="title">Codappix</span>\<span class="title">CdxSite</span>\<span class="title">Caching</span>;
|
||
|
||
<span class="keyword">use</span> <span class="title">TYPO3</span>\<span class="title">CMS</span>\<span class="title">Core</span>\<span class="title">Resource</span>\<span class="title">FileReference</span>;
|
||
<span class="keyword">use</span> <span class="title">TYPO3</span>\<span class="title">CMS</span>\<span class="title">Core</span>\<span class="title">SingletonInterface</span>;
|
||
<span class="keyword">use</span> <span class="title">TYPO3</span>\<span class="title">CMS</span>\<span class="title">Core</span>\<span class="title">Utility</span>\<span class="title">GeneralUtility</span>;
|
||
<span class="keyword">use</span> <span class="title">TYPO3</span>\<span class="title">CMS</span>\<span class="title">Frontend</span>\<span class="title">ContentObject</span>\<span class="title">ContentObjectRenderer</span>;
|
||
<span class="keyword">use</span> <span class="title">TYPO3</span>\<span class="title">CMS</span>\<span class="title">Frontend</span>\<span class="title">DataProcessing</span>\<span class="title">FilesProcessor</span>;
|
||
|
||
<span class="comment">/**
|
||
* Singleton, in order to keep instance between filling in information and accessing information.
|
||
*
|
||
* Use as data processor replacement to add file references for content element.
|
||
* Then add as userfunc to content element within TypoScript, to add cache tags.
|
||
*
|
||
* Example usage within TypoScript:
|
||
*
|
||
* stdWrap.addPageCacheTags {
|
||
* postUserFunc = Codappix\CdxSite\Caching\FilesProcessorAddingCacheTagsToPage->generateTags
|
||
* }
|
||
*
|
||
*/</span>
|
||
<span class="class"><span class="keyword">class</span> <span class="title">FilesProcessorAddingCacheTagsToPage</span> <span class="keyword">extends</span> <span class="title">FilesProcessor</span> <span class="keyword">implements</span> <span class="title">SingletonInterface</span>
|
||
</span>{
|
||
<span class="keyword">private</span> $tags = [];
|
||
|
||
<span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">process</span><span class="params">(
|
||
ContentObjectRenderer $cObj,
|
||
array $contentObjectConfiguration,
|
||
array $processorConfiguration,
|
||
array $processedData
|
||
)</span> </span>{
|
||
<span class="keyword">if</span> (<span class="keyword">isset</span>($processorConfiguration[<span class="string">'if.'</span>]) && !$cObj->checkIf($processorConfiguration[<span class="string">'if.'</span>])) {
|
||
<span class="keyword">return</span> $processedData;
|
||
}
|
||
|
||
$processedData = <span class="keyword">parent</span>::process(
|
||
$cObj,
|
||
$contentObjectConfiguration,
|
||
$processorConfiguration,
|
||
$processedData
|
||
);
|
||
|
||
$targetVariableName = $cObj->stdWrapValue(<span class="string">'as'</span>, $processorConfiguration, <span class="string">'files'</span>);
|
||
<span class="keyword">foreach</span> ($processedData[$targetVariableName] <span class="keyword">as</span> $fileReference) {
|
||
<span class="keyword">$this</span>->tags[] = <span class="string">'sys_file_metadata_'</span>
|
||
. $fileReference->getOriginalFile()->_getMetaData()[<span class="string">'uid'</span>];
|
||
<span class="keyword">$this</span>->tags[] = <span class="string">'sys_file_reference_'</span>
|
||
. $fileReference->getUid();
|
||
}
|
||
|
||
<span class="keyword">return</span> $processedData;
|
||
}
|
||
|
||
<span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">generateTags</span><span class="params">(string $content = <span class="string">''</span>, array $configuration = null)</span>
|
||
</span>{
|
||
<span class="keyword">return</span> implode(<span class="string">','</span>, array_unique(array_filter(array_merge(
|
||
GeneralUtility::trimExplode(<span class="string">','</span>, $content),
|
||
<span class="keyword">$this</span>->tags
|
||
))));
|
||
}
|
||
}</span></code></pre>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c21"></a>
|
||
<a name="acknowledgements"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Acknowledgements to <a href="https://www.ppw.de/" class="reference external">pietzpluswild GmbH</a> who allowed me to dive into the topic and to implement a solution for their customer <a href="https://www.stadtwerke-bonn.de/" class="reference external">Stadtwerke Bonn</a>.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c22"></a>
|
||
<a name="furtherReading"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<ul class="simple"> <li>TypoScript reference about adding cache tags to rendered page: <a href="https://docs.typo3.org/m/typo3/reference-typoscript/master/en-us/Functions/Stdwrap.html#stdwrap-addpagecachetags" class="reference external" title="(in TypoScript Reference)">addPageCacheTags</a></li> <li>TypoScript reference about using and defining user functions: <a href="https://docs.typo3.org/m/typo3/reference-typoscript/master/en-us/Functions/Stdwrap.html#stdwrap-postuserfunc" class="reference external" title="(in TypoScript Reference)">postUserFunc</a></li> </ul>
|
||
|
||
|
||
]]></description>
|
||
<link>https://daniel-siepmann.de/posts/2019/typo3-content-caching.html</link>
|
||
<pubDate>Fri, 04 Jan 2019 00:00:00 +0100</pubDate>
|
||
<guid isPermaLink="true">https://daniel-siepmann.de/posts/2019/typo3-content-caching.html</guid>
|
||
</item>
|
||
|
||
|
||
|
||
|
||
|
||
<item>
|
||
<title>Configure page UIDs for all content elements in TYPO3</title>
|
||
<description><![CDATA[TYPO3 uses pages to organise the structure of a website. This leads to situations where you have a specific page for a feature, e.g. a page “Search” containing the plugin to display search results. Or a page containing the profile of the currently logged in user. Typically links to these pages are scattered all over the website, e.g. within some content elements, inside the page layout like header and within some plugins.
|
||
|
||
This Blog post explains how to provide the page uid for a specific page, to all three kinds of “content”, where you typically need this information, with three lines of TypoScript.
|
||
|
||
|
||
|
||
<a name="c214"></a>
|
||
<a name="assumptions"></a>
|
||
|
||
<h2>Assumptions <small><a href="#assumptions">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>This Blog post makes the following assumptions about the TYPO3 installation, otherwise the solution might not work.</p><ol> <li> Page layout is rendered using <a href="https://docs.typo3.org/m/typo3/reference-typoscript/master/en-us/ContentObjects/Fluidtemplate/Index.html#cobj-fluidtemplate" class="reference external" title="(in TypoScript Reference vmaster (10-dev))">FLUIDTEMPLATE</a>. </li> <li> EXT:fluid_styled_content is used to render content elements. </li> <li> Plugins are always using Extbase. </li> </ol>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c215"></a>
|
||
<a name="pageLayout"></a>
|
||
|
||
<h2>Page Layout <small><a href="#pageLayout">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p><a href="https://docs.typo3.org/m/typo3/reference-typoscript/master/en-us/ContentObjects/Fluidtemplate/Index.html#cobj-fluidtemplate" class="reference external" title="(in TypoScript Reference vmaster (10-dev))">FLUIDTEMPLATE</a> provides two ways to provide information to the template. One can use <code>variables</code> or <code>settings</code>. As the page uid does not need TypoScript <code>stdWrap</code> but is a fix value, and fits more into “settings”, we will use <code>settings</code> here:</p><pre><code>page {
|
||
10 {
|
||
settings {
|
||
cdx {
|
||
pageUids {
|
||
search = 10
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}</code></pre><p>The above example assumes that <code>page.10</code> is of type <code>FLUIDTEMPLATE</code> and contains the existing setup to render page layout. We now add <code>settings</code> which is a plain PHP Array passed to the template.</p><p>As good practice namespaces are introduces, in above example <code>cdx.pageUids</code> where <code>cdx</code> is the company vendor, also used within PHP classes. <code>pageUids</code> is just a grouping, as we might also provide <code>paths</code> or further information.</p><p>With above example we could access the page uid using <code>{settings.cdx.pageUids.search}</code> within the template, layout and any partial, as <code>settings</code> are always passed to all further template files within Fluid.</p><p>One could now use the uid like:</p><pre><code><span class="tag"><<span class="name">f:form</span> <span class="attr">pageUid</span>=<span class="string">"{settings.cdx.pageUids.search}"</span> <span class="attr">action</span>=<span class="string">"search"</span>></span>
|
||
<span class="comment"><!-- search form --></span>
|
||
<span class="tag"></<span class="name">f:form</span>></span></code></pre>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c216"></a>
|
||
<a name="contentElements"></a>
|
||
|
||
<h2>Content Elements <small><a href="#contentElements">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>All content elements are rendered by inheriting <code>lib.contentElement</code>, which is an <code>FLUIDTEMPLATE</code>. Knowing this, we can re use the above knowledge from page layout, to add the necessary information to all content elements:</p><pre><code>lib.contentElement {
|
||
settings {
|
||
cdx {
|
||
pageUids {
|
||
search = 10
|
||
}
|
||
}
|
||
}
|
||
}</code></pre><p>This way all content elements have the same information available and links can be created to important pages.</p><p>Thanks to the namespacing, there is no risk that an setting already in use might be replaced.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c217"></a>
|
||
<a name="plugins"></a>
|
||
|
||
<h2>Plugins <small><a href="#plugins">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Plugins using Extbase are working nearly the same as content elements, all inherit an global “environment” from <code>config.tx_extbase</code>. This “environment” is merged with more specific further “environments” from TypoScript and Flexforms.</p><p>Knowing this, the general information can be added like this:</p><pre><code>config.tx_extbase {
|
||
settings {
|
||
cdx {
|
||
pageUids {
|
||
search = 10
|
||
}
|
||
}
|
||
}
|
||
}</code></pre>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c218"></a>
|
||
<a name="cleanup"></a>
|
||
|
||
<h2>Cleanup <small><a href="#cleanup">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Right now the page uid for page “search” is defined three times, which is a bad practice. Therefore a constant can be used instead:</p><pre><code>pageUids {
|
||
search = 10
|
||
}</code></pre><p>Which is then used in all three places:</p><pre><code>page {
|
||
<span class="number">10</span> {
|
||
settings {
|
||
cdx {
|
||
pageUids {
|
||
search = {$pageUids.search}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
lib.contentElement {
|
||
settings {
|
||
cdx {
|
||
pageUids {
|
||
search = {$pageUids.search}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
config.tx_extbase {
|
||
settings {
|
||
cdx {
|
||
pageUids {
|
||
search = {$pageUids.search}
|
||
}
|
||
}
|
||
}
|
||
}</code></pre>
|
||
|
||
|
||
|
||
|
||
|
||
<aside class="admonition-hint">
|
||
|
||
<a name="c485"></a>
|
||
<a name="hint"></a>
|
||
|
||
<h2>Hint <small><a href="#hint">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>TYPO3 introduced the site configuration with TYPO3 v9.5 LTS. 10.4 LTS added <a href="https://docs.typo3.org/c/typo3/cms-core/master/en-us/Changelog/10.4/Feature-91080-SiteSettingsAsTsConstantsAndInTsConfig.html" title="Official TYPO3 Changelog Entry">a feature to pass settings</a> from that configuration down to TypoScript. It is now possible to move the constant definition to the site configuration like this:</p><pre><code>settings:
|
||
pageUids:
|
||
search: 10</code></pre><p>Constants can still be defined in TypoScript, but it might be better to define those kinds of constants within your site configuration for some reasons:</p><ul> <li>The same value probably is used within the same file for routing.</li> <li>The constant might only be valid for that site (multisite setup).</li> <li>It communicates that this is a setting for the whole site which can be retrieved via API.</li> </ul><p>Those settings can also be retrieved via API, see <a href="https://docs.typo3.org/m/typo3/reference-coreapi/master/en-us/ApiOverview/SiteHandling/UseSiteInTypoScript.html">official Core API documentation</a>.</p>
|
||
</aside>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c219"></a>
|
||
<a name="theBenefits"></a>
|
||
|
||
<h2>The benefits <small><a href="#theBenefits">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Using the above approach, a new site can easily be added to the TYPO3 installation. To make the new site work, the constant has to be adjusted, that’s all one has to do.</p><p>Also replacing parts of the site is more easy to achieve. E.g. a single part of page tree is relaunched, one only has to change the constant again.</p><p>There is no need for “search and replace”.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c220"></a>
|
||
<a name="furtherReading"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<ul> <li><a href="https://docs.typo3.org/m/typo3/reference-typoscript/master/en-us/ContentObjects/Fluidtemplate/Index.html#cobj-fluidtemplate" class="reference external" title="(in TypoScript Reference vmaster (10-dev))">FLUIDTEMPLATE</a> especially <a href="https://docs.typo3.org/m/typo3/reference-typoscript/master/en-us/ContentObjects/Fluidtemplate/Index.html#cobj-fluidtemplate-properties-settings" class="reference external" title="(in TypoScript Reference vmaster (10-dev))">settings</a>.</li> <li>Right now there is no documentation about <code class="docutils literal notranslate">config.tx_extbase</code>.</li> <li>TYPO3 Core API <a href="https://docs.typo3.org/m/typo3/reference-coreapi/master/en-us/ApiOverview/SiteHandling/UseSiteInTypoScript.html">Using site configuration in TypoScript</a>.</li> <li>Changelog entry <a href="https://docs.typo3.org/c/typo3/cms-core/master/en-us/Changelog/10.4/Feature-91080-SiteSettingsAsTsConstantsAndInTsConfig.html">Feature: #91080 - Site settings as TypoScript constants and in TSconfig</a>.</li> </ul>
|
||
|
||
|
||
]]></description>
|
||
<link>https://daniel-siepmann.de/posts/2018/configure-page-uids-for-all-content-elements-in-typo3.html</link>
|
||
<pubDate>Mon, 24 Dec 2018 00:00:00 +0100</pubDate>
|
||
<guid isPermaLink="true">https://daniel-siepmann.de/posts/2018/configure-page-uids-for-all-content-elements-in-typo3.html</guid>
|
||
</item>
|
||
|
||
|
||
|
||
|
||
|
||
<item>
|
||
<title>How to use mbox with TYPO3 CMS</title>
|
||
<description><![CDATA[From time to time you need to test some email within your TYPO3 installation. Whether it might be that you are a developer sending some mail via a CommandController or Plugin, or you are an integrator configuring EXT:powermail or EXT:form to deliver some mail.
|
||
|
||
In all cases you need to make sure this mail is not send to external addresses like your customer during development and testing. Also you need to be able to check the content of the mail. In this blog post you will learn what mbox is and how to use it.
|
||
|
||
|
||
|
||
<a name="c189"></a>
|
||
<a name="whatsMbox"></a>
|
||
|
||
<h2>What’s mbox? <small><a href="#whatsMbox">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Accordingly to Wikipedia:</p><blockquote><p>Mbox is a generic term for a family of related file formats used for holding collections of email messages, […]</p><p>All messages in an mbox mailbox are concatenated and stored as plain text in a single file. […]</p><p>[…] the format used for the storage of email has never been formally defined through the RFC standardization mechanism […] In 2005 finally, the application/mbox media type was standardized as <a href="https://tools.ietf.org/html/rfc4155" class="reference external">RFC 4155</a>, and hints that mbox stores mailbox messages in their original Internet Message (<a href="https://tools.ietf.org/html/rfc2822" class="reference external">RFC 2822</a>) format, […]</p><p class="attribution">—<a href="https://en.wikipedia.org/wiki/Mbox" class="reference external">https://en.wikipedia.org/wiki/Mbox</a></p></blockquote><p>So mbox does not introduce any dependency at all, it’s just a plain text file inside the filesystem. Stored mail can be checked with any editor one feels comfortable with. Beside a plain editor, thanks due the RFC existing Mail clients are able to read the stored mails. This way it’s possible to open mails with an existing desktop GUI for inspection.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c190"></a>
|
||
<a name="setupTypo3"></a>
|
||
|
||
<h2>Setup TYPO3 <small><a href="#setupTypo3">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>TYPO3 provides an <a href="https://docs.typo3.org/typo3cms/CoreApiReference/ApiOverview/Mail/Index.html" class="reference external">API to deliver emails</a>. It’s a small wrapper around the <a href="https://packagist.org/packages/swiftmailer/swiftmailer" class="reference external">Swift Mailer</a> library. Every email send through this API is delivered accordingly to the configuration of TYPO3. This makes it possible to send all mails via an SMTP or some other mail system. But it’s also possible to use mbox as an alternative.</p><p>To use mbox as mail delivery within TYPO3, the following two options have to be set, e.g. within <code>AdditionalConfiguration.php</code>:</p><pre><code>$GLOBALS[<span class="string">'TYPO3_CONF_VARS'</span>][<span class="string">'MAIL'</span>][<span class="string">'transport'</span>] = <span class="string">'mbox'</span>;
|
||
$GLOBALS[<span class="string">'TYPO3_CONF_VARS'</span>][<span class="string">'MAIL'</span>][<span class="string">'transport_mbox_file'</span>] = <span class="string">'/path/to/mbox/file'</span>;</code></pre><p>Within <code>typo3/sysext/core/Configuration/DefaultConfigurationDescription.yaml</code> the option <code>transport_mbox_file</code> is documented:</p><blockquote><p>The file where to write the mails into. This file will be conforming the mbox format described in <a href="https://tools.ietf.org/html/rfc4155" class="reference external">RFC 4155</a>. It is a simple text file with a concatenation of all mails. Path must be absolute.</p></blockquote><p>Once the options are configured, one could use the “Environment” backend module to “Test Mail Setup”, which was located inside the Install Tool prior CMS v9. The Mail should be send and the file should be created by TYPO3 containing the example mail.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c191"></a>
|
||
<a name="setupEmailClient"></a>
|
||
|
||
<h2>Setup E-Mail client <small><a href="#setupEmailClient">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Not every client works with mbox files:</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c192"></a>
|
||
<a name="commandLine"></a>
|
||
|
||
<h3>Command Line <small><a href="#commandLine">¶</a></small></h3>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>For command line there is the “mail” utility, e.g. within the mailutils debian package or on macOS. Call <code>mail /path/to/mbox/file</code> to open all available mails.</p><p>Also on command line available is the mail client <a href="http://www.mutt.org/" class="reference external">mutt</a> and <a href="https://neomutt.org/" class="reference external">neomutt</a>. Call the program with path to the mbox file.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c193"></a>
|
||
<a name="forDesktopClientsThunderbirdWillDoTheJob"></a>
|
||
|
||
<h3>For desktop clients, Thunderbird will do the job. <small><a href="#forDesktopClientsThunderbirdWillDoTheJob">¶</a></small></h3>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Create a symlink to the mbox file within the Thunderbird profile. E.g. under Ubuntu 18.04 this can be done by calling:</p><pre><code>ln -s "/path/to/mbox/file" "/home/<span class="tag"><<span class="name">username</span>></span>/.thunderbird/<span class="tag"><<span class="name">profileFolder</span>></span>/Mail/Local Folders"</code></pre><p>An concrete example:</p><pre><code>ln -s ~<span class="regexp">/Projects/m</span>box/typo3-example.mbox <span class="string">"/home/daniels/.thunderbird/j30pjbfz.default/Mail/Local Folders"</span></code></pre><p>After the link was created, restart Thunderbird and one should see the mbox file in the bottom left of available mail accounts inside “Local Folders”.</p><p>Some further notes regarding mbox within Thunderbid:</p><ol class="arabic simple"> <li> <p>The local folders are not synced. Once a mail is send, one needs to refresh them. This can be achieved by right click on the mbox file within Thunderbird. Select “Properties” and click “Repair Folder”. This will refresh the content within Thunderbird.</p> </li> <li> <p>Thunderbird will not update the mbox file. E.g. if mails are deleted within Thunderbird, this will not be reflected within the file. So one might truncate the file content from time to time.</p> </li> </ol><p>I didn’t take a deeper look at this, but it looks like Thunderbird uses an internal database beside the mbox for indexing, leading to the above workarounds.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c194"></a>
|
||
<a name="sectionEnd"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>There might be further clients available I’m not aware of, just contact me and I’ll add them.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c195"></a>
|
||
<a name="furtherThoughts"></a>
|
||
|
||
<h2>Further thoughts <small><a href="#furtherThoughts">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Since mbox is just plain text one could easily integrate this for functional testing within a test suite, without the need to run any other software.</p><p>Also as the file can be opened with desktop mail clients, Frontend developers are able to “real world” test the output and styling. Contrary to solutions like <a href="https://github.com/mailhog/MailHog" class="reference external">MailHog</a> or <a href="https://mailcatcher.me/" class="reference external">MailCatcher</a> the rendering happens inside the client and not a browser.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c197"></a>
|
||
<a name="ack"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Added information about refreshing Thunderbird and clearing contents of mbox. Thanks to <a href="https://twitter.com/garbast" class="reference external">@garbast</a> for the hint to add these information.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c196"></a>
|
||
<a name="furtherReading"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<ul> <li>TYPO3 documentation about emails: <a href="https://docs.typo3.org/typo3cms/CoreApiReference/ApiOverview/Mail/Index.html" class="reference external">https://docs.typo3.org/typo3cms/CoreApiReference/ApiOverview/Mail/Index.html</a></li> <li>Reading an mbox file with Thunderbird - Berkeley Lab Commons: <a href="https://commons.lbl.gov/display/~jwelcher@lbl.gov/Reading+an+mbox+file+with+Thunderbird">https://commons.lbl.gov/display/~jwelcher@lbl.gov/Reading+an+mbox+file+with+Thunderbird</a></li> </ul>
|
||
|
||
|
||
]]></description>
|
||
<link>https://daniel-siepmann.de/posts/2018/how-to-use-mbox-with-typo3-cms.html</link>
|
||
<pubDate>Mon, 05 Nov 2018 00:00:00 +0100</pubDate>
|
||
<guid isPermaLink="true">https://daniel-siepmann.de/posts/2018/how-to-use-mbox-with-typo3-cms.html</guid>
|
||
</item>
|
||
|
||
|
||
|
||
|
||
|
||
<item>
|
||
<title>Auto login for TYPO3 Backend during development</title>
|
||
<description><![CDATA[As an TYPO3 integrator or developer you will login into the same TYPO3 installations multiple times during the same day. There are different ways to prevent the need to login over and over again. One is to add a bit of PHP to the installation, e.g. inside AdditionalConfiguration.php to prevent any login. This should save a lot of time during development.
|
||
|
||
In this blog post this solution will be shown and explained. You will never ever have to login on your local installation anymore.
|
||
|
||
|
||
<aside class="admonition-danger">
|
||
|
||
<a name="c180"></a>
|
||
<a name="danger"></a>
|
||
|
||
<h2>Danger <small><a href="#danger">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>This approach is only for a local development environment. Do never use this in a production or publical available environment!</p><p>This auto login automatically authentificates the configured user in. Please read the complete article and do nothing you do not understand.</p>
|
||
</aside>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c181"></a>
|
||
<a name="backgroundKnowledge"></a>
|
||
|
||
<h2>Background knowledge <small><a href="#backgroundKnowledge">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>To implement the feature we need to understand how TYPO3 processes the login, so we can provide our auto login. The process and implementation is documented at <a href="https://docs.typo3.org/m/typo3/reference-coreapi/master/en-us/ApiOverview/Authentication/Index.html#authentication" class="reference external" title="(in TYPO3 Explained vmaster (10-dev))">Authentication</a> section in TYPO3 Core API Reference.</p><p>TYPO3 will fetch all registered services to detect authentications in TYPO3 backend. By default the registered services will only be called if certain conditions are met, e.g. credentials were submitted in the current request. As we do <strong>not</strong> want to submit anything, we have to configure TYPO3 to try authentication all the time.</p><p>Once we have configured TYPO3 to process authentication all the time, we have to register a new service for authentication to automatically login the specified user.</p><p>With these information we can check the example implementation which will to both.</p>
|
||
|
||
|
||
|
||
|
||
|
||
<aside class="admonition-note">
|
||
|
||
<a name="c479"></a>
|
||
<a name="note"></a>
|
||
|
||
<h2>Note <small><a href="#note">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>TYPO3 v10 changed namespace of the class to extend. Use <code>\TYPO3\CMS\Sv\AbstractAuthenticationService\AbstractAuthenticationService</code> in case of a TYPO3 version lower then 10.</p>
|
||
</aside>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c182"></a>
|
||
<a name="implementation"></a>
|
||
|
||
<h2>Implementation <small><a href="#implementation">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>This is the working implementation, which is explained afterwards:</p><pre><code><span class="php"><span class="meta"><?php</span>
|
||
|
||
<span class="keyword">namespace</span> <span class="title">Codappix</span>\<span class="title">CdxAutoLogin</span> {
|
||
|
||
<span class="title">use</span> <span class="title">TYPO3</span>\<span class="title">CMS</span>\<span class="title">Core</span>\<span class="title">Utility</span>\<span class="title">ExtensionManagementUtility</span>;
|
||
<span class="keyword">use</span> <span class="title">TYPO3</span>\<span class="title">CMS</span>\<span class="title">Core</span>\<span class="title">Authentication</span>\<span class="title">AbstractAuthenticationService</span>;
|
||
|
||
<span class="comment">/**
|
||
* Auto login the configured user.
|
||
*/</span>
|
||
<span class="class"><span class="keyword">class</span> <span class="title">AutoAuthenticationTypo3Service</span> <span class="keyword">extends</span> <span class="title">AbstractAuthenticationService</span>
|
||
</span>{
|
||
<span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">getUser</span><span class="params">()</span>
|
||
</span>{
|
||
$record = <span class="keyword">$this</span>->fetchUserRecord(<span class="string">'dsiepmann'</span>);
|
||
<span class="keyword">if</span> (is_array($record)) {
|
||
<span class="keyword">return</span> $record;
|
||
}
|
||
|
||
<span class="keyword">return</span> <span class="keyword">$this</span>->fetchUserRecord(<span class="string">'daniel.siepmann'</span>);
|
||
}
|
||
|
||
<span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">authUser</span><span class="params">(array $user)</span>
|
||
</span>{
|
||
<span class="keyword">return</span> <span class="number">200</span>;
|
||
}
|
||
}
|
||
|
||
<span class="keyword">if</span> ((TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_CLI) === <span class="number">0</span>) {
|
||
ExtensionManagementUtility::addService(
|
||
<span class="string">'sitepackage'</span>,
|
||
<span class="string">'auth'</span>,
|
||
AutoAuthenticationTypo3Service::class,
|
||
[
|
||
<span class="string">'title'</span> => <span class="string">'Auto User authentication'</span>,
|
||
<span class="string">'description'</span> => <span class="string">'Auto authenticate user with configured username'</span>,
|
||
<span class="string">'subtype'</span> => <span class="string">'authUserBE,getUserBE'</span>,
|
||
<span class="string">'available'</span> => <span class="keyword">true</span>,
|
||
<span class="string">'priority'</span> => <span class="number">100</span>,
|
||
<span class="string">'quality'</span> => <span class="number">50</span>,
|
||
<span class="string">'className'</span> => AutoAuthenticationTypo3Service::class,
|
||
]
|
||
);
|
||
|
||
$GLOBALS[<span class="string">'TYPO3_CONF_VARS'</span>][<span class="string">'SVCONF'</span>][<span class="string">'auth'</span>][<span class="string">'setup'</span>][<span class="string">'BE_alwaysFetchUser'</span>] = <span class="keyword">true</span>;
|
||
$GLOBALS[<span class="string">'TYPO3_CONF_VARS'</span>][<span class="string">'SVCONF'</span>][<span class="string">'auth'</span>][<span class="string">'setup'</span>][<span class="string">'BE_alwaysAuthUser'</span>] = <span class="keyword">true</span>;
|
||
}
|
||
}</span></code></pre><p>The first thing you might see is the <strong>namespace</strong> declaration using curly braces. This feature was introduced in PHP 5.3.0 to enable multiple namespace declarations in one file. This is a <strong>bad practice</strong> as already mentioned in official php docs. We still use it here, to define a new scoped namespace for our code. This way we can separate the code from the global namespace, without the need of an additional file.</p><p>Inside of the namespace we define <strong>the service</strong> as a class. This class extends the <code>AbstractAuthenticationService</code> class of TYPO3. Thanks to the namespace we can import the namespace of the extended class. The service itself is very small, as we do not have any logic.</p><p>We just define that our service had <strong>authenticated</strong> the user and no further checks should be processed. This is the return value of <code>200</code> inside of the method <code>authUser</code>.</p><p>The service also returns <strong>the user</strong> which was logged in. In our case the username has to be placed in the method call <code>fetchUserRecord</code>.</p><p>Once the service exists, we can <strong>register the service</strong>. The registration is done using the <code>addService</code> method of <code>ExtensionManagementUtility</code>. The important part is that the service is available and has a higher priority to be called first. Also the quality has to be equal or higher then the required quality. The configured subtype defines which features are provided by the service. In the above example the service authenticates the user, which was the <code>200</code>, and provides the user, which was done inside <code>getUser</code> method.</p><p>All registered services are added to <code>$GLOBALS['T3_SERVICES']</code> which can be inspected using the <em>Configuration</em> module inside of TYPO3 backend. This way you can fetch the necessary information for the registration of the service.</p><p>Last but not least we have to <strong>force the authentication process</strong> even if no credentials were provided. This is done with this configuration:</p><pre><code>$GLOBALS[<span class="string">'TYPO3_CONF_VARS'</span>][<span class="string">'SVCONF'</span>][<span class="string">'auth'</span>][<span class="string">'setup'</span>][<span class="string">'BE_alwaysFetchUser'</span>] = <span class="keyword">true</span>;
|
||
$GLOBALS[<span class="string">'TYPO3_CONF_VARS'</span>][<span class="string">'SVCONF'</span>][<span class="string">'auth'</span>][<span class="string">'setup'</span>][<span class="string">'BE_alwaysAuthUser'</span>] = <span class="keyword">true</span>;</code></pre><p>For further information about the above configuration refer to <a href="https://docs.typo3.org/m/typo3/reference-coreapi/master/en-us/ApiOverview/Authentication/Index.html#authentication-advanced-options" class="reference external" title="(in TYPO3 Explained vmaster (10-dev))">Advanced Options</a> section of the TYPO3 Core Reference.</p><p>The registration and configuration are wrapped with a condition to check that we are not in CLI mode. As we do not need and want our code to run in CLI context. We only need this in backend and frontend mode.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c183"></a>
|
||
<a name="usage"></a>
|
||
|
||
<h2>Usage <small><a href="#usage">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>The above implementation can be pasted into <code>AdditionalConfiguration.php</code>. Only the username has to be inserted on line 15.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c184"></a>
|
||
<a name="multiplePhpNamespacesInOneFile"></a>
|
||
|
||
<h2>Multiple php namespaces in one file <small><a href="#multiplePhpNamespacesInOneFile">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>You should read the <a href="https://secure.php.net/manual/en/language.namespaces.definitionmultiple.php" class="reference external">official documentation</a> about multiple php namespaces in one file, otherwise you might run into issues with above implementation, depending on your <code>AdditionalConfiguration.php</code>.</p><p>If you already have code inside of your <code>AdditionalConfiguration.php</code> you should wrap that code with:</p><pre><code>namespace {
|
||
<span class="comment">// Existing code ...</span>
|
||
}
|
||
|
||
<span class="comment">// Above implementation</span></code></pre><p>As the <code>namsepace</code> has to be the first statement after any <code>declare</code> statements in a PHP file. Without the global namespace-scope you would receive a fatal error.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c185"></a>
|
||
<a name="simpleSolution"></a>
|
||
|
||
<h2>Simple “Solution” <small><a href="#simpleSolution">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Instead of a “full blown” auto login, you could also just raise the session timout. This way your session lasts longer and you do not have to login so often. Add the following configuration to <code>AdditionalConfiguration.php</code>:</p><pre><code>$GLOBALS[<span class="string">'TYPO3_CONF_VARS'</span>][<span class="string">'BE'</span>][<span class="string">'sessionTimeout'</span>] = <span class="number">60</span> * <span class="number">60</span> * <span class="number">24</span>;</code></pre><p>The value is in seconds, so <code>60 * 60 * 24</code> is a whole day. You could also add <code>*7</code> for a week.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c186"></a>
|
||
<a name="goingFurther"></a>
|
||
|
||
<h2>Going further <small><a href="#goingFurther">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>As everything here, this post and solution is open source. If you have ideas based upon this solution go ahead. E.g. write a small extension and use <code>require-dev</code> to fetch the extension. Make the user name configurable or check a cookie or IP address to activate this solution and detect the user name. That’s why all necessary resources are referenced and explained.</p><p>But always think about security.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c188"></a>
|
||
<a name="ack"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Thanks to <a href="https://twitter.com/Codemonkey1988" class="reference external">Tim Schreiner</a> the condition for CLI requests was added which will prevent the code from execution in CLI context.</p><p>Thanks to Felix Althaus from <a href="https://www.undkonsorten.com/" class="reference external">undkonsorten</a> these solution is available as a composer package <a href="https://packagist.org/packages/undkonsorten/typo3-auto-login" class="reference external">undkonsorten/typo3-auto-login</a>.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c187"></a>
|
||
<a name="furtherReading"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<ul> <li> <a href="https://secure.php.net/manual/en/language.namespaces.definitionmultiple.php" class="reference external">PHP documentation about multiple namespaces in one file</a>. </li> <li> <a href="https://docs.typo3.org/m/typo3/reference-coreapi/master/en-us/ApiOverview/Authentication/Index.html#authentication" class="reference external" title="(in TYPO3 Explained vmaster (10-dev))">Authentication</a> in TYPO3 Core API Reference. </li> <li> <a href="https://docs.typo3.org/m/typo3/reference-coreapi/master/en-us/ApiOverview/Authentication/Index.html#authentication-advanced-options" class="reference external" title="(in TYPO3 Explained vmaster (10-dev))">Advanced Options</a> section of the TYPO3 Core Reference. </li> <li> Composer package version: <a href="https://packagist.org/packages/undkonsorten/typo3-auto-login" class="reference external">undkonsorten/typo3-auto-login</a> </li> </ul>
|
||
|
||
|
||
]]></description>
|
||
<link>https://daniel-siepmann.de/posts/2018/auto-login-for-typo3-backend-during-development.html</link>
|
||
<pubDate>Wed, 25 Jul 2018 00:00:00 +0200</pubDate>
|
||
<guid isPermaLink="true">https://daniel-siepmann.de/posts/2018/auto-login-for-typo3-backend-during-development.html</guid>
|
||
</item>
|
||
|
||
|
||
|
||
|
||
|
||
<item>
|
||
<title>Executing TYPO3 acceptance tests</title>
|
||
<description><![CDATA[TYPO3 CMS is an open source content management system with lots of contributions. The system has a huge code base which is partially very old and therefore contains a lot of legacy code. Therefore it’s very important to cover existing features with tests, to not break anything if you improve the system.
|
||
|
||
TYPO3 CMS was improved with a lot of unit and functional tests in the past. Since some time, there are also acceptance tests available, which will test functionality of the backend. All of these tests are executed by the TYPO3 CMS own Bamboo to make sure no merge will break anything. If you want to execute the existing acceptance tests, you already might be a developer and luckily, the repository contains all necessary information to get started.
|
||
|
||
In this Blog post I will show how to get the necessary information out of the source files and to execute these tests. This should also help in the future, as the way to get there is described, not only how to execute the tests now.
|
||
|
||
|
||
|
||
<a name="c167"></a>
|
||
<a name="requirements"></a>
|
||
|
||
<h2>Requirements <small><a href="#requirements">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>All requirements are already documented at <a href="https://docs.typo3.org/m/typo3/guide-contributionworkflow/master/en-us/Setup/Index.html#setup" class="reference external" title="(in TYPO3 Contribution Guide - Core Development v1.0)">Setup</a> in the official contribution workflow. All requirements which are specific to acceptance tests, are already configured inside of <code>composer.json</code> of TYPO3 and therefore installed. Typically the requirements for acceptance tests consist of:</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c168"></a>
|
||
<a name="webdriver"></a>
|
||
|
||
<h3>WebDriver <small><a href="#webdriver">¶</a></small></h3>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>As acceptance tests are run inside a browser, most of the time, we need a browser and access to control the browser from the outside.</p><blockquote><p>WebDriver is a remote control interface that enables introspection and control of user agents. It provides a platform- and language-neutral wire protocol as a way for out-of-process programs to remotely instruct the behavior of web browsers.</p><p class="attribution">—<a href="https://www.w3.org/TR/webdriver/" class="reference external">https://www.w3.org/TR/webdriver/</a></p></blockquote>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c169"></a>
|
||
<a name="chromedriver"></a>
|
||
|
||
<h3>Chromedriver <small><a href="#chromedriver">¶</a></small></h3>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Also we need a browser that supports WebDriver, or a driver that will add the support. As Chrome is nowadays a browser that supports headless mode, Chrome is the de-facto-standard for acceptance tests nowadays. To add the capabilities to use WebDriver with Chrome, one has to install Chromedriver.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c170"></a>
|
||
<a name="phpunit"></a>
|
||
|
||
<h3>PHPUnit <small><a href="#phpunit">¶</a></small></h3>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>To write and execute tests, there is always PHPUnit as a framework. Even if you are using another framework it’s very likely that PHPUnit is a dependency and used under the hood.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c171"></a>
|
||
<a name="codeception"></a>
|
||
|
||
<h3>Codeception <small><a href="#codeception">¶</a></small></h3>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>To make writing acceptance tests easier, TYPO3 CMS is using Codeception as a testing framework on top of PHPUnit. Codeception has much easier to use API to make use of WebDriver.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c172"></a>
|
||
<a name="endOfSection"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>But all of these are installed through composer. So just take a look at the <code>require-dev</code> section of <code>composer.json</code> to get these information.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c173"></a>
|
||
<a name="findingTheTruth"></a>
|
||
|
||
<h2>Finding the truth <small><a href="#findingTheTruth">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Once everything is installed, we need to know how to execute tests. There is a single point of truth for that, its called Bamboo. As we do not have access rights to read the configuration inside of the web UI of <a href="https://bamboo.typo3.com" class="reference external">https://bamboo.typo3.com</a>, we have another way. Luckily the whole configuration is right in our hands, inside the TYPO3 CMS repositories <code>/Build/bamboo</code> folder.</p><p>Don’t be afraid, Bamboo is written in Java, and so are the configuration files at time of writing this Blog post. But we do not have to change anything, we just need to grab information, so that’s not a problem, you are a developer, right?</p><p>So let’s take a look at the file <code>/Build/bamboo/src/main/java/core/AbstractCoreSpec.java</code>, which is shown with only necessary lines and context here:</p><pre><code><span class="comment">/**
|
||
* Job acceptance test installs system on mysql
|
||
*
|
||
* <span class="doctag">@param </span>Requirement requirement
|
||
* <span class="doctag">@param </span>String requirementIdentfier
|
||
*/</span>
|
||
protected Job getJobAcceptanceTestInstallMysql(Requirement requirement, <span class="built_in">String</span> requirementIdentifier) {
|
||
<span class="keyword">return</span> <span class="keyword">new</span> Job(<span class="string">"Accept inst my "</span> + requirementIdentifier, <span class="keyword">new</span> BambooKey(<span class="string">"ACINSTMY"</span> + requirementIdentifier))
|
||
.description(<span class="string">"Install TYPO3 on mysql and create empty frontend page "</span> + requirementIdentifier)
|
||
.pluginConfigurations(<span class="keyword">this</span>.getDefaultJobPluginConfiguration())
|
||
.tasks(
|
||
<span class="keyword">this</span>.getTaskGitCloneRepository(),
|
||
<span class="keyword">this</span>.getTaskGitCherryPick(),
|
||
<span class="keyword">this</span>.getTaskComposerInstall(),
|
||
<span class="keyword">this</span>.getTaskPrepareAcceptanceTest(),
|
||
<span class="keyword">new</span> CommandTask()
|
||
.description(<span class="string">"Execute codeception AcceptanceInstallMysql suite"</span>)
|
||
.executable(<span class="string">"codecept"</span>)
|
||
.argument(<span class="string">"run AcceptanceInstallMysql -d -c "</span> + <span class="keyword">this</span>.testingFrameworkBuildPath + <span class="string">"AcceptanceTestsInstallMysql.yml --xml reports.xml --html reports.html"</span>)
|
||
.environmentVariables(<span class="keyword">this</span>.credentialsMysql)
|
||
)</code></pre><p>If you search for the string “acceptance” in this file, you will first find line 2 from above example. It’s like PHPDoc showing us that the installation procedure for MySQL is tested as an acceptance test here. Take a deeper look and you will see that different tasks are defined from line 12 to 20. First some basic tasks are executed like cloning, composer install and stuff like that. Afterwards <code>getTaskPrepareAcceptanceTest</code> is called, which sounds interesting. Let’s take a look at this method afterwards. Right after <code>getTaskPrepareAcceptanceTest</code> a command is defined, which already has a nice description “<em>Execute codeception AcceptanceInstallMysql suite</em>”. We can see the executable and the arguments in line 18 and 19.</p><p>So we know that Codeception is used to execute the acceptance test. We also know which reports are generated and which configuration is loaded. The configuration is prefixed with <code>this.testingFrameworkBuildPath</code> which is also defined in the same file:</p><pre><code>protected <span class="built_in">String</span> testingFrameworkBuildPath = <span class="string">"vendor/typo3/testing-framework/Resources/Core/Build/"</span>;</code></pre><p>So our configuration is looked up in <code>/vendor/typo3/testing-framework/Resources/Core/Build/AcceptanceTestsInstallMysql.yml</code>.</p><p>Also in line 20 some environment variables are added, which are defined in <code>this.credentialsMysql</code>:</p><pre><code>protected <span class="built_in">String</span> credentialsMysql =
|
||
<span class="string">"typo3DatabaseName=\"func\""</span> +
|
||
<span class="string">" typo3DatabaseUsername=\"funcu\""</span> +
|
||
<span class="string">" typo3DatabasePassword=\"funcp\""</span> +
|
||
<span class="string">" typo3DatabaseHost=\"localhost\""</span> +
|
||
<span class="string">" typo3InstallToolPassword=\"klaus\""</span>;</code></pre><p>So we know everything, except what happens inside <code>getTaskPrepareAcceptanceTest</code>, so let’s take a look at the last piece:</p><pre><code><span class="comment">/**
|
||
* Task to prepare an acceptance test starting selenium and others
|
||
*/</span>
|
||
<span class="keyword">protected</span> Task getTaskPrepareAcceptanceTest() {
|
||
<span class="keyword">return</span> <span class="keyword">new</span> ScriptTask()
|
||
.description(<span class="string">"Start php web server, chromedriver, prepare environment"</span>)
|
||
.interpreter(ScriptTaskProperties.Interpreter.BINSH_OR_CMDEXE)
|
||
.inlineBody(
|
||
this.getScriptTaskBashInlineBody() +
|
||
<span class="string">"php -n -c /etc/php/cli-no-xdebug/php.ini -S localhost:8000 >/dev/null 2>&1 &\n"</span> +
|
||
<span class="string">"echo $! > phpserver.pid\n"</span> +
|
||
<span class="string">"\n"</span> +
|
||
<span class="string">"./bin/chromedriver --url-base=/wd/hub >/dev/null 2>&1 &\n"</span> +
|
||
<span class="string">"echo $! > chromedriver.pid\n"</span> +
|
||
<span class="string">"\n"</span> +
|
||
<span class="string">"mkdir -p typo3temp/var/tests/\n"</span>
|
||
);
|
||
}</code></pre><p>We can see in the description, which is in line 6, that a web server and Chromedriver is started. Taking a look at the <code>inlineBody</code> we see that the local PHP server is started with some configuration, together with a Chromedriver and some configuration.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c174"></a>
|
||
<a name="playingAroundWithTheTruth"></a>
|
||
|
||
<h2>Playing around with the truth <small><a href="#playingAroundWithTheTruth">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Looks like the following is necessary to execute the tests:</p><p>Create a database and user with access to it, and access to create “sub database”. See: <a href="https://wiki.typo3.org/Acceptance_testing#Acceptance_Testing_since_TYPO3_v8" class="reference external">https://wiki.typo3.org/Acceptance_testing#Acceptance_Testing_since_TYPO3_v8</a></p><p>Start a web server which listens to localhost on port 8000:</p><pre><code>php \
|
||
-d memory_limit=128M \
|
||
-d max_execution_time=240 \
|
||
-d xdebug.max_nesting_level=400 \
|
||
-d max_input_vars=1500 \
|
||
-S localhost:8000</code></pre><p>This looks different from the Java-File. Yes, cause our local environment is different from Bamboos PHP environment; Once you start <code>php -S localhost:8000</code> and tests, you will see typical PHP errors in install routine. This errors tell you to configure PHP, that’s what we did in above example.</p><p>Start the Chromedriver which was installed via composer:</p><pre><code>./bin/chromedriver --url-base=<span class="regexp">/wd/</span>hub</code></pre><p>Execute Codeception to run the test:</p><pre><code>typo3DatabaseName=<span class="string">"typo3_acceptance"</span> \
|
||
typo3DatabaseUsername=<span class="string">"typo3_acceptance"</span> \
|
||
typo3DatabasePassword=<span class="string">"typo3_acceptance"</span> \
|
||
typo3DatabaseHost=<span class="string">"localhost"</span>
|
||
typo3InstallToolPassword=<span class="string">"klaus"</span> \
|
||
./bin/codecept run AcceptanceInstallMysql -d \
|
||
-c vendor/typo3/testing-framework/Resources/Core/Build/AcceptanceTestsInstallMysql.yml \
|
||
--html reports.html</code></pre><p>In above example the database, user and password are <code>typo3_acceptance</code>, because I liked it that way on my locale machine. Just place your values in there.</p><p>That’s what I’ve tried locally, which worked on my Ubuntu 18.04 setup with CMS 9 commit <code>bc5dcaacef26cecb29e89554e5b1775dc839a4ae</code>.</p><p>As above commands start long running processes, you have either to send them to background, or to start a new shell for each of the last three commands.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c175"></a>
|
||
<a name="gettingResults"></a>
|
||
|
||
<h2>Getting results <small><a href="#gettingResults">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Codeception will tell you where you can find the generated reports, they are inside <code>/typo3temp/var/tests/AcceptanceReportsInstallMysql/reports.html</code> in our above example. You can open the report with any web browser.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c176"></a>
|
||
<a name="executingFurtherTests"></a>
|
||
|
||
<h2>Executing further tests <small><a href="#executingFurtherTests">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>TYPO3 does not only provide the above test, but a larger range of acceptance tests.</p><p>If you search further for “acceptance” inside the <code>/Build/bamboo/src/main/java/core/AbstractCoreSpec.java</code>-file, you will find the method <code>getJobsAcceptanceTestsMysql</code>. There is a bit “magic” that will split the tests to parallelize execution inside of Bamboo, which we can ignore for local testing for now.</p><p>To execute all further acceptance tests of TYPO3s core, run:</p><pre><code>./bin/codecept run Acceptance -d \
|
||
-c vendor/typo3/testing-framework/Resources/Core/Build/AcceptanceTests.yml \
|
||
--html reports.html</code></pre><p>We just exchange the argument after <code>run</code> and the configuration file to use. At the moment of this Blog post, this will execute 77 acceptance tests.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c177"></a>
|
||
<a name="whereAreTheTests"></a>
|
||
|
||
<h2>Where are the tests? <small><a href="#whereAreTheTests">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>So far we covered how to execute tests, and how to find information how to execute the tests. What’s still missing is the information where to find the actual tests. You can check out the configuration for Codeception, that’s the file we provide after the <code>-c</code> option in the above examples. For all acceptance tests this is <code>/vendor/typo3/testing-framework/Resources/Core/Build/AcceptanceTests.yml</code>.</p><p>If we take a look at this file we find out all we need to know:</p><pre><code><span class="selector-tag">actor</span>: <span class="selector-tag">Tester</span>
|
||
<span class="selector-tag">paths</span>:
|
||
# <span class="keyword">@todo</span> clean up <span class="attribute">here:</span> <span class="attribute">https:</span>//forge.typo3.org/issues/<span class="number">79097</span>
|
||
<span class="attribute">tests:</span> ../../../../../../typo3/sysext/core/Tests
|
||
<span class="attribute">log:</span> ../../../../../../typo3temp/var/tests/AcceptanceReports
|
||
<span class="attribute">data:</span> Configuration/Acceptance/Data
|
||
<span class="attribute">support:</span> Configuration/Acceptance/Support
|
||
<span class="attribute">envs:</span> Configuration/Acceptance/Envs
|
||
<span class="attribute">settings:</span>
|
||
<span class="attribute">colors:</span> true
|
||
memory_<span class="attribute">limit:</span> <span class="number">1024</span>M
|
||
<span class="attribute">extensions:</span>
|
||
<span class="attribute">enabled:</span>
|
||
- Codeception\Extension\RunFailed
|
||
- Codeception\Extension\Recorder
|
||
- TYPO3\TestingFramework\Core\Acceptance\AcceptanceCoreEnvironment
|
||
<span class="attribute">groups:</span>
|
||
AcceptanceTests-Job-*: Configuration/Acceptance/AcceptanceTests-Job-*</code></pre><p>Line 4 is the important one. This tells us where tests can be found. All tests are available at <code>/typo3/sysext/core/Tests</code>. Also note the comment which points to <a href="https://forge.typo3.org/issues/79097" class="reference external">https://forge.typo3.org/issues/79097</a> and covers the architectural problem of dependencies evolving from this structure.</p><p>The last two lines are for splitting up the acceptance tests for parallelization inside Bamboo.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c178"></a>
|
||
<a name="furtherInformation"></a>
|
||
|
||
<h2>Further information <small><a href="#furtherInformation">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Acceptance tests are known to be fragile. Currently TYPO3 is not executing the acceptance tests inside of bamboo, as they are broken from time to time. As TYPO3 is GPL and Codeception MIT, both are joining forces to cover this issues in the future.</p><p>The above mentioned Java-Files are the single point of truth. They are used by Bamboo to execute the pre-merge and nightly plans.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c179"></a>
|
||
<a name="furtherReading"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<ul> <li> <a href="https://github.com/TYPO3/TYPO3.CMS/tree/master/Build/bamboo" class="reference external">https://github.com/TYPO3/TYPO3.CMS/tree/master/Build/bamboo</a> Bamboo files inside of TYPO3 CMS repository. </li> <li> <a href="https://wiki.typo3.org/Acceptance_testing#Acceptance_Testing_since_TYPO3_v8" class="reference external">https://wiki.typo3.org/Acceptance_testing#Acceptance_Testing_since_TYPO3_v8</a> Old documentation for TYPO3 CMS 8. </li> <li> <a href="https://github.com/TYPO3/testing-framework" class="reference external">https://github.com/TYPO3/testing-framework</a> The testing framework dependency which is used as a base in TYPO3 CMS. </li> <li> <a href="https://codeception.com/docs/modules/WebDriver" class="reference external">https://codeception.com/docs/modules/WebDriver</a> WebDriver documentation of Codeception, covering configuration and test API. </li> </ul>
|
||
|
||
|
||
]]></description>
|
||
<link>https://daniel-siepmann.de/posts/2018/executing-typo3-acceptance-tests.html</link>
|
||
<pubDate>Fri, 08 Jun 2018 00:00:00 +0200</pubDate>
|
||
<guid isPermaLink="true">https://daniel-siepmann.de/posts/2018/executing-typo3-acceptance-tests.html</guid>
|
||
</item>
|
||
|
||
|
||
|
||
|
||
|
||
<item>
|
||
<title>Integrate TYPO3 Linting with GitLab-CI</title>
|
||
<description><![CDATA[GitLab is the new buzzword for cloud- and self-hosted repository hosting connected to a CI. We are using GitLab-CI in combination with Docker for running tests, linting code and deployments.
|
||
|
||
In this post I’ll explain how to setup linting TypoScript and YAML for TYPO3 projects using GitLab-CI.
|
||
|
||
|
||
|
||
<a name="c162"></a>
|
||
<a name="typoscriptLinting"></a>
|
||
|
||
<h2>TypoScript linting <small><a href="#typoscriptLinting">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Martin Helmich has created a TypoScript parser and <a href="https://github.com/martin-helmich/typo3-typoscript-lint" class="reference external">TypoScript linter</a> which already provides basic rules. The concepts are inherited from PHP Code Sniffer with different Sniffs which can be configured.</p><p>You can install the linter as dev dependency using <a href="https://getcomposer.org/" class="reference external">composer</a>:</p><pre><code>composer <span class="built_in">require</span> --dev helmich/typo3-typoscript-lint</code></pre><p>The linter is pre-configured with a default configuration, which can be found at GitHub: <a href="https://github.com/martin-helmich/typo3-typoscript-lint/blob/v1.4.6/tslint.dist.yml" class="reference external">https://github.com/martin-helmich/typo3-typoscript-lint/blob/v1.4.6/tslint.dist.yml</a></p><p>Configuration is written as <a href="https://yaml.org/" class="reference external">yaml</a>. You can extend the default configuration. Most linters provide the .dist way to provide a distributed configuration which can be overwritten by a more specific configuration. We are using yaml as file extension, which is the preferred accordingly to FAQ of yaml. Our configuration looks like the following <code>tslint.yaml</code>:</p><pre><code>paths:
|
||
- localPackages/sitepackage/Configuration/PageTSConfig/
|
||
- localPackages/sitepackage/Configuration/TypoScript/
|
||
- localPackages/sitepackage/Configuration/UserTSConfig/
|
||
|
||
sniffs:
|
||
- class: Indentation
|
||
parameters:
|
||
indentConditions: true
|
||
- class: RepeatingRValue
|
||
disabled: true</code></pre><p>We define paths containing TypoScript to check. Also we adjust the default sniffs a bit. We force indentation of one level inside of conditions, as we do in PHP and JavaScript. Also we disable the <code>RepeatingRValue</code> sniff as we check this rule ourselves.</p><p>To actually lint anything, you will call the linter with the configuration:</p><pre><code>./vendor/bin/typoscript-lint -c tslint.yaml</code></pre><p>Which will generate something like the following if everything is fine:</p><pre><code>.................................................. <span class="selector-attr">[ 50 / 106, 47%]</span>
|
||
.................................................. <span class="selector-attr">[100 / 106, 94%]</span>
|
||
...... <span class="selector-attr">[106 / 106, 100%]</span>
|
||
|
||
<span class="selector-tag">Complete</span> <span class="selector-tag">without</span> <span class="selector-tag">warnings</span></code></pre><p>If errors or warnings exist, the following output will be generated:</p><pre><code>...............................W.................. [ <span class="number">50</span> / <span class="number">106</span>, <span class="number">47</span>%]
|
||
........W................................W........ [<span class="number">100</span> / <span class="number">106</span>, <span class="number">94</span>%]
|
||
...... [<span class="number">106</span> / <span class="number">106</span>, <span class="number">100</span>%]
|
||
|
||
Completed <span class="keyword">with</span> <span class="number">9</span> issues
|
||
|
||
CHECKSTYLE REPORT
|
||
=> localPackages/cdx_site/Configuration/TypoScript/Setup/Plugins/SearchCore.typoscript.
|
||
<span class="number">7</span> No whitespace after object accessor.
|
||
<span class="number">10</span> Accessor should be followed by single space.
|
||
<span class="number">11</span> Value <span class="keyword">of</span> object <span class="string">"plugin.tx_searchcore.view.templateRootPaths.10"</span> is overwritten <span class="keyword">in</span> line <span class="number">12.</span>
|
||
|
||
SUMMARY
|
||
<span class="number">9</span> issues <span class="keyword">in</span> total. (<span class="number">9</span> warnings)</code></pre><p>You can also output in <code>checkstyle</code> format which might be useful for some CI environments and editors.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c163"></a>
|
||
<a name="yamlLinting"></a>
|
||
|
||
<h2>yaml linting <small><a href="#yamlLinting">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>As we didn’t find any useful yaml linter written in PHP, we use one written in Python. Most php linters just call Symfony yaml parser and catch exceptions, which results in a single parsing error.</p><p>So we decided to use <a href="https://yamllint.readthedocs.io/en/latest/" class="reference external">yamllint</a>, which is no problem using Docker and GitLab-CI. I’ll not describe how to install locally, refer to GitLab-CI in next section.</p><p>As the TypoScript linter, this linter also provides a default of rules to check, which can be found at GitHub: <a href="https://github.com/adrienverge/yamllint/blob/v1.10.0/yamllint/conf/default.yaml" class="reference external">https://github.com/adrienverge/yamllint/blob/v1.10.0/yamllint/conf/default.yaml</a> There is also a “relaxed” version of the configuration. You can “inherit” one of the configurations in your own, which is provided as <code>yamllint.yaml</code> in your project root:</p><pre><code>extends: <span class="keyword">default</span>
|
||
|
||
rules:
|
||
line-length: disable
|
||
document-start: disable
|
||
braces:
|
||
min-spaces-inside-<span class="keyword">empty</span>: <span class="number">1</span>
|
||
max-spaces-inside-<span class="keyword">empty</span>: <span class="number">1</span>
|
||
brackets:
|
||
min-spaces-inside-<span class="keyword">empty</span>: <span class="number">1</span>
|
||
max-spaces-inside-<span class="keyword">empty</span>: <span class="number">1</span>
|
||
comments:
|
||
level: error
|
||
min-spaces-from-content: <span class="number">1</span>
|
||
comments-indentation:
|
||
level: error
|
||
<span class="keyword">empty</span>-lines:
|
||
max: <span class="number">1</span>
|
||
<span class="keyword">empty</span>-values:
|
||
forbid-in-block-mappings: <span class="keyword">true</span>
|
||
forbid-in-flow-mappings: <span class="keyword">true</span>
|
||
indentation:
|
||
spaces: <span class="number">4</span></code></pre><p>We adjust some rules, some become more strict, some are disabled completely.</p><p>You can call the linter using the following command:</p><pre><code>yamllint -c yamllint.yaml localPackages/ yamllint.yaml .gitlab-ci.yml tslint.yaml</code></pre><p>Nothing will be printed if everything is fine. Errors and warnings are reported like:</p><pre><code>localPackages/cdx_site/Configuration/Forms/Base.yaml
|
||
<span class="number">7</span>:<span class="number">21</span> error duplication of key <span class="string">"10"</span> in mapping (key-duplicates)
|
||
|
||
tslint.yaml
|
||
<span class="number">10</span>:<span class="number">28</span> error <span class="keyword">empty</span> value in block mapping (<span class="keyword">empty</span>-values)</code></pre>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c164"></a>
|
||
<a name="integrateLintingInGitlabci"></a>
|
||
|
||
<h2>Integrate linting in GitLab-CI <small><a href="#integrateLintingInGitlabci">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>As we now know how to configure the linter and how to execute them, we need to integrate them into our GitLab-CI through our <code>.gitlab-ci.yml</code>. Poorly GitLab does not allow yaml as file extension for that file.</p><p>The task for TypoScript looks like the following:</p><pre><code>lint:typoscriptcgl:
|
||
image: composer:1.6
|
||
stage: lint
|
||
script:
|
||
- composer install --no-progress --no-ansi --no-interaction
|
||
- ./vendor/bin/typoscript-lint -c tslint.yaml</code></pre><p>The task for Yaml looks like the following:</p><pre><code>lint:yaml:
|
||
image: python:alpine3.7
|
||
stage: lint
|
||
before_script:
|
||
- pip install yamllint==1.10.0
|
||
script:
|
||
- yamllint -c yamllint.yaml localPackages/ yamllint.yaml .gitlab-ci.yml tslint.yaml</code></pre><p>As both linter will provide proper exit codes, we are finished. Output is generated in a human friendly way, so everyone can take a look right into the log what goes wrong and can fix the issues.</p><p>By default, both tools will return an exit code <code>0</code> if only warnings were found, which is considered best practice, as warnings are just warnings and those okay.</p><p><code>typoscript-lint</code> will return an exit code <code>2</code> if an issue was found. This is only true for errors, not warnings. To enable exit code <code>2</code> also if warnings were found, add the option <code>--fail-on-warnings</code>.</p><p><code>yamllint</code> will return <code>1</code> if errors were found, <code>0</code> otherwise. To return <code>2</code> if an warning was found, enable strict mode via <code>--strict</code>. For further information see <a href="https://yamllint.readthedocs.io/en/stable/configuration.html#errors-and-warnings" class="reference external">Errors and warnings</a>.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c165"></a>
|
||
<a name="updates"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<blockquote><p>Updated on 20 Apr, 2019</p><p>Added further details about exit codes for TypoScript linter by Martin Helmich.</p><p>Thanks <a href="https://twitter.com/spooner_web" class="reference external">Twitter user spooner_web</a> for a hint that these information might be helpful.</p></blockquote>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c166"></a>
|
||
<a name="furtherReading"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<ul> <li><a href="https://about.gitlab.com/" class="reference external">GitLab</a></li> <li><a href="https://about.gitlab.com/product/continuous-integration/" class="reference external">GitLab-Ci</a></li> <li><a href="https://github.com/martin-helmich/typo3-typoscript-lint" class="reference external">TypoScript linter</a></li> <li><a href="https://yamllint.readthedocs.io/en/latest/" class="reference external">yamllint</a></li> <li>Blog post <a href="/posts/2018/integrate-typoscript-linter-into-vim.html">Integrate Typoscript linter into VIM</a>.</li> </ul>
|
||
|
||
|
||
]]></description>
|
||
<link>https://daniel-siepmann.de/posts/2018/integrate-typo3-linting-with-gitlab-ci.html</link>
|
||
<pubDate>Tue, 30 Jan 2018 00:00:00 +0100</pubDate>
|
||
<guid isPermaLink="true">https://daniel-siepmann.de/posts/2018/integrate-typo3-linting-with-gitlab-ci.html</guid>
|
||
</item>
|
||
|
||
|
||
|
||
|
||
|
||
<item>
|
||
<title>Integrate TypoScript linter into VIM</title>
|
||
<description><![CDATA[As more and more people like to lint their files, it’s obvious that we also should lint our TypoScript files in TYPO3 projects. Therefore, Martin Helmich has created a TypoScript parser and TypoScript linter.
|
||
|
||
In this blog post you will learn how to integrate this linter into vim and neovim by using syntastic or ale as a plugin.
|
||
|
||
|
||
|
||
<a name="c159"></a>
|
||
<a name="installDependencies"></a>
|
||
|
||
<h2>Install dependencies <small><a href="#installDependencies">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>First of all you need <i>syntastic</i> or <i>ale</i> to be installed properly, refer to their docs for installation guideline.</p><p>Also you need to have the linter installed. We prefer <a href="https://getcomposer.org/" class="reference external">composer</a>:</p><pre><code>composer <span class="built_in">require</span> --dev helmich/typo3-typoscript-lint</code></pre><p>That will install the linter package with all dependencies into your project. By default the binary will be installed into <code>vendor/bin/typoscript-lint</code>. But the concrete path depends on your composer settings. Also it’s possible to install the package globally.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c160"></a>
|
||
<a name="configureVimPluginSyntastic"></a>
|
||
|
||
<h2>Configure vim plugin syntastic <small><a href="#configureVimPluginSyntastic">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>After all dependencies are installed, we need to add the <code>syntax checker</code> for TypoScript. The file has to be located at <code>syntax_checkers/typoscript/lint.vim</code> inside your runtimepath, e.g. <code>~/.vim/syntax_checkers/typoscript/lint.vim</code>.</p><p>The file has the following content:</p><pre><code><span class="keyword">if</span> exists(<span class="string">'g:loaded_syntastic_typoscript_lint_checker'</span>)
|
||
finish
|
||
endif
|
||
<span class="keyword">let</span> g:loaded_syntastic_typoscript_lint_checker = <span class="number">1</span>
|
||
|
||
<span class="keyword">let</span> s:save_cpo = &cpo
|
||
<span class="keyword">set</span> cpo&vim
|
||
|
||
function! SyntaxCheckers_typoscript_lint_GetLocList() dict
|
||
let makeprg = self.makeprgBuild({
|
||
\ <span class="string">"exe"</span>: self.getExec(),
|
||
\ <span class="string">"args"</span>: <span class="string">'--format=checkstyle'</span>,
|
||
\ })
|
||
|
||
<span class="keyword">let</span> errorformat = <span class="string">'%f:%t:%l:%c:%m'</span>
|
||
|
||
<span class="keyword">return</span> SyntasticMake({
|
||
\ <span class="string">'makeprg'</span>: makeprg,
|
||
\ <span class="string">'errorformat'</span>: errorformat,
|
||
\ <span class="string">'preprocess'</span>: <span class="string">'checkstyle'</span>,
|
||
\ <span class="string">'postprocess'</span>: [<span class="string">'guards'</span>] })
|
||
endfunction
|
||
|
||
call g:SyntasticRegistry.CreateAndRegisterChecker({
|
||
\ <span class="string">'filetype'</span>: <span class="string">'typoscript'</span>,
|
||
\ <span class="string">'name'</span>: <span class="string">'lint'</span>})
|
||
|
||
<span class="keyword">let</span> &cpo = s:save_cpo
|
||
unlet s:save_cpo
|
||
|
||
<span class="string">" vim: set sw=4 sts=4 et</span></code></pre><p>Also add the path to your installed executable, e.g. inside of <code>~/.vimrc</code> like so:</p><pre><code><span class="keyword">let</span> g:syntastic_typoscript_lint_exec=<span class="string">'./vendor/bin/typoscript-lint'</span></code></pre><p>If you have installed the binary on a per project base with default paths. Otherwise adjust accordingly. If your path is different on a project level, take a look at <a href="/posts/migrated/folder-specific-settings-in-vim.html">Folder specific settings in Vim</a>.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c656"></a>
|
||
<a name="useAleInstead"></a>
|
||
|
||
<h2>Use ale instead <small><a href="#useAleInstead">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>ale is another plugin for vim/neovim that integrates linters. You can find my pull request to integrate the linter at <a href="https://github.com/dense-analysis/ale/pull/4673">https://github.com/dense-analysis/ale/pull/4673</a>.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c161"></a>
|
||
<a name="furtherReading"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<ul><li><a href="https://github.com/vim-syntastic/syntastic" class="reference external">syntastic</a></li><li><a href="https://github.com/dense-analysis/ale" class="reference external" title="Ale GitHub Project page">Ale</a></li><li><a href="https://github.com/martin-helmich/typo3-typoscript-lint" class="reference external">TypoScript linter</a></li></ul>
|
||
|
||
|
||
]]></description>
|
||
<link>https://daniel-siepmann.de/posts/2018/integrate-typoscript-linter-into-vim.html</link>
|
||
<pubDate>Sun, 28 Jan 2018 00:00:00 +0100</pubDate>
|
||
<guid isPermaLink="true">https://daniel-siepmann.de/posts/2018/integrate-typoscript-linter-into-vim.html</guid>
|
||
</item>
|
||
|
||
|
||
|
||
|
||
|
||
<item>
|
||
<title>Use Whoops as Exception handler for TYPO3</title>
|
||
<description><![CDATA[During development for TYPO3 you often run into Exceptions. They do not look very nice. A much nicer alternative might be whoops which @dk2kde told me about. It will not only handle exceptions, but also PHP Errors like syntax errors.
|
||
|
||
In this small blog post I will show you how to use whoops as exception handler for TYPO3 projects during local development.
|
||
|
||
|
||
|
||
<a name="c144"></a>
|
||
<a name="installDependencies"></a>
|
||
|
||
<h2>Install dependencies <small><a href="#installDependencies">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>First of all you have to install the <a href="https://packagist.org/packages/filp/whoops" class="reference external">filp/whoops</a> package. Also I recommend to install <a href="https://packagist.org/packages/symfony/var-dumper" class="reference external">symfony/var-dumper</a>. Only with both packages you will see arguments in stack traces.</p><p>To install both run the following in your terminal:</p><pre><code>composer <span class="keyword">global</span> <span class="keyword">require</span> filp/whoops
|
||
composer <span class="keyword">global</span> <span class="keyword">require</span> symfony/<span class="keyword">var</span>-dumper</code></pre>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c145"></a>
|
||
<a name="configureTypo3"></a>
|
||
|
||
<h2>Configure TYPO3 <small><a href="#configureTypo3">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Afterwards you can configure TYPO3 to use the new exception handler. Therefore insert the following in your <code>typo3conf/AdditionalConfiguration.php</code>. Of course you have to update line 4 to match your installation path:</p><pre><code>call_user_func(<span class="function"><span class="keyword">function</span> <span class="params">($exceptionHandling = <span class="string">'whoops'</span>)</span> </span>{
|
||
<span class="comment">// Use whoops error handler for errors.</span>
|
||
<span class="keyword">if</span> ($exceptionHandling === <span class="string">'whoops'</span>) {
|
||
<span class="keyword">require_once</span> <span class="string">'/Users/siepmann/.composer/vendor/autoload.php'</span>;
|
||
$handler = <span class="keyword">null</span>;
|
||
<span class="keyword">if</span> (defined(<span class="string">'TYPO3_cliMode'</span>) && TYPO3_cliMode) {
|
||
<span class="comment">// $handler = new \Whoops\Handler\PlainTextHandler();</span>
|
||
} <span class="keyword">else</span> {
|
||
$handler = <span class="keyword">new</span> \Whoops\Handler\PrettyPageHandler();
|
||
$handler->setApplicationPaths([
|
||
<span class="string">'web'</span> => realpath(PATH_site . <span class="string">'../web'</span>),
|
||
<span class="string">'typo3'</span> => realpath(PATH_site . <span class="string">'../vendor/typo3/cms'</span>),
|
||
<span class="string">'typo3conf'</span> => realpath(PATH_site . <span class="string">'typo3conf'</span>),
|
||
]);
|
||
}
|
||
<span class="keyword">if</span> ($handler !== <span class="keyword">null</span>) {
|
||
$whoops = <span class="keyword">new</span> \Whoops\Run;
|
||
$whoops->pushHandler($handler);
|
||
$whoops->register();
|
||
}
|
||
}
|
||
<span class="keyword">if</span> ($exceptionHandling === <span class="string">'xdebug'</span> || $exceptionHandling === <span class="string">'whoops'</span>) {
|
||
<span class="comment">// Disable original handler to use whoops or xdebug</span>
|
||
$GLOBALS[<span class="string">'TYPO3_CONF_VARS'</span>][<span class="string">'SYS'</span>][<span class="string">'productionExceptionHandler'</span>] = <span class="string">''</span>;
|
||
$GLOBALS[<span class="string">'TYPO3_CONF_VARS'</span>][<span class="string">'SYS'</span>][<span class="string">'debugExceptionHandler'</span>] = <span class="string">''</span>;
|
||
$GLOBALS[<span class="string">'TYPO3_CONF_VARS'</span>][<span class="string">'SYS'</span>][<span class="string">'errorHandler'</span>] = <span class="string">''</span>;
|
||
}
|
||
});</code></pre><p>With this setup you will get the new exception handler for web requests.</p><p>On CLI I recommend to use <code>typo3cms</code> which will use Symfony stack traces anyway.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c146"></a>
|
||
<a name="configurationOptions"></a>
|
||
|
||
<h2>Configuration options <small><a href="#configurationOptions">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>To switch to the handler of <em>xdebug</em>, exchange <code>whoops</code> on line 1 with <code>xdebug</code>. To use the original TYPO3 handler insert something else, e.g. <code>TYPO3</code>.</p><p>Also you might want to adjust the application paths to your setup. All paths listed as values in that array will be filterable as application in stack trace.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c147"></a>
|
||
<a name="furtherReading"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<ul> <li><a href="http://filp.github.io/whoops/" class="reference external">http://filp.github.io/whoops/</a></li> </ul>
|
||
|
||
|
||
]]></description>
|
||
<link>https://daniel-siepmann.de/posts/2017/use-whoops-as-exception-handler-for-typo3.html</link>
|
||
<pubDate>Fri, 17 Nov 2017 00:00:00 +0100</pubDate>
|
||
<guid isPermaLink="true">https://daniel-siepmann.de/posts/2017/use-whoops-as-exception-handler-for-typo3.html</guid>
|
||
</item>
|
||
|
||
|
||
|
||
|
||
|
||
<item>
|
||
<title>How to create TYPO3 Form select element with options selected from database</title>
|
||
<description><![CDATA[TYPO3s new form framework allows to write custom form elements. This way you are able to define a new select element, based on the existing one, but filled with options fetched from database.
|
||
|
||
E.g. you want your user to select from sys_category or some other custom records. In this blog post I will show how to provide the necessary logic in a custom PHP class, how to register a new element extending the existing one and how to use this new element in your forms.
|
||
|
||
|
||
|
||
<a name="c139"></a>
|
||
<a name="introduction"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>The following steps are necessary:</p><ol> <li>Write custom element as PHP Class, extending base element.</li> <li>Define custom element as prototype in yaml-Configuration, which inherits existing configuration of a select.</li> <li>Use the new element in the form.</li> </ol>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c140"></a>
|
||
<a name="thePhpClass"></a>
|
||
|
||
<h2>The PHP Class <small><a href="#thePhpClass">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>As we want to provide custom functionality, we need to create a PHP Class which will contain this functionality. In our example we want to provide <code>sys_category</code> records based on a parent <code>sys_category</code> as possible options. Therefore we need to provide one option, the <code>uid</code> of the parent system category. Also we need to fetch the records from database using Doctrine and provide them as options for the element, e.g. select in our case.</p><p>The implementation is done with the following class:</p><pre><code><span class="php"><span class="meta"><?php</span>
|
||
<span class="keyword">namespace</span> <span class="title">DS</span>\<span class="title">ExampleExtension</span>\<span class="title">Domain</span>\<span class="title">Model</span>\<span class="title">FormElements</span>;
|
||
|
||
<span class="keyword">use</span> <span class="title">TYPO3</span>\<span class="title">CMS</span>\<span class="title">Core</span>\<span class="title">Database</span>\<span class="title">ConnectionPool</span>;
|
||
<span class="keyword">use</span> <span class="title">TYPO3</span>\<span class="title">CMS</span>\<span class="title">Core</span>\<span class="title">Database</span>\<span class="title">Query</span>\<span class="title">Restriction</span>\<span class="title">FrontendRestrictionContainer</span>;
|
||
<span class="keyword">use</span> <span class="title">TYPO3</span>\<span class="title">CMS</span>\<span class="title">Core</span>\<span class="title">Utility</span>\<span class="title">GeneralUtility</span>;
|
||
<span class="keyword">use</span> <span class="title">TYPO3</span>\<span class="title">CMS</span>\<span class="title">Form</span>\<span class="title">Domain</span>\<span class="title">Model</span>\<span class="title">FormElements</span>\<span class="title">GenericFormElement</span>;
|
||
<span class="keyword">use</span> <span class="title">TYPO3</span>\<span class="title">CMS</span>\<span class="title">Frontend</span>\<span class="title">Category</span>\<span class="title">Collection</span>\<span class="title">CategoryCollection</span>;
|
||
|
||
<span class="class"><span class="keyword">class</span> <span class="title">SystemCategoryOptions</span> <span class="keyword">extends</span> <span class="title">GenericFormElement</span>
|
||
</span>{
|
||
<span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">setProperty</span><span class="params">(string $key, $value)</span>
|
||
</span>{
|
||
<span class="keyword">if</span> ($key === <span class="string">'systemCategoryUid'</span>) {
|
||
<span class="keyword">$this</span>->setProperty(<span class="string">'options'</span>, <span class="keyword">$this</span>->getOptions($value));
|
||
<span class="keyword">return</span>;
|
||
}
|
||
|
||
<span class="keyword">parent</span>::setProperty($key, $value);
|
||
}
|
||
|
||
<span class="keyword">protected</span> <span class="function"><span class="keyword">function</span> <span class="title">getOptions</span><span class="params">(int $uid)</span> : <span class="title">array</span>
|
||
</span>{
|
||
$options = [];
|
||
|
||
<span class="keyword">foreach</span> (<span class="keyword">$this</span>->getCategoriesForUid($uid) <span class="keyword">as</span> $category) {
|
||
$options[$category[<span class="string">'uid'</span>]] = $category[<span class="string">'title'</span>];
|
||
}
|
||
|
||
asort($options);
|
||
<span class="keyword">return</span> $options;
|
||
}
|
||
|
||
<span class="keyword">protected</span> <span class="function"><span class="keyword">function</span> <span class="title">getCategoriesForUid</span><span class="params">(int $uid)</span> : <span class="title">array</span>
|
||
</span>{
|
||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
|
||
->getQueryBuilderForTable(<span class="string">'sys_category'</span>);
|
||
$queryBuilder->setRestrictions(
|
||
GeneralUtility::makeInstance(FrontendRestrictionContainer::class)
|
||
);
|
||
|
||
<span class="keyword">return</span> $queryBuilder
|
||
->select(<span class="string">'*'</span>)
|
||
->from(<span class="string">'sys_category'</span>)
|
||
->where(
|
||
$queryBuilder->expr()->eq(
|
||
<span class="string">'parent'</span>,
|
||
$queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
|
||
)
|
||
)
|
||
->execute()
|
||
->fetchAll();
|
||
}
|
||
}</span></code></pre><p>First of all we extend the <code>setProperty</code> method, which receives all options. If the current option is the configured <code>systemCategoryUid</code>, we hook into and add the options. In all other situations we just call the original method.</p><p>Based on the configured <code>uid</code>, we fetch the records from database in our <code>getCategoriesForUid</code> method.</p><p>Afterwards we iterate over the results and prepare them to be used by the select, therefore we need a label and identifier. We use the saved <code>title</code> and <code>uid</code>. The result is set as the <code>options</code> for select element.</p><p>The class itself does not contain any relation to the specifics of a select-element. It should also be possible to use the same code for radio or checkboxes, as long as they make use of the <code>options</code> property.</p>
|
||
|
||
|
||
|
||
|
||
|
||
<aside class="admonition-note">
|
||
|
||
<a name="c158"></a>
|
||
<a name="note"></a>
|
||
|
||
<h2>Note <small><a href="#note">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Therefore it should be possible to separate logic from the elements themselves and to build the concrete elements via yaml. But I didn’t try that yet.</p>
|
||
</aside>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c141"></a>
|
||
<a name="defineCustomElement"></a>
|
||
|
||
<h2>Define custom element <small><a href="#defineCustomElement">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Once our functionality is provided, we need to create a new form element to be available to our forms. Therefore we define the new element:</p><pre><code>TYPO3:
|
||
CMS:
|
||
Form:
|
||
prototypes:
|
||
standard:
|
||
formElementsDefinition:
|
||
SingleSelectWithSystemCategory:
|
||
__inheritances:
|
||
<span class="number">10</span>: <span class="string">'TYPO3.CMS.Form.prototypes.standard.formElementsDefinition.SingleSelect'</span>
|
||
<span class="attr">implementationClassName</span>: <span class="string">'DS\ExampleExtension\Domain\Model\FormElements\SystemCategoryOptions'</span>
|
||
<span class="attr">renderingOptions</span>:
|
||
templateName: <span class="string">'SingleSelect'</span></code></pre><p>On line 7 we define the identifier of our new element, under which we can use the element in our forms. We inherit the existing configuration of the select element and exchange the concrete php class for implementation. As the path of fluid template is generated from the name, we define to use the same template as for the select element.</p><p>That’s all we have to do, to define a new select with different implementation.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c142"></a>
|
||
<a name="useElement"></a>
|
||
|
||
<h2>Use element <small><a href="#useElement">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>We are now able to use the defined element in our forms:</p><pre><code>type: Form
|
||
<span class="attr">identifier</span>: Example
|
||
<span class="attr">label</span>: <span class="string">'Example - Form'</span>
|
||
<span class="attr">prototypeName</span>: standard
|
||
<span class="attr">renderingOptions</span>:
|
||
submitButtonLabel: Submit
|
||
<span class="attr">renderables</span>:
|
||
-
|
||
type: Page
|
||
<span class="attr">identifier</span>: page1
|
||
<span class="attr">renderingOptions</span>:
|
||
previousButtonLabel: <span class="string">'previous page'</span>
|
||
<span class="attr">nextButtonLabel</span>: <span class="string">'next page'</span>
|
||
<span class="attr">renderables</span>:
|
||
-
|
||
type: SingleSelectWithSystemCategory
|
||
<span class="attr">identifier</span>: jobTitle
|
||
<span class="attr">label</span>: Job Title
|
||
<span class="attr">properties</span>:
|
||
systemCategoryUid: <span class="number">5</span>
|
||
<span class="attr">prependOptionLabel</span>: <span class="string">'please choose'</span>
|
||
<span class="attr">fluidAdditionalAttributes</span>:
|
||
required: required
|
||
<span class="attr">validators</span>:
|
||
-
|
||
identifier: NotEmpty</code></pre><p>We define our own <code>SingleSelectWithSystemCategory</code> element to be used and define our <code>systemCategoryUid</code> to be used. Everything else is exactly the same as for any other select, as we use the same template.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c588"></a>
|
||
<a name="fileCollectionExample"></a>
|
||
|
||
<h2>File Collection example <small><a href="#fileCollectionExample">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>I was able to build another example, thanks to our customer <a href="https://werkraum-media.de/">werkraum_media</a>, following the same approach to use a selected File Collection instead. The concept is the same, you can have a look at the code at:</p><ul><li><a href="https://git.daniel-siepmann.de/Customers/form_file_collection">https://git.daniel-siepmann.de/Customers/form_file_collection</a></li><li><a href="https://github.com/werkraum-media/form_file_collection/">https://github.com/werkraum-media/form_file_collection/</a></li></ul>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c143"></a>
|
||
<a name="furtherReading"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Check out the official doc sections:</p><ul> <li><a href="https://docs.typo3.org/c/typo3/cms-form/master/en-us/I/Concepts/FrontendRendering/Index.html#concepts-frontendrendering-codecomponents-customformelementimplementations" title="(in Form Framework vlatest (10-dev))">Custom form element implementations</a></li> <li><a href="https://docs.typo3.org/c/typo3/cms-form/master/en-us/I/Concepts/Configuration/Index.html#concepts-configuration-inheritances-operator" class="reference external" title="(in Form Framework vlatest (10-dev))">__inheritances operator</a></li> <li><a href="https://docs.typo3.org/c/typo3/cms-form/master/en-us/I/Concepts/Configuration/Index.html#concepts-configuration-prototypes" class="reference external" title="(in Form Framework vlatest (10-dev))">Prototypes</a></li> </ul>
|
||
|
||
|
||
]]></description>
|
||
<link>https://daniel-siepmann.de/posts/2017/how-to-create-typo3-form-select-element-with-options-selected-from-database.html</link>
|
||
<pubDate>Thu, 07 Sep 2017 00:00:00 +0200</pubDate>
|
||
<guid isPermaLink="true">https://daniel-siepmann.de/posts/2017/how-to-create-typo3-form-select-element-with-options-selected-from-database.html</guid>
|
||
</item>
|
||
|
||
|
||
|
||
|
||
|
||
<item>
|
||
<title>How to crypt submitted values using a custom finisher in TYPO3 CMS 8</title>
|
||
<description><![CDATA[Since TYPO3 CMS Version 8 there is a new Form framework heavily inspired by Neos Form Framework. As most parts of Neos / Flow it’s a great heavy dynamic component with great power.
|
||
|
||
In this post I will show how easy it is to write a custom finisher to crypt submitted values using the SaltedPasswords extension. This enables you to write fe_user registration forms without the need of 3rd party extensions.
|
||
|
||
|
||
|
||
<a name="c115"></a>
|
||
<a name="introduction"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>The new form framework already provides a <code>SaveToDatabase</code> finisher to persist submitted information to a database. Therefore it’s easy to create a <code>fe_user</code> registration. But there is one reason you can’t: passwords are stored in plain text as they get no special handling. Therefore you can write a custom finisher to crypt the password before saving to database.</p><p>The following steps are necessary:</p><ol> <li>Write custom finisher as PHP Class.</li> <li>Register custom finisher as prototype in yaml-Configuration.</li> <li>Use Finisher in your form before saving to database.</li> <li>Use modified data by finisher instead of submitted data while saving to database.</li> </ol>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c116"></a>
|
||
<a name="writeCustomFinisherAsPhpClass"></a>
|
||
|
||
<h2>Write custom finisher as PHP Class <small><a href="#writeCustomFinisherAsPhpClass">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Just create a new PHP class that extends <code>\TYPO3\CMS\Form\Domain\Finishers\AbstractFinisher</code>, configure the possible options with their defaults and implement the <code>executeInternal</code> abstract method.</p><p>In our example we provide the option to configure a single field to crypt. Inside of our implementation we check that this field was submitted and that salted passwords are enabled in frontend.</p><p>If so, we crypt the password and add it as a new variable at <code>Crypt.<fieldname></code>. Where <code>Crypt</code> is the <code>shortFinisherIdentifier</code> and <code><fieldname></code> the configured field to crypt.</p><pre><code><span class="php"><span class="meta"><?php</span>
|
||
<span class="keyword">namespace</span> <span class="title">DS</span>\<span class="title">ExampleExtension</span>\<span class="title">Domain</span>\<span class="title">Finishers</span>;
|
||
|
||
<span class="keyword">use</span> <span class="title">TYPO3</span>\<span class="title">CMS</span>\<span class="title">Form</span>\<span class="title">Domain</span>\<span class="title">Finishers</span>\<span class="title">AbstractFinisher</span>;
|
||
<span class="keyword">use</span> <span class="title">TYPO3</span>\<span class="title">CMS</span>\<span class="title">Saltedpasswords</span>\<span class="title">Salt</span>\<span class="title">SaltFactory</span>;
|
||
<span class="keyword">use</span> <span class="title">TYPO3</span>\<span class="title">CMS</span>\<span class="title">Saltedpasswords</span>\<span class="title">Utility</span>\<span class="title">SaltedPasswordsUtility</span>;
|
||
|
||
<span class="comment">/**
|
||
* This finisher for form framework will crypt the configured field with salted
|
||
* passwords, if enabled for frontend.
|
||
*/</span>
|
||
<span class="class"><span class="keyword">class</span> <span class="title">CryptFinisher</span> <span class="keyword">extends</span> <span class="title">AbstractFinisher</span>
|
||
</span>{
|
||
<span class="comment">/**
|
||
* <span class="doctag">@var</span> array
|
||
*/</span>
|
||
<span class="keyword">protected</span> $defaultOptions = [
|
||
<span class="string">'field'</span> => <span class="string">''</span>,
|
||
];
|
||
|
||
<span class="keyword">protected</span> <span class="function"><span class="keyword">function</span> <span class="title">executeInternal</span><span class="params">()</span>
|
||
</span>{
|
||
$fieldName = <span class="keyword">$this</span>->parseOption(<span class="string">'field'</span>);
|
||
$formValues = <span class="keyword">$this</span>->finisherContext->getFormValues();
|
||
<span class="keyword">if</span> (!<span class="keyword">isset</span>($formValues[$fieldName])) {
|
||
<span class="keyword">return</span>;
|
||
}
|
||
|
||
<span class="keyword">if</span> (SaltedPasswordsUtility::isUsageEnabled(<span class="string">'FE'</span>)) {
|
||
<span class="keyword">$this</span>->finisherContext->getFinisherVariableProvider()->add(
|
||
<span class="keyword">$this</span>->shortFinisherIdentifier,
|
||
$fieldName,
|
||
SaltFactory::getSaltingInstance(<span class="keyword">null</span>)->getHashedPassword($formValues[$fieldName])
|
||
);
|
||
}
|
||
}
|
||
}</span></code></pre><p>It’s nearly the same as a Fluid ViewHelper. We configure options instead of arguments and can provide defaults. Then we add new variables to the container, in this case the FinisherVariableProvider.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c117"></a>
|
||
<a name="registerCustomFinisher"></a>
|
||
|
||
<h2>Register custom finisher <small><a href="#registerCustomFinisher">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>New finishers are not registered out of the box, we have to register them manually. Therefore we add the following to our <code>yaml</code> configuration which is defined in TypoScript:</p><pre><code>TYPO3:
|
||
CMS:
|
||
Form:
|
||
prototypes:
|
||
standard:
|
||
finishersDefinition:
|
||
CryptFinisher:
|
||
implementationClassName: DS\ExampleExtension\Domain\Finishers\CryptFinisher</code></pre><p>This way we can prevent naming collisions as we define the name of the finisher to use.</p><p>We just register the existing PHP class under <code>CryptFinisher</code> and can now use the finisher.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c118"></a>
|
||
<a name="useFinisherInOwnForm"></a>
|
||
|
||
<h2>Use Finisher in own form <small><a href="#useFinisherInOwnForm">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>We register the new custom finisher before other finishers, so he can crypt the plain text password and we are able to use the crypted version while saving to database.</p><p>We just tell the form framework to run our finisher and provide the necessary options:</p><pre><code>finishers:
|
||
-
|
||
identifier: CryptFinisher
|
||
options:
|
||
field: password</code></pre>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c119"></a>
|
||
<a name="useModifiedDataByFinisher"></a>
|
||
|
||
<h2>Use modified data by finisher <small><a href="#useModifiedDataByFinisher">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>As the finisher has added new data, we can access the new data and save this instead of the original submitted plain text password. That’s done on line 18.</p><p>We use the curly braces as already known by Fluid. Inside of the braces we use the short identifier of the finisher, which is the class name without namespace and <code>Finisher</code> suffix. So for <code>\DS\ExampleExtension\Domain\Finishers\CryptFinisher</code> this would be <code>Crypt</code>. Then, as already known by Fluid, we separate the path with dots and access the added data as documented in our finisher.</p><pre><code>finishers:
|
||
-
|
||
identifier: SaveToDatabase
|
||
<span class="attr">options</span>:
|
||
<span class="number">1</span>:
|
||
table: <span class="string">'fe_users'</span>
|
||
<span class="attr">mode</span>: insert
|
||
<span class="attr">databaseColumnMappings</span>:
|
||
pid:
|
||
value: <span class="number">140</span>
|
||
<span class="attr">disable</span>:
|
||
value: <span class="number">1</span>
|
||
<span class="attr">usergroup</span>:
|
||
value: <span class="number">1</span>
|
||
<span class="attr">description</span>:
|
||
value: <span class="string">'Registered via form'</span>
|
||
<span class="attr">password</span>:
|
||
value: <span class="string">'{Crypt.password}'</span></code></pre><p>That’s it. We now save the crypted password instead of the original plain text submitted value. If, for any reason, multiple fields need to be crypted we can add the same finisher with different options multiplet times and don’t need to bloat the implementation itself.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c120"></a>
|
||
<a name="furtherReading"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Check out the official doc sections:</p><ul class="simple"> <li> <a href="https://docs.typo3.org/c/typo3/cms-form/master/en-us/I/Concepts/Finishers/Index.html#concepts-finishers-customfinisherimplementations" class="reference external" title="(in Form Framework vlatest (10-dev))">Write a custom finisher</a> </li> <li> <a href="https://docs.typo3.org/c/typo3/cms-form/master/en-us/I/Config/proto/finishersDefinition/Index.html#typo3-cms-form-prototypes-prototypeidentifier-finishersdefinition-finisheridentifier-implementationclassname" class="reference external" title="(in Form Framework vlatest (10-dev))">implementationClassName</a> </li> <li> <a href="https://docs.typo3.org/c/typo3/cms-form/master/en-us/I/ApiReference/Index.html#apireference-frontendrendering-programmatically-apimethods-abstractfinisher" class="reference external" title="(in Form Framework vlatest (10-dev))">TYPO3\CMS\Form\Domain\Finishers\AbstractFinisher</a> </li> </ul>
|
||
|
||
|
||
]]></description>
|
||
<link>https://daniel-siepmann.de/posts/2017/how-to-crypt-submitted-values-using-a-custom-finisher-in-typo3-cms-8.html</link>
|
||
<pubDate>Sat, 26 Aug 2017 00:00:00 +0200</pubDate>
|
||
<guid isPermaLink="true">https://daniel-siepmann.de/posts/2017/how-to-crypt-submitted-values-using-a-custom-finisher-in-typo3-cms-8.html</guid>
|
||
</item>
|
||
|
||
|
||
|
||
|
||
|
||
<item>
|
||
<title>TYPO3 (Extbase) Injection</title>
|
||
<description><![CDATA[TYPO3 provides a way of dependency injection. This way you do not need to resolve dependencies inside of your code, but the framework will resolve and provide the dependencies for you. This is provided by the framework Extbase, back ported of Flow.
|
||
|
||
The main benefit is the flexibility. Using Interfaces to define dependencies, instead of concrete classes, it’s possible to exchange injected dependencies just by configuring the framework. This way you can exchange classes in 3rd party code and receive a huge flexibility. Same goes for testing your code. In this Post I will show you the different ways to make use of dependency injection inside of TYPO3 and provide help for edge cases.
|
||
|
||
|
||
<aside class="admonition-note">
|
||
|
||
<a name="c308"></a>
|
||
<a name="note"></a>
|
||
|
||
<h2>Note <small><a href="#note">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Since TYPO3 v10, Dependency Injection is integrated in the whole system. It is no longer limited to Extbase and his <code>ObjectManager</code>.</p><p>When working with TYPO3 v10 or newer, please do not read this blog post, but the official documentation at <a href="https://docs.typo3.org/m/typo3/reference-coreapi/master/en-us/ApiOverview/DependencyInjection/Index.html" title="Dependency Injection at docs.typo3.org">docs.typo3.org</a>. There are also two blog posts at usetypo3.com which I highly recommend: <a href="https://usetypo3.com/dependency-injection.html" title="Blog Post at usetypo3.com about Dependency Injection">Dependency Injection in TYPO3</a> and <a href="https://usetypo3.com/di-and-events-example.html" title="Blog Post at usetypo3.com about Dependency Injection">Usecase: Caching, DI and Events</a>.</p><p>As TYPO3 uses the Symfony component, I would also recommend to read their documentation at <a href="https://symfony.com/doc/current/components/dependency_injection.html" title="Symfony Component Documentation about Dependency Injection">symfony.com</a>.</p>
|
||
</aside>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c122"></a>
|
||
<a name="possibleInjectMethods"></a>
|
||
|
||
<h2>Possible inject methods <small><a href="#possibleInjectMethods">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>There are three different ways to make use of dependency injection:</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c123"></a>
|
||
<a name="viaAnnotation"></a>
|
||
|
||
<h3>Via annotation <small><a href="#viaAnnotation">¶</a></small></h3>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Annotate a class property with <code>@inject</code> to enable Extbase to resolve the dependency through reflection:</p><pre><code><span class="comment">/**
|
||
* <span class="doctag">@var</span> \TYPO3\CMS\Extbase\Utility\ArrayUtility
|
||
* <span class="doctag">@inject</span>
|
||
*/</span>
|
||
<span class="keyword">protected</span> $arrayUtility;</code></pre>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c124"></a>
|
||
<a name="viaInjectMethod"></a>
|
||
|
||
<h3>Via inject method <small><a href="#viaInjectMethod">¶</a></small></h3>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>You can provide a method that’s reflected by the framework to inject a dependency. The main benefit is that you are able to initialize the dependency. A typical use case it to inject the logger through a method, as you inject the <code>LogManager</code> and fetch the concrete <code>Logger</code> inside the method:</p><pre><code><span class="comment">/**
|
||
* <span class="doctag">@var</span> \TYPO3\CMS\Core\Log\Logger
|
||
*/</span>
|
||
<span class="keyword">protected</span> $logger;
|
||
|
||
<span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">injectLogger</span><span class="params">(\TYPO3\CMS\Core\Log\LogManager $logManager)</span>
|
||
</span>{
|
||
<span class="keyword">$this</span>->logger = $logManager->getLogger(<span class="keyword">__CLASS__</span>);
|
||
}</code></pre><p>Another example is injection of settings through the <code>ConfigurationManager</code>, see <a href="/posts/migrated/inject-typoscript-settings.html">Inject TypoScript Settings</a>.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c125"></a>
|
||
<a name="viaConstructor"></a>
|
||
|
||
<h3>Via constructor <small><a href="#viaConstructor">¶</a></small></h3>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Extbase will also reflect the <code>__construct</code> method and inject dependencies not provided during construction:</p><pre><code><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">__construct</span><span class="params">(ConfigurationContainerInterface $configuration)</span>
|
||
</span>{
|
||
<span class="keyword">$this</span>->configuration = $configuration;
|
||
}</code></pre>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c126"></a>
|
||
<a name="differencesForMethods"></a>
|
||
|
||
<h2>Differences for methods <small><a href="#differencesForMethods">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>With each method comes some difference:</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c127"></a>
|
||
<a name="withAnnotation"></a>
|
||
|
||
<h3>With annotation <small><a href="#withAnnotation">¶</a></small></h3>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>You are not able to access the injected dependencies in your <code>__construct</code> but the special method <code>initializeObject</code>.</p><p>Also you have to use the FQCN (=Fully qualified class name), so import statements will not work here.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c128"></a>
|
||
<a name="withInjectMethod"></a>
|
||
|
||
<h3>With inject method <small><a href="#withInjectMethod">¶</a></small></h3>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Like with annotation, you are not able to access the injected dependencies in your <code>__construct</code> but the special method <code>initializeObject</code>.</p><p>It’s possible to use <code>use</code> statements. As only the signature is reflected, no comment is needed at all.</p><p>The method has to be <code>public</code> and start with <code>inject</code>. The special method <code>injectSettings</code> is blocked and will not work.</p><p>Also this will be supported by TYPO3 Version 10 LTS out of the box accross all code, not only Extbase.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c129"></a>
|
||
<a name="withConstructor"></a>
|
||
|
||
<h3>With constructor <small><a href="#withConstructor">¶</a></small></h3>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>It’s possible to use <code>use</code> statements. As only the signature is reflected, no comment is needed at all.</p><p>Also this will be supported by TYPO3 Version 10 LTS out of the box accross all code, not only Extbase.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c130"></a>
|
||
<a name="whenInjectionIsNotWorking"></a>
|
||
|
||
<h2>When injection is not working <small><a href="#whenInjectionIsNotWorking">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>As dependency injection is part of Extbase and not TYPO3 Core, it will not work if you instantiate new instances through <code>new</code> or <code>\TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance()</code>. You have to use <code>\TYPO3\CMS\Extbase\Object\ObjectManager::get()</code>. The <code>ObjectManager</code> will also take care of all sub dependencies.</p><p>Therefore you should use hooks and further stuff only as proxies, connecting your logic to the system through its API. This way you are able to load your logic through the <code>ObjectManager</code> resolving all dependencies, and are also able to reuse the logic in different places.</p><p>While calling <code>get</code> you can provide constructor arguments. You have to provide them in the way they are defined in the method signature. All arguments left undefined will be resolved through dependency injection. This way it’s possible to create a new instance and inject different dependencies:</p><pre><code><span class="class"><span class="keyword">class</span> <span class="title">MyOwnClass</span>
|
||
</span>{
|
||
<span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">__construct</span><span class="params">(
|
||
ArrayUtilityInterface $arrayUtility,
|
||
AnotherInterface $anotherDepdendency
|
||
)</span> </span>{
|
||
<span class="comment">// ...</span>
|
||
}
|
||
|
||
<span class="class"><span class="keyword">class</span> <span class="title">MyOwnArrayUtility</span> <span class="keyword">implements</span> <span class="title">ArrayUtilityInterface</span>
|
||
</span>{
|
||
<span class="comment">// ...</span>
|
||
}
|
||
|
||
$customArrayUtility = <span class="keyword">$this</span>->objectManager->get(MyOwnArrayUtility::class);
|
||
<span class="keyword">$this</span>->objectManager->get(MyOwnClass::class, $customArrayUtility);</code></pre><p>Just make sure to extend the original class or implement the expected interface. Therefore it’s much better to define interfaces and to use them in your signatures, then concrete class implementations.</p><p>This will change with TYPO3 Version 10 LTS. I'll try to update the blog post in the future.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c131"></a>
|
||
<a name="configuringDependencies"></a>
|
||
|
||
<h2>Configuring dependencies <small><a href="#configuringDependencies">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Once you make use of dependency injection, you might want to exchange one resolved dependency for some reason, e.g. in a 3rd party or core Extension.</p><p>There are two ways you can configure dependencies to be resolved. One is TypoScript and the other is PHP.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c132"></a>
|
||
<a name="typoscript"></a>
|
||
|
||
<h3>TypoScript <small><a href="#typoscript">¶</a></small></h3>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>You have to configure the dependencies the following way:</p><pre><code>config.tx_extbase {
|
||
object {
|
||
TYPO3\CMS\Extbase\Persistence\Storage\BackendInterface {
|
||
className = DS\ExampleExtension\Persistence\Storage\Backend
|
||
}
|
||
}
|
||
}</code></pre><p>The above example will inject our own implementation <code>\DS\ExampleExtension\Persistence\Storage\Backend</code> whenever <code>\TYPO3\CMS\Extbase\Persistence\Storage\BackendInterface</code> is required.</p><p>The downside of this approach is, that Extbase bootstrapping has to be run to initialize the <code>ObjectManager</code> with this configuration. But in TYPO3 there are enough situation when this is not the case, e.g. in Hooks.</p><p>The benefit is, you can also configure different dependencies per extension, plugin or module:</p><pre><code>plugin.tx_exampleextension {
|
||
object {
|
||
TYPO3\CMS\Extbase\Persistence\Storage\BackendInterface {
|
||
className = DS\ExampleExtension\Persistence\Storage\PluginSpecificBackend
|
||
}
|
||
}
|
||
}
|
||
|
||
<span class="built_in">module</span>.tx_exampleextension {
|
||
object {
|
||
TYPO3\CMS\Extbase\Persistence\Storage\BackendInterface {
|
||
className = DS\ExampleExtension\Persistence\Storage\ModuleSpecificBackend
|
||
}
|
||
}
|
||
}</code></pre>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c133"></a>
|
||
<a name="php"></a>
|
||
|
||
<h3>PHP <small><a href="#php">¶</a></small></h3>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>The other way is to directly configure the <code>ObjectManager</code>:</p><pre><code>\TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Object\Container\Container::class)
|
||
->registerImplementation(
|
||
\Codappix\SearchCore\Connection\ConnectionInterface::class,
|
||
\Codappix\SearchCore\Connection\Elasticsearch::class
|
||
);</code></pre><p>You should place this inside of a <code>ext_localconf.php</code>. This way the configuration is available no matter which context is used. Therefore this should be preferred. Still this will always configure globally.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c134"></a>
|
||
<a name="sectionEnd"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>If nothing is configured, Extbase will remove the trailing <code>Interface</code> of the dependency and try to inject the class name, so for <code>Vendor\Extension\Utility\ExampleUtilityInterface</code> Extbase will try to provide <code>Vendor\Extension\Utility\ExampleUtility</code>.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c135"></a>
|
||
<a name="caching"></a>
|
||
|
||
<h2>Caching <small><a href="#caching">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>As reflection is a bit expensive, Extbase will cache the information. Therefore you have to clear cache once you add a new dependency injection, no matter which method you are using. Otherwise you will see method calls to non objects, or non working instantiations, as they are not injected. The used cache is <code>extbase_object</code>.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c136"></a>
|
||
<a name="howToUseInTests"></a>
|
||
|
||
<h2>How to use in tests <small><a href="#howToUseInTests">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Another big benefit of the flexibility is used inside of tests. Compare the “old way” vs. the new way:</p><p>Old:</p><pre><code><span class="class"><span class="keyword">class</span> <span class="title">SomeClass</span>
|
||
</span>{
|
||
<span class="keyword">protected</span> $exampleUtility;
|
||
|
||
<span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">__construct</span><span class="params">()</span>
|
||
</span>{
|
||
<span class="keyword">$this</span>->exampleUtility = GeneralUtility::makeInstance(ExampleUtility::class);
|
||
}
|
||
}</code></pre><p>New:</p><pre><code><span class="class"><span class="keyword">class</span> <span class="title">SomeClass</span>
|
||
</span>{
|
||
<span class="keyword">protected</span> $exampleUtility;
|
||
|
||
<span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">__construct</span><span class="params">(ExampleUtilityInterface $exampleUtility)</span>
|
||
</span>{
|
||
<span class="keyword">$this</span>->exampleUtility = $exampleUtility
|
||
}
|
||
}</code></pre><p>The new one makes it very easy to pass a mocked version of a dependency inside of our tests, that’s true for all the methods, enabling us to mock the behaviour and create a unit test for a single class. For annotation and method there is a helper method you might use inside of your tests to inject the dependency.</p><p>The helper method is part of <code>BaseTestCase</code> and is called <code>inject</code>:</p><pre><code>$testSubject = <span class="keyword">new</span> ClassToTest();
|
||
<span class="keyword">$this</span>->inject($testSubject, <span class="string">'exampleUtility'</span>, <span class="keyword">$this</span>->getMockBuilder(ExampleUtilityInterface::class)->getMock());</code></pre>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c137"></a>
|
||
<a name="whichMethodShouldYouUse"></a>
|
||
|
||
<h2>Which method should you use? <small><a href="#whichMethodShouldYouUse">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>I would prefer the <code>_construct</code> approach, as it’s not only working with Extbase, but also the only way to really define dependencies. Everyone still can create instances through <code>makeInstance</code> or <code>new</code>. But they still have to provide the dependencies. Also they can not be altered once an instance exists.</p><p>Also the <a href="/posts/2017/typo3-extbase-injection.html#c125">construct</a> and <a href="/posts/2017/typo3-extbase-injection.html#c124">inject</a> versions will be supported from TYPO3 version 10 LTS accross the code base, not only in Extbase.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c138"></a>
|
||
<a name="furtherReading"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<ul class="simple"> <li><code>\TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance()</code></li> <li><code>\TYPO3\CMS\Extbase\Object\ObjectManager::get()</code></li> <li>Also you might find the post <a href="/posts/migrated/inject-typoscript-settings.html">Inject TypoScript Settings</a> useful.</li> <li>Github gist in a form of a Blog post about this topic <a href="https://gist.github.com/NamelessCoder/3b2e5931a6c1af19f9c3f8b46e74f837" class="reference external">https://gist.github.com/NamelessCoder/3b2e5931a6c1af19f9c3f8b46e74f837</a></li> <li>German Blog post about this topic <a href="https://www.typo3tiger.de/blog/post/extbase-dependency-injection.html" class="reference external">https://www.typo3tiger.de/blog/post/extbase-dependency-injection.html</a></li> </ul>
|
||
|
||
|
||
]]></description>
|
||
<link>https://daniel-siepmann.de/posts/2017/typo3-extbase-injection.html</link>
|
||
<pubDate>Thu, 17 Aug 2017 00:00:00 +0200</pubDate>
|
||
<guid isPermaLink="true">https://daniel-siepmann.de/posts/2017/typo3-extbase-injection.html</guid>
|
||
</item>
|
||
|
||
|
||
|
||
|
||
|
||
<item>
|
||
<title>Using PHP_CodeSniffer for automated code migrations</title>
|
||
<description><![CDATA[PHP_CodeSniffer is a command line tool allowing to check php, js and css. The main use case is to check code styles like the popular PSR-2. Beside checking coding styles, some communities are already using this tool for further checks like direct access to global variables like $_POST instead of using the provided API, e.g. take a look at Magento PHP_CodeSniffer Coding Standard. Also there is a standard to check compatibility of the code with PHP versions.
|
||
|
||
Beside this use cases and huge benefits, there is another use case: automated code migrations that can be achieved using PHP_CodeSniffer. In this blog post I will provide the necessary basics and an example how to auto migrate your PHP code using PHP_CodeSniffer.
|
||
|
||
|
||
|
||
<a name="c97"></a>
|
||
<a name="theIdea"></a>
|
||
|
||
<h2>The idea <small><a href="#theIdea">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>PHP_CodeSniffer already is able to parse PHP code. It can be used as a framework to check code against any possible rule, as you can implement new rules using PHP.</p><p>Beside the check, it’s also possible to use the cli tool <code class="docutils literal notranslate">phpcbf</code>, which comes with PHP_CodeSniffer, to auto fix the found issues. This way we can provide own rules to find code that needs migration and provide a fix for each occurrence.</p><p>As PHP_CodeSniffer already parses the source code for us, it’s very easy to implement new rules and fixes.</p><p>By providing an easy to use command line interface, we are able to include this migrations into existing workflows like pre-commit hooks or continuous integrations. Also we can run them on our local dev environments without complex installation instructions.</p><p>IDEs like <a href="https://confluence.jetbrains.com/display/PhpStorm/PHP+Code+Sniffer+in+PhpStorm" class="reference external">PHPStorm</a> and editors like <a href="https://github.com/vim-syntastic/syntastic" class="reference external">Vim</a> or <a href="https://github.com/squizlabs/sublime-PHP_CodeSniffer" class="reference external">Sublime Text</a> already integrate PHP_CodeSniffer directly, or through plugins, and make it easy to warn about outdated code while writing new code. Also you can fix the issues from within your IDE / Editor. That makes PHP_CodeSniffer the perfect tool for automated code migrations.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c98"></a>
|
||
<a name="benefits"></a>
|
||
|
||
<h2>Benefits <small><a href="#benefits">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<ul> <li>Integration into IDE / Editor</li> <li>Automation on CLI through CVS hooks / continuous integrations</li> <li>Easy to use</li> <li>Easy to extend</li> <li>A lot of available examples</li> </ul>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c99"></a>
|
||
<a name="theBasics"></a>
|
||
|
||
<h2>The basics <small><a href="#theBasics">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>PHP_CodeSniffer comes with two command line tools <code class="docutils literal notranslate">phpcs</code> for “PHP_CodeSniffer” which will check code against configured rules, and <code class="docutils literal notranslate">phpcbf</code> for “PHP Code Beautifier and Fixer” which will adjust the code accordingly.</p><p>To provide your own migration, you need a new “Coding Standard” that can be used with these tools. Creating a new standard basically consists of creating a new folder, and a <code class="file docutils literal notranslate">ruleset.xml</code> which configures the standard. Also you can provide further <em>Sniffs</em>, which are PHP files including further rules. A “<a href="https://github.com/squizlabs/PHP_CodeSniffer/wiki/Coding-Standard-Tutorial" class="reference external">Coding Standard Tutorial</a>” can be found at github.</p><p>Afterwards you need to “install” your new standard to enable the cli tools to execute the standard, that’s it. Installation of a new standard is also explained at Github, see “<a href="https://github.com/squizlabs/PHP_CodeSniffer/wiki/Configuration-Options#setting-the-installed-standard-paths" class="reference external">Setting the installed standard paths</a>”. Also you can always provide the path to the standard, like:</p><pre><code>$ phpcs --standard=<span class="regexp">/path/</span>to/MyStandard test.php</code></pre>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c100"></a>
|
||
<a name="howTo"></a>
|
||
|
||
<h2>How to <small><a href="#howTo">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>As already a tutorial is provided by the project itself, I’ll provide further information not covered by the tutorial.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c101"></a>
|
||
<a name="ownStandard"></a>
|
||
|
||
<h3>Own standard <small><a href="#ownStandard">¶</a></small></h3>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>There is not much to say, just follow the tutorial and “create” a standard. As documented on github in the <a href="https://github.com/squizlabs/PHP_CodeSniffer/wiki/Annotated-Ruleset" class="reference external">Annotated ruleset.xml</a> it’s possible to include existing standards. E.g. gather existing sniffs from other projects, here are some examples:</p><ul> <li><a href="https://github.com/magento-ecg/coding-standard" class="reference external">Magento PHP_CodeSniffer Coding Standard</a> to check for best practices in Magento development.</li> <li><a href="https://github.com/PHPCompatibility/PHPCompatibility" class="reference external">compatibility of the code with PHP versions</a> to check for your PHP version.</li> <li><a href="https://www.drupal.org/node/1419980" class="reference external">Drupal</a>.</li> <li><a href="https://github.com/Konafets/TYPO3SniffPool" class="reference external">TYPO3 Sniffs</a></li> </ul><p>Chances are that your cms / framework already provides a basic standard. At least <a href="https://www.php-fig.org/psr/psr-2/" class="reference external">PSR-2</a> which is already included in PHP_CodeSniffer installation is available.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c102"></a>
|
||
<a name="ownSniffsFixingCode"></a>
|
||
|
||
<h3>Own Sniffs fixing code <small><a href="#ownSniffsFixingCode">¶</a></small></h3>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Once you configured the standard to include some existing standard, you need to add custom Sniffs as documented in the tutorial.</p><p>Most of the time your sniff will gather the tokens for current file and check some parts before or after the current stack to match certain conditions. E.g. check out the small examples at our own project in the <a href="https://github.com/DanielSiepmann/automated-typo3-update/tree/develop/src/Standards/Typo3Update/Sniffs/LegacyClassname">LegacyClassnames folder at Github</a>.</p><p>Make sure you make use of <code>$phpcsFile->addFixableError</code> whenever possible, to allow <code>phpcbf</code> to fix the issues. Otherwise it’s not about automated code migration, but just a check providing you with a list of violations.</p><p>Allowing to fix an error instead of only reporting the error is done by the following code:</p><pre><code>$fix = $phpcsFile->addFixableError(
|
||
<span class="string">'Legacy classes are not allowed; found "%s", use "%s" instead'</span>, <span class="comment">// Error message</span>
|
||
$stackPtr, <span class="comment">// Stack pointer</span>
|
||
<span class="string">'legacyClassname'</span>, <span class="comment">// identifier inside the sniff</span>
|
||
[$classname, <span class="keyword">$this</span>->getNewClassname($classname)] <span class="comment">// Arguments used for replacement in error message</span>
|
||
);
|
||
|
||
<span class="comment">// Check whether fixing is active</span>
|
||
<span class="keyword">if</span> ($fix === <span class="keyword">true</span>) {
|
||
<span class="comment">// Execute code to modify the tokens to fix the violation</span>
|
||
$phpcsFile->fixer->replaceToken($stackPtr, <span class="string">'new token content'</span>);
|
||
}</code></pre><p>You add the error as usual but using a different method. This method will return <code>true</code> if <code>phpcbf</code> is run and fixes should be done. If fixes should happen, use the <a href="https://pear.php.net/package/PHP_CodeSniffer/docs/2.8.1/PHP_CodeSniffer/PHP_CodeSniffer_Fixer.html#methodreplaceToken" class="reference external">replaceToken</a> method of the <a href="https://pear.php.net/package/PHP_CodeSniffer/docs/2.8.1/PHP_CodeSniffer/PHP_CodeSniffer_Fixer.html" class="reference external">PHP_CodeSniffer_Fixer</a> class to adjust the code.</p><p><code>$stackPtr</code> in the above example is no longer the provided <code>$stackPtr</code> from PHP_CodeSniffer, but the token that contains the violation. So if you register <code>T_NEW</code> but the classname afterwards contains the violation, <code>$stackPtr</code> is the token of the classname.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c103"></a>
|
||
<a name="furtherHelpForNewSniffs"></a>
|
||
|
||
<h2>Further help for new sniffs <small><a href="#furtherHelpForNewSniffs">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>While writing own sniffs, some information might be handy, that are:</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c152"></a>
|
||
<a name="whereDoIFindTheTokensICanReturnInsideOfTheRegisterMethod"></a>
|
||
|
||
<h3>Where do I find the tokens I can return inside of the register method? <small><a href="#whereDoIFindTheTokensICanReturnInsideOfTheRegisterMethod">¶</a></small></h3>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>The first step is to check out the official php tokens at <a href="https://secure.php.net/manual/en/tokens.php" class="reference external">php.net</a> Also check out the additional tokens of PHP_CodeSniffer itself inside the <a href="https://github.com/squizlabs/PHP_CodeSniffer/blob/2.8.1/CodeSniffer/Tokens.php" class="reference external">Tokens.php</a> Also note that <a href="https://github.com/squizlabs/PHP_CodeSniffer/blob/2.8.1/CodeSniffer/Tokens.php" class="reference external">Tokens.php</a> contains some collections you can reuse, e.g.:</p><pre><code><span class="comment">/**
|
||
* Tokens that are comments.
|
||
*
|
||
* <span class="doctag">@var</span> array(int)
|
||
*/</span>
|
||
<span class="keyword">public</span> <span class="keyword">static</span> $commentTokens = <span class="keyword">array</span>(
|
||
T_COMMENT => T_COMMENT,
|
||
T_DOC_COMMENT => T_DOC_COMMENT,
|
||
T_DOC_COMMENT_STAR => T_DOC_COMMENT_STAR,
|
||
T_DOC_COMMENT_WHITESPACE => T_DOC_COMMENT_WHITESPACE,
|
||
T_DOC_COMMENT_TAG => T_DOC_COMMENT_TAG,
|
||
T_DOC_COMMENT_OPEN_TAG => T_DOC_COMMENT_OPEN_TAG,
|
||
T_DOC_COMMENT_CLOSE_TAG => T_DOC_COMMENT_CLOSE_TAG,
|
||
T_DOC_COMMENT_STRING => T_DOC_COMMENT_STRING,
|
||
);</code></pre>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c153"></a>
|
||
<a name="howDoIRunOnlyOneSniffTheOneImWorkingOnRightNow"></a>
|
||
|
||
<h3>How do I run only one sniff, the one I’m working on right now? <small><a href="#howDoIRunOnlyOneSniffTheOneImWorkingOnRightNow">¶</a></small></h3>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Just provide the <code>--sniffs</code> option during CLI calls:</p><pre><code>phpcbf -p --colors -s --sniffs=Typo3Update.LegacyClassnames.DocComment Classes/Controller.php</code></pre>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c154"></a>
|
||
<a name="howDoIGetTheSniffNameOfASniff"></a>
|
||
|
||
<h3>How do I get the sniff name of a sniff? <small><a href="#howDoIGetTheSniffNameOfASniff">¶</a></small></h3>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<ol> <li>Coding Standard name (<code>Typo3Update</code>)</li> <li>Folder name (<code>LegacyClassnames</code>)</li> <li>File name (<code>DocCommentSniff.php</code> -> <code>DocComment</code>)</li> </ol><p>Also they are displayed by running <code>phpcs</code> with option <code>-s</code>, like:</p><pre><code>$ ./vendor/bin/phpcs -s <span class="tag"><<span class="name">path</span>></span>
|
||
8 | ERROR | [x] Legacy classes are not allowed; found
|
||
| | backend_toolbarItem
|
||
| | (Typo3Update.LegacyClassnames.Inheritance.legacyClassname)</code></pre>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c155"></a>
|
||
<a name="makePartsConfigurableThroughRulesetxml"></a>
|
||
|
||
<h3>Make parts configurable through ruleset.xml <small><a href="#makePartsConfigurableThroughRulesetxml">¶</a></small></h3>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>All public properties of sniffs are configurable through the <code>ruleset.xml</code>. So all you have to do, is to provide a public property as an option. The properties are configured on a sniff base. So extending a class with a public option makes the option available to all sniffs, same goes for traits.</p><p>The configuration will look like the following:</p><pre><code><span class="tag"><<span class="name">rule</span> <span class="attr">ref</span>=<span class="string">"Typo3Update.LegacyClassnames.DocComment"</span>></span>
|
||
<span class="tag"><<span class="name">properties</span>></span>
|
||
<span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"allowedTags"</span> <span class="attr">type</span>=<span class="string">"array"</span> <span class="attr">value</span>=<span class="string">"@param,@return,@var,@see,@throws"</span>/></span>
|
||
<span class="tag"></<span class="name">properties</span>></span>
|
||
<span class="tag"></<span class="name">rule</span>></span></code></pre><p>You have to define the rule to configure, followed by Tag <code>properties</code> that contain each property you want to configure as a tag inside.</p><p>You can also take a look at <a href="https://github.com/squizlabs/PHP_CodeSniffer/wiki/Customisable-Sniff-Properties" class="reference external">Customisable Sniff Properties</a>.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c156"></a>
|
||
<a name="replYourSniffs"></a>
|
||
|
||
<h3>REPL your sniffs <small><a href="#replYourSniffs">¶</a></small></h3>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>I prefer to use <a href="https://psysh.org/" class="reference external">psysh</a> nowadays and it makes it easy to “discover” your code and write your sniffs interactively. It’s an Symfony Cli App you can call from within your code by including the following line:</p><pre><code><span class="keyword">require_once</span>(<span class="string">'~/bin/psysh'</span>);<span class="keyword">eval</span>(\Psy\sh());</code></pre><p>Like an <code>xdebug_break()</code> the execution will halt and you are inside the app and can play around.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c104"></a>
|
||
<a name="result"></a>
|
||
|
||
<h2>Result <small><a href="#result">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>The result is a check like:</p><pre><code>$ ./vendor/bin/phpcs -p --colors -s <span class="tag"><<span class="name">path</span>></span>
|
||
E
|
||
|
||
|
||
FILE: <span class="tag"><<span class="name">path</span>></span>
|
||
----------------------------------------------------------------------
|
||
FOUND 5 ERRORS AFFECTING 5 LINES
|
||
----------------------------------------------------------------------
|
||
8 | ERROR | [x] Legacy classes are not allowed; found
|
||
| | backend_toolbarItem
|
||
| | (Typo3Update.LegacyClassnames.Inheritance.legacyClassname)
|
||
14 | ERROR | [x] Legacy classes are not allowed; found TYPO3backend
|
||
| | (Typo3Update.LegacyClassnames.DocComment.legacyClassname)
|
||
16 | ERROR | [x] Legacy classes are not allowed; found TYPO3backend
|
||
| | (Typo3Update.LegacyClassnames.TypeHint.legacyClassname)
|
||
48 | ERROR | [x] Legacy classes are not allowed; found t3lib_extMgm
|
||
| | (Typo3Update.LegacyClassnames.StaticCall.legacyClassname)
|
||
61 | ERROR | [x] Legacy classes are not allowed; found t3lib_div
|
||
| | (Typo3Update.LegacyClassnames.StaticCall.legacyClassname)
|
||
----------------------------------------------------------------------
|
||
PHPCBF CAN FIX THE 5 MARKED SNIFF VIOLATIONS AUTOMATICALLY
|
||
----------------------------------------------------------------------
|
||
|
||
Time: 35ms; Memory: 5Mb</code></pre><p>And of course the auto migrated code.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c105"></a>
|
||
<a name="history"></a>
|
||
|
||
<h2>History <small><a href="#history">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>We are currently using PHP_CodeSniffer to auto migrate TYPO3 Extensions in a 6.2 installation, to be compatible with the latest LTS release. Due to massive namespace changes in versions between the original writing of the extensions, we make heavy use of PHP_CodeSniffer to auto migrate the code.</p><p>Before we did some small research how TYPO3 migrated the code itself and how Neos / Flow does the job. But plain regular expressions are not enough for us. Also regular expressions are not as well integrated into IDEs and editors as PHP_CodeSniffer.</p><p>You can check out the source code at my git instance: <a href="https://git.daniel-siepmann.de/danielsiepmann/automated-typo3-update" class="reference external">DanielSiepmann/automated-typo3-update</a>.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c106"></a>
|
||
<a name="furtherReading"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<ul><li><a href="https://github.com/squizlabs/PHP_CodeSniffer" class="reference external">PHP_CodeSniffer at Github</a></li><li><a href="https://github.com/squizlabs/PHP_CodeSniffer/wiki" class="reference external">PHP_CodeSniffer documentation (wiki) at Github</a></li><li><a href="https://pear.php.net/manual/en/package.php.php-codesniffer.php" class="reference external">PHP_CodeSniffer documentation at php.net</a></li><li><a href="https://git.daniel-siepmann.de/danielsiepmann/automated-typo3-update" class="reference external">DanielSiepmann/automated-typo3-update</a></li></ul>
|
||
|
||
|
||
]]></description>
|
||
<link>https://daniel-siepmann.de/posts/2017/using-php-codesniffer-for-automated-code-migrations.html</link>
|
||
<pubDate>Mon, 20 Mar 2017 15:21:00 +0100</pubDate>
|
||
<guid isPermaLink="true">https://daniel-siepmann.de/posts/2017/using-php-codesniffer-for-automated-code-migrations.html</guid>
|
||
</item>
|
||
|
||
|
||
|
||
|
||
|
||
<item>
|
||
<title>Build TYPO3 Language Menu without the need of optionSplit</title>
|
||
<description><![CDATA[TYPO3 CMS allows you to build a language menu to enable the frontend user to switch the current language. This menu is generated via TypoScript using optionSplit. Just start a query and take a look at the snippets. This way has one big drawback. In a multi domain setup you have to change the config
|
||
|
||
We have overcame this issue with one language menu working for all setup on all domains without the need to adjust anything. Read here how to achieve this.
|
||
|
||
|
||
|
||
<a name="c42"></a>
|
||
<a name="theIdea"></a>
|
||
|
||
<h2>The idea <small><a href="#theIdea">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>The basic idea is to use the <code>HMENU</code> like all other solutions, but instead of using the <code>optionSplit</code> we are using <a href="https://docs.typo3.org/m/typo3/reference-typoscript/master/en-us/Functions/Stdwrap.html#stdwrap-data" title="TypoScript Reference">data</a> to inject the values from a language file.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c43"></a>
|
||
<a name="theTyposcript"></a>
|
||
|
||
<h2>The TypoScript <small><a href="#theTyposcript">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<pre><code>tmp.language = HMENU
|
||
tmp.language {
|
||
wrap = <span class="xml"><span class="tag"><<span class="name">ul</span>></span>|<span class="tag"></<span class="name">ul</span>></span></span>
|
||
|
||
special = language
|
||
special {
|
||
value = <span class="number">0</span>, <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>
|
||
}
|
||
|
||
<span class="number">1</span> = TMENU
|
||
<span class="number">1</span> {
|
||
NO = <span class="number">1</span>
|
||
NO {
|
||
allWrap = <span class="xml"><span class="tag"><<span class="name">li</span>></span>|<span class="tag"></<span class="name">li</span>></span></span>
|
||
|
||
ATagTitle {
|
||
data = LLL:EXT:example/Resources/Private/Language/Frontend.xlf:languageMenu.title.{<span class="attr">field</span>: _PAGES_OVERLAY_LANGUAGE}
|
||
data {
|
||
insertData = <span class="number">1</span>
|
||
}
|
||
}
|
||
|
||
stdWrap {
|
||
cObject = COA
|
||
cObject {
|
||
<span class="number">1</span> = TEXT
|
||
<span class="number">1</span> {
|
||
data < tmp.language<span class="number">.1</span>.NO.ATagTitle.data
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
ACT < .NO
|
||
ACT {
|
||
allWrap = <span class="xml"><span class="tag"><<span class="name">li</span> <span class="attr">class</span>=<span class="string">"active"</span>></span>|<span class="tag"></<span class="name">li</span>></span></span>
|
||
}
|
||
|
||
USERDEF1 = <span class="number">1</span>
|
||
USERDEF1 {
|
||
doNotShowLink = <span class="number">1</span>
|
||
}
|
||
}
|
||
}</code></pre><p>On Line 5 to 8 we define the menu as you normally would. With one exception, we add all <code>sys_language_uid</code>’s, not only the one we want on the current site. Via <code>NO</code> all existing languages that are not currently active are rendered. With <code>ACT</code> the current active language is rendered and via <code>USERDEF1</code> we define to not show links to languages which are not available for the current site, depending on your configuration.</p><p>By Using <code>USERDEF1</code> we don’t have to adjust the set of languages for each page.</p><p>Inside of <code>data</code>, the <code>field: _PAGES_OVERLAY_LANGUAGE</code> contains the uid of the current sys_language to render.</p><p>Using the <code>data</code> we can render the content of a language file. This file can be different for each language. This way we can adjust the labels for each language depending on the language.</p>
|
||
|
||
|
||
|
||
|
||
|
||
<aside class="admonition-note">
|
||
|
||
<a name="c150"></a>
|
||
<a name="note"></a>
|
||
|
||
<h2>Note <small><a href="#note">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>The language configuration is needed in addition. But as this is documented anywhere else, it’s not part of this post. This post just covers the menu generation.</p><p>Take a look at <a href="https://docs.typo3.org/m/typo3/guide-frontendlocalization/master/en-us/TyposcriptConfiguration/Index.html">Frontend Localization Guide</a>.</p>
|
||
</aside>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c44"></a>
|
||
<a name="theLanguageFile"></a>
|
||
|
||
<h2>The language file <small><a href="#theLanguageFile">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>The language file for above example might look like the following.</p><pre><code><span class="meta"><?xml version="1.0"?></span>
|
||
<span class="tag"><<span class="name">xliff</span> <span class="attr">version</span>=<span class="string">"1.2"</span> <span class="attr">xmlns</span>=<span class="string">"urn:oasis:names:tc:xliff:document:1.2"</span>></span>
|
||
<span class="tag"><<span class="name">file</span> <span class="attr">original</span>=<span class="string">"en"</span> <span class="attr">datatype</span>=<span class="string">"plaintext"</span>></span>
|
||
<span class="tag"><<span class="name">body</span>></span>
|
||
<span class="tag"><<span class="name">trans-unit</span> <span class="attr">id</span>=<span class="string">"languageMenu.title."</span>></span>
|
||
<span class="tag"><<span class="name">source</span>></span>Default<span class="tag"></<span class="name">source</span>></span>
|
||
<span class="tag"></<span class="name">trans-unit</span>></span>
|
||
<span class="tag"><<span class="name">trans-unit</span> <span class="attr">id</span>=<span class="string">"languageMenu.title.1"</span>></span>
|
||
<span class="tag"><<span class="name">source</span>></span>Deutsch<span class="tag"></<span class="name">source</span>></span>
|
||
<span class="tag"></<span class="name">trans-unit</span>></span>
|
||
<span class="tag"><<span class="name">trans-unit</span> <span class="attr">id</span>=<span class="string">"languageMenu.title.2"</span>></span>
|
||
<span class="tag"><<span class="name">source</span>></span>Nederlands<span class="tag"></<span class="name">source</span>></span>
|
||
<span class="tag"></<span class="name">trans-unit</span>></span>
|
||
<span class="tag"><<span class="name">trans-unit</span> <span class="attr">id</span>=<span class="string">"languageMenu.title.3"</span>></span>
|
||
<span class="tag"><<span class="name">source</span>></span>English<span class="tag"></<span class="name">source</span>></span>
|
||
<span class="tag"></<span class="name">trans-unit</span>></span>
|
||
<span class="tag"></<span class="name">body</span>></span>
|
||
<span class="tag"></<span class="name">file</span>></span>
|
||
<span class="tag"></<span class="name">xliff</span>></span></code></pre>
|
||
|
||
|
||
|
||
|
||
|
||
<aside class="admonition-note">
|
||
|
||
<a name="c151"></a>
|
||
<a name="note"></a>
|
||
|
||
<h2>Note <small><a href="#note">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>You have to provide the id <code>languageMenu.title.</code> for our example, as default language <code>0</code> will not have a number in this menu. See Line 5.</p>
|
||
</aside>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c45"></a>
|
||
<a name="theResult"></a>
|
||
|
||
<h2>The result <small><a href="#theResult">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>The output will look like the following:</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c67"></a>
|
||
<a name="imageI7"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<figure id="i7">
|
||
<a href="/fileadmin/Blog_Post_Content/Build_TYPO3_Language_Menu_without_the_need_of_optionSplit/output.png" target="_blank"><img src="/fileadmin/Blog_Post_Content/Build_TYPO3_Language_Menu_without_the_need_of_optionSplit/output.png" width="759" height="394" alt="" title="Output of language menu" /></a>
|
||
<figcaption>Figure i7: Visible and source code result of the language menu without option split</figcaption>
|
||
</figure>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c46"></a>
|
||
<a name="credits"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>This solution was “invented” by <a href="https://justusmoroni.com/" class="reference external">Justus Moroni</a> and myself during one project, as we thought that option split and adjusting the settings for each site is not the best way. Also we were just to lazy, you know programmer?, to adjust the configuration for each multisite.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c47"></a>
|
||
<a name="ack"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>And further resources to TYPO3 documentation which are used in this example:</p><ul> <li><a href="https://docs.typo3.org/m/typo3/reference-typoscript/master/en-us/ContentObjects/Hmenu/Index.html#cobj-hmenu" title="TypoScript Reference">HMENU</a></li> <li><a href="https://docs.typo3.org/m/typo3/reference-typoscript/master/en-us/ContentObjects/Hmenu/Index.html#hmenu-special-language" title="TypoScript Reference">special = language</a></li> <li><a href="https://docs.typo3.org/m/typo3/reference-typoscript/master/en-us/MenuObjects/Tmenuitem/Index.html#tmenuitem" title="TypoScript Reference">TMENUITEM</a></li> <li><a href="https://docs.typo3.org/m/typo3/reference-typoscript/master/en-us/Functions/Stdwrap.html#stdwrap-data" title="TypoScript Reference">data</a></li> <li><a href="https://docs.typo3.org/m/typo3/reference-typoscript/master/en-us/Functions/Stdwrap.html#stdwrap-cobject" title="TypoScript Reference">cObject</a></li> </ul>
|
||
|
||
|
||
]]></description>
|
||
<link>https://daniel-siepmann.de/posts/migrated/build-typo3-language-menu-without-the-need-of-optionsplit.html</link>
|
||
<pubDate>Fri, 09 Sep 2016 16:41:00 +0200</pubDate>
|
||
<guid isPermaLink="true">https://daniel-siepmann.de/posts/migrated/build-typo3-language-menu-without-the-need-of-optionsplit.html</guid>
|
||
</item>
|
||
|
||
|
||
|
||
|
||
|
||
<item>
|
||
<title>How to find Hooks in TYPO3</title>
|
||
<description><![CDATA[Hooks inside of TYPO3 CMS allow you to hook into existing processes of the core, or of extensions, to manipulate the processes.
|
||
|
||
This post will explain in more depth what hooks are and how you can find and use them.
|
||
|
||
|
||
|
||
<a name="c48"></a>
|
||
<a name="whatAreHooksInTypo3"></a>
|
||
|
||
<h2>What are Hooks in TYPO3? <small><a href="#whatAreHooksInTypo3">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>TYPO3 has a lot of processes like evaluating data, authenticating users, displaying content and so on. All of this processes are handled inside of TYPO3. In some use cases you want to hook into the process and manipulate the process. E.g. you want to modify data inserted into the backend, before they get persisted into the database.</p><p>This can be achieved by using a hook to modify the process. The hook allows you to include your custom PHP into the process, which will get executed.</p><p>The execution of Hooks is typically implemented like the following:</p><pre><code><span class="keyword">if</span> (is_array($GLOBALS[<span class="string">'TYPO3_CONF_VARS'</span>][<span class="string">'SC_OPTIONS'</span>][<span class="string">'t3lib/class.t3lib_div.php'</span>][<span class="string">'devLog'</span>])) {
|
||
$params = <span class="keyword">array</span>(<span class="string">'msg'</span> => $msg, <span class="string">'extKey'</span> => $extKey, <span class="string">'severity'</span> => $severity, <span class="string">'dataVar'</span> => $dataVar);
|
||
$fakeThis = <span class="keyword">false</span>;
|
||
<span class="keyword">foreach</span> ($GLOBALS[<span class="string">'TYPO3_CONF_VARS'</span>][<span class="string">'SC_OPTIONS'</span>][<span class="string">'t3lib/class.t3lib_div.php'</span>][<span class="string">'devLog'</span>] <span class="keyword">as</span> $hookMethod) {
|
||
<span class="keyword">self</span>::callUserFunction($hookMethod, $params, $fakeThis);
|
||
}
|
||
}</code></pre><p>So your configured method will be called with some arguments, on line 5 in above example. Most of the time the arguments will be references enabling you to modify them.</p><p>Hooks differ in two ways:</p><ol> <li> <p>Some hooks need your class to implement a specific interface, or method. Others, like above will just call the function or method you have provided, like a <a href="https://docs.typo3.org/m/typo3/reference-typoscript/master/en-us/ContentObjects/UserAndUserInt/Index.html#cobj-user-examples" title="TypoScript Reference">userfunc</a>.</p> </li> <li> <p>Also some hooks will provide arguments via reference and others via copy.</p> </li> </ol>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c49"></a>
|
||
<a name="aSimpleExample"></a>
|
||
|
||
<h2>A simple example <small><a href="#aSimpleExample">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Let’s assume the following example: You want to provide latitude and longitude to frontend users, auto generated based on their address. That can be achieved by using a hook inside of <code>\TYPO3\CMS\Core\DataHandling\DataHandler</code>.</p><p>Configure the hook in <code>ext_localconf.php</code> of your extension like:</p><pre><code>$GLOBALS[<span class="string">'TYPO3_CONF_VARS'</span>][<span class="string">'SC_OPTIONS'</span>][<span class="string">'t3lib/class.t3lib_tcemain.php'</span>][<span class="string">'processDatamapClass'</span>][$_EXTKEY]
|
||
= <span class="string">'DanielSiepmann\FeuserLocations\Hook\DataMapHook'</span>;</code></pre><p>This will register the class <code>DanielSiepmann\FeuserLocations\Hook\DataMapHook</code> to be processed. Inside of the class we have the following method, called during the process:</p><pre><code><span class="comment">/**
|
||
* Hook to add latitude and longitude to locations.
|
||
*
|
||
* <span class="doctag">@param</span> string $action The action to perform, e.g. 'update'.
|
||
* <span class="doctag">@param</span> string $table The table affected by action, e.g. 'fe_users'.
|
||
* <span class="doctag">@param</span> int $uid The uid of the record affected by action.
|
||
* <span class="doctag">@param</span> array $modifiedFields The modified fields of the record.
|
||
*
|
||
* <span class="doctag">@return</span> void
|
||
*/</span>
|
||
<span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">processDatamap_postProcessFieldArray</span><span class="params">( // @codingStandardsIgnoreLine
|
||
$action,
|
||
$table,
|
||
$uid,
|
||
array &$modifiedFields
|
||
)</span> </span>{
|
||
<span class="keyword">if</span>(! <span class="keyword">$this</span>->processGeocoding($table, $action, $modifiedFields)) {
|
||
<span class="keyword">return</span>;
|
||
}
|
||
|
||
$geoInformation = <span class="keyword">$this</span>->getGeoinformation(
|
||
<span class="keyword">$this</span>->getAddress($modifiedFields, $uid)
|
||
);
|
||
|
||
$modifiedFields[<span class="string">'lat'</span>] = $geoInformation[<span class="string">'geometry'</span>][<span class="string">'location'</span>][<span class="string">'lat'</span>];
|
||
$modifiedFields[<span class="string">'lng'</span>] = $geoInformation[<span class="string">'geometry'</span>][<span class="string">'location'</span>][<span class="string">'lng'</span>];
|
||
}</code></pre><p>This method will get called for all data changed through <code>DataHandler</code> before they are processed by the <code>DataHandler</code>.</p>
|
||
|
||
|
||
|
||
|
||
|
||
<aside class="admonition-caution">
|
||
|
||
<a name="c149"></a>
|
||
<a name="caution"></a>
|
||
|
||
<h2>Caution <small><a href="#caution">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>As this method get called for <em>all</em> data, you should check whether to execute the method. Same is true for some other hooks like database queries. Your system will slow down without these checks called <a href="https://en.wikipedia.org/wiki/Guard_(computer_science)" class="reference external">guards</a>.</p>
|
||
</aside>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c50"></a>
|
||
<a name="howToFindHooks"></a>
|
||
|
||
<h2>How to find hooks <small><a href="#howToFindHooks">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Hooks are always configured through <code>$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']</code>, so finding hooks is as easy as a search for <code>SC_OPTIONS</code>. Of course you need to understand the surrounding code and where your hook should be executed to find the right place.</p><p>E.g. execute the following in your shell:</p><pre><code>grep -n -C <span class="number">5</span> <span class="string">"SC_OPTIONS"</span> -r vendor/typo3/cms</code></pre><p>Beside the core, also extensions might provide hooks, so adjust the path, <code>vendor/typo3/cms</code>, to search inside an extension.</p><p>Also all registered hooks can be found inside the backend “Configuration” module. Just select the <code>TYPO3_CONF_VARS</code> in dropdown and search for <code>SC_OPTIONS</code>. By <em>registed</em> I mean hooks that are already in use, it’s not a full list of available hooks.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c51"></a>
|
||
<a name="signalSlots"></a>
|
||
|
||
<h2>Signal Slots <small><a href="#signalSlots">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Beside the concept of hooks, TYPO3 also provides the concept of Signal Slots. I will not document that concept here, there are already some blog posts about the topic:</p><ul> <li><s>English blog post by Felix Oertel</s>. No longer available.</li> <li><a href="https://usetypo3.com/signals-and-hooks-in-typo3.html#c210" class="reference external">English blog post at usetypo3.com</a></li> <li><a href="https://typo3blogger.de/signal-slot-pattern/" class="reference external">German blog post at typo3blogger.de</a></li> <li><a href="https://flowframework.readthedocs.io/en/stable/TheDefinitiveGuide/PartIII/SignalsAndSlots.html#signals-and-slots" class="reference external">Official Documentation of Signal Slots in Flow Framework</a></li> </ul><p>In general it’s the same idea, just implemented in an object oriented way.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c52"></a>
|
||
<a name=""></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Checkout the official documentation at <a href="https://docs.typo3.org/m/typo3/reference-coreapi/master/en-us/ApiOverview/Hooks/Index.html#hooks" class="reference external" title="(in TYPO3 Explained vmaster (10-dev))">Events, Signals and Hooks</a>.</p><p>Also check out <a href="https://docs.typo3.org/m/typo3/reference-typoscript/master/en-us/ContentObjects/UserAndUserInt/Index.html#cobj-user-examples" class="reference external" title="(in TypoScript Reference vmaster (10-dev))">examples for userfunctions</a>.</p><p>Also you can check how other developers make usage of hooks, e.g. in the example extension <a href="https://github.com/web-vision/wv_feuser_locations/tree/develop" class="reference external">wv_feuser_locations</a>.</p>
|
||
|
||
|
||
]]></description>
|
||
<link>https://daniel-siepmann.de/posts/migrated/how-to-find-hooks-in-typo3.html</link>
|
||
<pubDate>Thu, 21 Jul 2016 16:41:00 +0200</pubDate>
|
||
<guid isPermaLink="true">https://daniel-siepmann.de/posts/migrated/how-to-find-hooks-in-typo3.html</guid>
|
||
</item>
|
||
|
||
|
||
|
||
|
||
|
||
<item>
|
||
<title>Workflow for: Read the docs, Sphinx and Plantuml</title>
|
||
<description><![CDATA[Documentation was never easier, with a Framework like Sphinx and a hoster like Read the docs. All you need is a repository containing your Documentation written with Sphinx and an account at Read the docs. Plantuml can be used in addition to generate UML-Diagrams using plain text. The result can be SVG files in the same color as your Documentation. The whole setup and workflow for that will be explained in this Blog post. After reading the post, you are able to kickstart a new documentation and host it at Read the docs. You then can dig deeper and adjust it to your needs.
|
||
|
||
All instructions are tested on OS X, I’ll not document for Windows. Unix systems should be the same as OS X and you probably already know how to do it yourself.
|
||
|
||
|
||
|
||
<a name="c53"></a>
|
||
<a name="sphinx"></a>
|
||
|
||
<h2>Sphinx <small><a href="#sphinx">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>First let’s start with Sphinx to get a first documentation rendered on the local machine.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c54"></a>
|
||
<a name="install"></a>
|
||
|
||
<h3>Install <small><a href="#install">¶</a></small></h3>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>To use Sphinx, we need to install it. Up to date instructions can be found at <a href="https://www.sphinx-doc.org/en/stable/install.html" class="reference external">Installing Sphinx</a>.</p><p>Basically you can install via:</p><pre><code>pip install -U Sphinx</code></pre>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c55"></a>
|
||
<a name="startDocumenting"></a>
|
||
|
||
<h3>Start documenting <small><a href="#startDocumenting">¶</a></small></h3>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>To start your documentation sphinx provides a CLI tool to create a new project:</p><pre><code>sphinx-quickstart</code></pre><p>It’s interactive and will ask all necessary information.</p><p>An example setup will look like:</p><pre><code>Welcome to the Sphinx <span class="number">1.4</span><span class="number">.3</span> quickstart utility.
|
||
|
||
Please enter values <span class="keyword">for</span> the following settings (just press Enter to
|
||
accept a <span class="keyword">default</span> value, <span class="keyword">if</span> one is given in brackets).
|
||
|
||
Enter the root path <span class="keyword">for</span> documentation.
|
||
> Root path <span class="keyword">for</span> the documentation [.]: example-documentation
|
||
|
||
You have two options <span class="keyword">for</span> placing the build directory <span class="keyword">for</span> Sphinx output.
|
||
Either, you <span class="keyword">use</span> <span class="title">a</span> <span class="title">directory</span> "<span class="title">_build</span>" <span class="title">within</span> <span class="title">the</span> <span class="title">root</span> <span class="title">path</span>, <span class="title">or</span> <span class="title">you</span> <span class="title">separate</span>
|
||
"<span class="title">source</span>" <span class="title">and</span> "<span class="title">build</span>" <span class="title">directories</span> <span class="title">within</span> <span class="title">the</span> <span class="title">root</span> <span class="title">path</span>.
|
||
> <span class="title">Separate</span> <span class="title">source</span> <span class="title">and</span> <span class="title">build</span> <span class="title">directories</span> (<span class="title">y</span>/<span class="title">n</span>) [<span class="title">n</span>]: <span class="title">y</span>
|
||
|
||
<span class="title">Inside</span> <span class="title">the</span> <span class="title">root</span> <span class="title">directory</span>, <span class="title">two</span> <span class="title">more</span> <span class="title">directories</span> <span class="title">will</span> <span class="title">be</span> <span class="title">created</span>; <span class="string">"_templates"</span>
|
||
<span class="keyword">for</span> custom HTML templates <span class="keyword">and</span> <span class="string">"_static"</span> <span class="keyword">for</span> custom stylesheets <span class="keyword">and</span> other <span class="keyword">static</span>
|
||
files. You can enter another prefix (such <span class="keyword">as</span> <span class="string">"."</span>) to replace the underscore.
|
||
> Name prefix <span class="keyword">for</span> templates <span class="keyword">and</span> <span class="keyword">static</span> dir [_]:
|
||
|
||
The project name will occur in several places in the built documentation.
|
||
> Project name: Example <span class="keyword">for</span> Blogpost
|
||
> Author name(s): Daniel Siepmann
|
||
|
||
Sphinx has the notion of a <span class="string">"version"</span> <span class="keyword">and</span> a <span class="string">"release"</span> <span class="keyword">for</span> the
|
||
software. Each version can have multiple releases. <span class="keyword">For</span> example, <span class="keyword">for</span>
|
||
Python the version is something like <span class="number">2.5</span> <span class="keyword">or</span> <span class="number">3.0</span>, <span class="keyword">while</span> the release is
|
||
something like <span class="number">2.5</span><span class="number">.1</span> <span class="keyword">or</span> <span class="number">3.0</span>a1. <span class="keyword">If</span> you don<span class="string">'t need this dual structure,
|
||
just set both to the same value.
|
||
> Project version: 1.0.0
|
||
> Project release [1.0.0]:
|
||
|
||
If the documents are to be written in a language other than English,
|
||
you can select a language here by its language code. Sphinx will then
|
||
translate text that it generates into that language.
|
||
|
||
For a list of supported codes, see
|
||
http://sphinx-doc.org/config.html#confval-language.
|
||
> Project language [en]:
|
||
|
||
The file name suffix for source files. Commonly, this is either ".txt"
|
||
or ".rst". Only files with this suffix are considered documents.
|
||
> Source file suffix [.rst]:
|
||
|
||
One document is special in that it is considered the top node of the
|
||
"contents tree", that is, it is the root of the hierarchical structure
|
||
of the documents. Normally, this is "index", but if your "index"
|
||
document is a custom template, you can also set this to another filename.
|
||
> Name of your master document (without suffix) [index]:
|
||
|
||
Sphinx can also add configuration for epub output:
|
||
> Do you want to use the epub builder (y/n) [n]: n
|
||
|
||
Please indicate if you want to use one of the following Sphinx extensions:
|
||
> autodoc: automatically insert docstrings from modules (y/n) [n]: n
|
||
> doctest: automatically test code snippets in doctest blocks (y/n) [n]: n
|
||
> intersphinx: link between Sphinx documentation of different projects (y/n) [n]: y
|
||
> todo: write "todo" entries that can be shown or hidden on build (y/n) [n]: y
|
||
> coverage: checks for documentation coverage (y/n) [n]: n
|
||
> imgmath: include math, rendered as PNG or SVG images (y/n) [n]: n
|
||
> mathjax: include math, rendered in the browser by MathJax (y/n) [n]: n
|
||
> ifconfig: conditional inclusion of content based on config values (y/n) [n]: n
|
||
> viewcode: include links to the source code of documented Python objects (y/n) [n]: n
|
||
> githubpages: create .nojekyll file to publish the document on GitHub pages (y/n) [n]: n
|
||
|
||
A Makefile and a Windows command file can be generated for you so that you
|
||
only have to run e.g. `make html'</span> instead of invoking sphinx-build
|
||
directly.
|
||
> Create Makefile? (y/n) [y]: y
|
||
> Create Windows command file? (y/n) [y]: n
|
||
|
||
Creating file example-documentation/source/conf.py.
|
||
Creating file example-documentation/source/index.rst.
|
||
Creating file example-documentation/Makefile.
|
||
|
||
Finished: An initial directory structure has been created.
|
||
|
||
You should now populate your master file example-documentation/source/index.rst <span class="keyword">and</span> create other documentation
|
||
source files. <span class="keyword">Use</span> <span class="title">the</span> <span class="title">Makefile</span> <span class="title">to</span> <span class="title">build</span> <span class="title">the</span> <span class="title">docs</span>, <span class="title">like</span> <span class="title">so</span>:
|
||
<span class="title">make</span> <span class="title">builder</span>
|
||
<span class="title">where</span> "<span class="title">builder</span>" <span class="title">is</span> <span class="title">one</span> <span class="title">of</span> <span class="title">the</span> <span class="title">supported</span> <span class="title">builders</span>, <span class="title">e</span>.<span class="title">g</span>. <span class="title">html</span>, <span class="title">latex</span> <span class="title">or</span> <span class="title">linkcheck</span>.</code></pre><p>As Sphinx is written in Python and used to document Python modules, most extensions can be omitted for your documentation, until you are working with Python code.</p><p>I definitely recommend to enable <code>todo</code> and <code>intersphinx</code> all the time. Also <code>ifconfig</code> can be helpful. But it’s just the kickstart and you can add extensions later on inside the configuration.</p><p>Also do yourself a favour and create the <code>Makefile</code> for easier usage.</p><p>Sphinx will setup a structure like:</p><pre><code>.
|
||
└── <span class="selector-tag">example-documentation</span>
|
||
├── <span class="selector-tag">Makefile</span>
|
||
├── <span class="selector-tag">build</span>
|
||
└── <span class="selector-tag">source</span>
|
||
├── _<span class="selector-tag">static</span>
|
||
├── _<span class="selector-tag">templates</span>
|
||
├── <span class="selector-tag">conf</span><span class="selector-class">.py</span>
|
||
└── <span class="selector-tag">index</span><span class="selector-class">.rst</span>
|
||
|
||
5 <span class="selector-tag">directories</span>, 3 <span class="selector-tag">files</span></code></pre><p>You now can render the documentation by calling:</p><pre><code>make html</code></pre><p>Sphinx will generate the full HTML and write it to <code>build/html</code>. You can open the documentation using:</p><pre><code>open ./build/html/index.html</code></pre><p>The first output will look like in <a href="/posts/migrated/workflow-for-read-the-docs-sphinx-and-plantuml.html#c64">Figure i2</a>.</p><p>You can now start writing the documentation, following the <a href="https://www.sphinx-doc.org/en/stable/contents.html" class="reference external">Sphinx Documentation</a>, and adjust the look and feel, e.g. change the theme using one of the <a href="https://www.sphinx-doc.org/en/master/usage/theming.html#builtin-themes">builtin Themes</a>.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c64"></a>
|
||
<a name="firstResultOfSphinxI2"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<figure id="i2">
|
||
<a href="/fileadmin/Blog_Post_Content/sphinx_workflow/FirstResult.png" target="_blank"><img src="/fileadmin/Blog_Post_Content/sphinx_workflow/FirstResult.png" width="956" height="377" alt="First Result of Sphinx rendering" title="First Result of Sphinx rendering" /></a>
|
||
<figcaption>Figure i2: The very first results that are visible after rendering a basic sphinx project with defaults.</figcaption>
|
||
</figure>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c56"></a>
|
||
<a name="github"></a>
|
||
|
||
<h2>Github <small><a href="#github">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>To make integration with Read the docs easier, we will publish our documentation as a Git repository to Github.</p><p>First of all you need to initialize a new Git-Repository, of course you can also use Mercurial or something else. Do so by running:</p><pre><code><span class="keyword">echo</span> <span class="string">"build"</span> > .gitignore && git init && git add . && git commit -m <span class="string">"First version"</span></code></pre><p>Next sign up at <a href="https://github.com/" class="reference external">Github</a> if you don’t have an account yet. <a href="https://github.com/new" class="reference external">Create a repository</a>, which is only possible if you are logged in. Github should redirect you to your new repository with a URL scheme like <code class="docutils literal notranslate"><UserName>/<RepositoryName></code>. Add the repository at Github to your local repository by running:</p><pre><code>git remote add origin https://github.com/<span class="tag"><<span class="name">UserName</span>></span>/<span class="tag"><<span class="name">RepositoryName</span>></span>.git && git push --mirror</code></pre><p>If you reload the web Gui of Github you should see a first commit.</p><p>Github provides full documentation at <a href="https://help.github.com/" class="reference external">https://help.github.com/</a> if something is not clear or you need further help setting everything up.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c57"></a>
|
||
<a name="readTheDocs"></a>
|
||
|
||
<h2>Read the docs <small><a href="#readTheDocs">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>To host our documentation without the need to setup the rendering or web space, we will use Read the docs.</p><p>Therefore <a href="https://readthedocs.org/accounts/signup/" class="reference external">register at Read the docs</a>, and <a href="https://readthedocs.org/accounts/social/connections/" class="reference external">connect</a> the account to your Github account. You can now see all your Github repositories and <a href="https://readthedocs.org/dashboard/import/?" class="reference external">select</a> the created one to automatically render the documentation on new commits. Have a look at <a href="/posts/migrated/workflow-for-read-the-docs-sphinx-and-plantuml.html#c66">figure i6 and i4</a>.</p><p>You are now ready to go, Read the docs should already render your documentation. You have an overview at <a href="https://readthedocs.org/dashboard/" class="reference external">https://readthedocs.org/dashboard/</a> where your project should appear. Navigate to the project by clicking the title and you should the <em>Last Built</em> on the right mentioning whether everything worked. Also at top you have a navigation. Go to <em>Builds</em> and you can get an detailed view what was going on and where something broke.</p><p>To setup further branches in your repository to render, head to <em>Versions</em> and set them up.</p><p>The green Button <em>View Docs</em> will bring you to your generated documentation. It’s already online and all you have to do in the future is to do a:</p><pre><code>git commit -m <span class="string">"Made changes"</span> && git push</code></pre><p>Read the docs will detect the changes and render your documentation.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c66"></a>
|
||
<a name="imageI6AndI4"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<figure id="i6">
|
||
<a href="/fileadmin/Blog_Post_Content/sphinx_workflow/ReadTheDocs-Connection.png" target="_blank"><img src="/fileadmin/Blog_Post_Content/sphinx_workflow/ReadTheDocs-Connection.png" width="850" height="569" alt="" title="Connected Services at read the docs" /></a>
|
||
<figcaption>Figure i6: Displays a list of all connected services, like GitHub, for your read the docs account.</figcaption>
|
||
</figure>
|
||
|
||
|
||
|
||
|
||
<figure id="i4">
|
||
<a href="/fileadmin/Blog_Post_Content/sphinx_workflow/ReadTheDocs-Import.png" target="_blank"><img src="/fileadmin/Blog_Post_Content/sphinx_workflow/ReadTheDocs-Import.png" width="840" height="319" alt="" title="Import GitHub Repository at read the docs" /></a>
|
||
<figcaption>Figure i4: Allows to import GitHub repositories, once the service is connected.</figcaption>
|
||
</figure>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c58"></a>
|
||
<a name="plantuml"></a>
|
||
|
||
<h2>Plantuml <small><a href="#plantuml">¶</a></small></h2>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>Everything is working now. Let’s add some sugar with nice looking UML diagrams to explain the structure of our project, or some complex workflows.</p><p>To provide nice looking UML diagrams like <a href="/posts/migrated/workflow-for-read-the-docs-sphinx-and-plantuml.html#c65">Figure i5</a>.</p><p>We will use PlantUml. As it’s not available as a Debian package yet, Read the docs doesn’t provide rendering for it. So you have to render the images on your local machine and provide them to Read the docs.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c65"></a>
|
||
<a name="exampleI5"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<figure id="i5">
|
||
<a href="/fileadmin/Blog_Post_Content/sphinx_workflow/Example.png" target="_blank"><img src="/fileadmin/Blog_Post_Content/sphinx_workflow/Example.png" width="1099" height="1104" alt="" title="Example output with theme and plantuml" /></a>
|
||
<figcaption>Figure i5: Example how a styled plantuml diagram might look within a rendered sphinx project, with a sphinx theme.</figcaption>
|
||
</figure>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c59"></a>
|
||
<a name="install"></a>
|
||
|
||
<h3>Install <small><a href="#install">¶</a></small></h3>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>First of all you need to install Java and Graphviz to draw the diagrams. Head over to <a href="http://plantuml.com/starting" class="reference external">http://plantuml.com/starting</a> and <a href="http://plantuml.com/faq-install" class="reference external">http://plantuml.com/faq-install</a> to follow the installation.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c60"></a>
|
||
<a name="provideWrapper"></a>
|
||
|
||
<h3>Provide wrapper <small><a href="#provideWrapper">¶</a></small></h3>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>After PlantUml is on your local system, make your live easier by providing the following shell script inside your <code>$PATH</code> to just call <code>plantuml</code> in the future anywhere on your CLI:</p><pre><code><span class="comment">#!/usr/bin/env sh -e</span>
|
||
java -Djava.awt.headless=<span class="keyword">true</span> -jar $HOME/Applications/plantuml.jar -tsvg -failfast2 <span class="string">"$@"</span></code></pre><p>Adjust the path according to your location of <code>plantuml.jar</code>. This wrapper will run PlantUml without the GUI, and generate SVGs as default for all provided PlantUml source files.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c61"></a>
|
||
<a name="integrationIntoDocumentation"></a>
|
||
|
||
<h3>Integration into Documentation <small><a href="#integrationIntoDocumentation">¶</a></small></h3>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>To integrate PlantUml into your Sphinx documentation, you can setup the following structure:</p><pre><code>.
|
||
└── <span class="selector-tag">example-documentation</span>
|
||
├── <span class="selector-tag">Makefile</span>
|
||
├── <span class="selector-tag">build</span>
|
||
└── <span class="selector-tag">source</span>
|
||
├── <span class="selector-tag">uml</span>
|
||
│ └── <span class="selector-tag">example</span><span class="selector-class">.uml</span>
|
||
├── _<span class="selector-tag">static</span>
|
||
├── _<span class="selector-tag">templates</span>
|
||
├── <span class="selector-tag">conf</span><span class="selector-class">.py</span>
|
||
└── <span class="selector-tag">index</span><span class="selector-class">.rst</span>
|
||
|
||
6 <span class="selector-tag">directories</span>, 4 <span class="selector-tag">files</span></code></pre><p>And adjust your <code>Makefile</code> to render all PlantUml files for you.</p><p>Add the following entry to your <code>Makefile</code>:</p><pre><code>plantuml:
|
||
plantuml -psvg -o ../images/uml/ ./source/uml/*.uml</code></pre><p>You now can call:</p><pre><code>make plantuml</code></pre><p>That will create a new folder with generated images:</p><pre><code>.
|
||
└── <span class="selector-tag">example-documentation</span>
|
||
├── <span class="selector-tag">Makefile</span>
|
||
├── <span class="selector-tag">build</span>
|
||
└── <span class="selector-tag">source</span>
|
||
├── <span class="selector-tag">images</span>
|
||
│ └── <span class="selector-tag">uml</span>
|
||
│ └── <span class="selector-tag">example</span><span class="selector-class">.svg</span>
|
||
├── <span class="selector-tag">uml</span>
|
||
│ └── <span class="selector-tag">example</span><span class="selector-class">.uml</span>
|
||
├── _<span class="selector-tag">static</span>
|
||
├── _<span class="selector-tag">templates</span>
|
||
├── <span class="selector-tag">conf</span><span class="selector-class">.py</span>
|
||
└── <span class="selector-tag">index</span><span class="selector-class">.rst</span>
|
||
|
||
8 <span class="selector-tag">directories</span>, 5 <span class="selector-tag">files</span></code></pre><p>To include the diagram into your documentation, use the <code>image</code> or <code>figure</code> directive of rst.</p><p>To ease workflow, adjust your <code>Makefile</code> further to run <code>plantuml</code> also for <code>html</code> and such by using:</p><pre><code><span class="attribute">html</span>: plantuml</code></pre>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c62"></a>
|
||
<a name="adjustLook"></a>
|
||
|
||
<h3>Adjust look <small><a href="#adjustLook">¶</a></small></h3>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>At the moment we will get the default styling of PlantUML which is not nice in our Template. You can adjust the styling by providing the a file called <code class="file docutils literal notranslate">plantuml.cfg</code> with the following content:</p><pre><code>skinparam backgroundColor white
|
||
|
||
skinparam note {
|
||
BackgroundColor <span class="comment">#F1FFFF</span>
|
||
BorderColor <span class="comment">#2980B9</span>
|
||
}
|
||
|
||
skinparam activity {
|
||
BackgroundColor <span class="comment">#BDE3FF</span>
|
||
ArrowColor <span class="comment">#2980B9</span>
|
||
BorderColor <span class="comment">#2980B9</span>
|
||
StartColor <span class="comment">#227BC6</span>
|
||
EndColor <span class="comment">#227BC6</span>
|
||
BarColor <span class="comment">#227BC6</span>
|
||
}
|
||
|
||
skinparam sequence {
|
||
ArrowColor <span class="comment">#2980B9</span>
|
||
DividerBackgroundColor <span class="comment">#BDE3FF</span>
|
||
GroupBackgroundColor <span class="comment">#BDE3FF</span>
|
||
LifeLineBackgroundColor white
|
||
LifeLineBorderColor <span class="comment">#2980B9</span>
|
||
ParticipantBackgroundColor <span class="comment">#BDE3FF</span>
|
||
ParticipantBorderColor <span class="comment">#2980B9</span>
|
||
BoxLineColor <span class="comment">#2980B9</span>
|
||
BoxBackgroundColor <span class="comment">#DDDDDD</span>
|
||
}
|
||
|
||
skinparam actorBackgroundColor <span class="comment">#FEFECE</span>
|
||
skinparam actorBorderColor <span class="comment">#A80036</span>
|
||
|
||
skinparam usecaseArrowColor <span class="comment">#A80036</span>
|
||
skinparam usecaseBackgroundColor <span class="comment">#FEFECE</span>
|
||
skinparam usecaseBorderColor <span class="comment">#A80036</span>
|
||
|
||
skinparam classArrowColor <span class="comment">#A80036</span>
|
||
skinparam classBackgroundColor <span class="comment">#FEFECE</span>
|
||
skinparam classBorderColor <span class="comment">#A80036</span>
|
||
|
||
skinparam objectArrowColor <span class="comment">#A80036</span>
|
||
skinparam objectBackgroundColor <span class="comment">#FEFECE</span>
|
||
skinparam objectBorderColor <span class="comment">#A80036</span>
|
||
|
||
skinparam packageBackgroundColor <span class="comment">#FEFECE</span>
|
||
skinparam packageBorderColor <span class="comment">#A80036</span>
|
||
|
||
skinparam stereotypeCBackgroundColor <span class="comment">#ADD1B2</span>
|
||
skinparam stereotypeABackgroundColor <span class="comment">#A9DCDF</span>
|
||
skinparam stereotypeIBackgroundColor <span class="comment">#B4A7E5</span>
|
||
skinparam stereotypeEBackgroundColor <span class="comment">#EB937F</span>
|
||
|
||
skinparam componentArrowColor <span class="comment">#A80036</span>
|
||
skinparam componentBackgroundColor <span class="comment">#FEFECE</span>
|
||
skinparam componentBorderColor <span class="comment">#A80036</span>
|
||
skinparam componentInterfaceBackgroundColor <span class="comment">#FEFECE</span>
|
||
skinparam componentInterfaceBorderColor <span class="comment">#A80036</span>
|
||
|
||
skinparam stateBackgroundColor <span class="comment">#BDE3FF</span>
|
||
skinparam stateBorderColor <span class="comment">#2980B9</span>
|
||
skinparam stateArrowColor <span class="comment">#2980B9</span>
|
||
skinparam stateStartColor black
|
||
skinparam stateEndColor black</code></pre><p>More about styling can be found at <a href="http://plantuml.com/skinparam" class="reference external">http://plantuml.com/skinparam</a> , <a href="http://plantuml.com/sequence-diagram" class="reference external">http://plantuml.com/sequence-diagram</a> .</p><p>And adjust your <code class="file docutils literal notranslate">Makefile</code> to provide this file to PlantUML:</p><pre><code>plantuml:
|
||
plantuml -config plantuml.cfg -psvg -o ../Images/Uml/ ./Uml/*.uml</code></pre>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<a name="c63"></a>
|
||
<a name="furtherReading"></a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<p>You should now be able to write basic documentation with hosting at Read the docs. The following links can be startpoints to get further:</p><ul> <li><a href="https://pypi.org/project/sphinx-autobuild/0.2.3/" class="reference external">Sphinx autobuild</a> will detect changes and autogenerate documentation. Also comes with server and autoreload.</li> <li><a href="https://www.sphinx-doc.org/en/stable/ext/intersphinx.html" class="reference external">Sphinx intersphinx</a> allow linking between sphinx projects without need to know urls.</li> <li>Sphinx <a href="https://www.sphinx-doc.org/en/master/usage/theming.html#builtin-themes">builtin Themes</a></li> </ul><p>Also the following links as a collection:</p><ul> <li><a href="https://www.sphinx-doc.org/en/stable/" class="reference external">Sphinx</a></li> <li><a href="https://github.com/" class="reference external">Github</a></li> <li><a href="https://readthedocs.org/" class="reference external">Read the docs</a></li> <li><a href="http://plantuml.com/" class="reference external">Plantuml</a></li> </ul>
|
||
|
||
|
||
]]></description>
|
||
<link>https://daniel-siepmann.de/posts/migrated/workflow-for-read-the-docs-sphinx-and-plantuml.html</link>
|
||
<pubDate>Sat, 11 Jun 2016 16:48:00 +0200</pubDate>
|
||
<guid isPermaLink="true">https://daniel-siepmann.de/posts/migrated/workflow-for-read-the-docs-sphinx-and-plantuml.html</guid>
|
||
</item>
|
||
|
||
</channel>
|
||
</rss>
|
||
|
||
|