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.
Related
I am modifying the template of a plugin, and I want to retrieve a field from the content element.
Using f:debug I see the only data available is from the plugin itself, and none from the content element.
Is there any way I can perhaps insert the field I need in the plugin settings?
eg. something like:
plugin.tx_plugin.settings {
contentUid = TEXT
contentUid.field = uid
}
The best way I can think of to do this is with a custom ViewHelper. Something like:
namespace MyVendor\MyExtension\ViewHelpers;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Configuration\ConfigurationManager;
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
class ContentUidViewHelper extends AbstractViewHelper
{
public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext)
{
$configurationManager = GeneralUtility::makeInstance(ConfigurationManager::class);
return $configurationManager->getContentObject()->data['uid'];
}
}
In your Fluid template:
<mynamespace:contentUid />
This will get the uid of the content element, but you can get any field this way. Just change the key of the data array to the field you need.
In the corresponding method (like the listAction or showAction) of the controller you can get the data of the content element in the following way:
$contentObject = $this->configurationManager->getContentObject();
$this->view->assign('contentObjectData', $contentObject->data);
As far as I know, you can't get to that data using typoscript, but I've never needed it anyway since I've been using the above code in the controller.
settings do not have stdWrap-type per default, but only string. So you can not use cObjects as values.
For one (or a few) settings, you could do the stdWrap-processing in your method/class yourself:
$settingsAsTypoScriptArray = $this->objectManager->get(TypoScriptService::class)->convertPlainArrayToTypoScriptArray($this->settings);
$contentObj = $this->configurationManager->getContentObject();
if ($contentObj === null) {
$contentObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
}
// now override the values in the settings array with the processed value
$contentUid = (int)$contentObj->stdWrap($settingsAsTypoScriptArray['contentUid'], $settingsAsTypoScriptArray['contentUid.']);
If you wanna have many settings to be stdWraped, have a look into EXT:news. Georg implemented an interesting concept via useStdWrap configuration.
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.
I created my own viewhelper for getting all Information of an Image with the UID.
I get all the Information, if I print the array in the viewhelper, but im not able to create a new Fluid variable with all these information, to work with in the Template.
I tried to create the new variable with:
$this->view->assign('sliderItems', $sliderItems);
But I receive „Call to a member function assign() on null“.
I render the Image with:
public function render() {
/* MY RENDER STUFF */
And then I will submit the array with:
$this->view->assign('sliderItems', $sliderItems);
}
How can I solve this, to get an access with fluid?
You can use the templateVariableContainer for that:
$this->templateVariableContainer->add('key', 'value');
If you use the viewhelper in a loop, you'll have to remove the variable after render:
$this->templateVariableContainer->add('key', 'value');
$output = $this->renderChildren();
$this->templateVariableContainer->remove('key');
return $output;
If you use your viewhelper in your template, the variable will be available in your fluid template.
<vendor:viewhelper>
{key}
</vendor:viewhelper>
I have a template with a line like this:
<f:format.htmlentitiesDecode>
{product.features}
</f:format.htmlentitiesDecode>
where "features" is an attribute of the "product" data model.
I would like to print the result if a method call on product instead. Something like:
<f:format.htmlentitiesDecode>
{product.getStrippedFeatures}
</f:format.htmlentitiesDecode>
but that gives me empty content.
How can I call a method in the product model and print its output?
Just add the function in your product model like:
public function getStrippedFeatures()
{
return your_stripping_method($this->features);
}
Fluid calls attributes with a prefixed get, so you only need this in the template:
{product.strippedFeatures}
at the moment I´m working with a custom Silverstripe Controller with a Director rule:
---
Name: myroutes
After: framework/routes#coreroutes
---
Director:
rules:
'category/$Action/$Slug': 'Category_Controller'
The Controller looks like that:
class Category_Controller extends Page_Controller {
public function show($arguments) {
echo "Slug: " . $arguments->param("Slug");
}
}
When I open in the browser the URL http://mysite.com/category/show/mobile
then the output look fine like this: "Slug: mobile".
I just wonder how I can use a Category.ss Template from the Folder "themes/templates/Layout" to render the Output. Then of course the container html (with header/footer) from Page.ss should be included as well. Just as usual when you have a custom Page Controller/Class and a corresponding Template in the Layout Folder.
I just tried this:
public function show($arguments) {
echo $this->renderWith("Category");
}
It uses Category.ss for rendering the output, but there is no container html...
Thx for any help.
Regards,
Florian
you can also pass an array to renderWith(), and it will try through the array until it finds a template.
so lets say $this->renderWith(array('Category', 'Page'));
it will first look for a template called Category.ss, but will not find it (in the templates folder, not layout folder), it will then find Page.ss and use it.
Now it hits $Layout inside Page.ss and it checks the array again inside the Layout folder, it will now find the Category.ss, which is exactly what you where looking for if I got the question right.
if you do not want to do return $this->renderWith(); you can also just do return $this; and silverstripe will get the action you called and the class hierarchy of $this and use that as array for renderWith()
So if your classes are Category_Controller > Page_Controller > ContentController the array will look like this:
array(
'Category_show', // because your action is show
'Category',
'Page_show',
'Page',
'ContentController_show',
'ContentController',
)
(I am not a 100% sure if it also includes Page_show and ContentController_show.)