TYPO3 Fluid complex if conditions - typo3

I am trying to write the following if condition in fluid but it is not working as I would hope.
Condition
As part of a for loop I want to check if the item is the first one or 4th, 8th etc
I would have thought the following would work but it display the code for every iteration.
<f:if condition="{logoIterator.isFirst} || {logoIterator.cycle % 4} == 0">
I have managed to get it working with a nested if but it just feels wrong having the same section of code twice and also having the cycle check use a <f:else> instead of == 0
<f:if condition="{logoIterator.isFirst}">
<f:then>
Do Something
</f:then>
<f:else>
<f:if condition="{logoIterator.cycle} % 4">
<f:else>
Do Something
</f:else>
</f:if>
</f:else>
</f:if>

TYPO3 v8
Updated the answer for TYPO3 v8. This is quoted from Claus answer below:
Updating this information with current situation:
On TYPO3v8 and later, the following syntax is supported which fits
perfectly with your use case:
<f:if condition="{logoIterator.isFirst}">
<f:then>First</f:then>
<f:else if="{logoIterator.cycle % 4}">n4th</f:else>
<f:else if="{logoIterator.cycle % 8}">n8th</f:else>
<f:else>Not first, not n4th, not n8th - fallback/normal</f:else>
</f:if>
In addition there is support for syntax like this:
<f:if condition="{logoIterator.isFirst} || {logoIterator.cycle} % 4">
Is first or n4th
</f:if>
Which can be more appropriate for some cases (in particular when using
a condition in inline syntax where you can't expand to tag mode in
order to gain access to the f:else with the new if argument).
TYPO3 6.2 LTS and 7 LTS
For more complex if-Conditions (like several or/and combinations) you can add your own ViewHelper in your_extension/Classes/ViewHelpers/. You just have to extend Fluids AbstractConditionViewHelper. The simple if-ViewHelper that shipps with Fluid looks like this:
class IfViewHelper extends \TYPO3\CMS\Fluid\Core\ViewHelper\AbstractConditionViewHelper {
/**
* renders <f:then> child if $condition is true, otherwise renders <f:else> child.
*
* #param boolean $condition View helper condition
* #return string the rendered string
* #api
*/
public function render($condition) {
if ($condition) {
return $this->renderThenChild();
} else {
return $this->renderElseChild();
}
}
}
All you have to do in your own ViewHelper is to add more parameter than $condition, like $or, $and, $not etc. Then you just write your if-Conditions in php and render either the then or else child. For your Example, you can go with something like this:
class ExtendedIfViewHelper extends \TYPO3\CMS\Fluid\Core\ViewHelper\AbstractConditionViewHelper {
/**
* renders <f:then> child if $condition or $or is true, otherwise renders <f:else> child.
*
* #param boolean $condition View helper condition
* #param boolean $or View helper condition
* #return string the rendered string
*/
public function render($condition, $or) {
if ($condition || $or) {
return $this->renderThenChild();
} else {
return $this->renderElseChild();
}
}
}
The File would be in your_extension/Classes/ViewHelpers/ExtendedIfViewHelper.php Then you have to add your namespace in the Fluid-Template like this (which enables all your self-written ViewHelpers from your_extension/Classes/ViewHelpers/ in the template:
{namespace vh=Vendor\YourExtension\ViewHelpers}
and call it in your template like this:
<vh:extendedIf condition="{logoIterator.isFirst}" or="{logoIterator.cycle} % 4">
<f:then>Do something</f:then>
<f:else>Do something else</f:else>
</vh:extendedIf>
Edit: updated.

Updating this information with current situation:
On TYPO3v8 and later, the following syntax is supported which fits perfectly with your use case:
<f:if condition="{logoIterator.isFirst}">
<f:then>First</f:then>
<f:else if="{logoIterator.cycle % 4}">n4th</f:else>
<f:else if="{logoIterator.cycle % 8}">n8th</f:else>
<f:else>Not first, not n4th, not n8th - fallback/normal</f:else>
</f:if>
In addition there is support for syntax like this:
<f:if condition="{logoIterator.isFirst} || {logoIterator.cycle} % 4">
Is first or n4th
</f:if>
Which can be more appropriate for some cases (in particular when using a condition in inline syntax where you can't expand to tag mode in order to gain access to the f:else with the new if argument).

v:if.condition will be deprecated in vhs V2.0
use v:if stack instead:
https://github.com/FluidTYPO3/vhs/issues/493

You could also use the If Condition Extend ViewHelper provided by the VHS extension:
<v:if.condition>
<v:if.condition.extend>
{logoIterator.isFirst} || {logoIterator.cycle % 4} == 0
</v:if.condition.extend>
<f:then>Output if TRUE</f:then>
<f:else>Output if FALSE</f:else>
</v:if.condition>
On a side note: the VHS extension provides lots of useful ViewHelpers. I feel a lot of them should be included in TYPO3 Fluid.

For many cases its enough to use an array-comparison - so you don't have to create a custom view-helper.
AND
<f:if condition="{0:user.number,1:user.zip}=={0:123,1:01234}">
OR
<f:if condition="{0:user.number,1:user.zip}!={0:false,1:false}">
Sadly this works just to check if a variable is set and not against a value. But for many cases this is enough.
PS:(with this array comparison you can also compare strings)

In addition to Daniels' answer, I made a ViewHelper that accepts multiple conditions, with either an "and"-mode (default) or an "or"-mode:
<?php
namespace TLID\Contentelements\ViewHelpers;
class IfViewHelper extends \TYPO3\CMS\Fluid\Core\ViewHelper\AbstractViewHelper {
/**
* Checks conditions
*
* #param mixed $checks
* #param string $type
*
* #return boolean whether is array or not
*/
public function render($checks, $type = "and") {
$success = $type === "and" ? true : false;
$doc = new \DOMDocument();
$doc->loadHTML($this->renderChildren());
$xpath = new \DOMXpath($doc);
// get store values
$storeNodes = $xpath->query("//body/store");
$store = "";
foreach ($storeNodes as $storeNode) {
foreach ($storeNode->childNodes as $childNode) {
$store .= $doc->saveHTML($childNode);
}
}
// do the actual check
foreach ($checks as $check) {
if (
($type === "and" && (is_array($check) && count($check) === 0 || is_object($check) && get_object_vars($check) === 0 || empty($check))) ||
(is_array($check) && count($check) !== 0 || is_object($check) && get_object_vars($check) !== 0 || !empty($check))
) {
$success = $type === 'and' ? false : true;
break;
}
}
// render content
$renderQueryElement = $success ? "success" : "failure";
$renderNodes = $xpath->query("//body/" . $renderQueryElement);
$content = "";
foreach ($renderNodes as $renderNode) {
foreach ($renderNode->childNodes as $childNode) {
$content .= $doc->saveHTML($childNode);
}
}
//insert contents
$matches;
$content = preg_replace("/<use[^>]*><\/use>/", $store, $content);
//return rendered content
return $content;
}
}
?>
Though it can be written alot better, it works.
Here is how i use it:
{namespace vhs=TLID\contentelements\ViewHelpers}
<vhs:if checks="{0: settings.link}">
<f:comment><!-- store the content --></f:comment>
<store>
<f:if condition="{images}">
<f:for each="{images}" as="image">
<f:image image="{image}" alt="{image.description}" title="{image.title}" />
</f:for>
</f:if>
<vhs:if checks="{0: settings.headline, 1: settings.text}" type="or">
<success>
<div>
<f:if condition="{settings.headline}"><h2><f:format.nl2br><vhs:shy>{settings.headline}</vhs:shy></f:format.nl2br></h2></f:if>
<f:if condition="{settings.text}"><p><f:format.nl2br><vhs:shy>{settings.text}</vhs:shy></f:format.nl2br></p></f:if>
</div>
</success>
</vhs:if>
</store>
<f:comment><!-- use the content of this container on success --></f:comment>
<success>
<vhs:link href="{settings.link}" target="{settings.target}" class="box">
<use />
</vhs:link>
</success>
<f:comment><!-- use the content of this container on failure --></f:comment>
<failure>
<div class="box">
<use />
</div>
</failure>
</vhs:if>
It additionally has a store-element, because i don't like it to write the same code twice. So you can optionally save some fluid and pass it to both the success and failure containers without the need for repetition.

It is possible to implement complex if conditions with a combination of f:if, v:variable.set and v:math. Use the math ViewHelper to do the magic and store its result in a variable. Then use if comparators to validate and act upon it.
Here is my sample code:
<f:for each="{customers}" as="customer" iteration="iterator">
<v:variable.set name="isFirst" value="{v:math.modulo(a: iterator.cycle, b: settings.itemsperrow, fail: 0)}" />
<f:if condition="{isFirst}==1">
<div class="row">
</f:if>
<div class="col-md-{settings.colWidth}">
<div class="clientlogo_ref">
<f:image src="{customer.logo.originalResource.publicUrl}" />
</div>
</div>
<f:if condition="{isFirst}==0">
</div>
</f:if>
</f:for>
This code begins / ends a grid row for every X item, defined by settings.itemsperrow. This is variable and can be set in the plugin's configuration. It uses modulo to calculate iterator.cycle (counter beginning with 1) mod settings.itemsperrow. If the result is 1, it is the first element of a row. 0 means it is the last, so row must be closed.

Yes it feels wrong but this is the only way you can do it. This is a very good site for viewhelper :: https://fluidtypo3.org/viewhelpers/fluid/master/IfViewHelper.html

For me best way to use 'f:cycle'. If i need devide for rows each 3th elements i just do:
<v:variable.set name="wraper" value='</div><div class="row">' />
<f:for each="{items}" as="item" iteration="itemIterator">
....
<f:cycle values="{0: '', 1: '', 2: '{wraper}'}" as="cycle">
{cycle -> f:format.raw()}
</f:cycle>
...
</f:for>

If you have CObjects help this workaround for a Logical OR:
# Sidebar | 1 ColPos = 78
lib.sidebar1 < styles.content.get
lib.sidebar1.select.where = colPos=78
# Sidebar | 2 ColPos = 79
lib.sidebar2 < styles.content.get
lib.sidebar2.select.where = colPos=79
#LogicalOR
lib.tempLogicalOrSidebar = COA
lib.tempLogicalOrSidebar {
10 < lib.sidebar1
10.stdWrap.override.cObject =< lib.sidebar2
}
FLUID IF CONDITION:
<f:if condition="{f:cObject(typoscriptObjectPath: 'lib.tempLogicalOrSidebar.10')}">

Status 2017:
Here is an example of a modern VHS viewhelper condition using the stack attribute. This example also contains a NULL check and a logical or (||) to show how it is done.
<v:if stack="{0: '{itemId}', 1:'==', 2:NULL, 3: '||', 4: '{itemId}', 5: '==', 6: '{falMedia.uid}'}">
<f:then>
...
</f:then>
</v:if>
Mind that NULL is NOT quoted!

Related

How to limit showPrevNext in news to categories?

In the TYPO3 CMS 9.5.18 LTS with tx_news 8.3.0 we use the following extension Typoscript:
plugin.tx_news.settings{
# what is allowed to overwrite with TS
overrideFlexformSettingsIfEmpty := addToList(categories)
overrideFlexformSettingsIfEmpty := addToList(categoryConjunction)
# ids of categories
categories = 3
# category conjunction mode
categoryConjunction = or
}
I wonder why I have to add categories to overrideFlexformSettingsIfEmpty to get the result below. Never the less this post is more about how to achieve that the prev/next links (settings.detail.showPrevNext) does respect the category definition at all.
Our customer has 3 categories for news. If I go to a single page that does has a one category limitation (for the detail and the list page) I still e.g. can go "forward" to newer news in a total different category. However the list page only shows the news of that one selected category.
<f:if condition="{paginated.prev}">
<n:link newsItem="{paginated.prev}" settings="{settings}" class="ts-prev">
{paginated.prev.title}
</n:link>
</f:if>
Wasn't that never the case? Do I have to add some Typoscript or make a change in Fluid? The original code uses this settings variable as argument which contains the category limitation.
Okay I've had a look into the GeorgRinger\News\ViewHelpers\SimplePrevNextViewHelper and there aren't any limitation for the current chosen categories.
So here is what I did:
register a new optional argument categories to the viewhelper
add categories="{settings.categories}" to the simplePrevNext tag in the Detail.html
add an 'extra where' for the main query in the getNeighbours function
add the content for the additional where ( I did that first in the getNeighbours function )
Extra where:
if( is_array($newsOfCategory) && count($newsOfCategory) > 0 ){
$extraWhere[] = $queryBuilder->expr()->in('uid', $queryBuilder->createNamedParameter($newsOfCategory, Connection::PARAM_INT_ARRAY));
}
Content for the additional where:
if( is_string($categories) && preg_match('/^[0-9]+(,[0-9]+)*$/',$categories) ){
$categories = explode(',', $categories);
$tmpCon = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('sys_category_record_mm');
$tmpQB = $tmpCon->createQueryBuilder();
$rows = $tmpQB
->select('uid_foreign')
->from('sys_category_record_mm')
->where(
$tmpQB->expr()->in('uid_local', $tmpQB->createNamedParameter($categories, Connection::PARAM_INT_ARRAY))
)
->andWhere(
$tmpQB->expr()->like('tablenames', $tmpQB->createNamedParameter('tx_news_domain_model_news'))
)
->execute()->fetchAll();
if( is_array($rows) && count($rows) > 0 ){
foreach($rows as $row){
$newsOfCategory[] = $row['uid_foreign'];
}
}
}
May be someone can use that in the future or the will integrate that to the repository.

How to remove strings at left of "-" in filename in TYPO3 Fluid?

Using TYPO3 8 LTS, we got many standardized filenames like:
ABC_105-Report.pdf
DEFGH_110-Brochure.ppt
We need to remove whatever is at left of "-" so it becomes a list like this in TYPO3 Frontend:
Report.pdf
Brochure.ppt
We are already using VHS Viewhelpers which contains Format:Eliminiate, Substring so it may be part of the solution.
One possible solution is VHS: Format / PregReplaceViewHelper.
<f:alias map="{filenames: {
0: 'ABC_105-Report.pdf',
1: 'DEFGH_110-Brochure.ppt',
2: 'FilenameWithoutMagicChar.jpg',
3: 'Multiple-Magic-Chars.jpg'}}">
<ul>
<f:for each="{filenames}" as="filename">
<li>
{v:format.pregReplace(
subject: filename,
pattern: '/^[^-]*-/',
replacement: ''
)}
</li>
</f:for>
</ul>
</f:alias>
Result:
Report.pdf
Brochure.ppt
FilenameWithoutMagicChar.jpg
Magic-Chars.jpg
If 'Chars.jpg' is required instead of 'Magic-Chars.jpg', the regular expression is /-.*/.
a very basic typoscript viewhelper:
in fluid:
<f:cObject typoscriptObjectPath="lib.filenameStub" data="{filename}" />
in typoscript:
lib.filenameStub = TEXT
lib.filenameStub {
current = 1
split {
max = 2
token = -
returnKey = 1
}
}

Repository query->setLimit(4) returns no result in some cases

In my Repository for an extentsion I have the following code:
function findByTyp($typ, $not, $gender) {
$query = $this->createQuery();
return $query->matching(
$query->logicalAnd(
$query->equals('pid', 96),
$query->equals('typ', $typ),
$query->logicalNot($query->equals('uid', $not)),
$query->equals('gender', $gender)
)
)
->setOrderings (Array('sort' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_ASCENDING))
->setLimit(4)
->execute();
}
Now in a specific case this should return a query with 1 item in it. But somehow it does not return any item. But when I remove ->setLimit(4) it returns the right item.
Now this makes not much sense to me. As in other cases it works how it should. So how can ->setLimit(4) remove items from the query when there are no more then 4 items (in this case just 1)
I couldnt find an answer so far so here is my
Workaround:
I dont use ->setLimit(4) in the Repository, but only use the first 4 items in the template:
<f:for each="{collection}" as="collectionItem" iteration="collectionCount">
<f:if condition="{collectionCount.index}<4">
....
</f:if>
</f:for>

Replace line break

In Fluid Template and tx_news, I need to replace line breaks with "\n" for passing into JavaScript function.
If a JavaScript string contains line break, console will print "Unexpected token."
<a onclick="doSomething('{newsItem.bodytext}');">Click me</a>
How can you replace line breaks with "\n" in this example?
AS urbantrout already wrote: you can write an own viewhelper in PHP.
But you also can use a TypoScript-Viewhelper:
<a onclick="doSomething('{newsItem.bodytext -> f:cObject(typoscriptObjectPath: \'lib.nlReplace\')}');">Click me</a>
(as you are in a string you need to escape the inner ')
and some TypoScript like
lib.nlReplace = TEXT
lib.nlReplace {
current = 1
stdWrap.replacement {
1 {
search = #\n#
replace = \\n
useRegExp = 1
}
}
}
You could write your own ViewHelper and use it like this:
{namespace ns=Vendor\ExtensionName\ViewHelpers}
<a onclick="doSomething('{newsItem.bodytext -> ns:viewhelperName()}');">Click me</a>
More infos here: Developing a custom ViewHelper

Get the current language code inside a TYPO3 Fluid Template

Is it possible to get the current language key (or code) in a TYPO3 Fluid template?
In the meantime I've found another solution using a view helper found here:
<?php
class Tx_AboUnitReservation_ViewHelpers_LanguageViewHelper extends Tx_Fluid_Core_ViewHelper_AbstractViewHelper
{
/**
* Get the current language
*/
protected function getLanguage()
{
if (TYPO3_MODE === 'FE') {
if (isset($GLOBALS['TSFE']->config['config']['language'])) {
return $GLOBALS['TSFE']->config['config']['language'];
}
} elseif (strlen($GLOBALS['BE_USER']->uc['lang']) > 0) {
return $GLOBALS['BE_USER']->uc['lang'];
}
return 'en'; //default
}
/**
* Return current language
* #return string
*/
public function render()
{
return $this->getLanguage();
}
}
Which I use in the fluid template as follows.
<f:alias map="{isGerman: 'de'}">
<f:if condition="{aboUnitReservation:language()} == {isGerman}">
<script type="text/javascript" src="{f:uri.resource(path:'js/jquery.ui.datepicker-de-CH.js')}"></script>
</f:if>
</f:alias>
Another solution using TypoScript object in Fluid template:
# German language
temp.language = TEXT
temp.language.value = at
# English language
[globalVar = GP:L = 1]
temp.language.value = en
[global]
lib.language < temp.language
And Fluid code:
<f:if condition="{f:cObject(typoscriptObjectPath: 'lib.language')} == 'at'">
<f:then>
...
</f:then>
<f:else>
...
</f:else>
</f:if>
Object temp.language can contain any values, of course.
You can just assign variable in your action:
$this->view->assign("sysLanguageUid", $GLOBALS['TSFE']->sys_language_uid);
and then read it in your view:
<f:if condition="{sysLanguageUid} == 0">
You're reading English version of page
</f:if>
on the other hand it would be easier and more comfortable to assign redy-to-use variable in controller as <f:if ...> block is quite simple and sometimes just uncomfortable:
switch ($GLOBALS['TSFE']->sys_language_uid) {
case 1:
$msg = "Bienvenidos";
break;
case 2:
$msg = "Willkommen";
break;
default:
$msg = "Welcome";
break;
}
$this->view->assign("myMessage", $msg);
In order to get the current langage, you can use the Page/LanguageViewHelper included with the VHS extension.
{v:page.language(languages: 'en,fr', pageUid: '0', normalWhenNoLanguage: 'en')}
Have a look here : http://fluidtypo3.org/viewhelpers/vhs/1.8.3/Page/LanguageViewHelper.html
my Solution is this:
data = TSFE:sys_language_uid
(The Output is the Language UID)
page = PAGE
page {
## Fluid-Template ##
10 = FLUIDTEMPLATE
10 {
## Variablen ##
variables {
pageTitle = TEXT
pageTitle.data = page:title
siteTitle = TEXT
siteTitle.data = TSFE:tmpl|setup|sitetitle
rootPage = TEXT
rootPage.data = leveluid:0
baseurl = TEXT
baseurl.value = {$config.domain}
pageLanguage = TEXT
pageLanguage.data = TSFE:sys_language_uid
}
## Settings ##
settings {
}
}
}
Now u can use the new "variables" in FLUID:
<f:if condition="{pageLanguage}==0">
<f:then>DE</f:then>
<f:else>EN</f:else>
</f:if>
Or you use this for more Lang-Code:
<f:if condition="{pageLanguage}==0">
<f:then>Do this</f:then>
<f:else if="{pageLanguage}==1">
Do this instead if variable two evals true
</f:else>
<f:else if="{pageLanguage}==2">
Or do this if variable three evals true
</f:else>
<f:else>
Or do this if nothing above is true
</f:else>
</f:if>
Another approach via the TYPO3 languageAspect
Example within an Extbase Extension (e.g. showAction)
# instantiate TYPO3 Context and get language aspect
$languageAspect = GeneralUtility::makeInstance(Context::class)->getAspect('language');
# now assign any property of the actual language to the view. In this example: the language uid.
$this->view->assign('actualLanguageId', $languageAspect->getId());
Example tested in TYPO3 10.4 LTS
Another option could be to use the v:page.languageMenu ViewHelper from the VHS extension. It would allow you to combine with other ViewHelpers and use something like the following in a Fluid template:
{namespace v=Tx_Vhs_ViewHelpers}
<v:page.languageMenu as="languages">
<!-- gets the current language key -->
{languages -> v:iterator.filter(propertyName: 'current', filter: 1)}
<!-- iterates over flag names of all languages which apply to the current page -->
<f:for each="{languages -> v:iterator.extract(key: 'flag')}" as="languageFlagName">
<!-- example suggestion: image -->
<f:image src="{f:uri.resources(path: 'Images/Flags/{languageFlagName}.png')}" />
<!-- example suggestion: label read from LLL:EXT:myext/Resources/Private/Language/locallang.xml:languages.$languageFlagName -->
<f:translate key="languages.{languageFlagName}" default="{languageFlagName} />
</f:for>
</v:page.languageMenu>
There is a lot more you can do with the values returned from each of these ViewHelpers - you can for example use v:var.set to define new variables in the template which contain extracted flag names:
<!-- Using {languages} inside <v:page.languageMenu> like above -->
{languages -> v:iterator.filter(propertyName: 'current', filter: 1) -> v:var.set(name: 'currentLanguage')}
<!-- variable {currentLanguage} now contains an array of values describing the current language -->
In TYPO3 10 and above you could access directly the language uid and use it together with a condition in your Fluid-Template (see here some hints for using Fluid):
<f:if condition="{data.sys_language_uid} = 1">
<f:then>
This is language with uid "1"
</f:then>
<f:else>
This is language with other uid than "1"
</f:else>
</f:if>