TYPO3: Set more than one storage pid for one extension - typo3

I builded an extension that has a 'details' table that holds details with a title and a description that be included inline to another object. Right now new details are stored in the same pid as the object, but I'd like to change that.
this question was answered by Merec and in the comments he points to a solution (add the column "pid" to your model, this is the first the model looks at) but asked to formulate a separate question for it ...
I took his suggestion but could not get it to work, so here is the separate question, in addition I would like to know how to get a value from the configuration to be used as pid for this.
update: René Pflamm pointed out that I should underline that I'm trying to set this Pid for saving in the backend, not in the frontend ... I basically recognized this destinction later on
my constants.ts :
plugin.tx_myext {
persistence {
# cat=plugin.tx_myext/storage/a; type=string; label=Default storage PID
defaultStoragePid =
# cat=plugin.tx_myext/storage/a; type=string; label=Details storage PID
detailsStoragePid =
}
}
my setup.ts
plugin.tx_myext {
persistence {
storagePid = {$plugin.tx_myext.persistence.defaultStoragePid}
detailPid = {$plugin.tx_myext.persistence.detailsStoragePid}
}
}

I am not sure if I understood you correctly but you can tell extbase to look in multiple pids for your records and state for each record where it should be stored:
plugin.tx_myext {
persistence {
storagePid = {$plugin.tx_myext.persistence.defaultStoragePid},{$plugin.tx_myext.persistence.detailStoragePid}
classes {
Vendor\MyExt\Domain\Model\Detail {
newRecordStoragePid = {$plugin.tx_myext.persistence.detailStoragePid}
}
}
}
}

Models inherits from TYPO3\CMS\Extbase\DomainObject\AbstractDomainObject which has getter and setter for $pid. If you set the field, all automation to set the field (i.e. newRecordStoragePid in typoscript) are not used.
With this, you can set all storage locations you want.
$myModel = $this->objectManager->create('Vendor\\Namespace\\Domain\\Model\\MyModel');
$myModel->setPid(4321);
Part from TYPO3\CMS\Extbase\DomainObject\AbstractDomainObject:
/**
* #var int The id of the page the record is "stored".
*/
protected $pid;
/**
* Setter for the pid.
*
* #param int|NULL $pid
* #return void
*/
public function setPid($pid)
{
if ($pid === null) {
$this->pid = null;
} else {
$this->pid = (int)$pid;
}
}
/**
* Getter for the pid.
*
* #return int The pid or NULL if none set yet.
*/
public function getPid()
{
if ($this->pid === null) {
return null;
} else {
return (int)$this->pid;
}
}

You can, when create elements in your extension, say the model which pid should be use.
In your TS:
plugin.tx_myext.settings {
detailPid = {$plugin.tx_myext.persistence.detailsStoragePid}
}
In your code above it can look like:
public function createDetailsAction(Detail $detail) {
$detail->setPid($this->settings['detailPid']);
$this->detailRepository->add($detail);
}

Related

Contentsliding is not stopping at sysfolder since TYPO3 v10 - How to solve?

The TSref entry for slide explains:
Up to Version 9 of TYPO3 the sliding stopped when reaching a folder.
Beginning with TYPO3 10 this is not longer the case. See
$cObj->checkPid_badDoktypeList.
Ok, this variable is still 255 (formerly directly, now via constant PageRepository::DOKTYPE_RECYCLER).
What exactly should I see there that will help me? Or better, how to get content sliding still working like before?
You have to extend the ContentObjectRenderer class and overwrite the getSlidePids method with your own extension.
In the boot function of ext_localconf.php:
$GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects'][\TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::class] = [
'className' => \YourVendor\YourExtensionKey\ContentObject\ContentObjectRenderer::class
];
Then you have to create your own "Classes/ContentObject/ContentObjectRenderer.php" with:
<?php
/*
* 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!
*/
namespace YourVendor\YourExtension\ContentObject;
use TYPO3\CMS\Core\Domain\Repository\PageRepository;
use TYPO3\CMS\Core\Utility\GeneralUtility;
class ContentObjectRenderer extends \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer
{
/**
* Returns all parents of the given PID (Page UID) list
*
* #param string $pidList A list of page Content-Element PIDs (Page UIDs) / stdWrap
* #param array $pidConf stdWrap array for the list
* #return string A list of PIDs
* #internal
*/
public function getSlidePids($pidList, $pidConf)
{
// todo: phpstan states that $pidConf always exists and is not nullable. At the moment, this is a false positive
// as null can be passed into this method via $pidConf. As soon as more strict types are used, this isset
// check must be replaced with a more appropriate check like empty or count.
$pidList = isset($pidConf) ? trim($this->stdWrap($pidList, $pidConf)) : trim($pidList);
if ($pidList === '') {
$pidList = 'this';
}
$tsfe = $this->getTypoScriptFrontendController();
$listArr = null;
if (trim($pidList)) {
$listArr = GeneralUtility::intExplode(',', str_replace('this', (string)$tsfe->contentPid, $pidList));
$listArr = $this->checkPidArray($listArr);
}
$pidList = [];
if (is_array($listArr) && !empty($listArr)) {
foreach ($listArr as $uid) {
$page = $tsfe->sys_page->getPage($uid);
if($page['doktype'] == PageRepository::DOKTYPE_SYSFOLDER)
break;
if (!$page['is_siteroot']) {
$pidList[] = $page['pid'];
}
}
}
return implode(',', $pidList);
}
}

How do I get uid of a File Reference Object in TYPO3?

I am trying to get a file through this code $f = $resourceFactory->getFileObject($uid); but the problem is the uid is a protected field in the file reference object, as seen below so I am not able to get the uid, and getUid() obviously wont work either.
So how can I get the uid of the file reference (FAL)
/**
* A file reference object (File Abstraction Layer)
*
* #api experimental! This class is experimental and subject to change!
*/
class FileReference extends
\TYPO3\CMS\Extbase\Domain\Model\AbstractFileFolder
{
/**
* Uid of the referenced sys_file. Needed for extbase to serialize the
* reference correctly.
*
* #var int
*/
protected $uidLocal;
/**
* #param \TYPO3\CMS\Core\Resource\ResourceInterface $originalResource
*/
public function setOriginalResource(\TYPO3\CMS\Core\Resource\ResourceInterface $originalResource)
{
$this->originalResource = $originalResource;
$this->uidLocal = (int)$originalResource->getOriginalFile()->getUid();
}
/**
* #return \TYPO3\CMS\Core\Resource\FileReference
*/
public function getOriginalResource()
{
if ($this->originalResource === null) {
$this->originalResource = \TYPO3\CMS\Core\Resource\ResourceFactory::getInstance()->getFileReferenceObject($this->getUid());
}
return $this->originalResource;
}
}
Given you have an instance of TYPO3\CMS\Extbase\Domain\Model\FileReference then you can use getOriginalResource() to get the wrapped TYPO3\CMS\Core\Resource\FileReference. If you need the referenced file, you can then use getOriginalFile(). Thus as a chained call:
$file = $fileReference->getOriginalResource()->getOriginalFile();
Notice that you don't have to use the ResourceFactory yourself in all of this, this is taken care of internally.
Work form me.
You can find or get file refernce uid using custom query.
In Controller :
$uid = $yourObject->getUid();
$fileReference = $this->yourRepository->getFileReferenceObject($uid);
In Repository
public function getFileRefernceHeaderLogo($uid){
$query = $this->createQuery();
$queryString = "SELECT *
FROM sys_file_reference
WHERE deleted = 0
AND hidden = 0
AND tablenames='your_table_name'
AND fieldname='your_field_name'
AND uid_foreign =".$uid;
$query->statement($queryString);
return $res = $query->execute(true);
}
In Controller
$fileRefUid = $fileReference[0]['uid'];
Here you can get uid of file reference table.It is long process.
You can also get sys_file table uid for getFileObject.like,
$sys_file_uid = $fileReference[0]['uid_local'];

Fluidtypo3: Use custom field as title of FCE

I don't want to use the default header in my FCE's, but only custom flux fields. In the backend list views my FCE's are shown as "[no title]" because the default header is not filled. This leads to much confusion for editors.
How can I define one of my custom flux fields to be used as title for the FCE in TYPO3 Backend list views etc.?
You can't just use a field from the flexform, because all fields from the FCE are stored in the same field in the database (pi_flexform).
What you can do is to render the content element title with a user function. It is registered with a line like this in the TCA config:
$GLOBALS['TCA']['tt_content']['ctrl']['label_userFunc'] = 'Vendor\\Extkey\\Utility\\ContentElementLabelRenderer->getContentElementTitle';
The user function itself could look like this:
<?php
namespace Vendor\Extkey\Utility;
/**
* This class renders a human readable title for FCEs,
* so one is able to find a content element by its headline.
*/
class ContentElementLabelRenderer implements \TYPO3\CMS\Core\SingletonInterface {
/**
* #var \TYPO3\CMS\Extbase\Service\FlexFormService
* #inject
*/
protected $flexFormService = null;
/**
* Returns the content element title for a given content element
*/
public function getContentElementTitle(&$params) {
if (null === $this->flexFormService) {
$this->flexFormService = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Extbase\\Service\\FlexFormService');
}
if (ctype_digit($params['row']['uid']) && 'fluidcontent_content' === $params['row']['CType']) {
// If this is a FCE, parse the flexform and template name and generate the
// title in a template specific way.
$row = $params['row'];
$additionalRowData = $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow('pi_flexform, tx_fed_fcefile', 'tt_content', 'uid = ' . $row['uid']);
$flexFormContent = $this->flexFormService->convertFlexFormContentToArray($additionalRowData['pi_flexform']);
$lastColonPosition = strrpos($additionalRowData['tx_fed_fcefile'], ':');
$contentElementType = (FALSE === $lastColonPosition) ? 'invalidtype' : substr($additionalRowData['tx_fed_fcefile'], $lastColonPosition + 1);
switch ($contentElementType) {
case 'Image.html':
$params['title'] = 'Image: "' . ($flexFormContent['title'] ?: $flexFormContent['subtitle']) . '"';
break;
default:
$params['title'] = 'Unknown content element type';
break;
}
}
else {
// If this is not a FCEm, print out "normal"
// title. Not the real thing, but comes pretty close, hopefully.
$params['title'] = $params['row']['header'] ?: ($params['row']['subheader'] ?: $params['row']['bodytext']);
}
}
}
This produces a maintainance problem though: Every time you add or change a content element, you have to update this file.

Symfony: exclude empty values from form save

I have a many to many relation between Product and Properties. I'm using embedRelation() in my Product form to edit a Product and it's Properties. Properties includes images which causes my issue. Every time I save the form the updated_at column is updated for file properties even when no file is uploaded.
Therefore, I want to exclude empty properties when saving my form.
I'm using Symfony 1.4 and Doctrine 1.2.
I'm thinking something like this in my ProductForm.class.php, but I need some input on how to make this work.
Thanks
class ProductForm extends BaseProductForm
{
public function configure()
{
unset($this['created_at'], $this['updated_at'], $this['id'], $this['slug']);
$this->embedRelation('ProductProperties');
}
public function saveEmbeddedForms($con = null, $forms = null)
{
if (null === $forms)
{
$properties = $this->getValue('ProductProperties');
$forms = $this->embeddedForms;
foreach($properties as $p)
{
// If property value is empty, unset from $forms['ProductProperties']
}
}
}
}
I ended up avoiding Symfony's forms and saving models instead of saving forms. It can be easier when playing with embedded forms. http://arialdomartini.wordpress.com/2011/04/01/how-to-kill-symfony%E2%80%99s-forms-and-live-well/
Solved it by checking if posted value is a file, and if both filename and value_delete is null I unset from the array. It might not be best practice, but it works for now.
Solution based on http://www.symfony-project.org/more-with-symfony/1_4/en/06-Advanced-Forms
class ProductPropertyValidatorSchema extends sfValidatorSchema
{
protected function configure($options = array(), $messages = array())
{
// N0thing to configure
}
protected function doClean($values)
{
$errorSchema = new sfValidatorErrorSchema($this);
foreach($values as $key => $value)
{
$errorSchemaLocal = new sfValidatorErrorSchema($this);
if(array_key_exists('value_delete', $values))
{
if(!$value && !$values['value_delete'])
{
unset($values[$key]);
}
}
// Some error for this embedded-form
if (count($errorSchemaLocal))
{
$errorSchema->addError($errorSchemaLocal, (string) $key);
}
}
// Throws the error for the main form
if (count($errorSchema))
{
throw new sfValidatorErrorSchema($this, $errorSchema);
}
return $values;
}
}

Zend Framework Bootstrap functions from where to get

I find many functions used in Bootstrap class in Zend Framework applications
like:
_initRoute()
_initLocale()
_initLayout()
.......
but i searched for it's reference but I fond nothing
Zend_Application_Bootstrap_BootstrapAbstract
Zend_Application_Bootstrap_Bootstrap
none of them contains any of these functions.
Where can i find the full reference of that functions?
Basically, these are resource plugins located in library/Zend/Application/Resource/. You may create also your custom ones.
See My detailed answer to very similar question which should fit also to this one.
Also, see BootstrapAbstract.php:
/**
* Get class resources (as resource/method pairs)
*
* Uses get_class_methods() by default, reflection on prior to 5.2.6,
* as a bug prevents the usage of get_class_methods() there.
*
* #return array
*/
public function getClassResources()
{
if (null === $this->_classResources) {
if (version_compare(PHP_VERSION, '5.2.6') === -1) {
$class = new ReflectionObject($this);
$classMethods = $class->getMethods();
$methodNames = array();
foreach ($classMethods as $method) {
$methodNames[] = $method->getName();
}
} else {
$methodNames = get_class_methods($this);
}
$this->_classResources = array();
foreach ($methodNames as $method) {
if (5 < strlen($method) && '_init' === substr($method, 0, 5)) {
$this->_classResources[strtolower(substr($method, 5))] = $method;
}
}
}
return $this->_classResources;
}
These function aren't defined anywhere, just in Bootstrap.php - these are called resource methods. At the bootstraping process, ZF automatically calls each function defined in Bootstrap.php which starts with _init.
Read more here: http://framework.zend.com/manual/en/zend.application.theory-of-operation.html