Blog
6OctSpoofing TCA columns
Posted on October 6, 2010 in Extension Development bySometimes, the normal approach for adding extra fields to a table will just not do. For example, if you don't really know what the other table is going to be. I came across this challenge some days ago. I was working on my sitemaps extension and wanted to have some nice options to use inside Google Sitemaps. To keep it flexible I didn't want to predefine the tables. So how do we add fields to a table, without actually adding them to a table?
What we will do
Actually it sounds harder than it is. Basically we need to extend the TCA of the tables we want to extend, but only in the backend. After this we need to the process the record storage inside the database. We will store the new values in a separate table to maintain maximum flexibility.
'Spoofing' TCA columns
First of all we will need to add some imaginary fields to the TCA columns of our desired table. We can do this nifty trick by using the following hook. (I'm using the code from the advanced sitemaps extension as example)
Utilizing the getMainFieldsClass hook 
- $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tceforms.php']['getMainFieldsClass'][] = 'EXT:advanced_sitemaps/class.tx_advancedsitemaps_recordConfigurations.php:&tx_advancedsitemaps_recordConfigurations';
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tceforms.php']['getMainFieldsClass'][] = 'EXT:advanced_sitemaps/class.tx_advancedsitemaps_recordConfigurations.php:&tx_advancedsitemaps_recordConfigurations';
This will call the following function. And basically you can modify the TCA like you would do when really extending a table with an extension. Just take a look at the code.
Spoofing TCA columns 
- public function getMainFields_preProcess($s_table, &$a_row, $o_parent) {
- if(!$this->isAllowedTable($s_table)) return;
- /**
- * Now we know that everything is OK, we will create some fake fields and
- * add them to the TCA
- */
- $a_additionalColumns = array(
- 'tx_advancedsitemaps_priority' => array (
- 'exclude' => 0,
- 'label' => 'LLL:EXT:advanced_sitemaps/locallang_db.xml:fields.priority',
- 'config' => array (
- 'type' => 'select',
- 'items' => array(
- array('','0'),
- array('0.1','0.1'),
- array('0.2','0.2'),
- array('0.3','0.3'),
- array('0.4','0.4'),
- array('0.5','0.5'),
- array('0.6','0.6'),
- array('0.7','0.7'),
- array('0.8','0.8'),
- array('0.9','0.9'),
- array('1.0','1.0')
- ),
- ),
- ),
- 'tx_advancedsitemaps_changeFreq' => array (
- 'exclude' => 0,
- 'label' => 'LLL:EXT:advanced_sitemaps/locallang_db.xml:fields.changeFreq',
- 'config' => array (
- 'type' => 'select',
- 'items' => array(
- array('',''),
- array('LLL:EXT:advanced_sitemaps/locallang_db.xml:fields.changeFreq.always','always'),
- array('LLL:EXT:advanced_sitemaps/locallang_db.xml:fields.changeFreq.hourly','hourly'),
- array('LLL:EXT:advanced_sitemaps/locallang_db.xml:fields.changeFreq.daily','daily'),
- array('LLL:EXT:advanced_sitemaps/locallang_db.xml:fields.changeFreq.weekly','weekly'),
- array('LLL:EXT:advanced_sitemaps/locallang_db.xml:fields.changeFreq.monthly','monthly'),
- array('LLL:EXT:advanced_sitemaps/locallang_db.xml:fields.changeFreq.yearly','yearly'),
- array('LLL:EXT:advanced_sitemaps/locallang_db.xml:fields.changeFreq.never','never'),
- ),
- 'selected' => 'never'
- ),
- ),
- );
- t3lib_extMgm::addTCAcolumns($s_table,$a_additionalColumns,1);
- t3lib_extMgm::addToAllTCAtypes($s_table,'--div--;LLL:EXT:advanced_sitemaps/locallang_db.xml:tabs.googleSitemaps,tx_advancedsitemaps_priority,tx_advancedsitemaps_changeFreq');
- // Add default values
- if(intval($a_row['uid']) > 0) {
- $a_row['tx_advancedsitemaps_priority'] = $this->checkRecordConfigurationValue($s_table,$a_row['uid'],'priority');
- $a_row['tx_advancedsitemaps_changeFreq'] = $this->checkRecordConfigurationValue($s_table,$a_row['uid'],'changeFreq');
- }
- }
public function getMainFields_preProcess($s_table, &$a_row, $o_parent) {
if(!$this->isAllowedTable($s_table)) return;
/**
* Now we know that everything is OK, we will create some fake fields and
* add them to the TCA
*/
$a_additionalColumns = array(
'tx_advancedsitemaps_priority' => array (
'exclude' => 0,
'label' => 'LLL:EXT:advanced_sitemaps/locallang_db.xml:fields.priority',
'config' => array (
'type' => 'select',
'items' => array(
array('','0'),
array('0.1','0.1'),
array('0.2','0.2'),
array('0.3','0.3'),
array('0.4','0.4'),
array('0.5','0.5'),
array('0.6','0.6'),
array('0.7','0.7'),
array('0.8','0.8'),
array('0.9','0.9'),
array('1.0','1.0')
),
),
),
'tx_advancedsitemaps_changeFreq' => array (
'exclude' => 0,
'label' => 'LLL:EXT:advanced_sitemaps/locallang_db.xml:fields.changeFreq',
'config' => array (
'type' => 'select',
'items' => array(
array('',''),
array('LLL:EXT:advanced_sitemaps/locallang_db.xml:fields.changeFreq.always','always'),
array('LLL:EXT:advanced_sitemaps/locallang_db.xml:fields.changeFreq.hourly','hourly'),
array('LLL:EXT:advanced_sitemaps/locallang_db.xml:fields.changeFreq.daily','daily'),
array('LLL:EXT:advanced_sitemaps/locallang_db.xml:fields.changeFreq.weekly','weekly'),
array('LLL:EXT:advanced_sitemaps/locallang_db.xml:fields.changeFreq.monthly','monthly'),
array('LLL:EXT:advanced_sitemaps/locallang_db.xml:fields.changeFreq.yearly','yearly'),
array('LLL:EXT:advanced_sitemaps/locallang_db.xml:fields.changeFreq.never','never'),
),
'selected' => 'never'
),
),
);
t3lib_extMgm::addTCAcolumns($s_table,$a_additionalColumns,1);
t3lib_extMgm::addToAllTCAtypes($s_table,'--div--;LLL:EXT:advanced_sitemaps/locallang_db.xml:tabs.googleSitemaps,tx_advancedsitemaps_priority,tx_advancedsitemaps_changeFreq');
// Add default values
if(intval($a_row['uid']) > 0) {
$a_row['tx_advancedsitemaps_priority'] = $this->checkRecordConfigurationValue($s_table,$a_row['uid'],'priority');
$a_row['tx_advancedsitemaps_changeFreq'] = $this->checkRecordConfigurationValue($s_table,$a_row['uid'],'changeFreq');
}
}
It looks like a big heap of code, but it really isn't. Just the configuration of the two extra columns. I have some extra functions inside my class that do some additional checking and data grabbing. For example, I check which tables are allowed because I don't want these fields to be rendered for every record. In order to add the stored values, I just add them to the actual data row since this is passed on by reference. My code gives me the following result.

Storing the data
Now of course, by default these fields do nothing else but look pretty. In order to actually store and update the data we will need to use an additional hook. It's one I actually use quite often, and I have to say I'm pretty fond of it.
Utilizing the processDatamapClass hook 
- $GLOBALS ['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processDatamapClass'][] = 'EXT:advanced_sitemaps/class.tx_advancedsitemaps_recordConfigurations.php:&tx_advancedsitemaps_recordConfigurations';
$GLOBALS ['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processDatamapClass'][] = 'EXT:advanced_sitemaps/class.tx_advancedsitemaps_recordConfigurations.php:&tx_advancedsitemaps_recordConfigurations';
It's actually a collection of hooks, that allow you to intervene with the entire storage process in several places. In my case I only need one of the hook functions it offers. For that function I've created the following code.
Processing and storing data 
- public function processDatamap_afterDatabaseOperations($s_status, $s_table, $i_uid, $a_fields, $o_parent) {
- if(!$this->isAllowedTable($s_table)) return;
- $i_realRecordUid = (is_numeric($i_uid)) ? $i_uid : $o_parent->substNEWwithIDs[$i_uid];
- if(isset($this->a_fields['tx_advancedsitemaps_priority'])) {
- if($this->checkRecordConfigurationValue($s_table,$i_realRecordUid,'priority')) {
- $this->updateRecordConfigurationValue($s_table,$i_realRecordUid,'priority',$this->a_fields['tx_advancedsitemaps_priority']);
- }
- else {
- $this->insertRecordConfigurationValue($s_table,$i_realRecordUid,'priority',$this->a_fields['tx_advancedsitemaps_priority']);
- }
- }
- if(isset($this->a_fields['tx_advancedsitemaps_changeFreq'])) {
- if($this->checkRecordConfigurationValue($s_table,$i_realRecordUid,'changeFreq')) {
- $this->updateRecordConfigurationValue($s_table,$i_realRecordUid,'changeFreq',$this->a_fields['tx_advancedsitemaps_changeFreq']);
- }
- else {
- $this->insertRecordConfigurationValue($s_table,$i_realRecordUid,'changeFreq',$this->a_fields['tx_advancedsitemaps_changeFreq']);
- }
- }
- }
public function processDatamap_afterDatabaseOperations($s_status, $s_table, $i_uid, $a_fields, $o_parent) {
if(!$this->isAllowedTable($s_table)) return;
$i_realRecordUid = (is_numeric($i_uid)) ? $i_uid : $o_parent->substNEWwithIDs[$i_uid];
if(isset($this->a_fields['tx_advancedsitemaps_priority'])) {
if($this->checkRecordConfigurationValue($s_table,$i_realRecordUid,'priority')) {
$this->updateRecordConfigurationValue($s_table,$i_realRecordUid,'priority',$this->a_fields['tx_advancedsitemaps_priority']);
}
else {
$this->insertRecordConfigurationValue($s_table,$i_realRecordUid,'priority',$this->a_fields['tx_advancedsitemaps_priority']);
}
}
if(isset($this->a_fields['tx_advancedsitemaps_changeFreq'])) {
if($this->checkRecordConfigurationValue($s_table,$i_realRecordUid,'changeFreq')) {
$this->updateRecordConfigurationValue($s_table,$i_realRecordUid,'changeFreq',$this->a_fields['tx_advancedsitemaps_changeFreq']);
}
else {
$this->insertRecordConfigurationValue($s_table,$i_realRecordUid,'changeFreq',$this->a_fields['tx_advancedsitemaps_changeFreq']);
}
}
}
Again I perform the check to see if the current table is allowed to have these extra fields, followed by a check to see if any of the fields are actually set. The 'checkRecordConfigurationValue' function checks to see if there is a record of this value, so we know wether we need to insert or update. The other 2 functions are just wrappers that store the actual data.
If you look closely you will see a variable called $i_realRecordUid. In case you are wondering what that's for, I'll explain. When creating a new record the $i_uid field is not actually the record's uid field inside the database. Since other queries might be executed after saving this record, using $GLOBALS['TYPO3_DB']->sql_insert_id() is also not a really great option. But luckily the TYPO3 core is shipped with something to lookup this 'NEW…ID' value, it's stored in the TCE object that is passed on by the function as well. It's simply an array with all the newly created record uids, and the keys are just the 'NEW…ID' values. This is a nice feature but tricky to find out about if you don't know where to look.
Advanced Sitemaps
I'm planning to release a new version of my sitemaps extension later this week, until then you can find this code inside the SVN repository on TYPO3 Forge.
- Advanced Sitemaps (repository)
- Advanced Sitemaps (class.tx_advancedsitemaps_recordConfigurations.php, HEAD revision)



at 08:32
tagpack
see also: tagpack extension
http://forge.typo3.org/projects/show/extension-tagpack
at 08:50
RE: tagpack
Nice addon Steffen, thanks. To be honest I had not heard of the extension, sounds like a nice all-round way to do this. Nevertheless, I guess it won't hurt to explain the process.
Cheers