Follow Sebastiaan de Jonge on Twitter
Sebastiaan de Jonge

Blog

6Oct

Spoofing TCA columns

Posted on October 6, 2010 in Extension Development by Sebastiaan de Jonge

Sometimes, 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 
  1. $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 
  1. public function getMainFields_preProcess($s_table, &$a_row, $o_parent) {
  2.     if(!$this->isAllowedTable($s_table)) return;
  3.    
  4.     /**
  5.      * Now we know that everything is OK, we will create some fake fields and
  6.      * add them to the TCA
  7.      */
  8.     $a_additionalColumns = array(
  9.         'tx_advancedsitemaps_priority' => array (        
  10.             'exclude' => 0,        
  11.             'label' => 'LLL:EXT:advanced_sitemaps/locallang_db.xml:fields.priority',        
  12.             'config' => array (
  13.                 'type' => 'select',
  14.                 'items' => array(
  15.                     array('','0'),
  16.                     array('0.1','0.1'),
  17.                     array('0.2','0.2'),
  18.                     array('0.3','0.3'),
  19.                     array('0.4','0.4'),
  20.                     array('0.5','0.5'),
  21.                     array('0.6','0.6'),
  22.                     array('0.7','0.7'),
  23.                     array('0.8','0.8'),
  24.                     array('0.9','0.9'),
  25.                     array('1.0','1.0')
  26.                 ),
  27.             ),
  28.         ),
  29.         'tx_advancedsitemaps_changeFreq' => array (        
  30.             'exclude' => 0,        
  31.             'label' => 'LLL:EXT:advanced_sitemaps/locallang_db.xml:fields.changeFreq',        
  32.             'config' => array (
  33.                 'type' => 'select',
  34.                 'items' => array(
  35.                     array('',''),
  36.                     array('LLL:EXT:advanced_sitemaps/locallang_db.xml:fields.changeFreq.always','always'),
  37.                     array('LLL:EXT:advanced_sitemaps/locallang_db.xml:fields.changeFreq.hourly','hourly'),
  38.                     array('LLL:EXT:advanced_sitemaps/locallang_db.xml:fields.changeFreq.daily','daily'),
  39.                     array('LLL:EXT:advanced_sitemaps/locallang_db.xml:fields.changeFreq.weekly','weekly'),
  40.                     array('LLL:EXT:advanced_sitemaps/locallang_db.xml:fields.changeFreq.monthly','monthly'),
  41.                     array('LLL:EXT:advanced_sitemaps/locallang_db.xml:fields.changeFreq.yearly','yearly'),
  42.                     array('LLL:EXT:advanced_sitemaps/locallang_db.xml:fields.changeFreq.never','never'),
  43.                 ),
  44.                 'selected' => 'never'
  45.             ),
  46.         ),
  47.     );
  48.     t3lib_extMgm::addTCAcolumns($s_table,$a_additionalColumns,1);
  49.     t3lib_extMgm::addToAllTCAtypes($s_table,'--div--;LLL:EXT:advanced_sitemaps/locallang_db.xml:tabs.googleSitemaps,tx_advancedsitemaps_priority,tx_advancedsitemaps_changeFreq');
  50.    
  51.     // Add default values
  52.     if(intval($a_row['uid']) > 0) {
  53.         $a_row['tx_advancedsitemaps_priority'] = $this->checkRecordConfigurationValue($s_table,$a_row['uid'],'priority');
  54.         $a_row['tx_advancedsitemaps_changeFreq'] = $this->checkRecordConfigurationValue($s_table,$a_row['uid'],'changeFreq');
  55.     }
  56. }
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 
  1. $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 
  1. public function processDatamap_afterDatabaseOperations($s_status, $s_table, $i_uid, $a_fields, $o_parent) {
  2. if(!$this->isAllowedTable($s_table)) return;
  3.     $i_realRecordUid = (is_numeric($i_uid)) ? $i_uid : $o_parent->substNEWwithIDs[$i_uid];
  4.    
  5.     if(isset($this->a_fields['tx_advancedsitemaps_priority'])) {
  6.         if($this->checkRecordConfigurationValue($s_table,$i_realRecordUid,'priority')) {
  7.             $this->updateRecordConfigurationValue($s_table,$i_realRecordUid,'priority',$this->a_fields['tx_advancedsitemaps_priority']);
  8.         }
  9.         else {
  10.             $this->insertRecordConfigurationValue($s_table,$i_realRecordUid,'priority',$this->a_fields['tx_advancedsitemaps_priority']);
  11.         }
  12.     }
  13.     if(isset($this->a_fields['tx_advancedsitemaps_changeFreq'])) {
  14.         if($this->checkRecordConfigurationValue($s_table,$i_realRecordUid,'changeFreq')) {
  15.             $this->updateRecordConfigurationValue($s_table,$i_realRecordUid,'changeFreq',$this->a_fields['tx_advancedsitemaps_changeFreq']);
  16.         }
  17.         else {
  18.             $this->insertRecordConfigurationValue($s_table,$i_realRecordUid,'changeFreq',$this->a_fields['tx_advancedsitemaps_changeFreq']);
  19.         }
  20.     }
  21. }
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.

 

 

Comments (2)

  1. Gravatar: Steffen MüllerSteffen Mülleron October 6, 2010
    at 08:32
    Reply to this comment

    tagpack

    see also: tagpack extension
    http://forge.typo3.org/projects/show/extension-tagpack

  2. Gravatar: Sebastiaan de JongeSebastiaan de Jongeon October 6, 2010
    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

Got something to say?

 
Notify me when someone adds another comment to this post
 

Search

Categories

Tags

Archive

Blogroll

Syndicate