Practically all important scripts have their main code encapsulated in a class (typically named SC_[scriptname] and instantiated as the global object $SOBE) and almost all library classes used in TYPO3 - both frontend and backend - can be extended by user defined classes. Extension of TYPO3 PHP classes may also be referred to as an "XCLASS extension".
Extending TYPO3s PHP classes is recommended mostly for special needs in individual projects. This is due to the limitation that a class can only be extended once. Thus, if many extensions try to extend the same class, only one of them will succeed and in turn the others will not function correctly.
So, extending classes is a great option for individual projects where special "hacks" are needed. But generally it is a poor way of programming TYPO3 extensions in which case you should look for a system hook or request a system hook to be made for your purpose if generally meaningful.
Configuring user-classes works like this:
In (ext_)localconf.php you configure for either frontend or backend that you wish to include a file with the extension of the class. This inclusion is usually done in the end of the class-file itself based on a lookup in TYPO3_CONF_VARS.
Whenever the class is instantiated as an object, the sourcecode checks if a user-extension of that class exists. If so, then that class (or an extension of the extended class) is instantiated and not the “normal” (parent) class. Getting the correct instance of a class is done by using the function t3lib_div::makeInstance() instead of "new ..." when an object is created.
Say you wish to make an addition to the stdWrap method found in the class “tslib_cObj” (found in the class file tslib/class.tslib_content.php).
The first thing to do is to create the extension class. So you create a file in the typo3conf/ directory named “class.ux_tslib_content.php”. “ux” is a prefix meaning “user-extension”. This file may look like this:
<?php/** * User-Extension of tslib_cObj class.** @author Kasper Skårhøj <kasper@typo3.com>*/class ux_tslib_cObj extends tslib_cObj { function stdWrap($content,$conf) { // Call the real stdWrap function in the parent class: $content = parent::stdWrap($content,$conf); // Process according to my user-defined property: if ($conf["userDefined_wrapInRed"]) { $content='<font color="red">'.$content.'</font>'; } return $content; }}?>The next thing is to configure TYPO3 to include this class file as well after the original file tslib/class.tslib_content.php:
$TYPO3_CONF_VARS["FE"]["XCLASS"]["tslib/class.tslib_content.php"]= PATH_typo3conf."class.ux_tslib_content.php";
So when the file “tslib/class.tslib_content.php” is included inside of class.tslib_pagegen.php, the extension class is included immediately from inside the “tslib/class.tslib_content.php” file (this is from the bottom of the file):
if (defined("TYPO3_MODE") && $TYPO3_CONF_VARS[TYPO3_MODE]["XCLASS"]["tslib/class.tslib_content.php"]) { include_once($TYPO3_CONF_VARS[TYPO3_MODE]["XCLASS"]["tslib/class.tslib_content.php"]);}The last thing which remains is to instantiate the class ux_tslib_cObj instead of tslib_cObj. This is done automatically, because everywhere tslib_cObj is instantiated, it is first examined if ux_tslib_cObj exists and if so, that class is instantiated instead!
This is done by instantiating the object with "t3lib_div::makeInstance()":
$cObj = t3lib_div::makeInstance("tslib_cObj");Originally it looked like this:
$cObj = new tslib_cObj;
Internally "t3lib_div::makeInstance()" does this:
$cObj = class_exists("ux_tslib_cObj") ? new ux_tslib_cObj : new tslib_cObj;When setting up the file to include, in particular from t3lib/, notice the difference between $TYPO3_CONF_VARS["BE"]["XCLASS"][...] and $TYPO3_CONF_VARS["FE"]["XCLASS"][...]. The key “FE” is used when the class is included by a front-end script (those initialized by tslib/index_ts.php and tslib/showpic.php - both also known as index.php and showpic.php in the root of the website), “BE” is used by backend scripts (those initialized by typo3/init.php or typo3/thumbs.php). This feature allows you to include a different extension when the (t3lib/-) class is used in the frontend and in the backend.
Most code in TYPO3 resides in classes and therefore anything in the system can be extended. So you should rather say to yourself: In which script (and thereby which class) is it that I'm going to extend/change something. When you know which script, you simply open it, look inside and somewhere you'll find the lines of code which are responsible for the inclusion of the extension, typically in the bottom of the script.
The exceptions to this rule is classes like "t3lib_div", "t3lib_extMgm" or "t3lib_BEfunc". These classes contain methods which are designed to be call non-instantiated, like "t3lib_div::fixed_lgd_cs()". Whether a class works on this basis is normally noted in the header of the class file. When methods in a class is called non-instantiated there is no way you can extend that method/class.
Say you wish to add a little section with help text in the bottom of the “New” dialog:
So this is what you do:
Find out that the script in question is “typo3/db_new.php” (right-click frame, select “Properties” and look at URL...:-)
Then examine the scripts for its classes and methods. In this case you'll find two classes in the file; “localPageTree” (extends t3lib_pageTree) and “SC_db_new”. The class “SC_db_new” is the so called “Script Class” - this will hold the code specifically for this script. You also find that the only code executed in the global scope is this:
$SOBE = t3lib_div::makeInstance("SC_db_new");$SOBE->init();$SOBE->main();$SOBE->printContent();
When you examine the SC_db_new class you find that the main() method is the one you would like to extend.
Finally you find that immediately after the definition of the two classes there is three lines of code which will provide you with the final piece of knowledge you need:
// Include extension?if (defined("TYPO3_MODE") && $TYPO3_CONF_VARS[TYPO3_MODE]["XCLASS"]["typo3/db_new.php"]) { include_once($TYPO3_CONF_VARS[TYPO3_MODE]["XCLASS"]["typo3/db_new.php"]);}
So now you know that the key to use is “typo3/db_new.php” when you wish to define a script which should be included as the extension.
So now after your investigations you do the trivial stuff:
Create your extension class (here typo3conf/class.test.php)<?phpclass ux_SC_db_new extends SC_db_new { function main() { global $doc; parent::main(); $this->content.=$doc->section("HELP","- make a choice!",0,1); }}?>
Configure your extension class in typo3conf/localconf.php
$TYPO3_CONF_VARS["BE"]["XCLASS"]["typo3/db_new.php"] = PATH_typo3conf."class.test.php";
There is no “table of extendable classes” in this document because 1) all classes are extendable and 2) the number of classes will grow as TYPO3 is further developed and extensions are made and 3) finally you cannot extend a class unless you know it exists and have analysed some of its internal structure (methods / variables) - so you'll have to dig into the source anyway!
Therefore; If you wish to extend something, follow this suggestion for an analysis of the situation and you'll end up with the knowledge needed in order to extend that class and thereby extend TYPO3 without loosing backwards compatibility with future updates. Great.
Notes on SC_* classes (script classes)
There is one more thing to note about especially the SC_* classes in the backend:
Global vars: They use a lot of variables from the global scope. This is due to historical reasons; The code formerly resided in the global scope and a quick conversion into classes demanded this approach. Future policy is to keep as many variables internal as possible and if any of these SC_* classes are developed further in the future, some of the globals might on that occasion be internalized.
Large methods: There are typically a init(), main() and printContent() method in the SC-classes. Each of these, in particular the main() method may grow large. Processing stuff in the start and end of the methods is easy - you just call parent::[methodname]() from your extension. But if you want to extend or process something in the middle of one of these methods, it would be necessary to call a dummy method at that point in the parent class. Such a dummy method would then be used for processing in your class, but would not affect the general use of the parent class. Such dummy-method calls are not widely included yet, but will be as suggestions for them appears. And you are very welcome to give in such suggestions.I'll just give an example to illustrate what I mean:
class SC_example { function main() { $number = 100; echo "The number is ".$number; }}
This class prints the text “The number is 100”. If you wish to do some calculations to the $number-variable before it is printed, you are forced to simply include the whole original main-method in your extension script. Here it would be no problem because the method is 2 codelines long. But it could be 200 codelines! So what you do is that you suggest to the TYPO3 development to call a “harmless” dummy method in the main() method...
class SC_example { function main() { $number = 100; $number = $this->processNumber($number); echo "The number is ".$number; } function processNumber($theNumber) { return $theNumber; }}
... and then you extend the class as follows:
class ux_SC_example extends SC_example { function processNumber($theNumber) { return $theNumber<100 ? "less than 100" : "greater than 100"; }}
... and now the main() method would print “The number is greater than 100” instead.Notice that you'll have to make such suggestions for dummy method calls because we will include them only as people need them.
When extending a method like in the case above with stdWrap() please observe that such a method might gain new parameters in the future without further notice. For instance stdWrap is currently defined like this:
function stdWrap($content,$conf){... but maybe some day this method will have another parameter added, eg:
function stdWrap($content,$conf,$yet_a_parameter=0){This means if you want to override stdWrap(), but still call the parent class' method, you must extend your own method call from...:
function stdWrap($content,$conf) { // Call the real stdWrap method in the parent class: $content = parent::stdWrap($content,$conf);...
... to:
function stdWrap($content,$conf,$yet_a_parameter=0) { // Call the real stdWrap method in the parent class: $content = parent::stdWrap($content,$conf,$yet_a_parameter);...
Also be aware of constuctors. If you have a constructor in your extension class you must observe if there is a constructor in the parent class which you should call first / after. In case, you can do it by “parent::[original class name]”
For instance the class tslib_fe is instantiated into the global object $TSFE. This class has a constructor looking like this:
/** * Class constructor */function tslib_fe($TYPO3_CONF_VARS, $id, $type, $no_cache="", $cHash="", $jumpurl="") { // Setting some variables: $this->id = $id; $this->type = $type; $this->no_cache = $no_cache ? 1 : 0; $this->cHash = $cHash; $this->jumpurl = $jumpurl; $this->TYPO3_CONF_VARS = $TYPO3_CONF_VARS; $this->clientInfo = t3lib_div::clientInfo(); $this->uniqueString=md5(microtime()); $this->makeCacheHash();}So as you see, you probably want to call this method. But lets also say you wish to make sure the $no_cache parameter is always set to 1 (for some strange reason...). So you make an extension class like this with a new constructor, ux_tslib_fe(), overriding the $no_cache variable and then calling the parent class constructor:
class ux_tslib_fe extends tslib_fe { function ux_tslib_fe($TYPO3_CONF_VARS, $id, $type, $no_cache="", $cHash="", $jumpurl=""){ $no_cache=1; parent::tslib_fe($TYPO3_CONF_VARS, $id, $type, $no_cache, $cHash, $jumpurl); }}Prefix user defined methods and internal variables with “ux_”! Thus you don't risk to choose a method name which may later be added to the parent class in the TYPO3 distribution!
Example, continued from above:
class ux_tslib_fe extends tslib_fe { var $ux_fLPmode = 1; // If you "feelLuckyPunk" this is the no_cache value function ux_tslib_fe($TYPO3_CONF_VARS, $id, $type, $no_cache="", $cHash="", $jumpurl=""){ // setting no_cache? $no_cache=$this->ux_settingNoCache(); // Calling parent constructor: parent::tslib_fe($TYPO3_CONF_VARS, $id, $type, $no_cache, $cHash, $jumpurl); } /** * Setting the no_cache value based on user-input in GET/POST var, feelLuckyPunk */ function ux_settingNoCache() { return t3lib_div::GPvar("feelLuckyPunk") ? $this->ux_fLPmode : 0; }}(User defined methods and variables are in purple)
The concept of extending classes in the backend can come in handy in many cases. First of all it's a brilliant way to make your own project specific extensions to TYPO3 without spoiling the compatibility with the distribution! This is a very important point! Stated another way: By making an "XCLASS extension" you can change one method in a TYPO3 class and next time you update TYPO3, your method is still there - but all the other TYPO3 code has been updated! Great!
Also for development and experimental situations is great. Generally the concept offers you quite a lot of freedom, because you are seriously able to take action if you need something solved here and now which cannot be fixed in the main distribution at the moment.
Anyway, here's a few simple examples:
1) Say you wish to have the backend user time out after 30 seconds instead of the default 6000.
In localconf.php, insert: $TYPO3_CONF_VARS["BE"]["XCLASS"]["t3lib/class.t3lib_beuserauth.php"] =PATH_typo3conf."class.ux_myBackendUserExtension.php";
Create the file “class.ux_myBackendUserExtension.php” in typo3conf/ folder and put this content in:
<?phpclass ux_t3lib_beUserAuth extends t3lib_beUserAuth { var $auth_timeout_field = 30;}?>Of course you need to know why it's the variable auth_timeout_field which must be set, but you are a bright person, so of course you go directly to the file t3lib/class.t3lib_beuserauth.php, open it and find that var $auth_timeout_field = 6000; there!
You could also easily insert an IP-filter (which is already present though...). Here you have to take a little adventure a bit further. As you see in “class.t3lib_beuserauth.php” extends “t3lib_userAuthGroup” which extends “t3lib_userAuth” the method start() is the place where the users are authenticated. This could quickly be exploited to make this IP filter for the backend:
<?phpclass ux_t3lib_beUserAuth extends t3lib_beUserAuth { var $auth_timeout_field = 30; function start() { if (!t3lib_div::cmpIP(getenv("REMOTE_ADDR"), "192.168.*.*")) { die("Wrong IP, you cannot be authenticated!"); } else { return parent::start(); } }}?>So now only users with client IP numbers in the 192.168.*.* series will gain access to the backend. If that is the case, notice how the parent start() method is called and any result is returned. Thus your overriding method is a wrapped for the original. Brilliant, right!
Here's another one:
<?phpclass ux_t3lib_TCEforms extends t3lib_TCEforms { function formWidth($size=48,$textarea=0) { $size=round($size*1.5); return parent::formWidth($size,$textarea); } function printPalette($palArr) { reset($palArr); while(list($k)=each($palArr)) { $palArr[$k]["NAME"] = strtoupper($palArr[$k]["NAME"]); } return parent::printPalette($palArr); }}?>... and configured in localconf.php as this:
$TYPO3_CONF_VARS["BE"]["XCLASS"]["t3lib/class.t3lib_tceforms.php"]=PATH_typo3conf."class.ux_myTCEformsExtension.php";
The result is this; Normally the “General options” palette of the forms in the backend looks like this:
But the extensions does two things: 1) All formfields have their width multiplied with 1.5 so they are wider, 2) the titles of the palette-fields are converted for uppercase. Looks like this:
So as you see you can do really stupid details - in fact almost any extension.
There are a few warnings about using XCLASS extensions:
Avoid using XCLASS extensions in your (public) extensions!A PHP class can only be extended by one extension class at a time. Thus, having two extension classes set up, only the latter one will be enabled. There is no way to work around this technologically in PHP. However "t3lib_div::makeInstance()" supports "cascaded" extension classes, meaning that you can do "ux_ux_someclass" which will extend "ux_someclass" but this requires an internal awareness of the extension class "ux_someclass" in the first place.The conclusion is that XCLASS extensions are best suited for project development where you need a quick hack of something in the core which should still stay backwards compatible with TYPO3 core upgrades.
Check if child classes are instantiatedQuite often people have been confused about extending for instance the "tslib_menu" class when they want to add a feature for "TMENU". But actually the class to extend is "tslib_tmenu" which is an extension of "tslib_menu". So make sure you are extending the right class name (and always make sure your extension class is included also).
Strange opcode caching behaviours when you upgrade TYPO3 coreWhen you upgrade the TYPO3 core and you have an extension which extends a core class, the upgraded core underneath might not be detected by opcode caches. In particular PHP-Accelerator is known for this behaviour producing "undefined function...." errors. The solution is: Always clear "/tmp/php_a_*" files and restart your webserver after upgrading source.