Sometimes TYPO3, the mighty opensource Content Management System with a large community in Europe, drives me crazy. It really is a Love/Hate relationship we're having. I love the fact that you can get everything done with TYPO3 and I hate the fact that sometimes it's so damn complicated to get things done with TYPO3, not the least because of Typoscript (TS) -- the 'interpreter' between the underlying PHP-core and the setups in TYPO3.
Even with more than 5 years of building medium and large sites for several clients with TYPO3 on my back, I occasionally run in a situation where a seemingly simple task unfolds as a frustrating venture and eats on budget and time and eventually takes toll on my sanity.
Usually it turns out that there's a problem in my initial idea, a logic flaw, or a not-so-well-thought-of underlying structure, but since it is really really hard to keep track of all the "functions" that come with Typoscript, and debugging the rendering process is even harder, sometimes working with TS is anything but fun.
Here is a simple example, fresh from my workbench. The initial idea is so simple, that I promised my client a quick solution, and never ever expected that finding a working solution would cost me the better part of a day - even with a idea what may be the problem very early on. Maybe it's me, maybe I was too stubborn to see the greater picture (more on that later), but here's a good example why TS is a love/hate thing: Yes, there is a solution, even for an exotic scenario, but finding it is effing hard.
The task: On the homepage is a content record (text with title and image) edited and maintained by the editor. The image and the title are displayed in the header-part of the site, the bodytext is displayed in the main content part of the site. As it turned out, this is the real root of my problems, since the content is not displayed in a single location, but in two.
So we have two template-markers (in fact, they are subparts, but for brevety's sake, let's name them markers for now), "header" and "maincontent", each one "reads" the Content-Element and renders the parts it needs:
header = CONTENT header { table = tt_content select { where = colPos=1 andWhere = deleted=0 andWhere = hidden=0 orderBy = sorting max = 1 language = sys_language_uid } renderObj = COA renderObj { 5 = IMAGE 5 { required = 1 wrap = | file { import = uploads/pics/ import.field = image width = 670 height = 230 } altText.field = altText } 10 = TEXT 10 { required = 1 parseFunc =< lib.parseFunc_RTE field = header wrap = <h1> | } } } maincontent =< .header maincontent { renderObj { 5 > 10 = TEXT 10 { required = 1 parseFunc =< lib.parseFunc_RTE field = bodytext } } } |
So far so good. The first, non-deleted and non-hidden Content Record in the main column of the TYPO3-backend for the given page will be grabbed and it's contents displayed in the locations determined by the marker's position in the template.
With this setup as a starting point, I was bold enough to tell my client that displaying not the first Content Record, but a randomly selected one was no big deal, only a matter of modifying the SELECT object in above TYPOSCRIPT. And the resulting TS is easily done:
... select { where = colPos=1 andWhere = deleted=0 andWhere = hidden=0 orderBy = rand() max = 1 language = sys_language_uid }... |
At a first glance everything worked. I padded myself on the shoulder and with a smug smile I hit the browser's reload button to see the next randomly chosen content. Yes. It's working. New reload, yes -- uhm, wait, what's that?!
Yes, the displayed records change, but the displayed images and titles do not always match the displayed text!
And now the painful journey into the intricacies of Typoscript starts.
The problem:
After pondering over the above TS, I realized, that the second marker, which references the first marker ("=< ") causes the SELECT to fire a second time, and since there is a "orderBy rand()" statement, another content element will be returned, not automatically the same as in the first marker. d'oh.
The solution:
So I need to fetch the Content Record, store it somewhere safe and then use it's contents in the two markers, not causing another SELECT round in the way.
Easily done, thinked me again, and came up with this:
temp.content = CONTENT temp.content { ... # all of above's SELECT and the the renderObj # in one go: renderObj { # image 5 {...} # title 10 {...} # bodytext 20 {...} } } header = COA header { 10 < temp.content # no bodytext in here 10.renderObj.20 > } maincontent = COA maincontent { 10 < temp.content # no image, no title in here 10.renderObj.5 > 10.renderObj.10 > } <pre>... |
In fact, finding this took quite a while, at first I tried something like "header.5 < temp.content.5
", but this obviously is too straight-forward thinking for TS-usage and didn't work.
But, it makes no difference at all to the first code, the image and bodytext do not match, again.
Wether I copied or referenced the temp.content object, whether I named it temp.content, lib.content or simply randomContent -- no difference. Playing around with COA, or COA_INT -- no difference.
Somehow TYPO3 insists in firing the SELECT each time the temp.content object is involved.
The next solution:
Making two seperate SELECTs for each marker, but both with the same Content-UID.
The Problem:
Getting randomly the CONTENT-UID once. TYPO3, you hear me? Once!
As it turned out (I spare you the resulting scripts I came up with) the problem remained; even with the most elaborated scripts in the end the random select was fired twice, resulting in different UIDs for the subsequent requests. D'Oh, d'oh, and double-d'oh.
So I dived into the web, seeked out tutorials and 'documentations'. What kind of "variables" does TS offer in which persistent data could be stored?
Finally and after hours of trying, researching and iterating I found the answer:
Store the randomly choosen UID in a register, BUT -- and this is important -- store the register at the PAGE object and not at the temp.content object: If stored at the temp.object, you cannot access the register inside the selects for the markers. And even if you stored it at the PAGE object, you can only access it once, not twice, I don't know why. And of course, nowhere in the 'documentation' is stated that the register has some kind of scope. Either I'm totally mistaken (which of course is possible) or the term "global register" is missleading. Perhaps I have a total misunderstanding of the page-rendering process and how temp objects, registers and cachable data is handled.
So what I had to do to get this simple task working:
1. Get the content's UID and store it in a register:
page.5 = LOAD_REGISTER page.5 { myID.cObject = CONTENT myID.cObject { table = tt_content select { # as above ... } renderObj = TEXT renderObj.field = uid } } |
2. Now that we have a random UID, make the temp object and fill the markers
temp.content = CONTENT temp.content { table = tt_content select { where = uid=###myUID### markers { myUID = TEXT myUID.data = register:myID } # no need for further select conditions } renderObj { # same as above 5 {...} 10 {...} 20 {...} } } header = COA header { 10 < temp.content # no bodytext in here 10.renderObj.20 > } maincontent = COA maincontent { 10 < temp.content # no image, no title in here 10.renderObj.5 > 10.renderObj.10 > } |
And finally, this works.
The rounds of debugging to find out if my syntax is wrong or what else could be the problem, of researchg and asking the right questions into google took several hours.
Maybe the better approach would have been to first question the need for the two seperate template markers - had I used a "complete" Content Object, with no need to "split" it into two (as it turned out) separate requests, the whole problem had not existed. But as often with ongoing projects, changing something as basic as the underlying html/template structure may not be an option.
Maybe someone who reads this has more experience than me and points out a solution that's more feasible, I would love to see it, and maybe how you go about debugging the TS rendering.
I don't feel like I wasted my time, but in regard of the project's budget this was not healthy.