Internal Link News: excludeAlreadyDisplayedNews - typo3

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>

Related

TYPO3 v11 persistence.storagePid is ignored

I have an TYPO3 Extbase extension with TYPO3 11.
My problem is, that every item is shown.
In my plugin there is a Flexform Field for the storagePid. But the plugin completely ignored this settings and every time lists all items.
How can I tell my extension that it should only load items from the selected page?
I have tried to set this:
public function getItems()
{
$table = 'TABLE';
$query = $this->itemRepository->createQuery();
$query->getQuerySettings()->setRespectStoragePage(true);
return $query->execute();
}
But that doesn't help.
This doesn't change anything in the repository
public function initializeObject() {
/** #var Typo3QuerySettings $querySettings */
$querySettings = GeneralUtility::makeInstance(Typo3QuerySettings::class);
$querySettings->setRespectStoragePage(TRUE);
$this->setDefaultQuerySettings($querySettings);
}
I am assuming you mean what you set in the default field tt_content.pages and tt_content.recursive.
Here is an example how to get the page IDs from the fields: https://github.com/TYPO3/typo3/blob/11.5/typo3/sysext/felogin/Classes/Controller/AbstractLoginFormController.php#L35-L50
And then you would set that for your Extbase query:
$querySettings = $this->myRepository->createQuery()->getQuerySettings();
$querySettings->setStoragePageIds(...);
Background:
Here is a great write-up how naming a Flexform field persistence.storagePid would give you that, too https://www.derhansen.de/2016/02/how-extbase-determines-storagepid.html

Typo3 9.5 prefill form field with get parameter

I use TYPO3 system extension "form" and want to prefill an input field with a GET parameter.
This TYPO3 8.7. Form prefill input field is working, but only is no_cache=1. Is there another solution without deactivate the whole cache?
Thanks
david
Yes, you can but you need to create HOOK.
This is described in the documentation
For example, the HOOK
/**
* #param \TYPO3\CMS\Form\Domain\Model\Renderable\RenderableInterface $renderable
* #return void
*/
public function initializeFormElement(\TYPO3\CMS\Form\Domain\Model\Renderable\RenderableInterface $renderable)
{
if ($renderable->getUniqueIdentifier() === 'contactForm-text-1') {
$renderable->setDefaultValue('foo');
}
}
And the connect the hook
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['initializeFormElement'][<useATimestampAsKeyPlease>]
= \VENDOR\YourNamespace\YourClass::class;
Please, read the documentation for "Form framework".
I did it and get results what I need.
You can disable the cache of the content column of your form page, for example:
lib.content = COA
lib.content{
10 < styles.content.get
}
[page["uid"] == 512]
lib.content = COA_INT
[global]
Thanks TYPO3UA for you answer. But you should use the hook 'afterBuildingFinished' because the 'initializeFormElement' hook is executed BEFORE the properties from the form definition are set in the form element. So the default values from the form definition (even it is an empty string) will override the values set in the initializeFormElement' hook.
See: https://forge.typo3.org/issues/82615
So this works for setting the default value of an form element:
/**
* #param \TYPO3\CMS\Form\Domain\Model\Renderable\RenderableInterface $renderable
* #return void
*/
public function afterBuildingFinished(\TYPO3\CMS\Form\Domain\Model\Renderable\RenderableInterface $renderable)
{
if (method_exists($renderable, 'getUniqueIdentifier') && $renderable->getUniqueIdentifier() === 'contactForm-text-1') {
$renderable->setDefaultValue('Value');
}
}
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['afterBuildingFinished'][<useATimestampAsKeyPlease>]
= \VENDOR\YourNamespace\YourClass::class;

TYPO3 Fluid resource.record.fal

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>

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!