TYPO3 Fluid resource.record.fal - typo3

With the vhs viewhelper "resource.record.fal" i select the image from the page ressources. This works very fine but i also want to inheritance the image as long as there is no other image uploaded.
Is there a way to do that? The slide attribute is not available for this viewhelper.
I know i can do all of this just with Typoscript but i want to find a solution based on Fluid.
Here is my code:
<v:resource.record.fal table="pages" field="media" uid="{data.uid}" as="resources" >
<f:for each="{resources}" as="resource">
<v:resource.image identifier="{resource.id}" />
</f:for>

Okay, here goes some Fluid free-styling inline syntax:
{v:page.rootLine()
-> v:iterator.column(columnKey: 'media', indexKey: 'uid')
-> v:iterator.filter(preserveKeys: 1)
-> v:iterator.keys()
-> v:iterator.last()
-> f:variable(name: 'firstPageUidWithMedia')}
In steps:
Extract the page root line
Extract a sub-array of all media column values, use column uid as keys
Filter this to remove any empty values but preserve the keys
Extract a sub-array of only the keys
Pick the last key, which is the real page UID we want
Assign that to a variable
Then use the {firstPageUidWithMedia} instead of {data.uid}.

I am working to upgrade Typo3 from 6.2 to 9.5 LTS, i founded that fluidcontent will be used anymore, now below code used for previewing image in Flux content.
I have replaced my old code:
<f:for each="{v:content.resources.fal(field: 'teaserImage')}" as="image">
<img src="{f:uri.image(src:'{image.id}',maxWidth:'64',treatIdAsReference:'1')}" alt="{image.alternative}"/>
</f:for>
To Below New code:
<v:content.resources.fal field="teaserImage" as="images" record="{record}">
<f:for each="{images}" as="image">
<f:if condition="{image}">
<f:image src="{image.id}" treatIdAsReference="1" maxWidth="100"/>
</f:if>
</f:for>
</v:content.resources.fal>

I developed this VH which do what you need :
namespace Your\Vendor\ViewHelpers;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
use TYPO3\CMS\Core\Resource\File;
use TYPO3\CMS\Core\Resource\FileReference;
use TYPO3\CMS\Core\Resource\FileRepository;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Utility\DebuggerUtility;
use TYPO3\CMS\Frontend\Page\PageRepository;
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic;
/**
* View helper to get the first image of rootline.
*/
class RootlineFirstImageViewHelper extends AbstractViewHelper
{
use CompileWithRenderStatic;
/**
* {#inheritdoc}
*/
public function initializeArguments()
{
$this->registerArgument('pid', 'int', '', true);
}
/**
* {#inheritdoc}
*/
public static function renderStatic(
array $arguments,
\Closure $renderChildrenClosure,
RenderingContextInterface $renderingContext
): ?File {
$fileRepository = GeneralUtility::makeInstance(FileRepository::class);
$pages = GeneralUtility::makeInstance(PageRepository::class)->getRootLine($arguments['pid']);
$files = [];
foreach ($pages as $page) {
/** #var FileReference[] $files */
$files = $fileRepository->findByRelation('pages', 'media', $page['uid']);
if (!empty($files)) {
break;
}
}
// It would be nice to have an extra argument to get a random image of the array.
return !empty($files) ? $files[0]->getOriginalFile() : null;
}
}
Then you can call it like this in your Fluid template :
<f:variable name="rootlineFirstImage"><whatever:rootlineFirstImage pid="{data.uid}"/></f:variable>
<f:if condition="{rootlineFirstImage}">
<f:image image="{rootlineFirstImage}" width="1280"/>
</f:if>

Related

Internal Link News: excludeAlreadyDisplayedNews

I am using internal link news, this means I am using standard pages as news pages. On these pages I have a news slider to display other news. Here I need the option: excludeAlreadyDisplayedNews to remove the current page from news.
But how to add <n:excludeDisplayedNews newsItem="{newsItem}"/> on standard pages?
Is there a typoscript for loading the related newsItem for this page? Maybe a dataprocesser could do the trick, but I do not know how?
Another idea was adding an additional field to pages, so the user adds the news record to the page. This is one click more, OK, but when passing the news UID to the viewhelper, I get this error message:
The argument "newsItem" was registered with type "GeorgRinger\News\Domain\Model\News", but is of type "integer" in view helper "GeorgRinger\News\ViewHelpers\ExcludeDisplayedNewsViewHelper".
Any help appreciated :)
I don't think there is a simple, clean way to do this. The page isn't the actual news records, so technically it's not displayed. I can think of 3 possible solutions:
Actually technically display the news record on the page, with an empty template (except for the n:excludeDisplayedNews ViewHelper) so it doesn't show anything. I'm not sure if this will work with "internal link" news items though, but if it does this is the easiest way.
You can do this using the following TypoScript:
lib.displayedNews = USER
lib.displayedNews {
userFunc = TYPO3\CMS\Extbase\Core\Bootstrap->run
extensionName = News
pluginName = Pi1
vendorName = GeorgRinger
switchableControllerActions {
News {
1 = detail
}
}
views =< plugin.tx_news.view
view {
templateRootPaths.200 = path/to/template/with/only/excludeDisplayedNews/
}
settings =< plugin.tx_news.settings
settings {
singleNews.current = 1
useStdWrap = singleNews
insertRecord = 1
}
}
In your page template you can use:
<f:for each="{displayedNews}" as="newsItem">
<f:cObject typoscriptObjectPath="lib.displayedNews" data="{newsItem.data.uid}" />
</f:for>
And your News/Detail.html template would just be:
<n:excludeDisplayedNews newsItem="{newsItem.uid}" />
Fetch the news record object with a custom ViewHelper or DataProcessor so you can fill it into the n:excludeDisplayedNews ViewHelper. In my opinion this is the cleanest solution, but also requires the most work.
Make a user function to fill the global array the news extension uses to keep track of the displayed news records, like: $GLOBALS['EXT']['news']['alreadyDisplayed'][$newsUid] = $newsUid;
This is the complete solution:
write a DataProcessor to get the related news record
add a NewsDataProcessor
add n:excludeDisplayedNews to Fluid Template
Step 1: in your Page Typoscript, add a DataProcessor to get the related news record. Add this to the dataProcession block of your page FLUIDTEMPLATE Typoscript:
10 = TYPO3\CMS\Frontend\DataProcessing\DatabaseQueryProcessor
10 {
table = tx_news_domain_model_news
#pid for news folder
pidInList = 123
where.data = page : uid
where.wrap = internalurl=|
as = displayedNews
dataProcessing {
10 = MyCompany\MyExtension\DataProcessor\NewsDataProcessor
10 {
field = uid
}
}
}
Attention: internalUrl is a string. For me it worked like this, but there might be typolink syntax required. Any query improvements welcome!
Step 2: Add a NewsDataProcessor in MyExtension/Classes/DataProcessor/NewsDataProcessor.php
<?php
namespace MyCompany\MyExtension\DataProcessor;
use GeorgRinger\News\Domain\Repository\NewsRepository;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Object\ObjectManager;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
use TYPO3\CMS\Frontend\ContentObject\DataProcessorInterface;
class NewsDataProcessor implements DataProcessorInterface
{
/**
* #param \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer $cObj
* #param array $contentObjectConfiguration
* #param array $processorConfiguration
* #param array $processedData
* #return array
*/
public function process(
ContentObjectRenderer $cObj,
array $contentObjectConfiguration,
array $processorConfiguration,
array $processedData
) {
/** #var \TYPO3\CMS\Extbase\Object\ObjectManager $objectManager */
$objectManager = GeneralUtility::makeInstance(ObjectManager::class);
/** #var \GeorgRinger\News\Domain\Repository\NewsRepository $newsRepository */
$newsRepository = $objectManager->get(NewsRepository::class);
$field = 'uid';
if (array_key_exists('field',$processorConfiguration)) {
$field = $processorConfiguration['field'];
}
$newsArray = $processedData['data'];
$news = $newsRepository->findByUid((int)$newsArray[$field], false);
$processedData['news'] = $news;
return $processedData;
}
}
In later EXT:news versions (currently 7.0.7) there might be a DataProcessor available, then you can skip this step and use the existing one
Step 3: Add the n:excludeDisplayedNews Viewhelper to the Fluid Template of your page. Don't forget to add the Viewhelper namespace to the template.
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
xmlns:n="http://typo3.org/ns/GeorgRinger/News/ViewHelpers"
data-namespace-typo3-fluid="true">
<f:layout name="MyLayout"/>
<f:section name="main">
<f:for each="{displayedNews}" as="newsItem">
<n:excludeDisplayedNews newsItem="{newsItem.news}" />
</f:for>
<div class="main-content">
...
</div>
</f:section>
</html>

How to get fal object instead of array when using flux:field.inline.fal

In my flux content template configuration section I define an image field like this:
<flux:field.inline.fal label="Image" name="images" maxItems="1" minItems="0" showThumbs="1"/>
In my flux content template main section I debug the output:
<f:for each="{v:resource.record.fal(table: 'tt_content',field: 'images', uid: '{record.uid}')}" as="image" iteration="iterator">
<f:debug>{image}</f:debug>
</f:for>
The debuging output shows an array but what I need is the FAL object of that image I added in the backend.
I googled a lot and found some older posts from 2015. All say it is not possible to get the fal object(!) in flux. Is it still true? Do you know any way?
One solution is to create a custom ViewHelper:
<?php
namespace Cmichael\Typo3projectprovider\ViewHelpers\Content;
/* FalViewHelper
* To get fal object by image uid (respectivly in flux templates)
* Usage example: <cm:content.fal referenceUid="{image.uid}">
* */
class FalViewHelper extends \TYPO3\CMS\Fluid\Core\ViewHelper\AbstractViewHelper {
/**
* #var bool
*/
protected $escapeOutput = false;
/**
* Initialize arguments.
*
*/
public function initializeArguments() {
$this->registerArgument('referenceUid', 'integer', 'File reference uid', true, 0);
}
/**
* Return file reference
*
* #return \TYPO3\CMS\Core\Resource\FileReference|null
*/
public function render() {
$referenceUid = $this->arguments['referenceUid'];
$fileReferenceData = $GLOBALS['TSFE']->sys_page->checkRecord('sys_file_reference', $referenceUid);
return $fileReferenceData ? \TYPO3\CMS\Core\Resource\ResourceFactory::getInstance()->getFileReferenceObject($referenceUid) : $referenceUid;
}
}

template for list view with system categories

I have an extbase extension (TYPO3 7) with a simple model of a contact person.
The person has a name and a picture.
So far this is clear.
But every Person has a category (e.g. where he works. Office, Marketing etc.)
Therefor i use the system categories, as described here:
https://wiki.typo3.org/TYPO3_6.0#Adding_categories_to_own_models_without_using_Extension_Builder
When creating a person via web > list, i can assign a category.
Now the question for templating:
If i debug my contact person, i get the output like screen below.
I want to have a list where every category (headline) is shown with it's contact persons.
How to do this?
Is the logic for this only in the template or also in the controller?
Has anybody an example for this?
Best regards
Markus
I guess the required logic you need is possible with Fluid with using the GroupedFor ViewHelper and many others. Because a person can have multiple categories this would become a huge nesting of Viewhelpers so I can not recommend to use Fluid for this even if its possible. This kind of logics belong to the controllers, models and repositories.
There are multiple ways to solve this logic. Here is an example how to realize this in the controller...
Controller:
/**
* #var \TYPO3\CMS\Extbase\Domain\Repository\CategoryRepository
* #inject
*/
protected $categoryRespoitory = NULL;
/**
* action list
* #return void
*/
public function listAction()
{
$allCategories = $this->categoryRespoitory->findAll();
$categoriesWithContacts = [];
/** #var \TYPO3\CMS\Extbase\Domain\Model\Category $category */
foreach($allCategories as $category) {
$contactsInCategory= $this->contactRepository->findByCategory($category);
if($contactsInCategory->count()>0) {
$categoriesWithContacts[] = [
'category' => $category,
'contacts' => $contactsInCategory
];
}
}
$this->view->assignMultiple([
'categoriesWithContacts' => $categoriesWithContacts
]);
}
Injecting the CategoryRespository will required clearing cache in install tool or reinstalling the extension.
Maybe you need this function in your ContactRepository:
/**
* #param \TYPO3\CMS\Extbase\Domain\Model\Category $category
* #return array|\TYPO3\CMS\Extbase\Persistence\QueryResultInterface
*/
public function findByCategory(\TYPO3\CMS\Extbase\Domain\Model\Category $category) {
$query = $this->createQuery();
return $query->matching($query->contains('categories', $category))->execute();
}
Then in Fluid you can do something like this:
<f:for each="{categoriesWithContacts}" as="categoryWithContact">
{categoryWithContact.category.title}
<f:for each="{categoryWithContact.contacts}" as="contact">
{contact.name}
</f:for>
</f:for>

Add/Create Element to ObjectStorage using Javascript in Extbase/Fluid TYPO3

what is the correct way to dynamically create new Child Elements in a Fluid Form using JavaScript?
Problem:
1:n Relation (Parent/Child) using Extbase ObjectStorages:
When the Parent Fluid Form is called it should be possible to add several childs (incl. properties of course!)
Dirty, partly working, Solution:
I added some JS Code and added the required input elements dynamically.
The "xxx" will be interated for each Child. The data will be correctly stored in the DB.
<input type="text" placeholder="First Name" name="tx_booking[newBooking][accompanyingperson][xxx][firstname]">
However, if an error occurres all child forms disappear and no f3-form-error will be shown. The reason for this, may be the redirect to originalRequest (initial form without child fields).
How can I handle this Problem without dirty tricks?
Please give me shirt hint.
Again, I will answer the question myself!
The following lines are the foulest code ever but it works.
I really want to know how to do this in a correct way. However, the solution is, to get the Arguments from the dynamically added JS Inputs. This is done in the errorAction and will be passed by the forward() to the initial Action, where the Errors should be appear.
I for all think, that must be a better way by using the PropertyMapper and modify the trustedProperties....
Here a short example:
// Error function in Controller
protected function errorAction() {
$referringRequest = $this->request->getReferringRequest();
// Manual added JS Data
if($this->request->hasArgument('newRegistration'))
{
$newRegistration = $this->request->getArgument('newRegistration');
$referringRequest->setArgument('accompanyingperson', $newRegistration['accompanyingperson']);
}
if ($referringRequest !== NULL) {
$originalRequest = clone $this->request;
$this->request->setOriginalRequest($originalRequest);
$this->request->setOriginalRequestMappingResults($this->arguments->getValidationResults());
$this->forward($referringRequest->getControllerActionName(), $referringRequest->getControllerName(), $referringRequest->getControllerExtensionName(), $referringRequest->getArguments());
}
}
// action new in Controller
public function newAction(\***\***\Domain\Model\Registration $newRegistration = NULL) {
if($this->request->hasArgument('accompanyingperson'))
{
$this->view->assign('accPer', $this->request->getArgument('accompanyingperson'));
}
.
.
.
}
//Fluid template of Action New
<f:if condition="{accPer}">
<f:for each="{accPer}" as="ap" key="key" iteration="i">
<f:form.textfield class="form-control" placeholder="First Name" property="accompanyingperson.{key}.firstname"/>
.
.
.
</f:for>
</f:if>
Following is my solution, something like yours.
Models
class Resume extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity {
/**
* #var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<Builder>
* #cascade remove
*/
protected $builders;
}
class Builder extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity {
/**
* #var string
*/
protected $title;
}
Controller
class ResumeController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionController {
/**
* #var \Dagou\Resume\Domain\Repository\ResumeRepository
* #inject
*/
protected $resumeRepository;
/**
* #param \Dagou\Resume\Domain\Model\Resume $resume
* #see \Dagou\Resume\Controller\ResumeController::saveAction()
*/
protected function createAction(\Dagou\Resume\Domain\Model\Resume $resume = NULL) {
$this->view->assignMultiple([
'resume' => $resume,
]);
}
protected function initializeCreateAction() {
if (($request = $this->request->getOriginalRequest())) {
$this->request->setArgument('resume', $request->getArgument('resume'));
$propertyMappingConfiguration = $this->arguments->getArgument('resume')->getPropertyMappingConfiguration();
$propertyMappingConfiguration->allowCreationForSubProperty('builders.*');
$propertyMappingConfiguration->allowProperties('builders')
->forProperty('builders')->allowAllProperties()
->forProperty('*')->allowAllProperties();
}
}
protected function initializeSaveAction() {
$propertyMappingConfiguration = $this->arguments->getArgument('resume')->getPropertyMappingConfiguration();
$propertyMappingConfiguration->allowCreationForSubProperty('builders.*');
$propertyMappingConfiguration->allowProperties('builders')
->forProperty('builders')->allowAllProperties()
->forProperty('*')->allowAllProperties();
}
}
/**
* #param \Dagou\Resume\Domain\Model\Resume $resume
*/
protected function saveAction(\Dagou\Resume\Domain\Model\Resume $resume) {
$this->resumeRepository->add($resume);
}
}
Template
<f:form class="form-horizontal" name="resume" action="save" object="{resume}">
<f:if condition="{resume.builders}">
<f:for each="{resume.builders}" as="builder" iteration="builderIteration">
<f:form.textfield class="form-control" property="builders.{builderIteration.index}.header" />
</f:for>
</f:if>
</f:form>
If you have a better one, please let me know. Thanks!

Access Extbase ObjectStorage from Fluid for loop chaos (how to avoid nested loops)

update 31.01.
Meanwhile I made a new test Ext as written on Marcels webpage:
http://lbrmedia.net/codebase/Eintrag/extbase-bidirektionale-mm-relation/
I have the same Issue with that.
The goal ist that a Feuser (logged-in) can see all available clients and pick his favorites. So every Feuser can have many clients and each client can be picked by many Feusers. The table relations are correct. In the backend I can see and update the relations on every side (clients can pick Feusers from the list and vice versa.
Please find all the data needed in my Gist:
https://gist.github.com/metaxos/91622c536588d0aa8440
The ZIP of the extension can be found here: http://www.filedropper.com/testmm000201501311251
initial question
For a small Extbase extension. I have a m:n connection between Feusers and Clients. Each Feuser can have multiple clients associated. I have build the relation with Extension Builder and in the backend everything works as needed.
In my frontend plugin I can also add clients to my feuser with attach (this works).
Every Feuser can see all clients (this works). In the for loop of all clients I want to show an icon if the client is associated with the feuser (this works).
But how can i avoid the inner for loop (see below)?
<f:for each="{feusers.clients}" as="singleClient">
Feusers model:
/**
* Clients
*
* #lazy
* #var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<Exapoint\Exaibbrplus\Domain\Model\Clients>
*/
protected $clients;
/**
* __construct
*/
public function __construct() {
$this->initStorageObjects();
}
/**
* Initializes all ObjectStorage properties.
*
* #return void
*/
protected function initStorageObjects() {
$this->clients = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage();
}
/**
* Returns the clients
* #return \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\Exapoint\Exaibbrplus\Domain\Model\Clients> $clients
*/
public function getClients() {
return $this->clients;
}
List.html:
<f:for each="{clients}" as="client">
<tr>
<td>
<f:for each="{feusers.clients}" as="singleClient">
<f:if condition="{client.identifier}=={singleClient.identifier}">
<span class="glyphicon glyphicon-star" aria-hidden="true"></span>
</f:if>
</f:for>
</td>
</tr>
</f:for>
Relation:
Here's the dump of my clients of feusers:
In my opinion, if a FrontendUser can have n clients, but a client can have only one FrontendUser, you should have used an 1:n relation.
Somehow, your code is very distracting: You iterate through {feusers.clients}, but you can only do that if feusers is in fact not an ObjectStorage of FrontendUsers, but a single FrontendUser. Maybe you could post more of your setup.
A suggest you add a transient property to your Clients model, e.g.
/**
* #var boolean
* #transient
*/
protected $isChildOfAuthenticatedUser`
For this property you create a getter. The getter compares if the currently authenticated user is identical to the parent user of the client. In the example I'm assuming that a Client only has one parent/FrontendUser:
/**
* #return boolean
*/
public function $isChildOfAuthenticatedUser {
if ((int)$GLOBALS['TSFE']->fe_user->user['uid'] === $this->frontendUser->getUid()) {
return TRUE;
} else {
return FALSE;
}
}
Then, in your Fluid template, you just need to check on that transient property:
<f:if condition="{client.isChildOfAuthenticatedUser}">
<span class="glyphicon glyphicon-star" aria-hidden="true"></span>
</f:if>
Keep in mind to add some additional check if the data is also to be displayed if no user is authenticated.