TYPO3 count objects with constraint and display result in fluid template - typo3

I have build an extension with the extension builder that handles objects, an object can be an "item" or a "project".
An object has a field status which has 4 options, and is filled with an integer (3 = sold).
An object can be signed as a "project", in that case it has a boolean 1 for isproject and a field items with related objects as items.
this all works fine, I can iterate trough the projects with my fluid template and with <f:count>{object.items}</f:count> display the number of items appartaining to the project.
In the same fashion I should display the count of only the sold items ...
(<f:count where="object.items.status == 3">{object.items}</f:count> this obviously does not work, just to render the idea)
with <f:debug>{object}</f:debug> I see the field status defined for all items ...
since I have no idea how to approch this I might have left out some vital information

As indicated in the previous answer, you can use the GroupedFor ViewHelper for this purpose. But using it would be to much logic inside the fluid template thats the reason why this should be done in the controller, model or repository.
Example: Adding a Getter to the Object-Model
/**
* #return int
*/
public function getSoldItems() {
$soldCount = 0;
foreach($this->getItems() as $item) {
// 0 = sold
if($item->getStatus()===0) {
$soldCount++;
}
}
return $soldCount;
}
In fluid you can call the Getter with {object.soldItems}
A better performance solution especially with lazy loading subobjects would be counting with the repository. For this you have to create a function in the repository and call it inside the Getter-Function. To use common repository methods for creating the query, you need a "Backrelation" of items to the object. Otherwise you have to write the query with "statement()" on your own.

You can count them in the controller or use the GroupedFor ViewHelper https://fluidtypo3.org/viewhelpers/fluid/master/GroupedForViewHelper.html

Check this workaround:
<f:variable name="counter" value="0" />
<!-- Loop over the elements -->
<f:for each="{bookings}" as="booking">
<!-- Check if the criteria is met -->
<f:if condition="{booking.paid}">
<!-- Increment the counter -->
<f:variable name="counter" value="{counter + 1}" />
</f:if>
</f:for>
<!-- Display the counter-Variable -->
{counter}

Related

TYPO3 - Display system categories and sub categories

I am a new typo3 user and actually I'm creating an extension that will allow me to display a list of categories created with the categories of the system, which will contain subcategories (categories and subcategories can have several subcategories), and finally a pdf file
I'm having a little trouble visualizing how to create this extension
Currently, I manage to retrieve and display the parent categories, but I have no idea how to display the different subcategories after clicking on a parent category
Does anyone have an idea how I could display my different subcategories?
there is some code :
Controller :
public function listAction()
{
$categories = $this->categorieRepository->findByPid(177);
$this->view->assign(
'categories' => $categories
);
}
List.html
<div>
<f:for each="{categories}" as="categorie">
<f:if condition="{categorie.parent} ">
<f:then>
</f:then>
<f:else>
<f:link.action action="list">
{categorie.title}
</f:link.action>
</f:else>
</f:if>
</f:for>
</div>
thanks In advance for your advice
Usually the listAction never get's any parameters and the pid is assigned by the storagePid that is automatically fetched by the repository. The repository also can have the setting to ignore the pid. You never mentioned it but as I see the pid in your code you could also include the option to transfer the pid by the URL as parameter.
Additionally, and that's the answer to your question, you can transfer the parent category, so the listAction would have one or two optional parameters. They have to be optional to display also something if no parameters are given:
public function listAction($parentCategeoryId = null, $pageId = null)
{
// HERE SOME LOGIC
// YOU HAD TO TRANSFER ALSO THE $pageId TO THE REPOSITORY IF USED
$categories = ...
$this->view->assign(
'categories' => $categories
);
}
The viewHelper can transfer the parameters if you add them there:
<f:link.action action="list" arguments="{parentCategeoryId: category.uid, pageId: categorie.pid}" >
{categorie.title}
</f:link.action>
This is no copy-paste answer, so you have still some work, but that's the general approach I'd follow.

Typo3: How to get the View exception error

I am Developing a custom extension for Typo3. now I am getting an error if the user did not include my extension from template's include section.
I want to catch this error to show a message from controller. How can I do this?
my controller action.
public function listAction()
{
$audits = $this->auditRepository->findAll();
$this->view->assign('arrDetails', $audits);
}
This could be one solution, but not the cleanest.
We first need to get the values from the field include_static_file that it is located on the sys_template table. So:
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('sys_template')->createQueryBuilder();
$result = $queryBuilder
->select('include_static_file')
->from('sys_template')
->execute()
->fetch(0);
The we need to get the string and evaluate if your extension key is present. So:
$extKey = 'your_extension_key';
if (strpos($result['include_static_file'], $extKey) !== false) {
$audits = $this->auditRepository->findAll();
$this->view->assign('arrDetails', $audits);
}
else {
$this->addFlashMessage(
'You forgot to add the static template',
$messageTitle = 'Template is missing',
$severity = \TYPO3\CMS\Core\Messaging\AbstractMessage::WARNING,
$storeInSession = TRUE
);
}
Your HTML
<f:if condition="{arrDetails}">
<f:then>
do something with your content
</:then>
<f:else>
<f:flashMessages />
</f:else>
</f:if>
Of course you can write a static function for this or you can use the LocalizationUtility in order to get the text in multiple languages. It is up to you.
Result:
Are you sure you need your static template?
I think it is important that some values you fetch from typoscript got meaningful content.
As you inspect them anyway you can output a message if one or more values are empty.
Your plugin would work in an installation if all necessary values are set in any way. Even if your static template is not included.
And your plugin would fail if your static template is included but following typoscript would erase the settings from it.
In your error message you can note the possibility to use the values from your static template.

Fluid array, if all elements are deleted the array is not empty, but a string

When I have an array in flux:
<flux:form.section name="links" label="Links">
<flux:form.object name="link" label="Link">
<flux:field.input name="linktext" label="Linktext"/>
</flux:form.object>
</flux:form.section>
I first check if the array is set before I render it with fluid:
<f:if condition="{links}">
<ul class="menulinks">
<f:for each="{links}" as="linkelement">
<li>{linkelement.link.linktext}</li>
</f:for>
</ul>
</f:if>
This works. But if there were items set but then deleted, {links} is not empty. It is set as a string with a whitespace " ". And that means the condition in the if-Tag returns true.
And this can lead to an error. In this case I had an error in the backend, but not on the frontend. Even I used nearly the same code in <f:section name="Preview"> and <f:section name="Main">.
My idea was to check the type of {links} and only return true if the type is array. But I am not sure if this is possible with fluid. What other options I have?
The errorreport I see in the backend:
The argument "each" was registered with type "array", but is of type "string" in view helper "TYPO3\CMS\Fluid\ViewHelpers\ForViewHelper"
My idea was to check the type of {links} and only return true if the type is array. But I am not sure if this is possible with fluid.
It is. You can always implement your own ViewHelper if you need support for something, fluid does not bring out of the box. To add the ViewHelper you need, create a php file called IfIsNonEmptyArrayViewHelper.php in some_extension/Classes/ViewHelpers/. The implementation is quite easy:
<?php
namespace Vendor\SomeExtension\ViewHelpers;
class IfIsNonEmptyArrayViewHelper extends \TYPO3\CMS\Fluid\Core\ViewHelper\AbstractConditionViewHelper {
/**
* #param mixed $variable
* #return string
*/
public function render($variable) {
if (is_array($variable) && !empty($variable)) {
return $this->renderThenChild();
}
return $this->renderElseChild();
}
}
After that you only have to add your own fluid namespace to your template like this: {namespace ns=Vendor\SomeExtension\ViewHelpers}
Now you can write your condition like this:
<ns:ifIsNonEmptyArray variable="{links}">
<ul class="menulinks">
<f:for each="{links}" as="linkelement">
<li>{linkelement.link.linktext}</li>
</f:for>
</ul>
</ns:ifIsNonEmptyArray>
Of course ns, Vendor and SomeExtension are just placeholders for the real names.

How to access a method and pass an argument within the template?

In my template I want check whether an entity has a relation to another one. Meaning one object is in an attached Object Storage of another one.
In the controller I can simply call:
if ($product->getCategory()->offsetExists($category) {
print 'In category ' . $category->getName();
}
But I can't figure out the correct syntax in the template. I tried those without luck (both evaluate to true everytime):
<f:if condition="{product.category.offsetExists(category)}">true</f:if>
<f:if condition="{product.category.offsetExists({category})}">true</f:if>
Is this even possible within the template?
You can only access properties via Getter from Fluid with no parameters, but you can implement an own ViewHelper to check that. As parameters you can use your Product and the Category. Then you can call your ViewHelper from Fluid this way:
<vh:checkOffset product="{product}" category="{category}" />
or inline
{vh:checkOffset(product: product, category: category)}
Within your ViewHelper you can check this in the way you've done it in your Controller:
public function render($product, $category){
return ($product->getCategory()->offsetExists($category));
}
Additionally to sretuer's answer, I'll only mention that you can create VH which will display block conditionally like:
File typo3conf/ext/your_ext/ViewHelpers/CheckOffsetViewHelper.php
<?php
namespace VENDORNAME\YourExt\ViewHelpers;
class CheckOffsetViewHelper extends \TYPO3\CMS\Fluid\Core\ViewHelper\AbstractViewHelper {
public function render() {
return ($product->getCategory()->offsetExists($category))
? $this->renderChildren()
: '';
}
}
?>
So you can use it in the view:
{namespace vh=VENDORNAME\YourExt\ViewHelpers}
<vh:checkOffset product="{product}" category="{category}" >
Display this only if product is in category
</vh:checkOffset>
Of course you need to fix VENDORNAME and YourExt according to your extension, can be found at the beginning of every controller, model, repository etc.
You may consider https://fluidtypo3.org/viewhelpers/vhs/master/Condition/Iterator/ContainsViewHelper.html which is designed for creating conditions in Fluid that check if an array or Iterator contains another object and works exactly like f:if regarding then and else arguments and f:then and f:else child nodes.

TYPO3: How can I access property of objects in a partial or section?

I have an object defined in TypoScript
page.10 {
variables {
myObject = COA
myObject{
1 = TEXT
1.value = yome Text
2 = TEXT
2.value = 42
}
}
}
and I need the data of the myObject in a partial
<f:render partial="myPartial" arguments="{content:myObject}" />
that looks like
<section id="myPartial">
<h2>{content.1}</h2>
<p>{content.2}</p>
</section>
Although the content is there ( because {content} will display all the properties) I cannot access it and h2 and p will be empty...
What should I do to fill h2 and p with the content of myObject?
That is not possible. TypoScript only returns text strings at the moment, not arrays. Thus the variable myObject contains the whole concatenated string of the COA, thus yome Text42.
Note that COA means Content Object Array, but the whole COA is one single object that is returned as one string.
Alternative: use the VHS extension's v:var.typoscript ViewHelper:
{namespace v=Tx_Vhs_ViewHelpers}
{v:var.typoscript(path: 'page.10.variables.myObject') -> v:var.set(name: 'myObject')}
After which you can access {myObject.1} etc. in your template. Note that the so-called "chained" usage of v:var.set is optional, but will make it easier to access your variables using an intermediate template variable instead of more expensive calls to retrieve the value completely in multiple locations. The other way:
{v:var.typoscript(path: 'page.10.variables.myObject.1')}
{v:var.typoscript(path: 'page.10.variables.myObject.2')}
etc.
VHS extension on TER: http://typo3.org/extensions/repository/view/vhs