Report
QFQ Report Keywords
QFQ content element
The QFQ extension is activated through tt-content records. One or more tt-content records per page are necessary to render forms and reports. Specify column and language per content record as wished.
The title of the QFQ content element will not be rendered. It’s only visible in the backend for orientation of the webmaster.
To display a report on any given TYPO3 page, create a content element of type ‘QFQ Element’ (plugin) on that page.
A simple example
Assume that the database has a table person with columns firstName and lastName. To create a simple list of all persons, we can do the following:
sql = SELECT firstName, lastName FROM Person
The ‘10’ indicates a root level of the report (see section Structure). The expression ‘sql’ defines an SQL query for the specific level. When the query is executed, it will return a result having one single column name containing first and last name separated by a space character.
The HTML output, displayed on the page, resulting from only this definition, could look as follows:
JohnDoeJaneMillerFrankStar
I.e., QFQ will simply output the content of the SQL result row after row for each single level.
Format output: mix style and content
Variant 1: pure SQL
To format the upper example, just create additional columns:
sql = SELECT firstName, ", ", lastName, "<br>" FROM Person
HTML output:
Doe, John<br>
Miller, Jane<br>
Star, Frank<br>
Variant 2: SQL plus QFQ helper
QFQ provides several helper functions to wrap rows and columns, or to separate them. E.g. fsep stands for field
separate and rend for row end:
{
sql = SELECT firstName, lastName FROM Person
fsep = ', '
rend = <br>
}
HTML output:
Doe, John<br>
Miller, Jane<br>
Star, Frank<br>
Check out all QFQ helpers under QFQ Keywords (Bodytext).
Due to mixing style and content, this becomes harder to maintain with more complex layout.
Format output: separate style and content
The result of the query can be passed to the Twig template engine in order to fill a template with the data.:
{
sql = SELECT firstName, lastName FROM Person
twig = {% for row in result %}
{{ row.lastName }}, {{ row.firstName }<br />
{% endfor %}
}
HTML output:
Doe, John<br>
Miller, Jane<br>
Star, Frank<br>
Check out Using Twig.
Syntax of report
All root level queries will be fired in the alphabetically order specified by ‘level’-name or, if no ‘level’ is given, than by order of writing.
For each row of a query (this means all selected records of the current query), all nested subqueries will be fired once.
E.g. if there is an outer and a nested query and the outer query selects 5 rows, and a nested query selects always 3 rows, than the total number of processed rows are 5 x 3 = 15 rows.
Note
Most SQL commands are possible: SELECT, INSERT, UPDATE, DELETE, SHOW, REPLACE, …
Only SELECT and SHOW queries will fire subqueries.
Note
variables are replaced before the SQL-Query gets executed.
If the column name is a QFQ keyword, than the value of such column is evaluated too.
See QFQ Keywords (Bodytext) for a full list.
See Variable for a full description of variable functionality.
See Access column values.
Processing of the resulting rows and columns:
In general, all columns of all rows will be processed and printed out sequentially.
On a per column base, printing of columns can be suppressed by starting the column name with an underscore ‘_’. E.g.
SELECT id AS _id.This might be useful to store values, which will be used later on in another query via the
{{id:R}}or{{<level>.columnName}}variable. To suppress printing of a column, use a underscore as column name prefix. E.g.SELECT id AS _id
Reserved column names have a special meaning and will be processed in a special way. See Processing of columns in the SQL result for details.
There are extensive ways to wrap columns and rows. See Wrapping rows and columns: Level
Using Twig
How to write Twig templates is documented by the Twig Project.
QFQ passes the result of a given query to the corresponding Twig template using the Twig variable result. So templates need to use this variable to access the result of a query.
Specifying a Template
By default the string passed to the twig-key is used as template directly, as shown in the simple example above:
{
twig = {% for row in result %}
{{ row.lastName }}, {{ row.firstName }<br />
{% endfor %}
}
However, if the string starts with file:, the template is read from the file specified.:
{
twig = file:table_sortable.html.twig
}
The file is searched relative to <site path> and if the file is not found there, it’s searched relative to QFQ’s twig_template folder where the included base templates are stored.
The following templates are available:
tables/default.html.twigA basic table with column headers, sorting and column filters using tablesorter and bootstrap.
tables/single_vertical.html.twigA template to display the values of a single record in a vertical table.
Links
The link syntax described in Column: _link is available inside Twig templates using the qfqlink filter:
{{ "u:http://www.example.com" | qfqlink }}
will render a link to http://www.example.com.
JSON Decode
A String can be JSON decoded in Twig the following way:
{% set decoded = '["this is one", "this is two"]' | json_decode%}
This can then be used as a normal object in Twig:
{{ decoded[0] }}
will render this is one.
Available Store Variables
QFQ also provides access to the following stores via the template context.
access
record
sip
typo3
user
system
var
web
All stores are accessed using their lower case name as attribute of the context variable store. The active Typo3 front-end user is therefore available as:
{{ store.typo3.feUser }}
Example
The following block shows an example of a QFQ report: Selects all users who have been assigned files in our file tracker.
Second query then selects all files belonging to this user, prints the username as header and then displays the files in a nice table with links to the files.
{
sql = SELECT assigned_to AS _user FROM FileTracker
WHERE assigned_to IS NOT NULL
GROUP BY _user
ORDER BY _user
{
sql = SELECT id, path_scan
FROM FileTracker
WHERE assigned_to = '{{user:R}}'
twig = <h2>{{ store.record.user }}</h2>
<table class="table table-hover tablesorter" id="{{pageSlug:T}}-twig">
<thead><tr><th>ID</th><th>File</th></tr></thead>
<tbody>
{% for row in result %}
<tr>
<td>{{ row.id }}</td>
<td>{{ ("d:|M:pdf|s|t:"~ row.path_scan ~"|F:" ~ row.path_scan ) | qfqlink }}</td>
</tr>
{% endfor %}
</tbody>
</table>
}
}
Reserved names
The following names have a special meaning in QFQ/Typo3. It is recommended to use such names only in the meaning of QFQ/Typo3 and not to use them as free definable user variables.
id,type,L,form,r
Debug the bodytext
The parsed bodytext could be displayed by activating ‘showDebugInfo’ (Debug) and specifying:
debugShowBodyText = 1
A small symbol with a tooltip will be shown, where the content record will be displayed on the webpage. Note: Debug information will only be shown with showDebugInfo: yes in Configuration.
tt-content Report editing
QFQ syntax highlight, suggestions and indentention is supported in backend and frontend.
Shortcuts:
Ctrl+Z |
Undo previous action, can also be used to undo auto capitalization of SQL keywords |
Tab |
With selection: increase indentation (without selection: add two spaces) |
Ctrl+Space |
Increase indentation by only one space |
Shift+Tab |
Decrease indentation |
Ctrl+Enter |
Auto indent current selection |
Ctrl+/ |
Comment / uncomment current selection |
Backend
Activation: ‘Admin Tools > Extensions > Editor with syntax highlighting`
Frontend
Whenever a TYPO3 BE user is logged in, a small button is shown in the frontend on the right-hand side where the QFQ report is rendered. On save in frontend editor, the TYPO3 frontend cache is purged.
Activation: take care to include the appropriate JS/CSS in Setup.
Structure
A report can be divided into several levels. This can make report definitions more readable because it allows for splitting of otherwise excessively long SQL queries. For example, if your SQL query on the root level selects a number of person records from your person table, you can use the SQL query on the second level to look up the city where each person lives.
See the example below:
{
sql = SELECT id AS _pId, CONCAT(firstName, " ", lastName, " ") AS name FROM Person
rsep = <br>
{
sql = SELECT CONCAT(postal_code, " ", city) FROM Address WHERE pId = {{10.pId}}
rbeg = (
rend = )
}
}
This would result in:
John Doe (3004 Bern)
Jane Miller (8008 Zürich)
Frank Star (3012 Bern)
Text across several lines
To get better human readable SQL queries, it’s possible to split a line across several lines. Lines with keywords are on their own (QFQ Keywords (Bodytext) start a new line). If a line is not a ‘keyword’ line, it will be appended to the last keyword line. ‘Keyword’ lines are detected on. Example:
{
sql = SELECT 'hello world'
FROM Mastertable
tail = End
}
{
sql = SELECT 'a warm welcome'
'some additional', 'columns'
FROM AnotherTable
WHERE id>100
head = <h3>
tail = </h3>
}
Join mode: SQL
This is the default. All lines are joined with a space in between. E.g.:
{
sql = SELECT 'hello world'
FROM Mastertable
}
Results to: sql = SELECT 'hello world' FROM Mastertable
Notice the space between “…world’” and “FROM …”.
Join mode: strip whitespace
Ending a line with a \ strips all leading and trailing whitespaces of that line joins the line directly (no extra space in between). E.g.:
{
sql = SELECT 'hello world', 'd:final.pdf \
|p:/export \
|t:Download' AS _pdf \
}
Results to: sql = SELECT 'hello world', 'd:final.pdf|p:/export|t:Download' AS _pdf
Note: the \ does not force the joining, it only removes the whitespaces.
To get the same result, the following is also possible:
{
sql = SELECT 'hello world', CONCAT('d:final.pdf'
'|p:/export',
'|t:Download') AS _pdf
}
Nesting of levels via alias
Levels can be nested without levels. E.g.:
{
sql = SELECT ...
{
sql = SELECT ...
head = ...
}
}
This is equal to:
1.sql = SELECT ...
1.2.sql = SELECT ...
1.2.head = ...
Levels are automatically numbered from top to bottom.
An alias can be used instead of levels. E.g.:
myAlias {
sql = SELECT ...
nextAlias {
sql = SELECT ...
head = ...
}
}
This is also equal to:
1.sql = SELECT ...
1.2.sql = SELECT ...
1.2.head = ...
Important
Allowed characters for an alias: [a-zA-Z0-9_-].
Important
The first level determines whether report notation numeric or alias is used. Using an alias or no level triggers report notation alias. It requires the use of delimiters throughout the report. A combination with the notation ‘sql = …’ is not possible.
Important
Report notation alias does not require that each level be assigned an alias. If an alias is used, it must be on the same line as the opening delimiter.
Nesting of levels numeric (deprecated)
Levels can be nested. E.g.:
10 {
sql = SELECT ...
5 {
sql = SELECT ...
head = ...
}
}
This is equal to:
10.sql = SELECT ...
10.5.sql = SELECT ...
10.5.head = ...
Leading / trailing spaces
By default, leading or trailing whitespaces are removed from strings behind ‘=’. E.g. ‘rend = test ‘ becomes ‘test’ for rend. To prevent any leading or trailing spaces, surround them by using single or double ticks. Example:
{
sql = SELECT name FROM Person
rsep = ' '
head = "Names: "
}
Braces character for nesting
By default, curly braces ‘{}’ are used for nesting. Alternatively angle braces ‘<>’, round braces ‘()’ or square braces ‘[]’ are also possible. To define the braces to use, the first line of the bodytext has to be a comment line and the last character of that line must be one of ‘{[(<’. The corresponding braces are used for that QFQ record. E.g.:
# Specific code. >
myAlias <
sql = SELECT
head = <script>
data = [
{
10, 20
}
]
</script>
>
Per QFQ tt-content record, only one type of nesting braces can be used.
Important
Be careful to:
write nothing else than whitespaces/newline behind an open brace
the closing brace has to be alone on a line
Access column values
Columns of the upper / outer level result can be accessed via variables in two ways
STORE_RECORD:
{{pId:R}}Label:
{{<label>.pId}}Level Key (deprecated):
{{<level>.pId}}
The STORE_RECORD will always be merged with previous content. The Level Keys are unique.
Important
Multiple columns, with the same column name, can’t be accessed individually. Only the last column is available.
Important
Retrieving the final value of Special column names is possible via
{{&<column>:R}} (there is an ‘&’ direct behind ‘{{‘)
Example:
{
sql = SELECT 'p:/home?form=Person|s|b:success|t:Edit' AS _link
{
sql = SELECT '{{link:R}}', '{{&link:R}}'
}
}
The first column of second query returns p:/home?form=Person|s|b:success|t:Edit,
the second column returns
‘<span class=”btn btn-success”><a href=”?home&s=badcaffee1234”>Edit</a></span>’.
Example STORE_RECORD:
{
sql= SELECT p.id AS _pId, p.name FROM Person AS p
{
sql = SELECT adr.city, 'dummy' AS _pId FROM Address AS adr WHERE adr.pId={{pId:R}}
{
sql = SELECT '{{pId:R}}'
}
{
sql = SELECT '{{pId:R}}'
}
}
}
The second query will output ‘dummy’ in cases where there is at least one corresponding address. If there are no addresses (all persons) it reports the person id. If there is at least one address, it reports ‘dummy’, cause that’s the last stored content.
Example ‘alias’:
myAlias {
sql = SELECT p.id AS _pId, p.name FROM Person AS p
myAlias2 {
sql = SELECT adr.city, 'dummy' AS _pId FROM Address AS adr WHERE adr.pId={{10.pId}}
myAlias3 {
sql = SELECT '{{myAlias.pId}}'
}
}
myAlias4 {
sql = SELECT '{{myAlias.pId}}'
}
}
Levels |
A report is divided into levels. The Example has levels 1, 2, 3, 3.1, 3.1.1, 4 |
Qualifier |
A level is divided into qualifiers 3.1.1 has 3 qualifiers 3, 1, 1 |
Root levels |
Is a level with one qualifier. E.g.: 1 |
Sub levels |
Is a level with more than one qualifier. E.g. levels 3.1 or 3.1.1 |
Child |
The level 3 has one child and child child: 3.1.1 and 3.1.1 |
Alias |
A variable that can be assigned to a level and used to retrieve its values. |
Example |
1, 2, 3, 4* are root level and will be completely processed one after each other. 3.1 will be executed as many times as 3 has row numbers. 3.1.1 will be executed as many times as 3.1 has row numbers. |
Report Example 1:
{
# Displays current date
sql = SELECT CURDATE()
}
{
# Show all students from the person table
sql = SELECT p.id AS pId, p.firstName, " - ", p.lastName FROM Person AS p WHERE p.typ LIKE "student"
{
# Show all the marks from the current student ordered chronological
sql = SELECT e.mark FROM Exam AS e WHERE e.pId={{pId:R}} ORDER BY e.date
}
}
Wrapping rows and columns: Level
Order and nesting of queries, will be defined with a TypoScript-alike syntax:
level.sublevel1.subsublevel2. ...
Each ‘level’ directive needs a final key, e.g: 20.30.10. sql.
See all QFQ Keywords (Bodytext).
Processing of columns in the SQL result
The content of all columns of all rows will be printed sequentially (by default without separator).
All cells of every row will be copied to STORE_RECORD - and overwrite former values of same column.
Access to each value is done via
{{<colname>:R}}.If multiple columns with same name exist, the last one will be taken.
Rows with Special column names will be rendered internally by QFQ. The result (if there is any) is copied to STORE_RECORD.
Access to the original, non QFQ processed, value is possible via
{{&<colname>:R}}
Special column names
Note
Twig: respect that the ‘special column name’-columns are rendered before Twig becomes active. The recommended way by using Twig is not to use special column names at all. Use the Twig version qfqLink.
Note
QFQ process SQL Queries:
Before the query is fired: replace all QFQ variables.
After fetching the result: Interpret the output by checking all column names starting with ‘_’.
Example:
sql = SELECT 'All', 'cats' AS red, 'are' AS _green, 'grey in the night' AS _link
The first (‘All’) and second (red) column are regular columns. No extra QFQ processing.
The third column (green) is no QFQ special column name, but has an ‘_’ at the beginning: such column content will not be printed, but is available via STORE_RECORD, e.g.
{{green:R}}.The fourth column (alias name ‘link’) uses a QFQ special column name and will be processed by QFQ. The syntax in this example is not useful, it’s only meant as an example.
All columns of a row, with the same special column name (e.g.
... AS _page) will have the same column name: ‘page’. To access individual columns a uniq column title is necessary and can be added...|column1:
{
# Those columns can be accessed via {{columnA:R}}, {{columnB:R}} (recommended) or {{1.columnA}}, {{1.columnB}}.
# The number ``{{1.` is dynamic assigned.
sql = SELECT 'day' AS '_page|columnA', 'night' AS '_page|columnB'
}
To skip wrapping via
fbeg,fsep,fendfor dedicated columns, add the keyword|_noWrapto the column alias. Example:
{
sql = SELECT 'world' AS 'title|_noWrap'
}
Summary:
Note
For special column names: the column name together with the value controls how QFQ processes the column.
Special column names always start with ‘_’.
Important: to reference a column, the name has to be given without ‘_’. E.g.
SELECT 'cat' AS _petwill be used with{{pet:R}}(notice the missing ‘_’).Columns starting with a ‘_’ but not defined as as QFQ special column name are hidden(!) - in other words: they are not printed as output.
Tip: if a column name starts with ‘_’ the column name becomes ‘_….’ - which will be hidden! To be safe: always define an alias!
Access to ‘hidden’ columns via
{{<level>.<column>}}or{{<column>:R}}are possible and often used.
Reserved column name |
Purpose |
|---|---|
_link |
Column: _link - Build links with different features. |
_authenticate |
Column: _authenticate - Build links to authenticate Typo3 frontend users. |
_pageX or _PageX |
Columns: _page[X] - Shortcut version of the link interface for fast creation of internal links. The column name is composed of the string page/Page and a optional character to specify the type of the link. |
_download |
Download - single file (any type) or concatenate multiple files (PDF, ZIP) |
_pdf, _file, _zip _Pdf, _File, _Zip |
Column: _pdf | _file | _zip - Shortcut version of the link interface for fast creation of Download links. Used to offer single file download or to concatenate several PDFs and printout of websites to one PDF file. |
_savePdf |
Column: _savePdf - pre render PDF files |
_saveZip |
Column: _saveZip - Generate and save zip file. |
_excel |
Excel export - creates Excel exports based on QFQ Report queries, optional with pre uploaded Excel template files |
_yank |
Copy to clipboard. Shortcut version of the link interface |
_mailto |
Column: _mailto - Build email links. A click on the link will open the default mailer. The address is encrypted via JS against email bots. |
_sendmail |
Column: _sendmail - Send emails. |
_exec |
Column: _exec - Run batch files or executables on the webserver. |
_script |
Column: _script - Run php function defined in an external script. |
_vertical |
Column: _vertical - Render Text vertically. This is useful for tables with limited column width. See also same effect done via CSS, see Table: vertical text via CSS |
_img |
Column: _img - Display images. |
_bullet |
Display a blue/gray/green/orange/pink/red/yellow bullet. If none color specified, show nothing. ‘blue|t:a blue bullet|o:feel blue’ AS _bullet |
_check |
Display a blue/gray/green/pink/red/yellow checked sign. If none color specified, show nothing. ‘blue|t:a blue check|o:feel blue’ AS check |
_nl2br |
All newline characters will be converted to |
_striptags |
HTML Tags will be stripped. |
_htmlentities |
Characters will be encoded to their HTML entity representation. |
_fileSize |
Show file size of a given file |
_mimeType |
Show mime type of a given file |
_thumbnail |
thumbnail - Create thumbnails on the fly. See Column: _thumbnail. |
_monitor |
Column: _monitor - Constantly display a file. See Column: _monitor. |
_XLS |
Used for Excel export. Append a newline character at the end of the string. Token must be part of string. See Excel export. |
_XLSs |
Used for Excel export. Prepend the token ‘s=’ and append a newline character at the string. See Excel export. |
_XLSb |
Used for Excel export. Like ‘_XLSs’ but encode the string base64. See Excel export. |
_XLSn |
Used for Excel export. Prepend ‘n=’ and append a newline character around the string. See Excel export. |
_noWrap |
Skip wrapping via |
_hide |
Hide a column with a special column name (like |
_+<html-tag attributes> |
The content will be wrapped with ‘<html-tag attributes>’. Example: |
_<varname>:<store> |
The content will be saved in store ‘<store>’ with key ‘<varname>’. Retrieve it later via |
_<nonReservedName> |
Suppress output. Column names with leading underscore are used to select data from the database and make it available in other parts of the report without generating any output. |
_formJson |
System internal. Return form with given id as JSON string. ( |
_encrypt |
Column: _encrypt - Encrypt value. |
_decrypt |
Column: _decrypt - Decrypt value. |
_upload |
Column: _upload - Upload field with drag and drop and file browser. |
_jwt |
Column: _jwt - generates a json web token from the provided data. |
_input |
Column: _input - Render FormElement inputs: select, multiselect, text without a HTML form-tag. E.g. used to build dynamic filter panels. |
Copy values to a store
By default, all columns of each row are copied to STORE_RECORD. This happens always, without any special syntax.
It is possible to copy values to one of STORE_ACCESS (A), STORE_RECORD (R), STORE_USER (U), STORE_VAR (V) or STORE_WEB (W) by using a column name of the form ‘_<column>:<store>. Such columns are not printed, only copied to store.
Note
To copy value to other stores than STORE_RECORD:
sql = SELECT ‘hello’, ‘world’ AS ‘_item:V’, ‘city district’ AS ‘_item:U’
Here, hello is printed, but world and city district not. Instead world is accessible via {{item:V}} and city district via {{item:U}}.
To store values in STORE_USER, the syntax
SELECT ... AS '_=<var>'is deprecated and should not be used anymore. See the above example for details on how to fill STORE_USER now.
Column: _link
Most URLs will be rendered via class link.
Column names like
_pagee,_mailto, … are wrapper to class link.The parameters for link contains a prefix to make them position-independent.
Parameter have to be separated with ‘|’. If ‘|’ is part of the value, it needs to be escaped ‘\|’. This can be done via QFQ SQL function
QBAR()- see QBAR: Escape QFQ Delimiter.When the column generates a HTTP redirect response then the rendering of the page is stopped immediately.
URL |
IMG |
Meaning |
Qualifier |
Example |
Description |
|---|---|---|---|---|---|
x |
URL |
u:<url> |
u:http://www.example.com |
If an image is specified, it will be rendered inside the link, default link class: external |
|
x |
m:<email>[|select] |
m:info@example.com |
Default link class: email. Option: ‘select’ - the user can select a subset of all provided email addresses. See Column: _mailto. |
||
x |
Page |
p:<pageSlug> |
p:/impressum?foo=bar |
Append optional GET parameters afeter ‘?’, no hostname qualifier (automatically set by browser) |
|
x |
p:_back |
p:_back |
The reserved name _back implies a browser back functionality. See Back Button |
||
x |
Download |
d:[<exportFilename>] |
d:complete.pdf |
Link points to |
|
x |
Copy to clipboard |
y:[some content] |
y:this will be copied |
Click on it copies the value of ‘y:’ to the clipboard. Optional a file (‘F:…’) might be specified as source. See Copy to clipboard. |
|
Dropdown menu |
z |
z||p:/home|t:Home |
Creates a dropdown menu. See Dropdown Menu. |
||
REST get/put |
n:<url> … AS _restClient |
See REST Client. |
|||
websocket |
w:ws://<host>:<port>/<path> |
w:ws://localhost:123/demo |
Send message given in ‘t:…’ to websocket. See WebSocket. |
||
Text |
t:<text> |
t:Firstname Lastname |
|||
Render |
r:<mode> |
r:3 |
See: Render mode, Default: 0 |
||
Button |
b[:0|1|<btn class>[ btn-small]] |
b:0, b:1, b:success |
‘b’, ‘b:1’: with Bootstrap class ‘btn btn-…’. ‘b:0’ no Bootstrap class. <btn class>: default, primary, success, info, warning,danger. To make the button smaller, add btn-small or btn-tiny. |
||
x |
Picture |
P:<filename> |
P:bullet-red.gif |
Picture ‘<img src=”bullet-red.gif”alt=”….”>’. |
|
x |
Edit |
E |
E |
Show ‘edit’ icon as image |
|
x |
New |
N |
N |
Show ‘new’ icon as image |
|
x |
Delete |
D |
D |
Show ‘delete’ icon as image (only the icon, no database record ‘delete’ functionality) |
|
x |
Help |
H |
H |
Show ‘help’ icon as image |
|
x |
Info |
I |
I |
Show ‘information’ icon as image |
|
x |
Show |
S |
S |
Show ‘show’ icon as image |
|
x |
Glyph |
G:<glyphname> |
G:glyphicon-envelope |
Show <glyphname>. Check: Bootstrap |
|
x |
Bullet |
B:[<color>] |
B:green |
Show bullet with ‘<color>’. Colors: blue, gray, green, orange, pink, red, yellow. Default Color: green. |
|
x |
Check |
C:[<color>] |
C:green |
Show checked with ‘<color>’. Colors: blue, gray, green, pink, red, yellow. Default Color: green. |
|
x |
Thumbnail |
T:<pathFileName> |
T:fileadmin/file.pdf |
Creates a thumbnail on the fly. Size is specified via ‘W’. See Column: _thumbnail |
|
Dimension |
W:[width]x[height] |
W:50x , W:x60 , W:50x60 |
Defines the pixel size of thumbnail. See Column: _thumbnail |
||
URL Params |
U:<key1>=<value1>[&<keyN>=<valueN>] |
U:a=value1&b=value2&c=… |
Any number of additional Params. Links to forms: U:form=Person&r=1234. Used to create ‘record delete’-links. |
||
Tooltip |
o:<text> |
o:More information here |
Tooltip text |
||
ToolTip sticky |
O:<text> |
O:Some Text |
Sticky Tool-Tip with copy to clipboard button. |
||
Order (invisible) |
Y:<value> |
Y:{{p.id:R}} |
Value wrapped in
|
||
Alttext |
a:<text> |
a:Name of person |
|
||
Class |
c:[n|<text>] |
c:text-muted |
CSS class for link. n:no class attribute, <text>: explicit named |
||
Attribute |
A:<key>=”<value”> |
A:data-reference=”person” |
Custom attributes and a corresponding value. Might be used by application tests. |
||
Target |
g:<text> |
g:_blank |
target=_blank,_self,_parent,<custom>. Default: no target |
||
Question |
q:<text> |
q:please confirm |
See: Alert: Question. Link will be executed only if user clicks ok/cancel, default: ‘Please confirm’ |
||
Encryption |
e:0|1|… |
e:1 |
Encryption of the e-mail: 0: no encryption, 1:via Javascript (default) |
||
Right |
R |
R |
Defines picture position: Default is ‘left’ (no definition) of the ‘text’. ‘R’ means ‘right’ of the ‘text’ |
||
SIP |
s[:0|1] |
s, s:0, s:1 |
If ‘s’ or ‘s:1’ a SIP entry is generated with all non Typo 3 Parameters. The URL contains only parameter ‘s’ and Typo 3 parameter |
||
Mode |
M:file|pdf|qfqpdf|zip |
M:file, M:pdf, M:qfqpdf, M:zip |
Mode. Used to specify type of download. One or more element sources needs to be configured. See Download. |
||
Text before |
v:text before link |
v:Some Text |
Useful to show text before a link, delivered through … AS _link. See Text before / after link |
||
Text after |
V:text after link |
V:Some Text |
Useful to show text after a link, delivered through … AS _link. See Text before / after link |
||
File |
F:<filename> |
F:fileadmin/file.pdf |
Element source for download mode file|pdf|zip. See Download. |
||
Delete record |
x[:a|r|c] |
x, x:r, x:c |
a: ajax (only QFQ internal used), r: report (default), c: close (current page, open last page) |
||
Cache |
cache[:table/id[/column]][,]] |
cache, cache:table1/23/column |
See Cache |
||
HTTP redirect |
h:[get|temp|perm|<http-code>] |
h:get, h:303, h:307 |
Only used with rendering mode ‘r:9’. See HTTP redirection |
Render mode
The following table might be hard to read - but it’s really useful to understand. It solves a lot of different situations. If there are no special condition (like missing value, or suppressed links), render mode 0 is sufficient. But if the URL text is missing, or the URL is missing, OR the link should be rendered in sql row 1-10, but not 5, then render mode might dynamically control the rendered link.
Column Mode is the render mode and controls how the link is rendered.
Mode |
Given: url & text |
Given: only url |
Given: only text |
Description |
|---|---|---|---|---|
0 (default) |
<a href=url>text</a> |
<a href=url>url</a> |
text or image will be shown, only if there is a url, page or mailto |
|
1 |
<a href=url>text</a> |
<a href=url>url</a> |
text |
text or image will be shown, independently of whether there is a url or not |
2 |
<a href=url>text</a> |
no link if text is empty |
||
3 |
text |
url |
text |
|
4 |
url |
url |
text |
no link, show text, if text is empty, show url, incl. optional tooltip |
5 |
nothing at all |
|||
6 |
pure text |
pure text |
no link, pure text |
|
7 |
pure url |
pure url |
no link, pure url |
|
8 |
pure sip |
pure sip |
no link, no html, only the 13 digit sip code. |
|
9 |
HTTP redirection |
HTTP redirection |
page rendering is stopped immediately and a redirect HTTP response is sent |
Example:
sql = SELECT CONCAT('u:', p.homepage, IF(p.showHomepage='yes', '|r:0', '|r:5') ) AS _link FROM Person AS p
Tip
An easy way to switch between different options of rendering a link, incl. Bootstrap buttons, is to use the render mode.
no render mode or ‘r:0’ - the full functional link/button.
‘r:3’ - the link/button is rendered with text/image/glyph/tooltip … but without a HTML a-tag! For Bootstrap button, the button get the ‘disabled’ class.
‘r:5’ - no link/button at all.
Link Examples
SQL-Query |
Result |
|---|---|
|
info@example.com as linked text, encrypted with javascript, class=external |
|
info@example.com as linked text, not encrypted, class=external |
|
info@example.com as linked image mail.gif, encrypted with javascript, class=external |
|
A button is shown to select a subset of provided email adresses. |
|
info@example.com as linked image mail.gif, encrypted with javascript, class=external, tooltip: “sendmail” |
|
‘mail to info@example.com’ as linked text, encrypted with javascript, class=external |
|
www.example as link, class=external |
|
http://www.example as link, class=external |
|
www.example as link, class=external, See: Alert: Question |
|
http://www.example as link, class=nicelink |
|
<a href=”/form_person?note=Text”>Person</a> |
|
<a href=”/form_person”><img alttext=”Edit” src=”typo3conf/ext/qfq/Resources/Public/icons/edit.gif”></a> |
|
<a target=”_blank” href=”/form_person”><img alttext=”Edit” src=”typo3conf/ext/qfq/Resources/Public/icons/edit.gif”></a> |
|
<a href=”/form_person”><img alttext=”Check” src=”typo3conf/ext/qfq/Resources/Public/icons/checked-green.gif”></a> |
|
<a href=”/form_person”><img alttext=”Check” src=”typo3conf/ext/qfq/Resources/Public/icons/checked-green.gif”></a> |
|
<a href=”typo3conf/ext/qfq/Classes/Api/delete.php?s=badcaffee1234”><span class=”glyphicon glyphicon-trash” ></span>”></a> |
|
<a href=”typo3conf/ext/qfq/Classes/Api/delete.php?s=badcaffee1234”>Delete</a> |
|
<a href=”typo3conf/ext/qfq/Classes/Api/download.php?s=badcaffee1234”>Download</a> |
|
|
|
|
|
|
Alert: Question
Syntax
q[:<alert text>[:<level>[:<positive button text>[:<negative button text>[:<timeout>[:<flag modal>]]]]]]
If a user clicks on a link, an alert is shown. If the user answers the alert by clicking on the ‘positive button’, the browser opens the specified link. If the user click on the negative answer (or waits for timout), the alert is closed and the browser does nothing.
All parameter are optional.
Parameter are separated by ‘:’
To use ‘:’ inside the text, the colon has to be escaped by \. E.g. ‘ok\ : I understand’.
Parameter |
Description |
|---|---|
Text |
The text shown by the alert. HTML is allowed to format the text. Any ‘:’ needs to be escaped. Default: ‘Please confirm’. |
Level |
info (default), success, warning, danger/error |
Positive button text |
Default: ‘Ok’ |
Negative button text |
Default: ‘Cancel’. To hide the second button: ‘-’ |
Timeout in seconds |
Default: 0, no timeout. > 0, after the specified time in seconds, the alert disappears (‘negative answer’). |
Flag modal |
Default: 1, alert behaves modal. 0, Alert does not behave modal and appears on the side. |
Examples:
SQL-Query |
Result |
|---|---|
SELECT ‘p:/form_person|q:Edit Person:warn’ AS _link |
Shows alert with level ‘warn’ |
SELECT ‘p:/form_person|q:Edit Person::I do:Nope’ AS _link |
Instead of ‘Ok’ and ‘Cancel’, the button text will be ‘I do’ and ‘Nope’ |
SELECT ‘p:/form_person|q:Edit Person:::10’ AS _link |
The Alert will be shown 10 seconds |
SELECT ‘p:/form_person|q:Edit Person:::10:0’ AS _link |
The Alert will be shown 10 seconds and is not modal. |
Text before / after link
Renders text before and/or after a link.
Example:
SELECT 'p:{{pageAlias:T}}|t:Reload|v:Some text before |V: some text after' AS _linkA typical use case is to get several
AS _linkcolumns in one HTML table cell, by still usingfbeg,fend:
{
sql = SELECT p.id
, 'p:{{pageAlias:T}}|t:Reload 1|v:<td>Some text before |V: - ' AS '_link|_noWrap'
, 'p:{{pageAlias:T}}|t:Reload 2|V:</td>' AS '_link|_noWrap'
, p.name
FROM Person AS p
head = <table>
tail = </table>
rbeg = <tr>
rend = </tr>
fbeg = <td>
fend = </td>
}
Ignore/Skip browser history
To ignore/skip adding a browser history entry when clicking on a link, add the attribute data-ignore-history. Example:
{
sql = SELECT 'p:{{pageSlug:T}}?form=test_form&r=2&variable1=test|s|b|t:Button|A:data-ignore-history' AS _link
}
HTTP redirection
To trigger an HTTP redirection use the link column in render mode ‘r:9’. Such a column make QFQ to create an HTTP response with status code 30x and the generated URL as the location header. The code can be specified with an option ‘h:’ parameter that is either the status code number or its alias:
Status code |
Alias |
Meaning |
User agent behavior |
|---|---|---|---|
301 |
Moved permanently |
Request method may change to GET |
|
302 |
Moved temporarily |
Request method may change to GET |
|
303 |
get |
See other |
Request method must change to GET |
307 |
temp |
Moved temporarily |
Request method cannot change |
308 |
perm |
Moved permanently |
Request method cannot change |
Full details under: mdn web docs
The default status code is 303 (see other) that makes the user agent to access the location using the GET request method - this is usually the desired method when the redirection is done after submitting a form to present the result of the action:
a user has logged in or logged out of the page
a file has been uploaded to the server
a user submitted a form to update some records in the database
The HTTP specification did not describe which request method must be used to handle 301 and 302 codes. As a result some user agents switch the method from POST to GET. Later codes 303, 307 and 308 were added to remove this ambiguity.
Example:
{
# Default
sql = SELECT 'p:/form-results|r:9' AS _link
}
{
# Force GET
sql = SELECT 'p:/path-to-resource|r:9|h:get' AS _link
}
{
# Specific HTTP code
sql = SELECT 'u:https://redirect.com/new-location|r:9|h:307' AS _link
}
Column: _authenticate
This is a link column specialized for authenticating users. The main purpose is to
make Typo3 pick a account (selected by its username field) as a logged-in frontend user with the
following additional functionality:
updating the account properties (all except
username,uid,crdate),unlocking a disabled account by setting
disable = 0,extending an expired account by settings its
endtimeproperty,activating an account by setting its
starttimeproperty to the current timestamp,creating an account if none exists for the given
username.
Syntax:
{
sql = SELECT '<username>|<account properties>|<options>|<link rendering>' AS _authenticate
}
Explanation:
<account properties>and<options>are&-separated lists of<key>=<value>pairs.
<link rendering>is a string matching the syntax of the link column.Only the first parameter is required and trailing pipes
|can be omitted.
All keys are allowed for <account properties>, but only the field names of fe_users table
are taken into consideration with the exception of username, uid, crdate, and tstamp.
Field |
Remarks |
|---|---|
|
The login string. Ignored within account properties |
|
The password for this account. If omitted and a user is created, then a long random password is set. |
|
|
|
|
|
|
|
The email of the user. Recommended, because it might be used to look up the user. |
|
|
|
Id of the user that created this record. 0 by default. |
|
The timestamp the record has been modified. Updated automatically. |
|
The timestamp the record has been created. Filled in automatically. |
|
The timestamp when the user has logged in last time. Updated automatically. |
|
The uid of the page to store the record. Overrides ``storage`` option. |
|
The user groups assigned to this user. Overrides ``groups`` option. |
Authentication options
The parameter <options> can be used to customize the behavior of the column. The default values
of most of the recognized options can be specified in qfq extension settings on tab Authentication.
Option name |
Setting name |
Description |
|---|---|---|
|
Create accounts |
Allows to create an account if none exists for the provided username. |
|
Update accounts |
Allows to update accounts with provided user data on successful authentication. |
|
Unlock accounts |
Allows to unlock disabled accounts. |
|
Activate accounts |
Allows to activate inactive or expired accounts. |
|
Account extension time |
The number of seconds (in settings: days) until the account expires. |
|
(none) |
The new expiration timestamp of the account. |
|
User storage |
A comma-separated list of IDs of pages to be searched for the account. The first ID is used for newly created accounts. |
|
*User groups |
A comma-separated list of IDs of frontend user groups that are assigned to a newly created account. |
Notes:
Options with empty strings are ignored from the list, so that their default values are taken.
enableByandenableUntilhave no effect ifactivateis not set.Because accounts are updated only after marked as valid, setting
disable=0in the list of account parameters does not unlock a disabled account and likewise forstarttimeandendtime.The options
storageandgroupscan be overriden with the account propertiespidandusergrouprespectively.
Examples:
{
# Log in a user 'luckyguy'
sql = SELECT 'luckyguy' AS _authenticate
}
{
# This user will be authenticated also when locked, expired, or inactive
sql = SELECT 'luckyguy||unlock=1&activate=1' AS _authenticate
}
{
# Create an account if none exists. Otherwise update the account with provided data,
# but only if enabled - do not unlock not activate.
sql = SELECT 'luckyguy|first_name=Lucky&last_name=Luke&email=lucky.luke@wildwest.com
|create=1&update=1&unlock=0&activate=0' AS _authenticate
}
Rendering links
By default the column generates a HTTP redirection to the current page.
In the present of <link rendering> parameter the following happens:
The rendering mode
r:<mode>always determines how the link is rendered if present. Note that the pure sip moder:8will generate a sip for all parameters except those added by the column itself.In the absence of
r:<mode>the column generates a link a usual if there is a nontrivial content (text, image, button, etc.).Otherwise, an HTTP redirect response is generated with status code 303 (see other).
Example:
{
# Create a button to authenticate a user and redirect to a specific page.
sql = SELECT 'luckyguy||unlock=1|p:/overview|b:1|t:Switch' to luckyguy AS _authenticate
}
Accessing authentication result
The variable {{authResult:V}} stores the result of the authentication. In case the column generates a link or button
(so that it does not trigger a redirection), then same variable can be consulted to check the current status of the account.
Here a value other than OK does not mean that the user cannot be logged-in - all depends on the authentication options.
Value |
After authentication |
After rendering a link |
|---|---|---|
OK |
The user has been authenticated successfully |
There is an unlocked and active account. |
NOT_FOUND |
The account does not exist and was not created. |
No account exists. |
LOCKED |
The account is disabled and was not unlocked. |
The account is disabled. |
EXPIRED |
The account is expired and was not extended. |
The account is expired. |
INACTIVE |
The account is inactive and was not activated. |
The account is inactive. |
Examples:
{
# Create a button to switch to a user and show a warning if the action mail fail
sql = SELECT 'luckyguy|||p:/overview|b:1|t:Switch to luckyguy' AS _authenticate
}
{
sql = SELECT '<p><strong>Caution!</strong> Account status: {{authResult:V}}</p>' FROM DUAL WHERE '{authResult:V}}' != 'OK'
}
{
# Try to switch user, but revert on an failure using
# This triggers a redirection if pass=0
sql = SELECT 'luckyguy|||p:/this-page?switchingUser={{feUser:T}}&pass=1' AS _authenticate
FROM DUAL WHERE '{{pass:S0}}' = 0
}
{
# This triggers a redirection if pass=1 and the authentication attempt has failed
sql = SELECT '{{switchingUser:SE}}|||p:/this-page?pass=2&reason={{authResult:V}}' AS _authenticate
FROM DUAL WHERE '{{pass:S0}}' = 1 AND '{{authResult:V}}' != 'OK'
althead = Authenticated!
}
{
# This is reached only if pass=2
sql = SELECT 'Authentication has failed: {{reason:SE}}' FROM DUAL WHERE '{{pass:S0}}' = 2
}
Extension settings
The extension setting page provides default values for authentication options as well as controls how the service is used by Typo3. Please check extension-manager-qfq-configuration for more information.
Columns: _page[X]
The colum name is composed of the string page and a trailing character to specify the type of the link.
Syntax:
sql = SELECT '[options]' AS _page[<link type>]
With: [options] = [p:<page slug ? param>][|t:<text>][|o:<tooltip>][|q:<question parameter>][|c:<class>][|g:<target>][|r:<render mode>].
This applies to <link type> = c,d,e,h,i,n,s
column name |
Purpose |
default value of question parameter |
Mandatory parameters |
|---|---|---|---|
_page |
Internal link without a graphic |
empty |
p:<pageSlug>[?param] |
_pagec |
Internal link without a graphic, with question |
Please confirm! |
p:<pageSlug>[?param] |
_paged |
Internal link with delete icon (trash) |
Delete record ? |
U:form=<formname>&r=<record id> or
U:table=<tablename>&r=<record id>
|
_pagee |
Internal link with edit icon (pencil) |
empty |
p:<pageSlug>[?param] |
_pageh |
Internal link with help icon (question mark) |
empty |
p:<pageSlug>[?param] |
_pagei |
Internal link with information icon (i) |
empty |
p:<pageSlug>[?param] |
_pagen |
Internal link with new icon (sheet) |
empty |
p:<pageSlug>[?param] |
_pages |
Internal link with show icon (magnifier) |
empty |
p:<pageSlug>[?param] |
All parameter are optional.
Optional set of predefined icons.
Optional set of dialog boxes.
Parameter |
Description |
Default value |
Example |
|---|---|---|---|
<page> |
TYPO3 page slug. |
The current page: |
45 application application&N_param1=1045 |
<text> |
Text, wrapped by the link. If there is an icon, text will be displayed to the right of it. |
empty string |
|
<tooltip> |
Text to appear as a ToolTip |
empty string |
|
<question> |
If there is a question text given, an alert will be opened. Only if the user clicks on ‘ok’, the link will be called |
||
<class> |
CSS Class for the <a> tag |
||
<target> |
Parameter for HTML ‘target=’. F.e.: Opens a new window |
empty |
P |
<render mode> |
Show/render a link at all or not. See Render mode |
||
<create sip> |
s |
‘s’: create a SIP |
Column: _paged
These column offers a link, with a confirmation question, to delete one record (mode ‘table’) or a bunch of records (mode ‘form’). After deleting the record(s), the current page will be reloaded in the browser.
Syntax:
{
sql = SELECT 'U:table=<tablename>&r=<record id>|q:<question>|...' AS _paged
}
{
sql = SELECT 'U:form=<formname>&r=<record id>|q:<question>|...' AS _paged
}
If the record to delete contains column(s), whose column name match on %pathFileName% and such a
column points to a real existing file, such a file will be deleted too. If the table contains records where the specific
file is multiple times referenced, than the file is not deleted (it would break the still existing references). Multiple
references are not found, if they use different column names or table names.
Mode: table
table=<table name>r=<record id>
Deletes the record with id ‘<record id>’ from table ‘<table name>’.
Mode: form
form=<form name>r=<record id>
Deletes the record with id ‘<record id>’ from the table specified in form ‘<form name>’ as primary table. Additional action FormElement of type beforeDelete or afterDelete will be fired too.
Examples
{
sql = SELECT 'U:table=Person&r=123|q:Do you want delete John Doe?' AS _paged
}
{
sql = SELECT 'U:form=person-main&r=123|q:Do you want delete John Doe?' AS _paged
}
Columns: _Page[X]
Similar to
_page[X]Parameter are position dependent and therefore without a qualifier!
SELECT ... , "[<page slug>[?param=value&...]] | [text] | [tooltip] | [question parameter] | [class] | [target] | [render mode]" as _Pagee.
Column: _Paged
Similar to
_pagedParameter are position dependent and therefore without a qualifier!
SELECT ..., "[table=<table name>&r=<record id>[¶m=value&...] | [text] | [tooltip] | [question parameter] | [class] | [render mode]" as _Paged.
SELECT ..., "[form=<form name>&r=<record id>[¶m=value&...] | [text] | [tooltip] | [question parameter] | [class] | [render mode]" as _Paged.
Column: _vertical
Use instead Table: vertical column title
Warning
The ‘… AS _vertical’ is deprecated - do not use it anymore.
Render text vertically. This is useful for tables with limited column width. The vertical rendering is achieved via CSS transformations (rotation) defined in the style attribute of the wrapping tag. You can optionally specify the rotation angle.
Syntax:
{
sql = SELECT '<text>|[<angle>]' AS _vertical
}
Parameter |
Description |
Default value |
|---|---|---|
<text> |
The string that should be rendered vertically. |
none |
<angle> |
How many degrees should the text be rotated? The angle is measured clockwise from baseline of the text. |
270 |
The text is surrounded by some HTML tags in an effort to make other elements position appropriately around it. This works best for angles close to 270 or 90.
Minimal Example:
{
sql = SELECT 'Hello' AS _vertical
}
{
sql = SELECT 'Hello|90' AS _vertical
}
{
sql = SELECT 'Hello|-75' AS _vertical
}
Column: _mailto
Simple mode:
SELECT 'info@banana.com' AS _mailtoLink mode:
SELECT 'm:info@banana.com' AS _linkShow the text ‘email’ instead of the link:
SELECT 'm:info@banana.com|t:email' AS _linkMultiple emails:
SELECT 'm:info@banana.com,info@orange.io,all@strawberry.fruit|t:email' AS _linkShow the link as a button:
SELECT 'm:info@banana.com|t:email|b' AS _linkSelect subset of emails:
SELECT 'm:info@banana.com|t:email|select' AS _link
Syntax:
{
sql = SELECT 'm:<email address>[|t:<link text>][|b][|select]'' AS _link
}
Minimal Example:
{
sql = SELECT 'john.doe@example.com' AS _mailto
}
Advanced Example:
{
sql = SELECT 'john.doe@example.com|John Doe' AS _mailto
}
Select emails
If there are a lot of links (e.g. mailing list), sometimes it’s wished to remove a few addresses:
SELECT 'm:info@banana.com,info@orange.io,all@strawberry.fruit|t:mail list|b|select' AS _link
Column: _sendmail
Format:
t:<TO:email[,email]>|f:<FROM:email>|s:<subject>|b:<body>
[|c:<CC:email[,email]]>[|B:<BCC:email[,email]]>[|r:<REPLY-TO:email>]
[|A:<flag autosubmit: on/off>][|g:<grId>][|x:<xId>][|y:<xId2>][|z:<xId3>][|h:<mail header>]
[|e:<subject encode: encode/decode/none>][E:<body encode: encode/decode/none>][|mode:html]
[|C][d:<filename of the attachment>][|F:<file to attach>][|u:<url>][|p:<T3 uri>]
The following parameters can also be written as complete words for ease of use:
to:<email[,email]>|from:<email>|subject:<subject>|body:<body>
[|cc:<email[,email]]>[|bcc:<email[,email]]>[|reply-to:<email>]
[|autosubmit:<on/off>][|grid:<grid>][|xid:<xId>][|xid2:<xId2>][|xid3:<xId3>][|header:<mail header>]
[|mode:html]
Send emails. Every mail will be logged in the table mailLog. Attachments are supported.
Syntax:
{
sql = SELECT 't:john@doe.com|f:jane@doe.com|s:Reminder tomorrow|b:Please dont miss the meeting tomorrow' AS _sendmail
}
{
sql = SELECT 't:john@doe.com|f:jane@doe.com|s:Reminder tomorrow|b:Please dont miss the meeting tomorrow|A:off|g:1|x:2|y:3|z:4' AS _sendmail
}
|
Parameter |
Description |
Required |
|---|---|---|---|
f
from
|
FROM: Sender of the email. Optional: ‘realname <john@doe.com>’ |
yes |
|
t
to
|
email[,email] |
TO: Comma separated list of receiver email addresses. Optional: |
yes |
c
cc
|
email[,email] |
CC: Comma separated list of receiver email addresses. Optional: |
yes |
B
bcc
|
email[,email] |
BCC: Comma separated list of receiver email addresses. Optional: |
yes |
r
reply-to
|
REPLY-TO:email |
Reply-to: Email address to reply to (if different from sender) |
yes |
s
subject
|
Subject |
Subject: Subject of the email |
yes |
b
body
|
Body |
Body: Message - see also: html-formatting |
yes |
h
header
|
Mail header |
Custom mail header: Separate multiple header with \r\n |
yes |
F |
Attach file |
Attachment: File to attach to the mail. Repeatable. |
|
u |
Attach created PDF of a given URL |
Attachment: Convert the given URL to a PDF and attach it the mail. Repeatable. |
|
p |
Attach created PDF of a given T3 URL |
Attachment: Convert the given URL to a PDF and attach it the mail. Repeatable. |
|
d |
Filename of the attachment |
Attachment: Useful for URL to PDF converted attachments. Repeatable. |
|
C |
Concat multiple F|p|u| together |
|
|
A
autosubmit
|
flagAutoSubmit ‘on’ / ‘off’ |
If ‘on’ (default), add mail header ‘Auto-Submitted: auto-send’ - suppress OoO replies |
yes |
g
grid
|
grId |
Will be copied to the mailLog record. Helps to setup specific logfile queries |
yes |
x
xid
|
xId |
Will be copied to the mailLog record. Helps to setup specific logfile queries |
yes |
y
xid2
|
xId2 |
Will be copied to the mailLog record. Helps to setup specific logfile queries |
yes |
z
xid3
|
xId3 |
Will be copied to the mailLog record. Helps to setup specific logfile queries |
yes |
e |
encode|decode|none |
Subject: will be htmlspecialchar() encoded, decoded (default) or none (untouched) |
|
E |
encode|decode|none |
Body: will be htmlspecialchar() encoded, decoded (default) or none (untouched). |
|
mode |
html |
Body: will be send as a HTML mail. |
e|E: By default, QFQ stores values ‘htmlspecialchars()’ encoded. If such values have to send by email, the html entities are unwanted. Therefore the default setting for ‘subject’ und ‘body’ is to decode the values via ‘htmlspecialchars_decode()’. If this is not wished, it can be turned off by
e=noneand/orE=none.
Minimal Example:
{
sql = SELECT 't:john.doe@example.com|f:company@example.com|s:Latest News|b:The new version is now available.' AS _sendmail
}
This will send an email with subject Latest News from company@example.com to john.doe@example.com.
Advanced Examples:
{
sql = SELECT 't:customer1@example.com,Firstname Lastname <customer2@example.com>, Firstname Lastname <customer3@example.com>| \
f:company@example.com|s:Latest News|b:The new version is now available.|r:sales@example.com|A:on|g:101|x:222|c:ceo@example.com|B:backup@example.com' AS _sendmail
}
This will send an email with subject Latest News from company@example.com to customer1, customer2 and customer3 by using a realname for customer2 and customer3 and suppress generating of OoO answer if any receiver is on vacation. Additional the CEO as well as backup will receive the mail via CC and BCC.
For debugging, please check Redirect all mail to (catch all).
Mail Body HTML Formatting
In order to send an email with HTML formatting, such as bold text or bullet lists, specify ‘mode=html’. The subsequent contents will be interpreted as HTML and is rendered correctly by most email programs.
Attachment
The following options are provided to attach files to an email:
Token |
Example |
Comment |
|---|---|---|
F |
|
Single file to attach |
u |
|
A URL, will be converted to a PDF and than attached. |
p |
|
A SIP protected local T3 page. Will be converted to a PDF and than attached. |
d |
|
Name of the attachment in the email. |
C |
|
Concatenate all named sources to one PDF file. The sources have to be PDF files or a web page, which will be converted to a PDF first. |
Any combination (incl. repeating them) are possible. Any source will be added as a single attachment.
Optional any number of sources can be concatenated to a single PDF file: ‘C|F:<file1>|F:<file2>|p:export&a=123’.
Examples in Report:
{
# One file attached.
sql = SELECT 't:john.doe@example.com|f:company@example.com|s:Latest News|b:The new version is now available.|F:fileadmin/summary.pdf' AS _sendmail
}
{
# Two files attached.
sql = SELECT 't:john.doe@example.com|f:company@example.com|s:Latest News|b:The new version is now available.|F:fileadmin/summary.pdf|F:fileadmin/detail.pdf' AS _sendmail
{
}
# Two files and a webpage (converted to PDF) are attached.
sql = SELECT 't:john.doe@example.com|f:company@example.com|s:Latest News|b:The new version is now available.|F:fileadmin/summary.pdf|F:fileadmin/detail.pdf|p:?id=export&r=123|d:person.pdf' AS _sendmail
}
{
# Two webpages (converted to PDF) are attached.
sql = SELECT 't:john.doe@example.com|f:company@example.com|s:Latest News|b:The new version is now available.|p:?id=export&r=123|d:person123.pdf|p:?id=export&r=234|d:person234.pdf' AS _sendmail
}
{
# One file and two webpages (converted to PDF) are *concatenated* to one PDF and attached.
sql = SELECT 't:john.doe@example.com|f:company@example.com|s:Latest News|b:The new version is now available.|C|F:fileadmin/summary.pdf|p:?id=export&r=123|p:?id=export&r=234|d:complete.pdf' AS _sendmail
}
{
# One T3 webpage, protected by a SIP, are attached.
sql = SELECT 't:john.doe@example.com|f:company@example.com|s:Latest News|b:The new version is now available.|p:?id=export&r=123&_sip=1|d:person123.pdf' AS _sendmail
}
Column: _img
Renders images. Allows to define an alternative text and a title attribute for the image. Alternative text and title text are optional.
If no alternative text is defined, an empty alt attribute is rendered in the img tag (since this attribute is mandatory in HTML).
If no title text is defined, the title attribute will not be rendered at all.
Syntax:
{
sql = SELECT '<path to image>|[<alt text>]|[<title text>]' AS _img
}
Parameter |
Description |
Default value/behaviour |
|---|---|---|
<pathtoimage> |
The path to the image file. |
none |
<alttext> |
Alternative text. Will be displayed if image can’t be loaded (alt attribute of img tag). |
empty string |
<titletext> |
Text that will be set as image title in the title attribute of the img tag. |
no title attribute rendered |
Minimal Example:
{
sql = SELECT 'fileadmin/img/img.jpg' AS _img
}
Advanced Examples:
sql = SELECT 'fileadmin/img/img.jpg|Alternative Text' AS _img # alt='Alternative Text', no title
sql = SELECT 'fileadmin/img/img.jpg|Alternative Text|' AS _img # alt='Alternative Text', no title
sql = SELECT 'fileadmin/img/img.jpg|Alternative Text|Title Text' AS _img # alt='Alternative Text', title='Title Text'
sql = SELECT 'fileadmin/img/img.jpg|Alternative Text' AS _img # alt='Alternative Text', no title
sql = SELECT 'fileadmin/img/img.jpg' AS _img # empty alt, no title
sql = SELECT 'fileadmin/img/img.jpg|' AS _img # empty alt, no title
sql = SELECT 'fileadmin/img/img.jpg||Title Text' AS _img # empty alt, title='Title Text'
sql = SELECT 'fileadmin/img/img.jpg||' AS _img # empty alt, no title
Column: _exec
Run any command on the web server.
The command is run via web server, so with the uid of the web server.
The current working directory is the current web instance (e.g.
/var/www/html) .All text send to stdout will be returned.
Text send to ‘stderr’ is not returned at all.
If ‘stderr’ should be shown, redirect the output:
SELECT 'touch /root 2>&1' AS _exec
If stdout / stderr should not be displayed, redirect the output:
SELECT 'touch /tmp >/dev/null' AS _exec
SELECT 'touch /root 2>&1 >/dev/null' AS _exec
Multiple commands can be concatenated by
;:
SELECT 'date; date' AS _exec
If the return code is not 0, the string
[<rc>], will be prepended.If it is not wished to see the return code, just add
trueto fake rc of 0 (only the last rc will be reported):
SELECT 'touch /root; true' AS _exec
Minimal Example:
{
sql = SELECT 'ls -s' AS _exec
}
{
sql = SELECT './batchfile.sh' AS _exec
}
To reuse the output or to show later :
{
sql = SELECT 'ls -s' AS '_exec|_hide|listing'
...
}
{
# Check the '&' before the variable name: without you'll see the command, not the output of the command.
sql = SELECT ... {{&listing:R}}...
}
Column: _script
Run a php function defined in an external script.
All column parameters are passed as an associative array to the function as the first argument.
The second argument (here called $qfq) is an object which acts as an interface to QFQ functionality (see below).
- The current working directory inside the function is the current web instance (e.g. location of index.php).
Hint: Inside the script
dirname(__FILE__)gives the path of the script.
All output (e.g. using echo) will be rendered by the special column as is.
If the function returns an associative array, then the key-value pairs will be accessible via the VARS store ``V``.
If the function throws an exception then a standard QFQ error message is shown.
Text sent to ‘stderr’ by the php function is not returned at all.
The script has access to the following qfq php functions using the interface (see examples below):
$qfq::apiCall($method, $url, $data = '', $header = [], $timeout = 5)arguments:
string $method: can be PUT/POST/GET/DELETE
string $url
string $data: a JSON string which will be added as GET parameters or as POST fields respectively.
array $header: is of the form [‘Content-type: text/plain’, ‘Content-length: 100’]
int $timeout: is the number of seconds to wait until call is aborted.
return array:
[0]: Http status code
[1]: API answer as string.
$qfq::getVar($key, $useStores = 'FSRVD', $sanitizeClass = '', &$foundInStore = '', $typeMessageViolate = 'c')arguments:
string $key: is the name of qfq variable
string $useStores: are the stores in which variable is searched (in order from left to right). see Store.
string $sanitizeClass: (see Sanitize class)
string $foundInStore: is filled with the name of the store in which the variable was found.
string $typeMessageViolate: defines what to return if the sanitize class was violated:
‘c’ : returns ‘!!<sanitize class>!!’
‘0’ : returns ‘0’
‘e’ : returns ‘’
return string|false:
The value of the variable if found.
A placeholder if the variable violates the sanitize class. (see argument $typeMessageViolate)
false if the variable was not found.
Column Parameters
Token |
Example |
Comment |
|---|---|---|
F |
|
Path to the custom script relative to the current web instance |
call |
|
PHP function to call |
arg |
|
Arguments are parsed and passed to the function together with the other parameters |
Example
QFQ report :
{
sql = SELECT 'IAmInRecordStore' AS _savedInRecordStore
}
{
sql = SELECT 'F:fileadmin/scripts/my_script.php|call:my_function|arg:a1=Hello&a2=World' AS _script
}
{
sql = SELECT '<br><br>Returned value: {{IAmInVarStore:V:alnumx}}'
}
PHP script (
fileadmin/scripts/my_script.php) :
<?php
function my_function($param, $qfq) {
echo 'The first argument contains all attributes including "F" and "c":<br>';
print_r($param);
echo '<br><br>get variable from record store:<br>';
print_r($qfq::getVar('savedInRecordStore', 'RE'));
echo '<br><br>Make API call:<br>';
list($http_code, $answer) = $qfq::apiCall('GET', 'google.com');
echo 'Http code: ' . $http_code;
// Returned array fills VARS store
return ["IAmInVarStore" => "FooBar"];
}
Output:
The first argument contains all parameters including "F", "call" and "arg": Array ( [a1] => Hello [a2] => World [F] => fileadmin/scripts/my_script.php [call] => my_function [arg] => a1=Hello&a2=World ) get variable from record store: IAmInRecordStore Make API call: Http code: 301 Returned value: FooBar
Column: _pdf | _file | _zip
Detailed explanation: Download
Most of the other Link-Class attributes can be used to customize the link. :
{
sql = SELECT "[options]" AS _pdf, "[options]" AS _file, "[options]" AS _zip
}
with: [options] = [d:<exportFilename][|p:<params>][|U:<params>][|u:<url>][|F:file[:path/file in zip]][|t:<text>][|a:<message>][|o:<tooltip>][|c:<class>][|r:<render mode>]
Parameter are position independent.
<params>: see Parameter and (element) sources
For column
_pdfand_zip, the element sourcesp:...,U:...,u:...,F:...might repeated multiple times.For column
_zip, an optional parameter might define the path and filename inside the ZIP:F:<orig filename>:<inside ZIP path and filename>.To only render the page content (mainly for PDF) without header/footer/menus add the parameter
type=2. For example:U:id=pageToPrint&type=2&_sip=1&r=', r.id. This is a configuration via Typocscript and depends on the current Typo3 Temnplate.Example:
# ... AS _file
sql = SELECT 'F:fileadmin/test.pdf' as _pdf
sql = SELECT 'p:id=export&r=1' as _pdf
sql = SELECT 't:Download PDF|F:fileadmin/test.pdf' as _pdf
sql = SELECT 't:Download PDF|p:id=export&r=1' as _pdf
sql = SELECT 'd:complete.pdf|t:Download PDF|F:fileadmin/test1.pdf|F:fileadmin/test2.pdf' as _pdf
sql = SELECT 'd:complete.pdf|t:Download PDF|F:fileadmin/test.pdf|p:id=export&r=1|u:www.example.com' AS _pdf
# ... AS _file
sql = SELECT 'F:fileadmin/test.pdf' as _file
sql = SELECT 'p:id=export&r=1' as _file
sql = SELECT 't:Download PDF|F:fileadmin/test.pdf' as _file
sql = SELECT 't:Download PDF|p:id=export&r=1' as _file
# ... AS _zip
sql = SELECT 'F:fileadmin/test.pdf' as _zip
sql = SELECT 'p:id=export&r=1' as _zip
sql = SELECT 't:Download ZIP|F:fileadmin/test.pdf' as _zip
sql = SELECT 't:Download ZIP|p:id=export&r=1' as _zip
# Several files
sql = SELECT 'd:complete.zip|t:Download ZIP|F:fileadmin/test1.pdf|F:fileadmin/test2.pdf' as _zip
# Several files with new path/filename
sql = SELECT 'd:complete.zip|t:Download ZIP|F:fileadmin/test1.pdf:data/file-1.pdf|F:fileadmin/test2.pdf:data/file-2.pdf' as _zip
Column: _savePdf
Generated PDFs can be stored directly on the server with this functionality. The link query consists of the following parameters:
One or more element sources (such as
F:,U:,p:, see Parameter and (element) sources), including possible wkhtmltopdf parametersThe export filename and path as
d:- for security reasons, this path has to start with fileadmin/ and end with .pdf.
Tip
Please note that this option does not render anything in the front end, but is executed each time it is parsed. You may want to add a check to prevent multiple execution.
It is not advised to generate the filename with user input for security reasons.
If the target file already exists it will be overwritten. To save individual files, choose a new filename, for example by adding a timestamp.
Example:
sql = SELECT 'd:fileadmin/result.pdf|F:fileadmin/_temp_/test.pdf' AS _savePdf
sql = SELECT 'd:fileadmin/result.pdf|F:fileadmin/_temp_/test.pdf|U:id=test&--orientation=landscape' AS _savePdf
Column: _saveZip
Save generated ZIP locally on the server.
One or more element sources (such as
F:,U:,p:, see Parameter and (element) sources), including possible wkhtmltopdf parameters.Element sources given with
U:orp:will first generated as pdf files before they are added to ZIP. Generated pdf files will be named “file-#.pdf”.The export filename and path as
d:- for security reasons, this path has to start with fileadmin/ and end with .zip.If the target file already exists it will be overwritten.
This option does not render anything in the front end, but is executed each time it is parsed! Add a check to prevent unnecessary execution.
Example:
sql = SELECT 'd:fileadmin/result.zip|F:fileadmin/_temp_/test.pdf' AS _saveZip
sql = SELECT 'd:fileadmin/result.zip|F:fileadmin/_temp_/test.pdf|U:id=test&--orientation=landscape' AS _saveZip
sql = SELECT 'd:fileadmin/result.zip|F:fileadmin/_temp_/test.pdf|F:fileadmin/_temp_/test2.xlsx|U:id=test&--orientation=landscape' AS _saveZip
Column: _thumbnail
For file T:<pathFileName> a thumbnail will be rendered, saved (to be reused) and a HTML <img> tag is returned,
With the SIP encoded thumbnail.
The thumbnail:
Size is specified via
W:<dimension>. The file is only rendered once and subsequent access is delivered via a local QFQ cache.Will be (re)rendered, if the source file is newer than the thumbnail or if the thumbnail dimension changes.
The caching is done by building the MD5 of pathFileName and thumbnail dimension.
Of multi page files like PDFs, the first page is used as the thumbnail.
All file formats, which ‘convert’ ImageMagick (https://www.imagemagick.org/) supports, can be used. Office file formats are not supported. Due to speed and quality reasons, SVG files will be converted by inkscape. If a file format is not known, QFQ tries to show a corresponding file type image provided by Typo3 - such an image is not scaled.
In Configuration the exact location of convert and inkscape can be configured (optional) as well as the directory
names for the cached thumbnails.
Token |
Example |
Comment |
|---|---|---|
T |
|
File render a thumbnail |
W |
|
Dimension of the thumbnail: ‘<width>x<height>. Both parameter are optional. If non is given the default is W:150x |
s |
|
Optional. Default: |
r |
|
Render Mode. Default ‘r:0’. With ‘r:7’ only the url will be delivered. |
a |
|
Add an alttext. Default is “thumbnail”. |
The render mode ‘7’ is useful, if the URL of the thumbnail have to be used in another way than the provided html-‘<img>’
tag. Something like <body style="background-image:url(bgimage.jpg)"> could be solved with
SELECT '<body style="background-image:url(", 'T:fileadmin/file3.pdf' AS _thumbnail, ')">'
Example:
# SIP protected, IMG tag, thumbnail width 150px
sql = SELECT 'T:fileadmin/file3.pdf' AS _thumbnail
# SIP protected, IMG tag, thumbnail width 50px
sql = SELECT 'T:fileadmin/file3.pdf|W:50' AS _thumbnail
# No SIP protection, IMG tag, thumbnail width 150px
sql = SELECT 'T:fileadmin/file3.pdf|s:0' AS _thumbnail
# SIP protected, only the URL to the image, thumbnail width 150px
sql = SELECT 'T:fileadmin/file3.pdf|s:1|r:7' AS _thumbnail
Dimension
ImageMagick support various settings to force the thumbnail size. See https://www.imagemagick.org/script/command-line-processing.php#geometry or http://www.graphicsmagick.org/GraphicsMagick.html#details-geometry.
Cleaning
By default, the thumbnail directories are never cleaned. It’s a good idea to install a cronjob which purges all files older than 1 year:
find /path/to/files -type f -mtime +365 -delete
Render
Public thumbnails are rendered at the time when the T3 QFQ record is executed. Secure thumbnails are rendered when the ‘download.php?s=…’ is called. The difference is, that the ‘public’ thumbnails blocks the page load until all thumbnails are rendered, instead the secure thumbnails are loaded asynchronous via the browser - the main page is already delivered to browser, all thumbnails appearing after a time.
A way to pre render thumbnails, is a periodically called (hidden) T3 page, which iterates over all new uploaded files and
triggers the rendering via column _thumbnail.
Thumbnail: secure
Mode ‘secure’ is activated via enabling SIP (s:1, default). The thumbnail is saved under the path thumbnailDirSecure
as configured in Configuration.
The secure path needs to be protected against direct file access by the webmaster / webserver configuration too.
QFQ returns a HTML ‘img’-tag:
<img src="api/download.php?s=badcaffee1234">
Thumbnail: public
Mode ‘public’ has to be explicit activated by specifying s:0. The thumbnail is saved under the path thumbnailDirPublic
as configured in Configuration.
QFQ returns a HTML ‘img’-tag:
<img src="{{thumbnailDirPublic:Y}}/<md5 hash>.png">
Column: _monitor
Detailed explanation: Monitor
Syntax:
sql = SELECT 'file:<filename>|tail:<number of last lines>|append:<0 or 1>|interval:<time in ms>|htmlId:<id>' AS _monitor
Parameter |
Description |
Default value/behaviour |
|---|---|---|
<filename> |
The path to the file. Relative to T3 installation directory or absolute. |
none |
<tail> |
Number of last lines to show |
30 |
<append> |
0: Retrieved content replaces current. 1: Retrieved content will be added to current. |
0 |
<htmlId> |
Reference to HTML element to whose content replaced by the retrieve one. |
monitor-1 |
Column: _encrypt
Encrypting selected fields or strings with AES. It is possible to give your own encryption method. If no method is given then the default is used. See here for more information about default method: Extension Manager: QFQ Configuration
Syntax:
sql = SELECT firstName AS _encrypt FROM Person WHERE id = 1
sql = SELECT 'Words to be encrypted' AS _encrypt=AES-128
A useful situation:
sql = SELECT 'Words to be encrypted' AS '_encrypt=AES-128|encryptedValue|_hide'
sql = UPDATE Person SET secret = '{{&encryptedValue:RE:all}}' WHERE id = 1
Valid encryption methods:
AES-128
AES-256
Column: _decrypt
Decrypting selected columns or strings which are encrypted with QFQ.
Syntax:
sql = SELECT secret AS _decrypt FROM Person WHERE id = 1
Column: _upload
Creates an upload field which allows to upload files per drag and drop or over file browser. There is a qfq delivered table named FileUpload which will be used to store the upload information’s as default. The files will be stored directly in destination folder, no need to trigger something after upload.
Token |
Default value |
Note |
|---|---|---|
uploadId |
0 (Using this token is optional) |
Load record id from upload table. If multi upload: Column uploadId will be used as reference instead of id. |
F |
fileadmin/protected/upload/[currentYear]/ |
File destination path |
x |
1 |
Delete file option for preloaded files |
table |
FileUpload |
DB destination table |
M |
0 |
Enable multi upload option |
maxFileSize |
(none, takes maxFileSize from QFQ config) |
Max. allowed file size. Extension Manager: QFQ Configuration |
maxImageDimension |
(takes maxImageDimension from QFQ config) |
Max Image Upload Dimensions expected format 4000x4000. Extension Manager: QFQ Configuration |
accept |
application/pdf |
Allowed file types. See Type: upload |
allowUpload |
true |
Allow file upload. Download is always enabled. |
t |
Drag and drop or <span class=”btn btn-default filepond–label-action”> Browse </span> |
Upload field text |
recordData |
(none) |
Define own column values to pass in upload table. “recordData:xId:23,grId:125” |
maxFiles |
(unlimited if multi upload) |
Allow a max count of files (multi upload) |
dbIndex |
(QFQ db index number) |
Database index for destination table |
r |
(none) |
r:3 = Readonly (no Upload, no Download), r:5 (not shown) |
uploadAction |
(none, takes uploadAction from QFQ config) |
none: Don’t check(cannot be combined with other options) denyProtect: Deny if the PDF is protected. unProtect: Try to unProtect PDF (does not deny it fails) denyAcrobatOnly: Deny if the PDF requires Adobe Reader. |
- The upload destination table must have at least following columns:
id (unique)
pathFileName
uploadId
fileSize
mimeType
ord
More columns are optional and up to you.
Base64 Encrypt File Names:
To Base64 encode the file names, use {{filenameUnique:V}} in the File Destination Path. This will encode all files and allow uploading files that have the same name in the same directory.
The download name will remain the same as the upload name and does not require any extra configuration.
Optionally, you can add a prefix to the file name by using Pre_{{filenameUnique:V}}. This prefix will then also be added to the downloaded file name.
Example:
sql = SELECT 'uploadId:0|F:fileadmin/dummy/{{filenameUnique:V}}' AS _upload
sql = SELECT 'uploadId:0|F:fileadmin/dummy/Pre_{{filenameUnique:V}}' AS _upload
For multi-database setups with destination tables outside the QFQ database index, define them using the dbIndex parameter.
Syntax:
sql = SELECT 'uploadId:0' AS _upload
sql = SELECT 'uploadId:2|M|x:0' AS _upload
sql = SELECT 'uploadId:0|F:fileadmin/protected/testfolder/file123.png|dbIndex:2|x|M|accept:image/*|recordData:xId:23,grId:125' AS _upload
If multi upload is enabled then the given uploadId references to the column uploadId otherwise it will reference to the column id.
Column: _jwt
Creates a json web token from the provided data.
Supported options:
Parameter |
Default value |
Note |
|---|---|---|
alg |
HS256 |
The signing algorithm - it is included in the header |
key |
(none) |
The secret key used for signing the token |
Predefined claims:
Claim |
Present |
Default value |
Note |
|---|---|---|---|
iss |
always |
qfq |
The default value might be also specified in QFQ settings |
iat |
always |
current timestamp |
Ignores any provided value |
exp |
when specified |
none |
Prefix with + to specify a relative timestamp |
nbf |
when specified |
none |
Prefix with + to specify a relative timestamp |
Syntax:
sql = SELECT 'exp:+3600,data:{msg:default alg}|<secret key>' AS _jwt
sql = SELECT 'exp:+60,data:{msg:explicit agl}|<secret key>|ES384' AS _jwt
Column: _input
Note
Use case: interactive page to show filtered content, updated on each filter change.
A FormElement in a report, which acts as a filter to select specific content (=rows from a QFQ report). On page load and on change, it triggers a QFQ function call to update the shown content. The input element itself is configured as a regular QFQ FormElement in any form.
Renders a HTML input element based on a FormElement definition. No HTML form-tag is rendered. Instead an AJAX call to a QFQ Function is triggered on modify/focus lost. The return content replaces the content of a HTML with a custom defined CSS class name. This can be used to modify data and/or to reload content on the page in time (without page reload).
Supported FormElement types:
select - Dropdown with single selection (see Type: select)
select with size > 1 - Multiple selections with checkbox list (set
FormElement.size > 1), with scrollbars if needed.text - Free text search input (no
sql1required; for count (see below) display,sql1must return the count)checkbox - Single checkbox toggle (use
checkedanduncheckedparameters to define values)checkbox with checkBoxMode=multi - Multiple checkboxes for multi-value filtering (similar to
select with size >1)radio - Radio button group for single selection with visual button style
The definition for such elements are saved as regular FormElement in a Form. The Form itself don’t have to be used somewhere, it’s just a container to hold such elements.
Syntax:
{
sql = SELECT 'f:<formName>.<feName>|uid:<qfqFunctionName>|targetClass:<targetContainerClass>' AS _input
head = <div class="filter-panel">
tail = </div>
<div class="<targetContainerClass>"></div>
}
Required tokens:
Token |
Description |
|---|---|
|
Reference to a FormElement (form name + FormElement name) |
|
Name (=tt-content ‘subheader’) of a QFQ Function.
Optional parameters: |
|
CSS class of the HTML element, which is replaced by the AJAX call.
(without leading dot, e.g., |
Optional tokens:
Token |
Description |
|---|---|
|
Text/HTML rendered before the input element |
|
Text/HTML rendered after the input element |
|
Tooltip on the input element (highest priority, overrides DB value) |
|
Render mode: 0=show, 3=readonly, 5=hidden |
Example - Filter Panel:
The example generates 3 product filters (category, brand, text) and shows/triggers the corresponding report. Modifying a filter updates the matches immediately.
QFQ Form productFilters contains 3 FormElements:
FormElement with name =
categoryId(type: select) -sql1returns categoriesFormElement with name =
brandId(type: select) -sql1returns brandsFormElement with name =
searchText(type: text) - free text search
Example sql1 for categoryId FormElement (with dynamic counts):
{{!SELECT c.id AS id, c.name AS label, COUNT(p.id) AS count
FROM categories AS cat
LEFT JOIN products prod
ON prod.categoryId = cat.id
AND ('{{brandId:RE:alnumx}}' = '' OR FIND_IN_SET(prod.brandId, '{{brandId:RE:alnumx}}'))
AND ('{{searchText:RE:allbut}}' = '' OR prod.name LIKE CONCAT('%', '{{searchText:RE:allbut}}', '%'))
GROUP BY cat.id
ORDER BY cat.name}}
The count column enables dynamic count display (e.g., “Electronics (15)”). Counts are
refreshed via AJAX when other filters change. Filter values are available in STORE_RECORD (:R).
The QFQ function productList returns the newly filtered result (wthout any filter values, it shows all):
render = api
{
sql = SELECT prod.name, CONCAT(prod.price, ' EUR') AS price,
IFNULL(cat.name, '-') AS category,
IFNULL(b.name, '-') AS brand
FROM products AS prod
LEFT JOIN categories AS cat ON prod.categoryId = cat.id
LEFT JOIN brands AS b ON prod.brandId = b.id
WHERE ('{{categoryId:CE:digit}}' = '' OR FIND_IN_SET(prod.categoryId, '{{categoryId:CE:digit}}'))
AND ('{{brandId:CE:digit}}' = '' OR FIND_IN_SET(prod.brandId, '{{brandId:CE:digit}}'))
AND ('{{searchText:CE:allbut}}' = '' OR prod.name LIKE CONCAT('%', '{{searchText:CE:allbut}}', '%'))
ORDER BY prod.name
head = <table class="table table-striped tablesorter tablesorter-pager tablesorter-filter">
<thead><tr><th>Product</th><th>Price</th><th>Category</th><th>Brand</th></tr></thead>
<tbody>
tail = </tbody></table>
rbeg = <tr>
rend = </tr>
fbeg = <td>
fend = </td>
althead = No products found
}
Product page:
# Form: productFilters
# FormElements: categoryId, brandId, searchText
# QFQ Function: productList
# Target container (CSS class): product-results
{
sql = SELECT "f:productFilters.categoryId|uid:productList|targetClass:product-results" AS _input
, "f:productFilters.brandId|uid:productList|targetClass:product-results" AS _input
, "f:productFilters.searchText|uid:productList|targetClass:product-results|o:Search products" AS _input
head = <div class="filter-panel">
tail = </div>
<div class="product-results"></div>
}
Example - Checkbox and Radio filters:
For a single checkbox filter (e.g., “Show only active”), create a FormElement with:
type =
checkboxchecked =
yes(value when checked)unchecked =
no(value when unchecked)sql1 = Query returning
countfor dynamic count display
For a radio button filter, create a FormElement with:
type =
radiosql1 = Query returning
id,label, and optionallycount
{
# Single checkbox: "Show only available"
sql = SELECT "f:productFilters.available|uid:productList|targetClass:product-results" AS _input
# Radio buttons: "Sort by"
sql = SELECT "f:productFilters.sortOrder|uid:productList|targetClass:product-results" AS _input
# Multi-checkbox: "Features" (checkBoxMode=multi in FormElement)
sql = SELECT "f:productFilters.features|uid:productList|targetClass:product-results" AS _input
head = <div class="filter-panel">
tail = </div>
<div class="product-results"></div>
}
Features:
URL persistence - Filter values are stored in the URL for bookmarking/sharing
Dynamic counts - If the FormElement has
sql1returning a count column, counts are refreshed after each filter changeServer-side validation - Filter values are validated against allowed options (tamper-proof via SIP)
Tablesorter compatible - Tables with class
tablesorterare re-initialized after AJAX load
Preselection via FE.value:
To set a default/initial value for a filter input, use the value field of the FormElement definition.
The value is used when the page loads and no filter value exists in the URL yet.
Example FormElement configuration:
name =
categoryIdtype =
selectvalue =
5(preselects the option with id=5)
For multi-select or multi-checkbox filters, use comma-separated values:
value =
1,3,5(preselects options with id 1, 3 and 5)
For text inputs:
value =
search term(prefills the text input)
Copy to clipboard
Token |
Example |
Comment |
|---|---|---|
y[:<content>] |
|
Initiates ‘copy to clipboard’ mode. Source might given text or page or url |
F:<pathFileName> |
|
pathFileName in DocumentRoot |
Example:
sql = SELECT 'y:hello world (yank)|t:content direct (yank)' AS _yank
, 'y:hello world (link)|t:content direct (link)' AS _link
, CONCAT('F:', p.pathFileName,'|t:File (yank)|o:', p.pathFileName) AS _yank
, CONCAT('y|F:', p.pathFileName,'|t:File (link)|o:', p.pathFileName) AS _link
FROM Person AS p
API Call QFQ Report (e.g. AJAX)
Note
QFQ Report functionality protected by SIP offered to simple API calls: typo3conf/ext/qfq/Classes/Api/dataReport.php?s=....
General use API call to fire a specific QFQ tt-content record. Useful for e.g. AJAX calls. No Typo3 is involved. No FE-Group access control.
This defines just a simple API endpoint. For defining a rest API see: REST.
Custom response headers can be defined by setting the variable apiResponseHeader in the record store.
Multiple headers should be separated by
\nor\r\n. e.g.: Content-Type: application/jsonncustom-header: fooBar
If the api call succeeds the rendered content of the report is returned as is (no additional formatting, no JSON encoding).
You can use MYSQL to create Json. See: MYSQL create Json and MariaDB Json functions
If a QFQ error occurs then a http-status of 400 is returned together with a JSON encoded response of the form:
{"status":"error", "message":"..."}
Example QFQ record JS (with tt_content.uid=12345):
{
sql = SELECT 'See console log for output'
}
{
# Register SIP with given arguments.
sql = SELECT 'U:uid=12345&arg1=Hello&arg2=World|s|r:8' AS '_link|col1|_hide'
# Build JS
tail = <script>
console.log('start api request');
$.ajax({
url: 'typo3conf/ext/qfq/Classes/Api/dataReport.php?s={{&col1:RE}}',
data: {arg3:456, arg4:567},
method: 'POST',
dataType: 'TEXT',
success: function(response, status, jqxhr) {console.log(response); console.log(jqxhr.getAllResponseHeaders());},
error: function(jqXHR, textStatus, errorThrown) {console.log(jqXHR.responseText, textStatus, errorThrown);}
});
</script>
}
Example QFQ record called by above AJAX:
# Create a dedicated tt-content record (on any T3 page, might be on the same page as the JS code).
# The example above assumes that this record has the tt_content.uid=12345.
render = api
{
sql = SELECT '{{arg1:S}} {{arg2:S}} {{arg3:C}} {{arg4:C}}', NOW()
, 'Content-Type: application/json\ncustom-header: fooBar' AS _apiResponseHeader
}
Example text returned by the above AJAX call:
Hello World 456 5672020-09-22 18:09:47
REST Client
Note
POST and GET data to external REST interfaces or other API services.
Access to external services via HTTP / HTTPS is triggered via special column name restClient. QFQ uses the php stream interface for the API calls. Because of that, allow_url_fopen needs to be set to 1 on the installation. The received data can be processed in subsequent calls.
Example:
{
# Retrieve information. Received data is delivered in JSON and decoded / copied on the fly to CLIENT store (CLIENT store is emptied beforehand)
sql = SELECT 'n:https://www.dummy.ord/rest/person/id/123' AS _restClient
}
{
sql = SELECT 'Status: {{http-status:C}}<br>Name: {{name:C:alnumx}}<br>Surname: {{surname:C:alnumx}}'
}
{
# Simple POST request via https. Result is printed on the page.
sql = SELECT 'n:https://www.dummy.ord/rest/person/id/123|method:POST|content:{"name":"John";"surname":"Doe"}' AS _restClient
}
Token |
Example | Comment |
||
|---|---|---|---|
n |
|
||
method |
|
GET, POST, PUT or DELETE |
|
content |
|
Depending on the REST server JSON might be expected |
|
contentFile |
|
Replaces content, if given. Recommended for large data sections, such as binary data for files. |
|
header |
see below |
||
timeout |
|
Default: 5 seconds. |
|
Header
Each header must be separated by
\r\nor\n.An explicit given header will overwrite the named default header.
Default header:
content-type: application/json - if content starts with a
{.content-type: text/plain - if content does not start with a
{.connection: close - Necessary for HTTP 1.1.
Basic Authorization Example
Warning: Only use base64 for SSL encrypted connections:
{
sql = SELECT CONCAT('n:https://sample.com/id/1234|header:Authorization: Basic ', TO_BASE64('{{username}}:{{password}}') )
}
Result received
After a REST client call is fired, QFQ will wait up to timeout seconds for the answer.
By default, the whole received answer will be shown. To suppress the output:
... AS '_restClient|_hide'The variable
{{http-status:C}}shows the HTTP status code. A value starting with ‘2..’ shows success.In case of an error, the HTTP status code is set to 0 and
{{error-message:C:allbut}}shows some details.In case the returned answer is a valid JSON string, it is flattened and automatically copied to STORE_CLIENT with corresponding key names.
NOTE: The CLIENT store is emptied beforehand!
JSON answer example:
Answer from Server: { 'name' : 'John', 'address' : {'city' : 'Bern'} }
Retrieve the values via: {{name:C:alnumx}}, {{city:C:alnumx}}
Special SQL Functions (stored procedure)
QBAR: Escape QFQ Delimiter
The SQL function QBAR(text) replaces “|” with “\|” in text to prevent conflicts with the QFQ special column notation. In general this function should be used when there is a chance that unplanned ‘|’-characters occur.
Example:
{
sql = SELECT CONCAT('p:notes|t:Information: ', QBAR(Note.title), '|b') AS _link FROM Note
}
In case ‘Note.title’ contains a ‘|’ (like ‘fruit | numbers’), it will confuse the ‘… AS _link’ class. Therefore it’s
necessary to ‘escape’ (adding a ‘' in front of the problematic character) the bar which is done by using QBAR().
QENT_ENCODE: Encode HTML Entities
The SQL function QENT_ENCODE(input_text TEXT) encodes special characters in the input text into HTML-safe numeric entities.
This is especially useful when working with user-provided content or dynamic values that may include characters which conflict with HTML or QFQ templates.
Example:
SELECT QENT_ENCODE('<script>alert("x")</script>');
Result:
<script>alert("x")</script>
Characters that are converted include:
- Non-ASCII characters (code > 127)
- Special characters: " & ' < > { }
Limitation:
This works reliably for characters within the Basic Multilingual Plane (BMP), like Ä, é, or Ü, but it does not handle surrogate pairs or characters outside BMP (e.g., certain emoji or historic scripts).
Use this function to sanitize output and avoid interpretation issues in HTML or QFQ contexts.
QENT_DECODE: Decode HTML Entities
The SQL function QENT_DECODE(input_text TEXT) decodes both HTML numeric and common named entities back into their original characters.
This is useful when you’ve previously encoded values for safe transport or rendering and now want to recover the readable form.
Example:
SELECT QENT_DECODE('<div>Hello & Welcome</div>');
Result:
<div>Hello & Welcome</div>
The decoder handles:
- Named entities: &, <, >, ", ', {, },
- Numeric entities: { (decimal) and { (hexadecimal)
Note: Only ASCII characters (code <= 127) are decoded from numeric entities. Invalid or non-ASCII codes are skipped.
Use this to reverse QENT_ENCODE when processing HTML-safe data into user-visible content.
QCC: Escape colon / coma
The SQL function QCC(text) replaces : with \: and , with \,
in text to prevent conflicts with the QFQ notation.
QNL2BR: Convert newline to HTML ‘<br>’
The SQL function QNL2BR(text) replaces LF or CR/LF by <br>. This can be used for data (containing LF) to output on a HTML page with correctly displayed linefeed.
Example:
{
sql = SELECT QNL2BR(Note.title) FROM Note
}
One possibility how LF comes into the database is with form elements of type textarea if the user presses enter inside.
QNBSP: Convert space to ‘ ’
The SQL function QNBSP(text) replaces ` ` (space) by . This prevents unwanted line breaks in text. E.g. the title ‘Prof. Dr.’ should never be separated: QNBSP(‘Prof. Dr.’)
Example:
{
sql = SELECT QNBSP(Person.title) FROM Person
}
QLEFT: Left truncate text, if cut: add ‘…’
The SQL function QLEFT(text, n) is like LEFT(text, n), but adds ‘…’ at the end if the text is cut. Example:
{
sql = SELECT QLEFT("abcdefghij", 6)
}
Output:
abcdef...
QRIGHT: Right truncate text, if cut: insert ‘…’
The SQL function QRIGHT(text, n) is like RIGHT(text, n), but insert ‘…’ at the beginning if the text is cut. Example:
{
sql = SELECT QRIGHT("abcdefghij", 6)
}
Output:
...efghij
QMORE: Truncate Long Text - click for more/less
The SQL function QMORE(text, n) truncates text if it is longer than n characters and adds a “[…]” button to show there’s more. If the button is clicked, the whole text is displayed.
Technical: The stored procedure QMORE() will wrap a CSS class around the hidden remaining string.
To simplify usage:
The function removes all HTML tags before counting the length.
HTML entities are counted as one character.
If length is greater than max len, the cleaned string is used (loosing any HTML tags) and cut.
HTML enties are protected and won’t be splitted.
Example:
{
sql = SELECT QMORE("Hello World", 7)
, QMORE('<b>World</b>',5)
, QMORE('<b>World</b>',4)
, QMORE('<b>World&</b>',6)
}
Output:
Hello W`[...]`
<b>World</b>
Worl
<b>World&</b>
QIFEMPTY: if empty show token
The SQL function QIFEMPTY(input, token) returns ‘token’ if ‘input’ is ‘empty string’ / ‘0’ / ‘0000-00-00’ / ‘0000-00-00 00:00:00’.
Example:
{
sql = SELECT QIFEMPTY('hello world','+'), QIFEMPTY('','-')
}
Output:
hello world-
QIFPREPEND: if not empty show input with prepend separator
The SQL function QIFPREPEND(separator, input) returns input with prepend separator if input is not an empty string, 0 or NULL.
Example:
{
sql = SELECT 'lastName', QIFPREPEND(', ','title'), QIFPREPEND(', ','')
}
Output:
lastName, title
QDATE_FORMAT: format a timestamp, show ‘-’ if empty
The SQL function QDATE_FORMAT(timestamp) returns ‘dd.mm.YYYY hh:mm’, if ‘timestamp’ is 0 returns ‘-’
Example:
{
sql = SELECT QDATE_FORMAT( '2019-12-31 23:55:41' ), ' / ', QDATE_FORMAT( 0 ), ' / ', QDATE_FORMAT( '' )
}
Output:
31.12.2019 23:55 / - / -
QSLUGIFY: clean a string
Convert a string to only use alphanumerical characters and ‘-’. Characters with accent will be replaced without the accent. Non alphanumerical characters are stripped off. Spaces are replaced by ‘-’. All characters are lowercase.
Example:
{
sql = SELECT QSLUGIFY('abcd ABCD ae.ä.oe.ö.ue.ü z[]{}()<>.,?Z')
}
Output:
abcd-abcd-ae-a-oe-o-ue-u-z-z
QENT_SQUOTE: convert single tick to HTML entity '
Convert all single ticks in a string to the HTML entity “'”
Example:
{
sql = SELECT QENT_SQUOTE("John's car")
}
Output:
John's car
QENT_DQUOTE: convert double tick to HTML entity "
Convert all double ticks in a string to the HTML entity “"”
Example:
{
sql = SELECT QENT_SQUOTE('A "nice" event')
}
Output:
A "nice" event
QESC_SQUOTE: escape single tick
Escape all single ticks with a backslash. Double escaped single ticks (two backslashes) will be replaced by a single escaped single tick.
Example:
# Be Music.style = "Rock'n' Roll"
{
sql = SELECT QESC_SQUOTE(style) FROM Music
}
Output:
Rock\'n\'n Roll
QESC_DQUOTE: escape double tick
Escape all double ticks with a backslash. Double escaped double ticks (two backslashes) will replaced by a single escaped double tick.
Example:
{
# Set Comment.note = 'A "nice" event'
sql = SELECT QESC_DQUOTE(style) FROM Music
}
Output:
Rock\'n\'n Roll
QMANR: format Matrikel-Nr.
The SQL function QMANR(manr) returns ‘00-000-000’, if ‘manr’ is 00000000.
Example:
{
sql = SELECT QMANR(12345678)
}
Output:
12-345-678
QFQ Function
QFQ SQL reports can be reused, similar to a function in a regular programming language, including call parameter and return values.
Per tt-content record the field ‘subheader’ (in Typo3 backend) defines the function name. The function can also referenced by using the tt-content number (uid) - but this is less readable.
The calling report calls the function by defining
<level>.function = <function name>(var1, var2, ...) => return1, return2, ...STORE_RECORD will be saved before calling the function and will be restored when the function has been finished.
The function has only access to STORE_RECORD variables which has been explicitly defined in the braces (var1, var2, …).
Variables can be set to the STORE_RECORD inside the function by using the subrecord detail notation (
var1:var2, &123:var3, &{{var4:C}}:var5, ...). See Type: subrecord.The function return values will be copied to STORE_RECORD after the function finished.
Inside the QFQ function, all other STORES are fully available.
If
<level>.functionand<level>.sqlare both given,<level>.functionis processed first.If
<level>.functionis given, but<level>.sqlnot, the values ofshead, stail, altheadare even processed.If a function outputs something, this is not shown.
The output of a QFQ function is accessible via
{{_output:R}}.It is possible to call functions inside of a function.
If
render = apiis defined in the function, both tt-content records can be saved on the same page and won’t interfere.FE Groups are not respected - don’t use them on QFQ functions.
Example tt-content record for the function:
Subheader: getFirstName
Code:
#
# {{pId:R}}
#
render = api
{
sql = SELECT p.firstName AS _firstName
, NOW() AS now
, CONCAT('p:{{pageSlug:T}}?form=person&r=', p.id ) AS '_pagee|_hide|myLink'
FROM Person AS p
WHERE p.id={{pId:R}}
AND ('{{var2:R}}' = 'val'
OR '{{var4:R}}' = 'val')
}
Example tt-content record for the calling report:
#
# Example how to use `<level>.function = ...`
#
out {
sql = SELECT p.id AS _pId
, p.name
, 'val' AS _var1
FROM Person AS p
ORDER BY p.name
head = <table class="table"><tr><th>Name</th><th>Firstname</th><th>Link (final)</th><th>Link (source)</th><th>NOW() (via Output)</th></tr>
tail = </table>
rbeg = <tr>
renr = </tr>
fbeg = <td>
fend = </td>
alias2 {
function = getFirstName(pId, var1:var2, &{{var3:C}}:var4) => firstName, myLink
}
alias3 {
sql = SELECT '{{firstName:R}}', "{{myLink:R}}", "{{&myLink:R}}", '{{_output:R}}'
fbeg = <td>
fend = </td>
}
}
Explanation:
Level out iterates over all person.
Level out.alias2 calls QFQ function
getFirstName()by delivering thepIdvia STORE_RECORD. The function expects the return valuefirstNameandmyLink.The function selects in level 100 the person given by
{{pId:R}}. ThefirstNameis not printed but a hidden column. Columnnowis printed. Column ‘myLink’ is a rendered link, but not printed.Level out.alias3 prints the return values
firstNameandmyLink(as rendered link and as source definition). The last column is the output of the function - the value ofNOW()
Tip
If there are STORE_RECORD variables which can’t be replaced: typically they have been not defined as function parameter or return values.
Download
Mode |
Security |
Note |
|---|---|---|
Direct File access |
Files are public available. No access restriction
Pro: Simple, links can be copied.
Con: Directory access, guess of filenames, only
removing the file will deny access.
|
Use
<a href="...">Merge multiple sources: no
but check Column: _savePdf
Custom ‘save as filename’: no
|
Persistent Link |
Access is be defined by a SQL statement. In
T3/BE > Extension > QFQ > File > download define a
SQL statement.
Pro: speaking URL, link can be copied, access can
can be defined a SQL statement.
Con: Key might be altered by user, permission
can’t be user logged in dependent.
|
Use
..., 'd:1234|s:0' AS _linkMerge multiple sources: yes
Custom ‘save as filename’: yes
|
Secure Link |
Default. SIP protected link.
Pro: Parameter can’t be altered, most easy definition
in QFQ, access might be logged in user dependent.
Cons: Links are assigned to a browser session and
can’t be copied
|
Use
..., 'd|F:file.pdf' AS _linkMerge multiple sources: yes
Custom ‘save as filename’: yes
|
The rest of this section applies only to Persistent Link and Secure Link. Download offers:
Single file - download a single file (any type),
PDF create - one or concatenate several files (uploaded) and/or web pages (=HTML to PDF) into one PDF output file,
ZIP archive - filled with several files (‘uploaded’ or ‘HTML to PDF’-converted).
Excel - created from scratch or fill a template xlsx with database values.
By using the _link column name:
the option
d:...initiate creating the download link and optional specifiesin
SIPmode: an export filename (),in
persistent linkmode: path download script (optional) and key(s) to identify the record with the PathFilename information (see below).
the optional
M:...(Mode) specifies the export type (file, pdf, qfqpdf, zip, export),the alttext
a:...specifies a message in the download popup.
By using _pdf, _Pdf, _file, _File, _zip, _Zip, _excel as column name, the options d,
M (pdf: wkhtml) and s will be set.
All files will be read by PHP - therefore the directory might be protected against direct web access. This is the preferred option to offer secure downloads via QFQ. Check Secure direct file access.
Base64 Encoded Files
If the file to be downloaded has a Base64-encoded name generated by QFQ and you download it using a secure link,
you can also add a prefix and suffix to the downloaded file name. Use {{filenameDecode:V}} as a placeholder for the decoded file name.
Example:
{
# File Name => test.pdf
sql = SELECT 'd:Prefix_{{filenameDecode:V}}_Suffix|F:<FILE PATH>' AS _link
# File Name Output => Prefix_test_Suffix.pdf
}
Parameter and (element) sources
mode secure link (s:1) - download:
d[:<exportFilename>]exportFilename = <filename for save as> - Name, offered in the ‘File save as’ browser dialog. Default: ‘output.<ext>’.
If there is no
exportFilenamedefined, then the original filename is taken (if there is one, else: output…).The user typically expects meaningful and distinct file names for different download links.
mode
persistent link(s:0) - download:d:d/<tokenName>/<key1>[/<keyN>]This setup is divided in part a) and b):
Part a) - offering the download link.
The whole part a) is optional. The download itself will work without it.
tokenName = name of the token defined in the
sqlDirectconfig (see part b).key1 = give a unique identifier to select the wished record.
Part b) - process the download
In the QFQ extension config: File >
sqlDirect(INI textarea), define one token per line:images = SELECT CONCAT('d|F:', n.pathFileName) FROM Note AS n WHERE n.id=? images.error = Record not found report = SELECT CONCAT('d:output.pdf|F:', n.pathFileName) FROM Note AS n WHERE n.id=? AND NOW()<n.expire
Each token maps to a SQL query with the regular QFQ download link syntax (skip the visual elements like button, text, glyph icon, question,…).
All
?in the SQL statement will be replaced by the specified parameter. If there are more?than parameter, the last parameter will be reused for all pending?.E.g.
sql = SELECT 'd:d/images/1234|t:File.pdf|s:0' AS _linkcreates a link<a href="d.php/images/1234"><span class="btn btn-default">File.pdf</span></span>. If the user clicks on the link, QFQ will parse the PATH_INFO, look up the SQL for tokenimagesin thesqlDirectconfig and fireSELECT CONCAT('d|F:', n.pathFileName) FROM Note AS n WHERE n.id=1234. The download of the file, specified byn.pathFileName, will start.If no record is selected, a custom error will be shown (token-specific via
token.erroror global viasqlDirectError). If the query selects more than one record, a general error will be shown.
Speaking URL)
Instead of using a numeric value reference key, also a text can be used. Always take care that exactly one record is selected. The key is transferred by URL therefore untrusted: The sanitize class alnumx is applied. Example:
Config: person = SELECT CONCAT('d|F:', n.pathFileName) FROM Person AS p WHERE p.name=? AND p.firstName=? AND p.publish='yes' Report: SELECT 'd:d/person/doe/john|s:0' AS _link
popupMessage:
a:<text>- will be displayed in the popup window during download. If the creating/download is fast, the window might disappear quickly.mode:
M:<mode>mode = <file | pdf | qfqpdf | zip | excel>
pdf:
wkhtmlwill be used to render the pdf.qfqpdf:
qfqpdfwill be used to render the pdf.If
M:file, the mime type is derived dynamically from the specified file. In this mode, only one element source is allowed per download link (no concatenation).In case of multiple element sources, only pdf, zip and excel (template mode) is supported.
If
M:zipis used together withp:...,U:...oru:.., those HTML pages will be converted to PDF. Those files get generic filenames inside the archive.If not specified, the default ‘Mode’ depends on the number of specified element sources (=file or web page):
If only one file is specified, the default is file.
If there is a) a page defined or b) multiple elements, the default is pdf.
element sources - for
M:pdf,M:qfqpdforM:zip, all of the following element sources may be specified multiple times. Any combination and order of these options are allowed.file:
F:<pathFileName>- relative or absolute pathFileName offered for a) download (single), or to be concatenated in a PDF or ZIP.page:
p:id=<t3 page>&<key 1>=<value 1>&<key 2>=<value 2>&...&<key n>=<value n>.By default, the options given to wkhtml will not be encoded by a SIP!
To encode the parameter via SIP: Add ‘_sip=1’ to the URL GET parameter.
E.g.
p:id=form&_sip=1&form=Person&r=1.In that way, specific sources for the download might be SIP encrypted.
Any current HTML cookies will be forwarded to/via wkhtml. This includes the current FE Login as well as any QFQ session. Also the current User-Agent are faked via the wkhtml page request.
If there are trouble with accessing FE_GROUP protected content, please check wkhtmltopdf.
url:
u:<url>- any URL, pointing to an internal or external destination.uid:
uid:<function name>- the output is treated as HTML (will be converted to PDF) or EXCEL data.The called tt-content record is identified by function name, specified in the subheader field. Optional the numeric id of the tt-content record (=uid) can be given.
Only the specified QFQ content record will be rendered, without any Typo3 layout elements (Menu, Body,…)
QFQ will retrieve the tt-content’s bodytext from the Typo3 database, parse it, and render it as a PDF or Excel data.
Parameters can be passed:
uid:<tt-content record id>[&arg1=value1][&arg2=value2][...]and will be available via STORE_SIP in the QFQ PageContent, or passed as wkhtmltopdf arguments, if applicable.For more obviously structuring, put the additional tt-content record on the same Typo3 page (where the QFQ tt-content record is located which produces the link) and specify
render = api(Report: render).
source:
source:<function name>[&arg1=value1][&arg2=value2][&...]- (similar to a uid) the output is treated as further sources. Example result reported by function name might be:F:file.pdf1|uid:myData&arg=2|...Use this functionality to define a central managed download function, which can be reused anywhere by just specify the function name and required arguments.
The called tt-content record is identified by function name, specified in the subheader field. Optional the numeric id of the tt-content record (=uid) can be given.
The output of the tt-content record will be treated as further source arguments. Nothing else than valid source references should be printed. Separate the references as usual by ‘|’.
The supplied arguments are available via STORE_SIP (this is different from QFQ Function).
Tip: For more obviously structuring, put the additional tt-content record on the same Typo3 page (where the QFQ tt-content record is located which produces the link) and specify
render = api(Report: render).
M:pdf- WKHTML Options for page, urlParam or url:The ‘HTML to PDF’ will be done via wkhtmltopdf.
All possible options, suitable for wkhtmltopdf, can be submitted in the
p:...,u:...orU:...element source. Check wkhtmltopdf.txt for possible options. Be aware that key/value tuple in the documentation is separated by a space, but to respect the QFQ key/value notation of URLs, the key/value tuple inp:...,u:...orU:...has to be separated by ‘=’. Please see last example below.If an option contains an ‘&’ it must be escaped with double \ . See example.
M:qfqpdf- qfqpdf Options for page, urlParam or url:The ‘HTML to PDF’ will be done via qfqpdf.
Check https://puppeteer.github.io/puppeteer and https://git.math.uzh.ch/bbaer/qfqpdf/-/tree/master
All possible options, suitable for qfqpdf, can be submitted in the
p:...,u:...orU:...element source. Be aware that key/value tuple in the documentation is separated by a space, but to respect the QFQ key/value notation of URLs, the key/value tuple inp:...,u:...orU:...has to be separated by ‘=’. Please see last example below.If an option contains an ‘&’ it must be escaped with double \ . See example.
Page numbering is done via HTML templating / CSS classes:
--header-template '<div style="font-size:5mm;" class="pageNumber"></div>'
Most of the other Link-Class attributes can be used to customize the link as well.
Example _link:
# single `file`. Specifying a popup message window text is not necessary, cause a file directly accessed is fast.
SELECT "d:file.pdf|s|t:Download|F:fileadmin/pdf/test.pdf" AS _link
# single `file`, with mode
SELECT "d:file.pdf|M:pdf|s|t:Download|F:fileadmin/pdf/test.pdf" AS _link
# three sources: two pages and one file
SELECT "d:complete.pdf|s|t:Complete PDF|p:id=detail&r=1|p:id=detail2&r=1|F:fileadmin/pdf/test.pdf" AS _link
# qfqpdf - three sources: two pages and one file
SELECT "d:complete.pdf|M:qfqpdf|s|t:Complete PDF|p:id=detail&r=1|p:id=detail2&r=1|F:fileadmin/pdf/test.pdf" AS _link
# three sources: two pages and one file
SELECT "d:complete.pdf|s|t:Complete PDF|p:id=detail&r=1|p:id=detail2&r=1|F:fileadmin/pdf/test.pdf" AS _link
# three sources: two pages and one file, parameter to wkhtml will be SIP encoded
SELECT "d:complete.pdf|s|t:Complete PDF|p:id=detail&r=1&_sip=1|p:id=detail2&r=1&_sip=1|F:fileadmin/pdf/test.pdf" AS _link
# three sources: two pages and one file, the second page will be in landscape and page size A3
SELECT "d:complete.pdf|s|t:Complete PDF|p:id=detail&r=1|p:id=detail2&r=1&--orientation=Landscape&--page-size=A3|F:fileadmin/pdf/test.pdf" AS _link
# One source and a header file. Note: the parameter to the header URL is escaped with double backslash.
SELECT "d:complete.pdf|s|t:Complete PDF|p:id=detail2&r=1&--orientation=Landscape&--header={{URL:R}}?index.php?id=head\\&L=1|F:fileadmin/pdf/test.pdf" AS _link
# One indirect source reference
SELECT "d:complete.pdf|s|t:Complete PDF|source:centralPdf&pId=1234" AS _link
An additional tt-content record is defined with `sub header: centralPdf`. One or multiple attachments might be concatenated.
SELECT '|F:', a.pathFileName FROM Attachments AS a WHERE a.pId={{pId:S}}
Example _pdf, _zip:
# Page 1: p:id=1&--orientation=Landscape&--page-size=A3
# Page 2: p:id=form
# File 3: F:fileadmin/file.pdf
SELECT 't:PDF|a:Creating a new PDF|p:id=1&--orientation=Landscape&--page-size=A3|p:id=form|F:fileadmin/file.pdf' AS _pdf
# Page 1: p:id=1
# Page 2: u:http://www.example.com
# File 3: F:fileadmin/file.pdf
SELECT 't:PDF - 3 Files|a:Please be patient|p:id=1|u:http://www.example.com|F:fileadmin/file.pdf' AS _pdf
# Page 1: p:id=1
# Page 2: p:id=form
# File 3: F:fileadmin/file.pdf
SELECT CONCAT('t:ZIP - 3 Pages|a:Please be patient|p:id=1|p:id=form|F:', p.pathFileName) AS _zip
Use the --print-media-type as wkhtml option to access the page with media type ‘printer’. Depending on the website
configuration this switches off navigation and background images.
Cache
Parameter: cache[:[timestamp]|[table/id[/column][,...]]
Caching will be enabled if the keyword
cacheis given in the download link definition. Example see below.On the fly rendered files (like PDF, ZIP, Excel) can be cached on the server (_pdf, _zip, _excel, _file, _savePdf, _saveZip).
Any further access won’t trigger a new rendering, instead the already cached file will be delivered.
Cached files will be identified by the md5 sum of their source definition. The md5 name doesn’t affect the final save as filename.
Cached files are saved under
fileadmin/protected/cache. The directory can be configured in the QFQ extension configuration File > cacheDirSecure. See QFQ credentials.A cached file becomes outdated (will be rendered and saved again), if:
The source definition changes: different source definition/md5 leads to a nonexistent cached file.
Any of the source files is newer than the cached one.
Any of the direct given timestamps are younger than the cached file modified timestamp.
Any of the given database records returns a younger timestamp than the cached file modified timestamp.
Optional cache parameter(s) to detect an ‘outdated’ situation:
Multiple timestamp and/or table/id[/column] definitions are supported.
timestamp: format = yyyy-mm-dd [hh[:mm[:ss]]]
table/id[/column]:
Fire query
SELECT <column> FROM <table> WHERE <id>=id. Compare the result with the cached file modification timestamp.Default for column is modified.
If more complex queries are needed, use timestamp instead and precalculate them first.
Purge old cached files:
QFQ extension configuration File > cachePurgeFilesOlderDays. See QFQ credentials.
Maximum age (in days) of cached files.
Default: 365 days.
QFQ will check once a day if cached files should be purged. This happens automatically when QFQ is called.
Example:
# Page 1: p:id=1&--orientation=Landscape&--page-size=A3
# Page 2: p:id=form&r=234
# File 3: F:fileadmin/file.pdf
# This definition flushes the cached file, if file.pdf is younger than the cached file. This is fine if page 1 & 2 never changes.
SELECT 't:PDF|a:Creating a new PDF|p:id=1&--orientation=Landscape&--page-size=A3|p:id=form&r=234|F:fileadmin/file.pdf|cache' AS _pdf
# This definition flushes the cached file if file.pdf or content which represent page 1 by table form.id=234 is younger than the cached file.
SELECT 't:PDF|a:Creating a new PDF|p:id=1&--orientation=Landscape&--page-size=A3|p:id=form|F:fileadmin/file.pdf|cache:Form/{{fId:r}}' AS _pdf
# Example with limited use: Cache is skipped until 2022-12-31 23:59:59 or if record in table Form with id=123,column 'modified' younger than cached file.
SELECT 't:PDF - 3 Files|a:Please be patient|p:id=1|u:http://www.example.com|F:fileadmin/file.pdf|cache:2022-12-31 23:59:59,:Form/123' AS _pdf
Rendering PDF letters
wkhtmltopdf, with the header and footer options, can be used to render multi page PDF letters (repeating header, pagination) in combination with dynamic content. Such PDFs might look-alike official letters, together with logo and signature.
Best practice:
Create a clean (=no menu, no website layout) letter layout in a separated T3 branch:
page = PAGE page.typeNum = 0 page.includeCSS { 10 = typo3conf/ext/qfq/Resources/Public/Css/qfq-letter.css } // Grant access to any logged in user or specific development IPs [usergroup = *] || [IP = 127.0.0.1,192.168.1.* ] page.10 < styles.content.get [else] page.10 = TEXT page.10.value = access forbidden [global]
Create a T3 body page (e.g. page slug: ‘/letterbody’) with some content. Example static HTML content:
<div class="letter-receiver"> <p>Address</p> </div> <div class="letter-sender"> <p><b>firstName name</b><br> Phone +00 00 000 00 00<br> Fax +00 00 000 00 00<br> </p> </div> <div class="letter-date"> Zurich, 01.12.2017 </div> <div class="letter-body"> <h1>Subject</h1> <p>Dear Mrs...</p> <p>Lucas ipsum dolor sit amet organa solo skywalker darth c-3p0 anakin jabba mara greedo skywalker.</p> <div class="letter-no-break"> <p>Regards</p> <p>Company</p> <img class="letter-signature" src=""> <p>Firstname Name<br>Function</p> </div> </div>
Create a T3 letter-header page (e.g. page slug: ‘/letterheader’) , with only the header information:
<header> <img src="fileadmin/logo.png" class="letter-logo"> <div class="letter-unit"> <p class="letter-title">Department</p> <p> Company name<br> Company department<br> Street<br> City </p> </div> </header>
Create a) a link (Report) to the PDF letter or b) attach the PDF (on the fly rendered) to a mail. Both will call the wkhtml via the download mode and forwards the necessary parameter.
Use in report:
sql = SELECT CONCAT('d:Letter.pdf|t:',p.firstName, ' ', p.name
, '|p:id=letterbody&pId=', p.id, '&_sip=1'
, '&--margin-top=50mm'
, '&--header-html={{BASE_URL_PRINT:Y}}?id=letterheader'
# IMPORTANT: set margin-bottom to make the footer visible!
, '&--margin-bottom=20mm'
, '&--footer-right="Seite: [page]/[toPage]"'
, '&--footer-font-size=8&--footer-spacing=10') AS _pdf
FROM Person AS p ORDER BY p.id
Sendmail. Parameter:
sendMailAttachment={{SELECT 'd:Letter.pdf|t:', p.firstName, ' ', p.name, '|p:id=letterbody&pId=', p.id, '&_sip=1&--margin-top=50mm&--margin-bottom=20mm&--header-html={{BASE_URL_PRINT:Y}}?id=letterheader&--footer-right="Seite: [page]/[toPage]"&--footer-font-size=8&--footer-spacing=10' FROM Person AS p WHERE p.id={{id:S}} }}
Replace the static content elements from 2. and 3. by QFQ Content elements as needed:
{
sql = SELECT '<div class="letter-receiver"><p>', p.name AS '_+br', p.street AS '_+br', p.city AS '_+br', '</p>'
FROM Person AS p
WHERE p.id={{pId:S}}
}
Export area
This description might be interesting if a page can’t be protected by SIP.
To offer protected pages, e.g. directly referenced files (remember: this is not recommended) in download links, the regular FE_GROUPs can’t be used, cause the download does not have the current user privileges (it’s a separate process, started as the webserver user).
Create a separated export tree in Typo3 Backend, which is IP access restricted. Only localhost or the FE_GROUP ‘admin’ is allowed to access:
tmp.restrictedIPRange = 127.0.0.1,::1
[IP = {$tmp.restrictedIPRange} ][usergroup = admin]
page.10 < styles.content.get
[else]
page.10 = TEXT
page.10.value = Please access from localhost or log in as 'admin' user.
[global]
Excel export
This chapter explains how to create Excel files on the fly.
Tip
For just up/downloading of excel files (without modification), check the generic Form Type: upload element and the report ‘download’ (column_pdf) function.
The Excel file is build in the moment when the user request it, by clicking on a download link.
Mode building:
New: The export file will be completely build from scratch.
Template: The export file is based on an earlier uploaded xlsx file (template). The template itself is unchanged.
Injecting data into the Excel file is done in the same way in both modes: a Typo3 page (rendered without any HTML header or tags) contains one or more Typo3 QFQ records. Those QFQ records will create plain ASCII output.
If the export file has to be customized (colors, pictures, headlines, …), the Template mode is the preferred option. It’s much easier to do all customizations via Excel and creating a template than by coding in QFQ / Excel export notation.
Setup
Create a special column name
_excel(or_link) in QFQ/Report. As a source, define a T3 PageContent, which has to deliver the dynamic content (also excel-export-sample).SELECT CONCAT('d:final.xlsx|M:excel|s:1|t:Excel (new)|uid:<tt-content record id>') AS _linkCreate a T3 PageContent which delivers the content.
It is recommended to use the
uid:<tt-content record id>syntax for excel imports, because there should be no html code on the resulting content. QFQ will retrieve the PageContent’s bodytext from the Typo3 database, parse it, and pass the result as the instructions for filling the excel file.Parameters can be passed:
uid:<tt-content record id>?param=<value1>¶m2=<value2>and will be accessible in the SIP Store (S) in the QFQ PageContent.Use the regular QFQ Report syntax to create output.
The newline at the end of every line needs to be CHAR(10). To make it simpler, the special column name
... AS _XLS(see _XLS, _XLSs, _XLSb, _XLSn) can be used.One option per line.
Empty lines will be skipped.
Lines starting with ‘#’ will be skipped (comments). Inline comment signs are NOT recognized as comment sign.
Separate <keyword> and <value> by ‘=’.
Keyword |
Example |
Description |
|---|---|---|
‘worksheet’ |
|
Select a worksheet in case the excel file has multiple of them. |
‘mode’ |
|
Values: insert,overwrite. |
‘position’ |
|
Default is ‘A1’. Use the excel notation. |
‘newline’ |
|
Start a new row. The column will be the one of the last ‘position’ statement. |
‘str’, ‘s’ |
|
Set the given string on the given position. The current position will be shifted one to the right. If the string contains newlines, option ‘b’ (base64) should be used. |
‘b’ |
|
Same as ‘s’, but the given string has to Base64 encoded and will be decoded before export. |
‘n’ |
|
Set number on the given position. The current position will be shift one to the right. |
‘f’ |
|
Set a formula on the given position. The current position will be shift one to the right. |
Create a output like this:
position=D11
s=Hello
s=World
s=First Line
newline
s=Second line
n=123
This fills D11, E11, F11, D12
In Report Syntax:
{
# With ... AS _XLS (token explicit given)
sql = SELECT 'position=D10' AS _XLS
, 's=Hello' AS _XLS
, 's=World' AS _XLS
, 's=First Line' AS _XLS
, 'newline' AS _XLS
, 's=Second line' AS _XLS
, 'n=123' AS _XLS
}
{
# With ... AS _XLSs (token generated internally)
sql = SELECT 'position=D20' AS _XLS
, 'Hello' AS _XLSs
, 'World' AS _XLSs
, 'First Line' AS _XLSs
, 'newline' AS _XLS
, 'Second line' AS _XLSs
, 'n=123' AS _XLS
}
{
# With ... AS _XLSb (token generated internally and content is base64 encoded)
sql = SELECT 'position=D30' AS _XLS
, '<some content with special characters like newline/carriage return>' AS _XLSb
}
Excel export samples (54 is a example <tt-content record id>):
# From scratch (both are the same, one with '_excel' the other with '_link')
SELECT CONCAT('d:new.xlsx|t:Excel (new)|uid:54') AS _excel
SELECT CONCAT('d:new.xlsx|t:Excel (new)|uid:54|M:excel|s:1') AS _link
# Template
SELECT CONCAT('d:final.xlsx|t:Excel (template)|F:fileadmin/template.xlsx|uid:54') AS _excel
# With parameter (via SIP) - get the Parameter on page 'exceldata' with '{{arg1:S}}' and '{{arg2:S}}'
SELECT CONCAT('d:final.xlsx|t:Excel (parameter)|uid:54&arg1=hello&arg2=world') AS _excel
Best Practice
To keep the link of the Excel export close to the Excel export definition, the option Report: render can be used.
On a single T3 page create two QFQ tt-content records:
tt-content record 1:
Type: QFQ
Content:
render = single
{
sql = SELECT CONCAT('d:new.xlsx|t:Excel (new)|uid:54|M:excel|s:1') AS _link
}
tt-content record 2 (uid=54):
Type: QFQ
Content:
render = api
{
sql = SELECT 'position=D10' AS _XLS
, 's=Hello' AS _XLS
, 's=World' AS _XLS
, 's=First Line' AS _XLS
, 'newline' AS _XLS
, 's=Second line' AS _XLS
, 'n=123' AS _XLS
}
WebSocket
Sending messages via WebSocket and receiving the answer is done via:
SELECT 'w:ws://<host>:<port>/<path>|t:<message>' AS _websocket
Instead of ... AS _websocket it’s also possible to use ... AS _link (same syntax).
The answer from the socket (if there is something) is written to output and stored in STORE_RECORD by given column (in this case ‘websocket’ or ‘link’).
Tip
To suppress the direct output, add |_hide to the column name.
Example:
SELECT 'w:ws://<host>:<port>/<path>|t:<message>' AS '_websocket|_hide'
Tip
To define a uniq column name (easy access by column name via STORE_RECORD) add |myName (replace myName).
Example:
SELECT 'w:ws://<host>:<port>/<path>|t:<message>' AS '_websocket|myName'
Tip
Get the answer from STORE_RECORD by using {{&.... Check access-column-values.
Example:
SELECT 'w:ws://<host>:<port>/<path>|t:<message>' AS '_websocket|myName'
Results:
'{{myName:R}}' >> 'w:ws://<host>:<port>/<path>|t:<message>'
'{{&myName:R}}' >> '<received socket answer>'
Drag and drop
Order elements
Ordering of elements via HTML5 drag and drop is supported via QFQ. Any element to order should be represented by a database record with an order column. If the elements are unordered, they will be ordered after the first ‘drag and drop’ move of an element.
Functionality divides into:
Display: the records will be displayed via QFQ/report.
Order records: updates of the order column are managed by a specific definition form. The form is not a regular form (e.g. there are no FormElements), instead it’s only a container to held the SQL update query as well as providing access control via SIP. The form is automatically called via AJAX.
Part 1: Display list
Display the list of elements via a regular QFQ content record. All ‘drag and drop’ elements together have to be nested by a HTML element. Such HTML element:
With
class="qfq-dnd-sort".With a form name:
{{'form=<form name>' AS _data-dnd-api}}(will be replaced by QFQ)Only direct children of such element can be dragged.
Every children needs a unique identifier
data-dnd-id="<unique>". Typically this is the corresponding record id.The record needs a dedicated order column, which will be updated through API calls in time.
A <div> example HTML output (HTML send to the browser):
<div class="qfq-dnd-sort" data-dnd-api="typo3conf/ext/qfq/Classes/Api/dragAndDrop.php?s=badcaffee1234">
<div class="anyClass" id="<uniq1>" data-dnd-id="55">
Numbero Uno
</div>
<div class="anyClass" id="<uniq2>" data-dnd-id="18">
Numbero Deux
</div>
<div class="anyClass" id="<uniq3>" data-dnd-id="27">
Numbero Tre
</div>
</div>
A typical QFQ report which generates those <div> HTML:
{
sql = SELECT '<div id="anytag-', n.id,'" data-dnd-id="', n.id,'">' , n.note, '</div>'
FROM Note AS n
WHERE grId=28
ORDER BY n.ord
head = <div class="qfq-dnd-sort" {{'form=dndSortNote&grId=28' AS _data-dnd-api}}">
tail = </div>
}
A <table> based setup is also possible. Note the attribute data-columns="3" - this generates a dropzone
which is the same column width as the outer table.
<table>
<tbody class="qfq-dnd-sort" data-dnd-api="typo3conf/ext/qfq/Classes/Api/dragAndDrop.php?s=badcaffee1234" data-columns="3">
<tr> class="anyClass" id="<uniq1>" data-dnd-id="55">
<td>Numbero Uno</td><td>Numbero Uno.2</td><td>Numbero Uno.3</td>
</tr>
<tr class="anyClass" id="<uniq2>" data-dnd-id="18">
<td>Numbero Deux</td><td>Numbero Deux.2</td><td>Numbero Deux.3</td>
</tr>
<tr class="anyClass" id="<uniq3>" data-dnd-id="27">
<td>Numbero Tre</td><td>Numbero Tre.2</td><td>Numbero Tre.3</td>
</tr>
</tbody>
</table>
A typical QFQ report which generates this HTML:
{
sql = SELECT '<tr id="anytag-', n.id,'" data-dnd-id="', n.id,'" data-columns="3">' , n.id AS '_+td', n.note AS '_+td', n.ord AS '_+td', '</tr>'
FROM Note AS n
WHERE grId=28
ORDER BY n.ord
head = <table><tbody class="qfq-dnd-sort" {{'form=dndSortNote&grId=28' AS _data-dnd-api}} data-columns="3">
tail = </tbody><table>
}
Show / update order value in the browser
The ‘drag and drop’ action does not trigger a reload of the page. In case the order number is shown and the user does a ‘drag and drop’, the order number shows the old. To update the draggable elements with the latest order number, a predefined html id has to be assigned them. After an update, all changed order number (referenced by the html id) will be updated via AJAX.
The html id per element is defined by qfq-dnd-ord-id-<id> where <id> is the record id. Same example as above, but
with an updated n.ord column:
{
sql = SELECT '<tr id="anytag-', n.id,'" data-dnd-id="', n.id,'" data-columns="3">' , n.id AS '_+td', n.note AS '_+td',
'<td id="qfq-dnd-ord-id-', n.id, '">', n.ord, '</td></tr>'
FROM Note AS n
WHERE grId=28
ORDER BY n.ord
head = <table><tbody class="qfq-dnd-sort" {{'form=dndSortNote&grId=28' AS _data-dnd-api}} data-columns="3">
tail = </tbody><table>
}
Part 2: Order records
A dedicated Form, without any FormElements, is used to define the order logic (database update definition).
Form:
Column |
Description |
|---|---|
Name |
<custom form name> - used in Part 1 in the |
Table |
<table with the element records> - used to update the records specified by |
Form.Parameter:
Attribute |
Description |
|---|---|
orderInterval = <number> |
Optional. By default ‘10’. Use number <0 for descending order |
orderColumn = <column name> |
Optional. By default ‘ord’. |
dragAndDropOrderSql = {{!SELECT n.id AS id, n.ord AS ord FROM Note AS n ORDER BY n.ord}} |
Query to selects the same records as the report in the
same order! Inconsistencies results in order differences.
The columns |
The form related to the example of part 1 (‘div’ or ‘table’): :
Form.name: dndSortNote
Form.table: Note
Form.parameter: orderInterval = 1
Form.parameter: orderColumn = ord
Form.parameter: dragAndDropOrderSql = {{!SELECT n.id AS id, n.ord AS ord FROM Note AS n WHERE n.grId={{grId:S0}} ORDER BY n.ord}}
Re-Order:
QFQ iterates over the result set of dragAndDropOrderSql. The value of column id have to correspond to the dragged HTML
element (given by data-dnd-id).
Reordering always start with orderInterval and is incremented by orderInterval with each
record of the result set.
The client reports
the id of the dragged HTML element,
the id of the hovered element and
the dropped position of above or below the hovered element.
This information is compared to the result set and changes are applied where appropriate.
Take care that the query of part 1 (display list) does a) select the same records and b) in the same order as the query
defined in part 2 (order records) via dragAndDropOrderSql.
Warning
If you find that the reorder does not work at expected, those two sql queries are not identical.
QFQ Icons
Located under typo3conf/ext/qfq/Resources/Public/icons
black_dot.png
blue_dot.png
bulb.png
bullet-blue.gif
bullet-gray.gif
bullet-green.gif
bullet-orange.gif
bullet-pink.gif
bullet-red.gif
bullet-yellow.gif
checkboxinvert.gif
checked-blue.gif
checked-gray.gif
checked-green.gif
checked-pink.gif
checked-red.gif
checked-yellow.gif
construction.gif
copy.gif
delete.gif
down.gif
edit.gif
emoji.svg
gear.svg
help.gif
home.gif
icons.svg
info.gif
loading.gif
mail.gif
marker.svg
new.gif
note.gif
pan.svg
paste.gif
pencil.svg
pointer.svg
rectangle.svg
resize.svg
show.gif
trash.svg
turnLeft.svg
turnRight.svg
up.gif
upload.gif
wavy-underline.gif
zoom.svg
QFQ CSS Classes
qfq-table-50,qfq-table-80,qfq-table-100- assigned to<table>, set min-width and column width to ‘auto’.Background Color:
qfq-color-grey-1,qfq-color-grey-2- assigned to different tags (table, row, cell).qfq-100- assigned to different tags, makes an element ‘width: 100%’.qfq-left- assigned to different tags, Text align left.qfq-sticky- assigned to<thead>, makes the header sticky.letter-no-break- assigned to adivwill protect a paragraph (CSS: page-break-before: avoid;) not to break around a page border (converted to PDF via wkhtml). Take care thatqfq-letter.cssis included in TypoScript setup.qfq-badge,qfq-badge-error,qfq-badge-warning,qfq-badge-success,qfq-badge-info,qfq-badge-invers- colorized BS3 badges:<span class="badge">classic</span> <span class="qfq-badge qfq-badge-success">qfq-badge-success</span> <span class="qfq-badge qfq-badge-warning">qfq-badge-warning</span> <span class="qfq-badge qfq-badge-error">qfq-badge-error</span> <span class="qfq-badge qfq-badge-info">qfq-badge-info</span> <span class="qfq-badge qfq-badge-inverse">qfq-badge-inverse</span>
badge-title- adds a margin around the badge and the font-size is the regular one (not reduced).btn-tiny,btn-small- add to'...|b:btn-info btn-small|t:..' AS linkto render button in small or tiny size.
qfq-img-25,qfq-img-50,qfq-img-75,qfq-img-100- assigned to<img>, set max-width and height to ‘auto’.qfq-img-modal- assigned to<img>, modal view in original size when clicked.
Table: vertical text via CSS
Use class vertical and qfq-vertical-text. Example:
<table>
<tr>
<th class="qfq-vertical"><span class="qfq-vertical-text">Column 1</span></th>
<th class="qfq-vertical"><span class="qfq-vertical-text">2</span></th>
<th class="qfq-vertical"><span class="qfq-vertical-text">Very long column title</span></th>
<th class="qfq-vertical"><span class="qfq-vertical-text">4</span></th>
<th class="qfq-vertical"><span class="qfq-vertical-text">5</span></th>
</tr>
<tr><td>1</td><td>2</td><td>3</td><td>very wide text</td><td>5</td></tr>
</table>
Same effect is also possible via special column name _vertical, see Special column names.
Bootstrap
‘Badges <https://getbootstrap.com/docs/3.4/components/#badges>’_
‘Panels <https://getbootstrap.com/docs/3.4/components/#panels>’_
‘Buttons <https://getbootstrap.com/docs/3.4/css/#buttons>’_
‘Glyphicons <https://getbootstrap.com/docs/3.4/components/#glyphicons>’_
‘Tables <https://getbootstrap.com/docs/3.4/css/#tables>’_
Table: table
Table > hover: table-hover
Table > condensed: table-condensed
Example:
{
sql = SELECT id, name, firstName, ...
head = <table class='table table-condensed qfq-table-50'>
}
qfq-100,qfq-left- makes e.g. a button full width and aligns the text left.
Example:
{
sql = SELECT "p:home&r=0|t:Home|c:qfq-100 qfq-left" AS _pages
}
Tablesorter
QFQ includes a third-party client-side table sorter: https://mottie.github.io/tablesorter/docs/index.html
Make a table sortable and/or filterable:
Ensure that your QFQ installation imports the appropriate js/css files, see Setup CSS & JS.
Add the class=”tablesorter” to your <table> element.
Take care the <table> has a <thead> and <tbody> tag.
Every table with active tablesorter should have a uniq HTML id.
Example:
{
sql = SELECT p.name, p.firstName, p.id, FROM Person AS p
head = <table class="tablesorter tablesorter-filter tablesorter-pager tablesorter-column-selector" id="demoTable">
<thead><tr>
<th>Name</th><th>First name</th><th class="filter-false sorter-false">ID</th>
</tr></thead>
tail = </table>
}
Important
Custom settings will be saved per table automatically in the browser local storage. To distinguish different table
settings, define an uniq HTML id per table.
Example: <table class="tablesorter" id="{{pageSlug:T}}-person"> - the {{pageSlug:T}} makes it easy to keep the
overview over given name on the site.
The tablesorter options:
Class
tablesorter-filterenables row filtering.Class
clear-filteradds a clear input button to filters.
Class
tablesorter-pageradds table paging functionality. A page navigation is shown.Class
tablesorter-column-selectoradds a column selector widget.
Tablesorter View Saver
Tablesorter view saver: inside of a HTML
table-tag the command:{{ '<uniqueName>' AS _tablesorter-view-saver }}
This adds a menu to save the current view (column filters, selected columns, sort order).
<uniqueName>should be a name which is unique. Example:<table {{ 'allperson' AS _tablesorter-view-saver }} class="tablesorter tablesorter-filter tablesorter-column-selector" id="{{pageSlug:T}}-example"> ... </table>
Important
Always specify a unique (over your whole T3 installation) HTML ID (id="{{pageSlug:T}}-example">). On page load, this
reference will be used to load
the last used settings again. If not specified, and if there are at least two tablesorter without an HTML ID, those
will be mixed and might confuse the whole tablesorter.
‘Views’ can be saved as:
group: every user will see the
viewand can modify it.personal: only the user who created the
viewwill see/modify it.readonly: manually mark a
viewas readonly (no FE User can change it) by setting columnreadonly='true'in tableSettingof the corresponding view (identified byname).
Views will be saved in the QFQ system DB table ‘Setting’.
Every setting is saved with the T3 FE username. If there is no T3 FE username, the current QFQ cookie is used instead.
Include ‘font-awesome’ CSS in your T3 page setup:
typo3conf/ext/qfq/Resources/Public/Css/font-awesome.min.cssto get the icons.The view ‘Clear’ is always available and can’t be modified.
To preselect a view, append a HTML anker to the current URL. Get the anker by selecting the view and copy it from the browser address bar. Example:
https://localhost/index.php?id=person#allperson=public:email * 'allperson' is the '<uniqueName>' of the ``tablesorter-view-saver`` command. * 'public' means the view is tagged as 'public' visible. * 'email' is the name of the view, as it is shown in the dropdown list.
If there is a public view with the name ‘Default’ and a user has not chosen a view earlier, that one will be selected.
Tablesorter CSV Export
You can export your tablesorter tables as CSV files using the output widget (be sure to include the separate JS file):
Create a button to trigger the export with the following Javascript:
$('table.tablesorter').trigger('outputTable');Default export file name:
tableExport.csvExported with column separator
;Only currently filtered rows are exported.
Values are exported as text, without HTML tags
You can change the formatting/value of each cell as follows:
<td data-name="12345">CHF 12,345.-</td>Headers and footers are exported as well.
Customization of tablesorter
See docs for more options: https://mottie.github.io/tablesorter/docs/index.html
Add the desired classes or data attributes to your table html, e.g. a dropdown:
class="filter-select"(example-widget-filter-custom)
Description |
Syntax |
|---|---|
Disable sorter |
|
Customize sorter |
|
Disable filter |
|
Filter as dropdown |
|
Ignore entire row |
Wrap |
Custom value for cell |
|
Sorting for tables with child rows (e.g. drilldown) |
|
Add row numbering to table |
|
When using qfq-rowCounter, a new column is automatically inserted into the table. If you want more control over the position or style of the column, you can add it manually by including a <th class=”row-number filter-false sorter-false”>…</th> in the header and a corresponding <td class=”row-number-cell”>…</td> in the body.
Custom sort order:
<span style="display: none;">sort order</span>You can pass in a default configuration object for the main
tablesorter()function by using the attributedata-tablesorter-configon the table. Use JSON syntax when passing in your own configuration, such as:data-tablesorter-config='{"theme":"bootstrap","widthFixed":true,"headerTemplate":"{content} {icon}","dateFormat":"ddmmyyyy","widgets":["uitheme","filter","saveSort","columnSelector","output"],"widgetOptions":{"filter_columnFilters":true,"filter_reset":".reset","filter_cssFilter":"form-control","columnSelector_mediaquery":false,"output_delivery":"download","output_saveFileName":"tableExport.csv","output_separator":";"} }'If the above customization options are not enough, you can output your own HTML for the pager and/or column selector, as well as your own
$(document).ready()function with the desired config. In this case, it is recommended not to use the above tablesorter classes since the QFQ javascript code could interfere with your javascript code.
Example:
{
sql = SELECT id, CONCAT('form&form=person&r=', id) AS _Pagee, lastName, title FROM Person
head = <table class="table tablesorter tablesorter-filter tablesorter-pager tablesorter-column-selector" id="{{pageSlug:T}}-ts1">
<thead><tr><th>Id</th><th class="filter-false sorter-false">Edit</th>
<th>Name</th><th class="filter-select" data-placeholder="Select a title">Title</th>
</tr></thead><tbody>
tail = </tbody></table>
rbeg = <tr>
rend = </tr>
fbeg = <td>
fend = </td>
}
Monitor
Display a (log)file from the server, inside the browser, which updates automatically by a user defined interval. Access to the file is SIP protected. Any file on the server is possible.
On a Typo3 page, define a HTML element with a unique html-id. E.g.:
{
head = <pre id="monitor-1">Please wait</pre>
}
On the same Typo3 page, define an SQL column ‘_monitor’ with the necessary parameter:
{
sql = SELECT 'file:fileadmin/protected/log/sql.log|tail:50|append:1|refresh:1000|htmlId:monitor-1' AS _monitor
}
Short version with all defaults used to display system configured sql.log:
{
sql = SELECT 'file:{{sqlLog:Y}}' AS _monitor, '<pre id="monitor-1" style="white-space: pre-wrap;">Please wait</pre>'
}
Calendar View
QFQ is shipped with the JavaScript library https://fullcalendar.io/ (respect that QFQ uses V3) to provides various calendar views.
Docs: https://fullcalendar.io/docs/v3
Include the JS & CSS files via Typoscript
typo3conf/ext/qfq/Resources/Public/Css/fullcalendar.min.css
typo3conf/ext/qfq/Resources/Public/JavaScript/moment.min.js
typo3conf/ext/qfq/Resources/Public/JavaScript/fullcalendar.min.js
Integration: Create a <div> with
CSS class “qfq-calendar”
Tag
data-config. The content is a Javascript object.
Example:
{
sql = SELECT 'Calendar, Standard'
tail = <div class="qfq-calendar"
data-config='{
"themeSystem": "bootstrap3",
"height": "auto",
"defaultDate": "2020-01-13",
"weekends": false,
"defaultView": "agendaWeek",
"minTime": "05:00:00",
"maxTime": "20:00:00",
"businessHours": { "dow": [ 1, 2, 3, 4 ], "startTime": "10:00", "endTime": "18:00" },
"events": [
{ "id": "a", "title": "my event",
"start": "2020-01-21"},
{ "id": "b", "title": "my other event", "start": "2020-01-16T09:00:00", "end": "2020-01-16T11:30:00"}
]}'>
</div>
}
{
# "now" is in the past to switchoff 'highlight of today'
sql = SELECT 'Calendar, 3 day, custom color, agend&list' AS '_+h2'
tail = <div class="qfq-calendar"
data-config='{
"themeSystem": "bootstrap3",
"height": "auto",
"header": {
"left": "title",
"center": "",
"right": "agenda,listWeek"
},
"defaultDate": "2020-01-14",
"now": "1999-12-31",
"allDaySlot": false,
"weekends": false,
"defaultView": "agenda",
"dayCount": 3,
"minTime": "08:00:00",
"maxTime": "18:00:00",
"businessHours": { "dow": [ 1, 2, 3, 4 ], "startTime": "10:00", "endTime": "18:00" },
"events": [
{ "id": "a", "title": "my event", "start": "2020-01-15T10:15:00", "end": "2020-01-15T11:50:00", "color": "#25adf1", "textColor": "#000"},
{ "id": "b", "title": "my other event", "start": "2020-01-16T09:00:00", "end": "2020-01-16T11:30:00", "color": "#5cb85c", "textColor": "#000"},
{ "id": "c", "title": "Eventli", "start": "2020-01-15T13:10:00", "end": "2020-01-15T16:30:00", "color": "#fbb64f", "textColor": "#000"},
{ "id": "d", "title": "Evento", "start": "2020-01-15T13:50:00", "end": "2020-01-15T15:00:00", "color": "#fb4f4f", "textColor": "#000"},
{ "id": "d", "title": "Busy", "start": "2020-01-14T09:00:00", "end": "2020-01-14T12:00:00", "color": "#ccc", "textColor": "#000"},
{ "id": "e", "title": "Banana", "start": "2020-01-16T13:30:00", "end": "2020-01-16T16:00:00", "color": "#fff45b", "textColor": "#000"}
]}'>
</div>
}
Selection with forward
For the selection of a date range, the calendar view supports forwarding to a specific defined page with selected datetime as parameter. Benefit here is that the forwarded url can be target to a form in which the delivered selected date range can be used to prefill the form fields. Following client store variables will be delivered:
start (e.g. 2025-07-17 09:30:00)
end (e.g. 2025-07-19 11:30:00)
Parameters for data-config which are needed or optional:
selectable |
true, false:(default) - Calendar view allows selecting a date range. |
urlForward |
Url to forward to a page after selection. If not defined then feature is disabled. |
urlForwardMode |
new:Open a new window with given urlForward, empty:Redirect current page to urlForward. If not defined then feature is disabled. |
restrictSelection |
(Optional) day:Restrict user selection to same day. If not defined, no restriction. |
Example data-config with predefined sip in forward:
{
sql = SELECT 'U:pIdUser={{pIdUser:Y}}&resCatId=310&pageId={{pageId:T0}}|s|r:8' AS '_link|sipData|_hide'
}
{
sql = SELECT ''
tail = <div class="qfq-calendar" data-config='{
"themeSystem": "bootstrap3",
"height": "auto",
"allDaySlot": false,
"weekends": true,
"defaultView": "agendaWeek",
"defaultDate": "2025-07-17",
"now": "2025-07-17",
"minTime": "08:00:00",
"maxTime": "22:00:00",
"locale": "en-gb",
"selectable": true,
"urlForward": "{{baseUrl:Y}}reservation?s={{&sipData:RE}}",
"urlForwardMode": "new",
"restrictSelection": "day",
"timezone": "Europe/Zurich",
"ignoreTimezone": false}'></div>
}
Setup with websocket
In general the websocket implementation should have been done, see Websocket implementation.
To use the calendar with a websocket connection and realtime refresh, you need to set up the calendar div with the following parent container (qfq-calendar-container):
{
sql = SELECT 'U:pIdUser={{pIdUser:Y}}&resCatId=310&pageId={{pageId:T0}}|s|r:8' AS '_link|sipData|_hide'
}
{
sql = SELECT ''
head = <div class="messenger-output qfq-calendar-container"
data-ws-group="reservationCalendar"
data-baseurl="{{baseUrl:Y}}"
data-ws-sub="calendarData"
data-ws-params="s={{&sipData:RE}}">
<div class="qfq-calendar" data-config='{...}'></div>
tail = </div>
}
On the other side in a form (for example) the ws-group and ws-sub should be triggered with given tt-content subheader. see message. The whole calendar will be updated with the new data from the websocket connection given by the defined tt-content subheader.
Report As File
If the toplevel token
fileis present inside the body of a QFQ tt-content element then the given report file is loaded and rendered.The tt-content body is ignored in that case.
The path to the report file must be given relative to the report directory inside the qfq project directory. See qfq.project.path.php
QFQ provides some special system reports which are located inside the extension directory typo3conf/ext/qfq/Resources/Private/Report and can be directly rendered by prepending an underscore and omitting the file extension:
file=_formEditorwill render the standard formEditor report. Check also FormEditor.file=_searchRefactorwill render the standard searchRefactor report.
If the QFQ setting
reportAsFileAutoExport(see Extension Manager: QFQ Configuration) is enabled, then every QFQ tt-content element which does not contain thefilekeyword is exported automatically when the report is rendered the first time.The path of the created file is given by the typo3 page structure
The tt-content element body is replaced with
file=<path-to-new-file>
Backups: Whenever a report file is edited via the frontend report editor then a backup of the previous version is saved in the
.backupdirectory located in the same directory as the report file.
Example tt-content body:
file=Home/myPage/qfq-report.qfqr
{
# Everything else is ignored!!
sql = SELECT 'This is ignored!!'
}
Example Home/myPage/qfq-report.qfqr:
{
# Some comment
sql = SELECT 'The file content is executed.'
}
Example of rendered report:
The file content is executed.
Search & Refactor
This report shows a search page, which can be used for easier way to refactor your project. Following tables will be searched with this report:
FormElement
Form
tt_content (DB: Typo3, Reports)
pages (DB: Typo3)
For single use:
file=_searchRefactor
For multi use with formEditor (included switch button):
file={{file:SU:::_formEditor}}
Merge Data
The ‘Merge Data’ tool merges records and updates affected id’s in child records.
Define a query to find duplicated records. The critera of ‘what is a duplicate’ can be defined in the custom query.
Custom rules to find child records can be defined.
An ‘auto rule create function’ helps to define rules, based on column names.
Optional show details per potential duplicates.
Individual duplicates can be marked to be merged.
Merging actions will be logged to merge.log.
Merge Tool
The merge data page (QFQ report) lists duplicated data and offers to merge them.
Create a T3 page and insert a QFQ tt-content record with content:
file=_mergeData
Open the page in the frontend. There are two main sections: Merge Data and Merge Rules. Merge Data: This is where the query for finding duplicates is configured. Once everything is set up, the found duplicates will be displayed here along with the Merge button.
Define the duplicate search query in qfq Extension Manager: QFQ Configuration
These keywords has to be defined:
_tableName
_id1
_id2
_column1
_column2
_key
Example configuration query:
SELECT 'Person' AS _tableName
, p1.id AS _id1
, p2.id AS _id2
, CONCAT(p1.firstName,' ', p1.lastName, ' (', p1.account,')') AS _column1
, CONCAT(p2.firstName,' ', p2.lastName, ' (', p2.account,')') AS _column2
FROM Person AS p1, Person AS p2
WHERE p1.id < p2.id
AND p1.account = p2.account
AND p1.account != ''
GROUP BY p1.account
It’s not necessary to use a second table with _id2 and _column2 if there is no data to compare.
Table “Grp” and two specific group records are recommended for using the merge feature. Query’s for creating table and records can be found in typo3conf/ext/qfq/Classes/Sql/customTables.sql.
Merge Rules: This section allows you to edit the merge rules. You need to specify the tables and which columns should be merged.
Report Examples
Basic Queries
One simple query:
{
sql = SELECT "Hello World"
}
Result:
Hello World
Two simple queries:
{
sql = SELECT "Hello World"
}
{
sql = SELECT "Say hello"
}
Result:
Hello WorldSay hello
Two simple queries, with break:
{
sql = SELECT "Hello World<br>"
}
{
sql = SELECT "Say hello"
}
Result:
Hello World
Say hello
Accessing the database
Real data, one single column:
{
sql = SELECT p.firstName FROM ExpPerson AS p
}
Result:
BillieElvisLouisDiana
Real data, two columns:
{
sql = SELECT p.firstName, p.lastName FROM ExpPerson AS p
}
Result:
BillieHolidayElvisPresleyLouisArmstrongDianaRoss
The result of the SQL query is an output, row by row and column by column, without adding any formatting information. See Formatting Examples for examples of how the output can be formatted.
Formatting Examples
Formatting (i.e. wrapping of data with HTML tags etc.) can be achieved in two different ways:
One can add formatting output directly into the SQL by either putting it in a separate column of the output or by using concat to concatenate data and formatting output in a single column.
One can use ‘level’ keys to define formatting information that will be put before/after/between all rows/columns of the actual levels result.
Two columns:
{
# Add the formatting information as a column
sql = SELECT p.firstName, " " , p.lastName, "<br>" FROM ExpPerson AS p
}
Result:
Billie Holiday
Elvis Presley
Louis Armstrong
Diana Ross
One column rend as linebreak - no extra column <br> needed:
{
sql = SELECT p.firstName, " " , p.lastName, " ", p.country FROM ExpPerson AS p
rend = <br>
}
Result:
Billie Holiday USA
Elvis Presley USA
Louis Armstrong USA
Diana Ross USA
Same with fsep (column “ “ removed):
{
sql = SELECT p.firstName, p.lastName, p.country FROM ExpPerson AS p
rend = <br>
fsep = " "
}
Result:
Billie Holiday USA
Elvis Presley USA
Louis Armstrong USA
Diana Ross USA
More HTML:
{
sql = SELECT p.name FROM ExpPerson AS p
head = <ul>
tail = </ul>
rbeg = <li>
rend = </li>
}
Result:
o Billie Holiday
o Elvis Presley
o Louis Armstrong
o Diana Ross
Two queries (not correct if there are more than one person or address record):
{
sql = SELECT p.name FROM ExpPerson AS p
rend = <br>
}
{
sql = SELECT a.street FROM ExpAddress AS a
rend = <br>
}
Two queries: nested (not correct if there are more than one person or address record):
{
# outer query
sql = SELECT p.name FROM ExpPerson AS p
rend = <br>
{
# inner query
sql = SELECT a.street FROM ExpAddress AS a
rend = <br>
}
}
For every record of first query, all records of second query will be printed.
Two queries: nested with variables:
{
# outer query
sql = SELECT p.id, p.name FROM ExpPerson AS p
rend = <br>
{
# inner query
sql = SELECT a.street FROM ExpAddress AS a WHERE a.pId='{{id:R}}'
rend = <br>
}
}
Two queries: nested with hidden (… AS _pId) variables in a table:
{
sql = SELECT p.id AS _pId, p.name FROM ExpPerson AS p
rend = <br>
{
# inner query
sql = SELECT a.street FROM ExpAddress AS a WHERE a.pId='{{pId:R}}'
rend = <br>
}
}
Create HTML tables. Each column is wrapped in <td>, each row is wrapped in <tr>:
{
sql = SELECT p.firstName, p.lastName, p.country FROM Person AS p
head = <table class="table">
tail = </table>
rbeg = <tr>
rend = </tr>
fbeg = <td>
fend = </td>
}
Maybe a few columns belongs together and should be in one table column.
Joining columns, variant A: firstName and lastName in one table column:
{
sql = SELECT CONCAT(p.firstName, ' ', p.lastName), p.country FROM Person AS p
head = <table class="table">
tail = </table>
rbeg = <tr>
rend = </tr>
fbeg = <td>
fend = </td>
}
Joining columns, variant B: firstName and lastName in one table column:
{
sql = SELECT '<td>', p.firstName, ' ', p.lastName, '</td><td>', p.country, '</td>' FROM Person AS p
head = <table class="table">
tail = </table>
rbeg = <tr>
rend = </tr>
}
Joining columns, variant C: firstName and lastName in one table column.
Notice fbeg, fend` and ``fskipwrap:
{
sql = SELECT '<td>', p.firstName, ' ', p.lastName, '</td>', p.country FROM Person AS p
head = <table class="table">
tail = </table>
rbeg = <tr>
rend = </tr>
fbeg = <td>
fend = </td>
fskipwrap = 1,2,3,4,5
}
Joining columns, variant D: firstName and lastName in one table column.
Notice fbeg, fend and fskipwrap:
{
sql = SELECT CONCAT('<td>', p.firstName, ' ', p.lastName, '</td>') AS '_noWrap', p.country FROM Person AS p
head = <table class="table">
tail = </table>
rbeg = <tr>
rend = </tr>
fbeg = <td>
fend = </td>
}
Recent List
A nice feature is to show a list with last changed records. The following will show the 10 last modified (Form or FormElement) forms:
{
sql = SELECT CONCAT('p:{{pageSlug:T}}?form=form&r=', f.id, '|t:', f.name,'|o:', GREATEST(MAX(fe.modified), f.modified)) AS _page
FROM Form AS f
LEFT JOIN FormElement AS fe
ON fe.formId = f.id
GROUP BY f.id
ORDER BY GREATEST(MAX(fe.modified), f.modified) DESC
LIMIT 10
head = <h3>Recent Forms</h3>
rsep = , 
}
Table: vertical column title
To orientate a column title vertical, use the QFQ CSS class qfq-vertical in td|th and qfq-vertical-text around the text.
HTML example (second column title is vertical):
<table><thead>
<tr>
<th>horizontal</th>
<th class="qfq-vertical"><span class="qfq-vertical-text">text vertical</span></th>
</tr>
</thead></table>
QFQ example:
{
sql = SELECT title FROM Settings ORDER BY title
fbeg = <th class="qfq-vertical"><span class="qfq-vertical-text">
fend = </span></th>
head = <table><thead><tr>
rend = </tr></thead>
tail = </table>
{
sql = SELECT ...
}
}
STORE_USER examples
Keep variables per user session.
Two pages (pass variable)
Sometimes it’s useful to have variables per user (=browser session). Set a variable on page ‘A’ and retrieve the value on page ‘B’.
Page ‘A’ - set the variable:
{
sql = SELECT 'hello' AS '_greeting:U'
}
Page ‘B’ - get the value:
{
sql = SELECT '{{greeting:UE}}'
}
If page ‘A’ has never been opened with the current browser session, nothing is printed (STORE_EMPTY gives an empty string). If page ‘A’ is called, page ‘B’ will print ‘hello’.
One page (collect variables)
A page will be called with several SIP variables, but not at all at the same time. To still get all variables at any time:
{
# Normalize
sql = SELECT '{{order:USE:::sum}}' AS '_order:U', '{{step:USE:::5}}' AS _step, '{{direction:USE:::ASC}}' AS _direction
}
{
# Different links
sql = SELECT 'p:{{pageSlug:T}}?order=count|t:Order by count|b|s' AS _link,
'p:{{pageSlug:T}}?order=sum|t:Order by sum|b|s' AS _link,
'p:{{pageSlug:T}}?step=10|t:Step=10|b|s' AS _link,
'p:{{pageSlug:T}}?step=50|t:Step=50|b|s' AS _link,
'p:{{pageSlug:T}}?direction=ASC|t:Order by up|b|s' AS _link,
'p:{{pageSlug:T}}?direction=DESC|t:Order by down|b|s' AS _link
}
{
sql = SELECT * FROM Items ORDER BY {{order:U}} {{direction:U}} LIMIT {{step:U}}
}
Simulate/switch user: feUser
Just set the STORE_USER variable ‘feUser’.
All places with {{feUser:T}} has to be replaced by {{feUser:UT}}:
{
# Normalize
sql = SELECT '{{feUser:UT}}' AS '_feUser:U'
# Offer switching feUser
sql = SELECT 'p:{{pageSlug:T}}?feUser=account1|t:Become "account1"|b|s' AS _link,
'p:{{pageSlug:T}}?feUser={{feUser:T}}|t:Back to own identity|b|s' AS _link,
}
Semester switch (remember last choice)
A current semester is defined via configuration in STORE_SYSTEM {{semId:Y}}. The first column in 10.sql
'{{semId:SUY}}' AS '_semId:U' saves
the semester to STORE_USER via ‘_semId:U’. The priority ‘SUY’ takes either the latest choose (STORE_SIP) or reuse the
last used (STORE_USER) or (first time call during browser session) takes the default from config (STORE_SYSTEM):
# Semester switch
{
sql = SELECT '{{semId:SUY}}' AS '_semId:U'
, CONCAT('p:{{pageSlug:T}}?semId=', sp.id, '|t:', QBAR(sp.name), '|s|b|G:glyphicon-chevron-left') AS _link
, ' <button class="btn disabled ', IF({{semId:Y0}}=sc.id, 'btn-success', 'btn-default'), '">',sc.name, '</button> '
, CONCAT('p:{{pageSlug:T}}?semId=', sn.id, '|t:', QBAR(sn.name), '|s|b|G:glyphicon-chevron-right|R') AS _link
FROM Semester AS sc
LEFT JOIN semester AS sp
ON sp.id=sc.id-1
LEFT JOIN semester AS sn
ON sc.id+1=sn.id AND sn.show_semester_from<=CURDATE()
WHERE sc.id={{semId:SUY}}
ORDER BY sc.semester_von
head = <div class="btn-group" style="position: absolute; top: 15px; right: 25px;">
tail = </div><p></p>
}