Open "Link to External Url" in new tab by default in TYPO3

In TYPO3 you can create a pages of the type "Link to External Url". Those pages appear just like normal pages in the navigation, but link to the absolute url given configured for the page. Links to those external pages are generated just like any link to a TYPO3-internal page. This leads to the kind of counter-intuitive behavior that these pages are opened in the same browser tab even if config.extTarget = _blank is set.

I was looking for a solution to open all external pages in a new tab. The bad news is: There is no built-in way to do that, but depending on your needs there are some fixes. Here are five solutions I found in order of increasing complexity.

The built-in way to set link targets for pages is the Link Target field in the page records Behavior tab. Just type the string _blank into it and all links to this page will open in a new tab.

The advantage is that it is really simple and does not require any deeper knowledge of TypoScript or the core. On the downside this approach only works for single pages. And should you ever convert such a page into an internal page you should not forget to remove this value again. Also I don't see this as a solution for non-technical editors. The strings they need to enter there are rather cryptic and don't make any sense if you don't know HTML.

I'd recommend using this approach if you only have a few external pages and do not plan to change those often.

2. TypoScript in menu generation

Another approach is to modify your TypoScript for menu generation. I found this solution on typo3wizard.com

The basic pattern looks like this:

lib.menu = HMENU
lib.menu {
  1=TMENU
  1.wrap=<ul>|</ul>
  1.NO {
    doNotLinkIt = 1
    wrapItemAndSub=<li>|</li>
    stdWrap.cObject = CASE
    stdWrap.cObject {
      key.field = doktype
      # default
      default = TEXT
      default {
        typolink.parameter.field = uid
        typolink.target.field = target
        field = title
      }
      # external url
      3 = TEXT
      3 < .default
      3.typolink.target.ifEmpty = _blank
    }
  }
}

The big downside of this approach should be clear just from reading: It's hard to. This example is kind of a Hello World for TypoScript-Menus, but it still takes up 20 lines. But it does work.

This might be a good solution if you do not like to write PHP. Just remember to do this for every menu you want to render.

Ok, let's put away the toys and write some code. By X-Classing the menu generation you get a nice and clean solution that works for all menus without further TypoScript.

I assume you manage your template code in a custom extension inside the typo3conf/ext folder. Create a file with the following contents

extending menuTypoLink()
<?php
// for TYPO3 4: typo3conf/ext/template/Classes/class.ux_tslib_tmenu.php
// for TYPO3 6: typo3conf/ext/template/Classes/Xclass/TextMenuContentObject.php
 
namespace \MyNamespace\Template\Xclass;
 
class TextMenuContentObject extends \TYPO3\CMS\Frontend\ContentObject\Menu\TextMenuContentObject {
// or for TYPO3 4 remove the "namespace" line and replace the above line with
// class ux_tslib_tmenu extends tslib_tmenu {
 
	function menuTypoLink($page, $oTarget, $no_cache, $script, $overrideArray = '', $addParams = '', $typeOverride = '') {
		if(intval($page['doktype']) === t3lib_pageSelect::DOKTYPE_LINK && empty($page['target'])) {
			$oTarget = $GLOBALS['TSFE']->extTarget;
		}
 
		return parent::menuTypoLink($page, $oTarget, $no_cache, $script, $overrideArray, $addParams, $typeOverride);
	}
 
}

Also, to make the X-Class work, you need to add this line to ext_localconf.php.

ext_localconf.php
// typo3conf/ext/template/ext_localconf.php
 
// TYPO3 4
$GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['tslib/class.tslib_menu.php'] = t3lib_extMgm::extPath($_EXTKEY) . 'Classes/class.ux_tslib_tmenu.php';
 
// TYPO3 6
GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects']['TYPO3\\CMS\\Frontend\\ContentObject\\Menu\\TextMenuContentObject'] = array(
    'className' => 'MyNamespace\\Template\\Xclass\\TextMenuContentObject',
);

Now, if you configure config.extTarget = _blank, all your links to external urls should open in a new tab. And you can still override the target in the page settings.

If you want a universal solution for menus only, this is a nice solution and you should probably go with it. Just hope that none of your other extensions wants to create the same X-Class.

4. Patching the core

The main method responsible for generating links in TYPO3 is typoLink() in tslib_cObj (TYPO3 4) or ContentObjectRenderer (TYPO3 6) respectively. Unfortunately there are no usable hooks to influence link generation in it. X-Classing is also no real option. You would either have to copy the whole function (no good idea, when the function has 500 lines) or do additional database queries before passing parameters to the original function (no good idea, if you have a few hundred links on your page).

So, in this case it might be a good excuse to patch the TYPO3 core directly. In times of Git, this is not so hard and less dirty.

All you need to add are 4 lines that set the target for external pages:

in typoLink()
if(intval($page['doktype']) === \TYPO3\CMS\Frontend\Page\PageRepository::DOKTYPE_LINK && empty($page['target'])) {
	// if: link to external url
	$target = $GLOBALS['TSFE']->extTarget;
}

Here you can download the diff file for TYPO3 4 and the diff file for TYPO3 6.

If you have a patched core already and consider the default behavior as bug (not a feature) this might be the way to go. Just don't forget to apply the patch again, if you upgrade the TYPO3 Core. As already mentioned: Git is your friend here.

5. X-Classing the core

The best solution I could find without patching the core is X-Classing another function that at first does not look related. getPageOverlay() is responsible for overlaying localized records, but we can also use it to modify records:

extending getPageOverlay()
<?php
// TYPO3 6: typo3conf/ext/template/Xclass/ContentObjectRenderer.php
// TYPO3 4: typo3conf/ext/template/Classes/class.ux_t3lib_pageSelect.php
 
namespace \MyNamespace\Template\Xclass;
 
class ContentObjectRenderer extends \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer {
// or for TYPO3 4 remove the "namespace" line and replace the above line with
// class ux_t3lib_pageSelect extends t3lib_pageSelect {
 
	/**
	 * set default target for doktype "link to external page" if none is set
	 *
	 * @param array $pageInput
	 * @param integer $lUid
	 * @return array
	 */
	function getPageOverlay($pageInput, $lUid = -1) {
		$pageOverlayed = parent::getPageOverlay($pageInput, $lUid);
 
		if(is_array($pageOverlayed) && $pageOverlayed['doktype'] == self::DOKTYPE_LINK && empty($pageOverlayed['target'])) {
			$pageOverlayed['target'] = $GLOBALS['TSFE']->extTarget;
		}
 
		return $pageOverlayed;
	}
}
ext_localconf.php
// typo3conf/ext/template/ext_localconf.php
 
// TYPO3 4
$GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_page.php'] = t3lib_extMgm::extPath($_EXTKEY) . 'Classes/class.ux_t3lib_pageSelect.php';
 
// TYPO3 6
GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects']['TYPO3\\CMS\\Frontend\\ContentObject\\ContentObjectRenderer'] = array(
    'className' => 'MyNamespace\\Template\\Xclass\\ContentObjectRenderer',
);

This solution works for all links (no matter if in a menu or any other typoLink), is configurable through config.extTarget and can be overridden in the page record, TypoScript or in the RTE. Also you don't have to update the code for each minor version upgrade.

But there is also a downside: As this X-Class modifies the array-representation of database records, there might be some troubling edge-cases when a programmer is not aware of that. I would also be cautious if you have some extension that makes page records editable in the frontend. It might expect the value to written in the database and maybe write it back in there on save. So you better double-check that.

Conclusion

There is no perfect solution. Depending on your task and skills there might be a good solution. I hope this post helps you picking the right one.

Let me know, if you have any other solutions in the comments.

No Comments yet

Respond to this post