classVisible: 't3-form-field-container-inline-visible',
classCollapsed: 't3-form-field-container-inline-collapsed',
structureSeparator: '-',
+ flexFormSeparator: '---',
+ flexFormSubstitute: ':',
prependFormFieldNames: 'data',
noTitleString: '[No title]',
lockedAjaxMethod: {},
expandCollapseRecord: function(objectId, expandSingle, returnURL) {
var currentUid = this.parseObjectId('none', objectId, 1);
var objectPrefix = this.parseObjectId('full', objectId, 0, 1);
+ var escapedObjectId = this.escapeObjectId(objectId);
- var currentObject = TYPO3.jQuery('#' + objectId + '_div');
+ var currentObject = TYPO3.jQuery('#' + escapedObjectId + '_div');
// if content is not loaded yet, get it now from server
- if((TYPO3.jQuery('#' + objectId + '_fields') && $("irre-loading-indicator" + objectId)) || inline.isLoading) {
+ if((TYPO3.jQuery('#' + escapedObjectId + '_fields') && $("irre-loading-indicator" + objectId)) || inline.isLoading) {
return false;
} else if ($(objectId + '_fields') && $(objectId + '_fields').innerHTML.substr(0,16) == '<!--notloaded-->') {
inline.isLoading = true;
// add loading-indicator
- if (TYPO3.jQuery('#' + objectId + '_icon')) {
- TYPO3.jQuery('#' + objectId + '_icon').hide();
- TYPO3.jQuery('#' + objectId + '_iconcontainer').addClass('loading-indicator');
+ if (TYPO3.jQuery('#' + escapedObjectId + '_icon')) {
+ TYPO3.jQuery('#' + escapedObjectId + '_icon').hide();
+ TYPO3.jQuery('#' + escapedObjectId + '_iconcontainer').addClass('loading-indicator');
}
return this.getRecordDetails(objectId, returnURL);
}
},
toggleElement: function(objectId) {
- var jQueryObject = TYPO3.jQuery('#' + objectId + '_div');
+ var escapedObjectId = this.escapeObjectId(objectId);
+ var jQueryObject = TYPO3.jQuery('#' + escapedObjectId + '_div');
+
if (jQueryObject.hasClass(this.classCollapsed)) {
jQueryObject.removeClass(this.classCollapsed).addClass(this.classVisible);
- jQueryObject.find('#' + objectId + '_header .t3-icon-irre-collapsed').removeClass('t3-icon-irre-collapsed').addClass('t3-icon-irre-expanded');
+ jQueryObject.find('#' + escapedObjectId + '_header .t3-icon-irre-collapsed').removeClass('t3-icon-irre-collapsed').addClass('t3-icon-irre-expanded');
} else {
jQueryObject.removeClass(this.classVisible).addClass(this.classCollapsed);
- jQueryObject.find('#' + objectId + '_header .t3-icon-irre-expanded').addClass('t3-icon-irre-collapsed').removeClass('t3-icon-irre-expanded');
+ jQueryObject.find('#' + escapedObjectId + '_header .t3-icon-irre-expanded').addClass('t3-icon-irre-collapsed').removeClass('t3-icon-irre-expanded');
}
},
collapseAllRecords: function(objectId, objectPrefix, callingUid) {
if (formObj.length) {
// the uid of the calling object (last part in objectId)
- var recObjectId = '';
+ var recObjectId = '', escapedRecordObjectId;
var records = formObj[0].value.split(',');
for (var i=0; i<records.length; i++) {
recObjectId = objectPrefix + this.structureSeparator + records[i];
- var recordEntry = TYPO3.jQuery('#' + recObjectId);
+ escapedRecordObjectId = this.escapeObjectId(recObjectId);
+
+ var recordEntry = TYPO3.jQuery('#' + escapedRecordObjectId);
if (records[i] != callingUid && recordEntry.hasClass(this.classVisible)) {
- TYPO3.jQuery('#' + recObjectId + '_div').removeClass(this.classVisible).addClass(this.classCollapsed);
+ TYPO3.jQuery('#' + escapedRecordObjectId + '_div').removeClass(this.classVisible).addClass(this.classCollapsed);
if (this.isNewRecord(recObjectId)) {
this.updateExpandedCollapsedStateLocally(recObjectId, 0);
} else {
},
getRecordDetails: function(objectId, returnURL) {
- inline.makeAjaxCall('getRecordDetails', [inline.getNumberOfRTE(), objectId, returnURL], true);
+ var context = this.getContext(this.parseObjectId('full', objectId, 0, 1));
+ inline.makeAjaxCall('getRecordDetails', [inline.getNumberOfRTE(), objectId, returnURL], true, context);
return false;
},
createNewRecord: function(objectId, recordUid) {
if (this.isBelowMax(objectId)) {
+ var context = this.getContext(objectId);
if (recordUid) {
objectId += this.structureSeparator + recordUid;
}
- this.makeAjaxCall('createNewRecord', [this.getNumberOfRTE(), objectId], true);
+ this.makeAjaxCall('createNewRecord', [this.getNumberOfRTE(), objectId], true, context);
} else {
alert('There are no more relations possible at this moment!');
}
},
synchronizeLocalizeRecords: function(objectId, type) {
+ var context = this.getContext(objectId);
var parameters = [this.getNumberOfRTE(), objectId, type];
- this.makeAjaxCall('synchronizeLocalizeRecords', parameters, true);
+ this.makeAjaxCall('synchronizeLocalizeRecords', parameters, true, context);
},
setExpandedCollapsedState: function(objectId, expand, collapse) {
- this.makeAjaxCall('setExpandedCollapsedState', [objectId, expand, collapse]);
+ var context = this.getContext(objectId);
+ this.makeAjaxCall('setExpandedCollapsedState', [objectId, expand, collapse], false, context);
},
- makeAjaxCall: function(method, params, lock) {
+ makeAjaxCall: function(method, params, lock, context) {
var max, url='', urlParams='', options={};
if (method && params && params.length && this.lockAjaxMethod(method, lock)) {
url = TBE_EDITOR.getBackendPath() + 'ajax.php';
for (var i=0, max=params.length; i<max; i++) {
urlParams += '&ajax['+i+']='+params[i];
}
+ if (context) {
+ urlParams += '&ajax[context]=' + Object.toJSON(context);
+ }
options = {
method: 'post',
parameters: urlParams,
processAjaxResponse: function(method, xhr, json) {
var addTag=null, restart=false, processedCount=0, element=null, errorCatch=[], sourcesWaiting=[];
if (!json && xhr) {
- json = eval('('+xhr.responseText+')');
+ json = xhr.responseJSON;
}
// If there are elements the should be added to the <HEAD> tag (e.g. for RTEhtmlarea):
if (json.headData) {
importNewRecord: function(objectId) {
var selector = $(objectId+'_selector');
if (selector.selectedIndex != -1) {
+ var context = this.getContext(objectId);
var selectedValue = selector.options[selector.selectedIndex].value;
if (!this.data.unique || !this.data.unique[objectId]) {
selector.options[selector.selectedIndex].selected = false;
}
- this.makeAjaxCall('createNewRecord', [this.getNumberOfRTE(), objectId, selectedValue], true);
+ this.makeAjaxCall('createNewRecord', [this.getNumberOfRTE(), objectId, selectedValue], true, context);
}
return false;
},
// foreign_selector: used by element browser (type='group/db')
importElement: function(objectId, table, uid, type) {
- inline.makeAjaxCall('createNewRecord', [inline.getNumberOfRTE(), objectId, uid], true);
+ var context = this.getContext(objectId);
+ inline.makeAjaxCall('createNewRecord', [inline.getNumberOfRTE(), objectId, uid], true, context);
},
importElementMultiple: function(objectId, table, uidArray, type) {
Sortable.create(
objectId,
{
- format: /^[^_\-](?:[A-Za-z0-9\-\_]*)-(.*)_div$/,
+ format: /^[^_\-](?:[A-Za-z0-9\-\_\.]*)-(.*)_div$/,
onUpdate: inline.dragAndDropSorting,
tag: 'div',
handle: 'sortableHandle',
// Remove from TBE_EDITOR (required fields, required range, etc.):
if (TBE_EDITOR && TBE_EDITOR.removeElement) {
var removeStack = [];
+ // Iterate over all child records:
inlineRecords = Element.select(objectId+'_div', '.inlineRecord');
// Remove nested child records from TBE_EDITOR required/range checks:
for (i=inlineRecords.length-1; i>=0; i--) {
splitObjectId: function(objectId) {
objectId = objectId.substr(objectId.indexOf(this.structureSeparator)+1);
+ objectId = objectId.split(this.flexFormSeparator).join(this.flexFormSubstitute);
var parts = objectId.split(this.structureSeparator);
return parts;
if (wrap == 'full') {
elReturn = this.prependFormFieldNames+'['+parts.join('][')+']';
+ elReturn = elReturn.split(this.flexFormSubstitute).join('][');
} else if (wrap == 'parts') {
elReturn = '['+parts.join('][')+']';
+ elReturn = elReturn.split(this.flexFormSubstitute).join('][');
} else if (wrap == 'none') {
elReturn = parts.length > 1 ? parts : parts.join('');
}
if (wrap == 'full') {
elReturn = this.prependFormFieldNames+this.structureSeparator+parts.join(this.structureSeparator);
+ elReturn = elReturn.split(this.flexFormSubstitute).join(this.flexFormSeparator);
} else if (wrap == 'parts') {
elReturn = this.structureSeparator+parts.join(this.structureSeparator);
+ elReturn = elReturn.split(this.flexFormSubstitute).join(this.flexFormSeparator);
} else if (wrap == 'none') {
elReturn = parts.length > 1 ? parts : parts.join('');
}
if ($(element)) {
new Effect.Fade(element, { afterFinish: function() { Element.remove(element); } });
}
- }
+ },
+
+ getContext: function(objectId) {
+ var result = null;
+
+ if (objectId !== '' && typeof this.data.config[objectId] !== 'undefined' && typeof this.data.config[objectId].context !== 'undefined') {
+ result = this.data.config[objectId].context;
+ }
+
+ return result;
+ },
+
+ /**
+ * Escapes object identifiers to be used in jQuery.
+ *
+ * @param string objectId
+ * @return string
+ */
+ escapeObjectId: function(objectId) {
+ var escapedObjectId;
+ escapedObjectId = objectId.replace(/:/g, '\\:');
+ escapedObjectId = objectId.replace(/\./g, '\\.');
+ return escapedObjectId;
+ }
}
Object.extend(Array.prototype, {
class InlineElement {
const Structure_Separator = '-';
+ const FlexForm_Separator = '---';
+ const FlexForm_Substitute = ':';
const Disposal_AttributeName = 'Disposal_AttributeName';
const Disposal_AttributeId = 'Disposal_AttributeId';
/**
}
}
// Add the current inline job to the structure stack
- $this->pushStructure($table, $row['uid'], $field, $config);
+ $this->pushStructure($table, $row['uid'], $field, $config, $PA);
// e.g. data[<table>][<uid>][<field>]
$nameForm = $this->inlineNames['form'];
// e.g. data-<pid>-<table1>-<uid1>-<field1>-<table2>-<uid2>-<field2>
'top' => array(
'table' => $top['table'],
'uid' => $top['uid']
- )
+ ),
+ 'context' => array(
+ 'config' => $config,
+ 'hmac' => \TYPO3\CMS\Core\Utility\GeneralUtility::hmac(serialize($config)),
+ ),
);
// Set a hint for nested IRRE and tab elements:
$this->inlineData['nested'][$nameObject] = $this->fObj->getDynNestedStack(FALSE, $this->isAjaxCall);
$this->processAjaxRequestConstruct($ajaxArguments);
// Parse the DOM identifier (string), add the levels to the structure stack (array) and load the TCA config:
$this->parseStructureString($ajaxArguments[0], TRUE);
+ $this->injectAjaxConfiguration($ajaxArguments);
// Render content:
$ajaxObj->setContentFormat('jsonbody');
$ajaxObj->setContent(call_user_func_array(array(&$this, $ajaxMethod), $ajaxArguments));
}
}
+ /**
+ * Injects configuration via AJAX calls.
+ * The configuration is validated using HMAC to avoid hijacking.
+ *
+ * @param array $ajaxArguments
+ * @return void
+ */
+ protected function injectAjaxConfiguration(array $ajaxArguments) {
+ $level = $this->calculateStructureLevel(-1);
+
+ if (empty($ajaxArguments['context']) || $level === FALSE) {
+ return;
+ }
+
+ $current = &$this->inlineStructure['stable'][$level];
+ $context = json_decode($ajaxArguments['context'], TRUE);
+
+ if (\TYPO3\CMS\Core\Utility\GeneralUtility::hmac(serialize($context['config'])) !== $context['hmac']) {
+ return;
+ }
+
+ $current['config'] = $context['config'];
+ $current['localizationMode'] = \TYPO3\CMS\Backend\Utility\BackendUtility::getInlineLocalizationMode(
+ $current['table'],
+ $current['config']
+ );
+ }
+
/**
* Construct runtime environment for Inline Relational Record Editing.
* - creates an anoymous SC_alt_doc in $GLOBALS['SOBE']
* @param string $uid The uid of the record that embeds the inline data
* @param string $field The field name which this element is supposed to edit
* @param array $config The TCA-configuration of the inline field
+ * @param array $parameters The full parameter array (PA)
* @return void
* @todo Define visibility
*/
- public function pushStructure($table, $uid, $field = '', $config = array()) {
- $this->inlineStructure['stable'][] = array(
+ public function pushStructure($table, $uid, $field = '', $config = array(), array $parameters = array()) {
+ $structure = array(
'table' => $table,
'uid' => $uid,
'field' => $field,
'config' => $config,
- 'localizationMode' => \TYPO3\CMS\Backend\Utility\BackendUtility::getInlineLocalizationMode($table, $config)
+ 'localizationMode' => \TYPO3\CMS\Backend\Utility\BackendUtility::getInlineLocalizationMode($table, $config),
);
+
+ // Extract FlexForm parts (if any) from element name,
+ // e.g. array('vDEF', 'lDEF', 'FlexField', 'vDEF')
+ if (!empty($parameters['itemFormElName'])) {
+ $flexFormParts = $this->extractFlexFormParts($parameters['itemFormElName']);
+
+ if ($flexFormParts !== NULL) {
+ $structure['flexform'] = $flexFormParts;
+ }
+ }
+
+ $this->inlineStructure['stable'][] = $structure;
$this->updateStructureNames();
}
* @todo Define visibility
*/
public function popStructure() {
+ $popItem = NULL;
+
if (count($this->inlineStructure['stable'])) {
$popItem = array_pop($this->inlineStructure['stable']);
$this->updateStructureNames();
* @todo Define visibility
*/
public function getStructureItemName($levelData, $disposal = self::Disposal_AttributeId) {
+ $name = NULL;
+
if (is_array($levelData)) {
$parts = array($levelData['table'], $levelData['uid']);
- if (isset($levelData['field'])) {
+
+ if (!empty($levelData['field'])) {
$parts[] = $levelData['field'];
}
+
// Use in name attributes:
if ($disposal === self::Disposal_AttributeName) {
+ if (!empty($levelData['field']) && !empty($levelData['flexform']) && $this->getStructureLevel(-1) === $levelData) {
+ $parts[] = implode('][', $levelData['flexform']);
+ }
$name = '[' . implode('][', $parts) . ']';
+ // Use in object id attributes:
} else {
$name = implode(self::Structure_Separator, $parts);
+
+ if (!empty($levelData['field']) && !empty($levelData['flexform'])) {
+ array_unshift($levelData['flexform'], $name);
+ $name = implode(self::FlexForm_Separator, $levelData['flexform']);
+ }
}
}
+
return $name;
}
* @todo Define visibility
*/
public function getStructureLevel($level) {
+ $level = $this->calculateStructureLevel($level);
+
+ if ($level !== FALSE) {
+ return $this->inlineStructure['stable'][$level];
+ } else {
+ return FALSE;
+ }
+ }
+
+ /**
+ * Calculates structure level.
+ *
+ * @param integer $level Which level to return
+ * @return boolean|integer
+ */
+ protected function calculateStructureLevel($level) {
+ $result = FALSE;
+
$inlineStructureCount = count($this->inlineStructure['stable']);
if ($level < 0) {
$level = $inlineStructureCount + $level;
}
if ($level >= 0 && $level < $inlineStructureCount) {
- return $this->inlineStructure['stable'][$level];
- } else {
- return FALSE;
+ $result = $level;
}
+
+ return $result;
}
/**
public function parseStructureString($string, $loadConfig = TRUE) {
$unstable = array();
$vector = array('table', 'uid', 'field');
+
+ // Substitute FlexForm additon and make parsing a bit easier
+ $string = str_replace(self::FlexForm_Separator, self::FlexForm_Substitute, $string);
+ // The starting pattern of an object identifer (e.g. "data-<firstPidValue>-<anything>)
$pattern = '/^' . $this->prependNaming . self::Structure_Separator . '(.+?)' . self::Structure_Separator . '(.+)$/';
+
if (preg_match($pattern, $string, $match)) {
$this->inlineFirstPid = $match[1];
$parts = explode(self::Structure_Separator, $match[2]);
}
$unstable['localizationMode'] = \TYPO3\CMS\Backend\Utility\BackendUtility::getInlineLocalizationMode($unstable['table'], $unstable['config']);
}
+
+ // Extract FlexForm from field part (if any)
+ if (strpos($unstable['field'], self::FlexForm_Substitute) !== FALSE) {
+ $fieldParts = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(self::FlexForm_Substitute, $unstable['field']);
+ $unstable['field'] = array_shift($fieldParts);
+ // FlexForm parts start with data:
+ if (count($fieldParts) > 0 && $fieldParts[0] === 'data') {
+ $unstable['flexform'] = $fieldParts;
+ }
+ }
+
$this->inlineStructure['stable'][] = $unstable;
$unstable = array();
}
return $result;
}
+ /**
+ * Extracts FlexForm parts of a form element name like
+ * data[table][uid][field][sDEF][lDEF][FlexForm][vDEF]
+ *
+ * @param string $formElementName The form element name
+ * @return array|NULL
+ */
+ protected function extractFlexFormParts($formElementName) {
+ $flexFormParts = NULL;
+
+ $matches = array();
+ $prefix = preg_quote($this->fObj->prependFormFieldNames, '#');
+
+ if (preg_match('#^' . $prefix . '(?:\[[^]]+\]){3}(\[data\](?:\[[^]]+\]){4,})$#', $formElementName, $matches)) {
+ $flexFormParts = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(
+ '][',
+ trim($matches[1], '[]')
+ );
+ }
+
+ return $flexFormParts;
+ }
+
}
protected function setUp() {
// @todo Use $this->buildAccessibleProxy() if properties are protected
$this->fixture = new \TYPO3\CMS\Backend\Form\Element\InlineElement();
+ $this->fixture->fObj = new \TYPO3\CMS\Backend\Form\FormEngine();
}
/**
unset($this->fixture);
}
+ /**
+ * @param array $arguments
+ * @param array $expectedInlineStructure
+ * @param array $expectedInlineNames
+ * @dataProvider pushStructureFillsInlineStructureDataProvider
+ * @test
+ */
+ public function pushStructureFillsInlineStructure(array $arguments, array $expectedInlineStructure, array $expectedInlineNames) {
+ $this->fixture->inlineFirstPid = 'pageId';
+
+ call_user_func_array(array($this->fixture, 'pushStructure'), $arguments);
+
+ $this->assertEquals($expectedInlineStructure, $this->fixture->inlineStructure);
+ $this->assertEquals($expectedInlineNames, $this->fixture->inlineNames);
+ }
+
+ public function pushStructureFillsInlineStructureDataProvider() {
+ return array(
+ 'regular field' => array(
+ array(
+ 'parentTable',
+ 'parentUid',
+ 'parentField'
+ ),
+ array(
+ 'stable' => array(
+ array(
+ 'table' => 'parentTable',
+ 'uid' => 'parentUid',
+ 'field' => 'parentField',
+ 'config' => array(),
+ 'localizationMode' => FALSE,
+ ),
+ ),
+ ),
+ array(
+ 'form' => '[parentTable][parentUid][parentField]',
+ 'object' => 'data-pageId-parentTable-parentUid-parentField',
+ )
+ ),
+ 'flexform field' => array(
+ array(
+ 'parentTable',
+ 'parentUid',
+ 'parentField',
+ array(),
+ array(
+ 'itemFormElName' => 'data[parentTable][parentUid][parentField][data][sDEF][lDEF][grandParentFlexForm][vDEF]'
+ )
+ ),
+ array(
+ 'stable' => array(
+ array(
+ 'table' => 'parentTable',
+ 'uid' => 'parentUid',
+ 'field' => 'parentField',
+ 'config' => array(),
+ 'localizationMode' => FALSE,
+ 'flexform' => array(
+ 'data', 'sDEF', 'lDEF', 'grandParentFlexForm', 'vDEF',
+ ),
+ ),
+ ),
+ ),
+ array(
+ 'form' => '[parentTable][parentUid][parentField][data][sDEF][lDEF][grandParentFlexForm][vDEF]',
+ 'object' => 'data-pageId-parentTable-parentUid-parentField---data---sDEF---lDEF---grandParentFlexForm---vDEF',
+ )
+ ),
+ );
+ }
+
/**
* @param string $string
* @param array $expectedInlineStructure
+ * @param array $expectedInlineNames
* @dataProvider structureStringIsParsedDataProvider
* @test
*/
- public function structureStringIsParsed($string, array $expectedInlineStructure) {
+ public function structureStringIsParsed($string, array $expectedInlineStructure, array $expectedInlineNames) {
$this->fixture->parseStructureString($string, FALSE);
$this->assertEquals('pageId', $this->fixture->inlineFirstPid);
$this->assertEquals($expectedInlineStructure, $this->fixture->inlineStructure);
+ $this->assertEquals($expectedInlineNames, $this->fixture->inlineNames);
}
+ /**
+ * @return array
+ */
public function structureStringIsParsedDataProvider() {
return array(
'simple 1-level table structure' => array(
'table' => 'childTable',
),
),
+ array()
),
'simple 1-level table-uid structure' => array(
'data-pageId-childTable-childUid',
'uid' => 'childUid',
),
),
+ array()
),
'simple 1-level table-uid-field structure' => array(
'data-pageId-childTable-childUid-childField',
'field' => 'childField',
),
),
+ array(),
),
'simple 2-level table structure' => array(
'data-pageId-parentTable-parentUid-parentField-childTable',
'table' => 'childTable',
),
),
+ array(
+ 'form' => '[parentTable][parentUid][parentField]',
+ 'object' => 'data-pageId-parentTable-parentUid-parentField',
+ ),
),
'simple 2-level table-uid structure' => array(
'data-pageId-parentTable-parentUid-parentField-childTable-childUid',
'uid' => 'childUid',
),
),
+ array(
+ 'form' => '[parentTable][parentUid][parentField]',
+ 'object' => 'data-pageId-parentTable-parentUid-parentField',
+ ),
),
'simple 2-level table-uid-field structure' => array(
'data-pageId-parentTable-parentUid-parentField-childTable-childUid-childField',
'field' => 'childField',
),
),
+ array(
+ 'form' => '[parentTable][parentUid][parentField]',
+ 'object' => 'data-pageId-parentTable-parentUid-parentField',
+ ),
),
'simple 3-level table structure' => array(
'data-pageId-grandParentTable-grandParentUid-grandParentField-parentTable-parentUid-parentField-childTable',
'table' => 'childTable',
),
),
+ array(
+ 'form' => '[parentTable][parentUid][parentField]',
+ 'object' => 'data-pageId-grandParentTable-grandParentUid-grandParentField-parentTable-parentUid-parentField',
+ ),
),
'simple 3-level table-uid structure' => array(
'data-pageId-grandParentTable-grandParentUid-grandParentField-parentTable-parentUid-parentField-childTable-childUid',
'uid' => 'childUid',
),
),
+ array(
+ 'form' => '[parentTable][parentUid][parentField]',
+ 'object' => 'data-pageId-grandParentTable-grandParentUid-grandParentField-parentTable-parentUid-parentField',
+ ),
),
'simple 3-level table-uid-field structure' => array(
'data-pageId-grandParentTable-grandParentUid-grandParentField-parentTable-parentUid-parentField-childTable-childUid-childField',
'field' => 'childField',
),
),
+ array(
+ 'form' => '[parentTable][parentUid][parentField]',
+ 'object' => 'data-pageId-grandParentTable-grandParentUid-grandParentField-parentTable-parentUid-parentField',
+ ),
+ ),
+ 'flexform 3-level table-uid structure' => array(
+ 'data-pageId-grandParentTable-grandParentUid-grandParentField---data---sDEF---lDEF---grandParentFlexForm---vDEF-parentTable-parentUid-parentField-childTable-childUid',
+ array(
+ 'stable' => array(
+ array(
+ 'table' => 'grandParentTable',
+ 'uid' => 'grandParentUid',
+ 'field' => 'grandParentField',
+ 'flexform' => array(
+ 'data', 'sDEF', 'lDEF', 'grandParentFlexForm', 'vDEF',
+ ),
+ ),
+ array(
+ 'table' => 'parentTable',
+ 'uid' => 'parentUid',
+ 'field' => 'parentField',
+ ),
+ ),
+ 'unstable' => array(
+ 'table' => 'childTable',
+ 'uid' => 'childUid',
+ ),
+ ),
+ array(
+ 'form' => '[parentTable][parentUid][parentField]',
+ 'object' => 'data-pageId-grandParentTable-grandParentUid-grandParentField---data---sDEF---lDEF---grandParentFlexForm---vDEF-parentTable-parentUid-parentField',
+ ),
),
);
}
* @param string $field Field name. Must NOT be set if the call is for a flexform field (since flexforms are not allowed within flexforms).
* @param [type] $uploadedFiles
* @param [type] $tscPID
+ * @param array $additionalData Additional data to be forwarded to sub-processors
* @return array Returns the evaluated $value as key "value" in this array.
* @todo Define visibility
*/
- public function checkValue_SW($res, $value, $tcaFieldConf, $table, $id, $curValue, $status, $realPid, $recFID, $field, $uploadedFiles, $tscPID) {
+ public function checkValue_SW($res, $value, $tcaFieldConf, $table, $id, $curValue, $status, $realPid, $recFID, $field, $uploadedFiles, $tscPID, array $additionalData = NULL) {
$PP = array($table, $id, $curValue, $status, $realPid, $recFID, $tscPID);
switch ($tcaFieldConf['type']) {
case 'text':
$res = $this->checkValue_group_select($res, $value, $tcaFieldConf, $PP, $uploadedFiles, $field);
break;
case 'inline':
- $res = $this->checkValue_inline($res, $value, $tcaFieldConf, $PP, $field);
+ $res = $this->checkValue_inline($res, $value, $tcaFieldConf, $PP, $field, $additionalData);
break;
case 'flex':
// FlexForms are only allowed for real fields.
*/
public function checkValue_flex($res, $value, $tcaFieldConf, $PP, $uploadedFiles, $field) {
list($table, $id, $curValue, $status, $realPid, $recFID) = $PP;
+
if (is_array($value)) {
// This value is necessary for flex form processing to happen on flexform fields in page records when they are copied.
// The problem is, that when copying a page, flexfrom XML comes along in the array for the new record - but since $this->checkValue_currentRecord does not have a uid or pid for that sake, the t3lib_BEfunc::getFlexFormDS() function returns no good DS. For new records we do know the expected PID so therefore we send that with this special parameter. Only active when larger than zero.
// Passthrough...:
$res['value'] = $value;
}
+
return $res;
}
* @param array $tcaFieldConf Field configuration from TCA
* @param array $PP Additional parameters in a numeric array: $table,$id,$curValue,$status,$realPid,$recFID
* @param string $field Field name
+ * @param array $additionalData Additional data to be forwarded to sub-processors
* @return array Modified $res array
* @todo Define visibility
*/
- public function checkValue_inline($res, $value, $tcaFieldConf, $PP, $field) {
+ public function checkValue_inline($res, $value, $tcaFieldConf, $PP, $field, array $additionalData = NULL) {
list($table, $id, $curValue, $status, $realPid, $recFID) = $PP;
if (!$tcaFieldConf['foreign_table']) {
// Fatal error, inline fields should always have a foreign_table defined
$this->addNewValuesToRemapStackChildIds($valueArray);
$this->remapStack[] = array(
'func' => 'checkValue_inline_processDBdata',
- 'args' => array($valueArray, $tcaFieldConf, $id, $status, $table, $field),
+ 'args' => array($valueArray, $tcaFieldConf, $id, $status, $table, $field, $additionalData),
'pos' => array('valueArray' => 0, 'tcaFieldConf' => 1, 'id' => 2, 'table' => 4),
- 'field' => $field
+ 'additionalData' => $additionalData,
+ 'field' => $field,
);
unset($res['value']);
} elseif ($value || \TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($id)) {
- $res['value'] = $this->checkValue_inline_processDBdata($valueArray, $tcaFieldConf, $id, $status, $table, $field);
+ $res['value'] = $this->checkValue_inline_processDBdata($valueArray, $tcaFieldConf, $id, $status, $table, $field, $additionalData);
}
return $res;
}
} else {
// Default
list($CVtable, $CVid, $CVcurValue, $CVstatus, $CVrealPid, $CVrecFID, $CVtscPID) = $pParams;
- $res = $this->checkValue_SW(array(), $dataValues[$key][$vKey], $dsConf['TCEforms']['config'], $CVtable, $CVid, $dataValues_current[$key][$vKey], $CVstatus, $CVrealPid, $CVrecFID, '', $uploadedFiles[$key][$vKey], array(), $CVtscPID);
+
+ $additionalData = array(
+ 'flexFormId' => $CVrecFID,
+ 'flexFormPath' => trim(rtrim($structurePath, '/') . '/' . $key . '/' . $vKey, '/'),
+ );
+
+ $res = $this->checkValue_SW(array(), $dataValues[$key][$vKey], $dsConf['TCEforms']['config'], $CVtable, $CVid, $dataValues_current[$key][$vKey], $CVstatus, $CVrealPid, $CVrecFID, '', $uploadedFiles[$key][$vKey], $CVtscPID, $additionalData);
// Look for RTE transformation of field:
if ($dataValues[$key]['_TRANSFORM_' . $vKey] == 'RTE' && !$this->dontProcessTransformations) {
// Unsetting trigger field - we absolutely don't want that into the data storage!
* @param string $status Status string ('update' or 'new')
* @param string $table Table name, needs to be passed to t3lib_loadDBGroup
* @param string $field The current field the values are modified for
+ * @param array $additionalData Additional data to be forwarded to sub-processors
* @return string Modified values
*/
- protected function checkValue_inline_processDBdata($valueArray, $tcaFieldConf, $id, $status, $table, $field) {
+ protected function checkValue_inline_processDBdata($valueArray, $tcaFieldConf, $id, $status, $table, $field, array $additionalData = NULL) {
$newValue = '';
$foreignTable = $tcaFieldConf['foreign_table'];
$valueArray = $this->applyFiltersToValues($tcaFieldConf, $valueArray);
public function processRemapStack() {
// Processes the remap stack:
if (is_array($this->remapStack)) {
+ $remapFlexForms = array();
+
foreach ($this->remapStack as $remapAction) {
// If no position index for the arguments was set, skip this remap action:
if (!is_array($remapAction['pos'])) {
$table = $remapAction['args'][$remapAction['pos']['table']];
$valueArray = $remapAction['args'][$remapAction['pos']['valueArray']];
$tcaFieldConf = $remapAction['args'][$remapAction['pos']['tcaFieldConf']];
+ $additionalData = $remapAction['additionalData'];
// The record is new and has one or more new ids (in case of versioning/workspaces):
if (strpos($id, 'NEW') !== FALSE) {
// Replace NEW...-ID with real uid:
$newValue = implode(',', $this->checkValue_checkMax($tcaFieldConf, $newValue));
}
// Update in database (list of children (csv) or number of relations (foreign_field)):
- $this->updateDB($table, $id, array($field => $newValue));
+ if (!empty($field)) {
+ $this->updateDB($table, $id, array($field => $newValue));
+ // Collect data to update FlexForms
+ } elseif (!empty($additionalData['flexFormId']) && !empty($additionalData['flexFormPath'])) {
+ $flexFormId = $additionalData['flexFormId'];
+ $flexFormPath = $additionalData['flexFormPath'];
+
+ if (!isset($remapFlexForms[$flexFormId])) {
+ $remapFlexForms[$flexFormId] = array();
+ }
+
+ $remapFlexForms[$flexFormId][$flexFormPath] = $newValue;
+ }
// Process waiting Hook: processDatamap_afterDatabaseOperations:
if (isset($this->remapStackRecords[$table][$rawId]['processDatamap_afterDatabaseOperations'])) {
$hookArgs = $this->remapStackRecords[$table][$rawId]['processDatamap_afterDatabaseOperations'];
}
}
}
+
+ if ($remapFlexForms) {
+ foreach ($remapFlexForms as $flexFormId => $modifications) {
+ $this->updateFlexFormData($flexFormId, $modifications);
+ }
+ }
}
// Processes the remap stack actions:
if ($this->remapStackActions) {
$this->remapStackRefIndex = array();
}
+ /**
+ * Updates FlexForm data.
+ *
+ * @param string $flexFormId, e.g. <table>:<uid>:<field>
+ * @param array $modifications Modifications with paths and values (e.g. 'sDEF/lDEV/field/vDEF' => 'TYPO3')
+ * @return void
+ */
+ protected function updateFlexFormData($flexFormId, array $modifications) {
+ list ($table, $uid, $field) = explode(':', $flexFormId, 3);
+ $record = $this->recordInfo($table, $uid, '*');
+
+ if (!$table || !$uid || !$field || !is_array($record)) {
+ return;
+ }
+
+ \TYPO3\CMS\Backend\Utility\BackendUtility::workspaceOL($table, $record);
+
+ // Get current data structure and value array:
+ $valueStructure = \TYPO3\CMS\Core\Utility\GeneralUtility::xml2array($record[$field]);
+
+ // Do recursive processing of the XML data:
+ foreach ($modifications as $path => $value) {
+ $valueStructure['data'] = \TYPO3\CMS\Core\Utility\ArrayUtility::setValueByPath(
+ $valueStructure['data'], $path, $value
+ );
+ }
+
+ if (is_array($valueStructure['data'])) {
+ // The return value should be compiled back into XML
+ $values = array(
+ $field => $this->checkValue_flexArray2Xml($valueStructure, TRUE),
+ );
+
+ $this->updateDB($table, $uid, $values);
+ }
+ }
+
/**
* Triggers a remap action for a specific record.
*