Installation

The following features are only tested / supported on linux hosts:

  • General: QFQ is coded to run on Linux hosts, preferable on Debian derivates like Ubuntu.
  • HTML to PDF conversion - command wkhtmltopdf .
  • Concatenation of PDF files - command pdfunite .
  • Mime type detection for uploads - command file .

Preparation

Report & Form

To normalize UTF8 input, php-intl package is needed by

  • normalizer::normalize()

For the download function, the programs pdfunite and file are necessary to concatenate PDF files.

Preparation for Ubuntu:

sudo apt install php-intl
sudo apt install poppler-utils libxrender1 file pdf2svg  # for file upload, PDF and 'HTML to PDF' (wkhtmltopdf), PDF split
sudo apt install inkscape imagemagick            # to render thumbnails

wkhtmltopdf

wkhtmltopdf will be used by QFQ to offer ‘website print’ and ‘HTML to PDF’ conversion. The program is not included in QFQ and has to be manually installed.

  • The Ubuntu package wkhtmltopdf needs a running Xserver - this does not work on a headless webserver.
    • Best is to install the QT version from the named website above.
    • In case of trouble with wkhtmltopdf, also install ‘libxrender1’.
    • The current version 0.12.4 might have trouble with https connections. Version 0.12.5-dev (github master branch) seems more reliable. Please contact the QFQ authors if you need a compiled Ubuntu version of wkhtmltopdf.

In configuration specify:

config.cmdWkhtmltopdf:  /opt/wkhtmltox/bin/wkhtmltopdf
config.baseUrl: http://www.example.com/

If wkhtml has been compiled with dedicated libraries (not part of LD_LIBRARY_PATH), specify the LD_LIBRARY_PATH together with the path-filename:

config.cmdWkhtmltopdf: LD_LIBRARY_PATH=/opt/wkhtmltox/lib /opt/wkhtmltox/bin/wkhtmltopdf

Important

To access FE_GROUP protected pages or content, it’s necessary to disable the [FE][lockIP] check! wkhtml will access the Typo3 page locally (localhost) and that IP address is different from the client (=user) IP.

Configure via Typo3 Installtool All configuration > $TYPO3_CONF_VARS[‘FE’] :

[FE][lockIP] = 0

Warning

[FE][lockIP] = 0 disables an important anti-‘session hijacking’ protection. The security level of the whole installation will be lowered ! Again, this is only needed if wkhtml needs access to FE_GROUP protected pages & content. As an alternative to lower the security level, create a separated page subtree which is only accessible (configured via Typoscript) from specific IPs or if a FE-User is logged in.

If there are problems with converting/downloading FE_GROUP protected pages, check SHOW_DEBUG_INFO = download to debug.

Note

Converting HTML to PDF gives no error message but RC=-1? Check carefully all includes of CSS, JS, images and so on! Typically some of them fails to load and wkhtml stops running!

HTML to PDF conversion

wkhtmltopdf converts a website (local or remote) to a (multi)-page PDF file. It’s mainly used in download .

Print

Different browser prints the same page in different variations. To prevent this, QFQ implements a small PHP wrapper print.php with uses wkhtmltopdf to convert HTML to PDF.

Provide a print this page -link (replace ‘current pageId’ ):

<a href="typo3conf/ext/qfq/Classes/Api/print.php?id={current pageId}">Print this page</a>

Any parameter specified after print.php will be delivered to wkhtmltopdf as part of the URL.

Typoscript code to implement a print link on every page:

10 = TEXT
10 {
  wrap = <a href="typo3conf/ext/qfq/Classes/Api/print.php?id=|&type=99"><span class="glyphicon glyphicon-print" aria-hidden="true"></span> Printview</a>
  data = page:uid
}

Send Email

QFQ sends mail via sendEmail http://caspian.dotconf.net/menu/Software/SendEmail/ - a small perl script without a central configuration.

By default, sendEmail uses the local installed MTA, writes a logfile to fileadmin/protected/log/mail.log and handles attachments via commandline options. A basic HTML email support is implemented.

The latest version is v1.56, which has at least one bug. That one is patched in the QFQ internal version v1.56p1 (see QFQ GIT sources in directory ‘patches/sendEmail.patch’).

Nevertheless, on latest system the TLS support is broken - please check sendEmailProblem .

The Typo3 sendmail eco-system is not used at all by QFQ.

Thumbnail

Thumbnails will be rendered via ImageMagick ( https://www.imagemagick.org/ ) ‘convert’ and ‘inkscape’ ( https://inkscape.org ). ‘inkscape’ is only used for ‘.svg’ files.

The Typo3 graphic eco-system is not used at all by QFQ.

Usage: column-thumbnail .

Setup

  • Install the extension via the Extension Manager.
    • If you install the extension by manual download/upload and get an error message “can’t activate extension”: rename the downloaded zip file to qfq.zip or qfq_<version>.zip (e.g. version: 18.12.0).
    • If the Extension Manager stops after importing: check your memory limit in php.ini.
  • Copy/rename the file <site path>/typo3conf/ext/qfq/config.example.qfq.php to <site path>/typo3conf/config.qfq.php . Configure the necessary settings configuration The configuration file is outside of the extension directory, to not loose it during de-install and install again.
  • When the QFQ Extension is called the first time on the Typo3 frontend, the file <ext_dir>/Classes/Sql/formEditor.sql will played and fills the database with the Form editor records. This also happens automatically after each update of QFQ.
  • Configure Typoscript to include Bootstrap, jQuery, QFQ javascript and CSS files.

Setup CSS & JS

page.meta {
    X-UA-Compatible = IE=edge
    X-UA-Compatible.attribute = http-equiv
    viewport=width=device-width, initial-scale=1
}

page.includeCSS {
    file01 = typo3conf/ext/qfq/Resources/Public/Css/bootstrap.min.css
    file02 = typo3conf/ext/qfq/Resources/Public/Css/bootstrap-theme.min.css
    file03 = typo3conf/ext/qfq/Resources/Public/Css/jqx.base.css
    file04 = typo3conf/ext/qfq/Resources/Public/Css/jqx.bootstrap.css
    file05 = typo3conf/ext/qfq/Resources/Public/Css/qfq-bs.css
    file06 = typo3conf/ext/qfq/Resources/Public/Css/tablesorter-bootstrap.css
    file07 = typo3conf/ext/qfq/Resources/Public/Css/font-awesome.min.css
}

page.includeJS {
    file01 = typo3conf/ext/qfq/Resources/Public/JavaScript/jquery.min.js
    file02 = typo3conf/ext/qfq/Resources/Public/JavaScript/bootstrap.min.js
    file03 = typo3conf/ext/qfq/Resources/Public/JavaScript/validator.min.js
    file04 = typo3conf/ext/qfq/Resources/Public/JavaScript/jqx-all.js
    file05 = typo3conf/ext/qfq/Resources/Public/JavaScript/globalize.js
    file06 = typo3conf/ext/qfq/Resources/Public/JavaScript/tinymce.min.js
    file07 = typo3conf/ext/qfq/Resources/Public/JavaScript/EventEmitter.min.js
    file08 = typo3conf/ext/qfq/Resources/Public/JavaScript/typeahead.bundle.min.js
    file09 = typo3conf/ext/qfq/Resources/Public/JavaScript/qfq.min.js
    file10 = typo3conf/ext/qfq/Resources/Public/JavaScript/jquery.tablesorter.combined.min.js
    file11 = typo3conf/ext/qfq/Resources/Public/JavaScript/jquery.tablesorter.pager.min.js
    file12 = typo3conf/ext/qfq/Resources/Public/JavaScript/widget-columnSelector.min.js

    # Only needed in case FormElement 'annotate' is used.
    file20 = typo3conf/ext/qfq/Resources/Public/JavaScript/fabric.min.js
    file21 = typo3conf/ext/qfq/Resources/Public/JavaScript/qfq.fabric.min.js
}

FormEditor

Setup a report to manage all forms :

  • Create a Typo3 page.

  • Set the ‘URL Alias’ to form (recommended) or the individual defined value in parameter editFormPage ( configuration ).

  • Insert a content record of type qfq .

  • In the bodytext insert the following code:

    # If there is a form given by SIP: show
    form={{form:SE}}
    
    # In case indexQfq != indexData, set dbIndex=indexQfq.
    dbIndex = {{indexQfq:Y}}
    
    10 {
        # List of Forms: Do not show this list of forms if there is a form given by SIP.
        # Table header.
        sql = SELECT CONCAT('p:{{pageAlias:T}}&form=form|A:data-reference=newForm') as _pagen, '#', 'Name', 'Title', 'Table', '' FROM (SELECT 1) AS fake WHERE '{{form:SE}}'=''
        head = {{'b|p:id={{pageAlias:T}}&form=copyFormFromExt|t:Copy form from ExtForm|A:data-reference=copyForm' AS _link}}
               <table class="table table-hover qfq-table-50 tablesorter tablesorter-filter">
        tail = </table>
        rbeg = <thead><tr>
        rend = </tr></thead>
        fbeg = <th>
        fend = </th>
    
        10 {
            # All forms
            sql = SELECT CONCAT('p:{{pageAlias:T}}&form=form&r=', f.id, '|A:data-reference=editForm', f.name) as _pagee
                         , f.id, f.name, f.title AS '_striptags', f.tableName
                         , CONCAT('U:form=form&r=', f.id, '|A:data-reference=deletForm') as _paged
                      FROM Form AS f
                      ORDER BY f.name
            rbeg = <tr>
            rend = </tr>
            fbeg = <td>
            fend = </td>
        }
    }
    

Installation: Check List

  • Protect the directory <T3 installation>/fileadmin/protected in Apache against direct file access.
    • <T3 installation>/fileadmin/protected/ should be used for confidential (uploaded / generated) data.
    • <T3 installation>/fileadmin/protected/log/… is the default place for QFQ log files.
  • Protect the directory <T3 installation>/fileadmin in Apache to not execute PHP Scripts - malicious uploads won’t be executed.
  • Setup a log rotation rule for sqlLog .
  • Check that sqlLogMode is set to modify on productive sites. With none you have no chance to find out who changed which data and all really logs a mass of data.

Configuration

config.qfq.php

Keyword Example Description
DB_<n>_USER DB_1_USER=qfqUser Credentials configured in MySQL
DB_<n>_PASSWORD DB_1_PASSWORD=1234567890 Credentials configured in MySQL
DB_<n>_SERVER DB_1_SERVER=localhost Hostname of MySQL Server
DB_<n>_NAME DB_1_NAME=qfq_db Database name
LDAP_1_RDN LDAP_1_PASSWORD LDAP_1_RDN=’ou=Admin,ou=example,dc=com’ LDAP_1_PASSWORD=’mySecurePassword’ Credentials for non-anonymous LDAP access. Only one set supported.

Example: typo3conf/config.qfq.php :

<?php

// QFQ configuration
//
// Save this file as: <site path>/typo3conf/config.qfq.php

return [
    'DB_1_USER' => '<DBUSER>',
    'DB_1_SERVER' => '<DBSERVER>',
    'DB_1_PASSWORD' => '<DBPW>',
    'DB_1_NAME' => '<DB>',

    //DB_2_USER => <DBUSER>
    //DB_2_SERVER => <DBSERVER>
    //DB_2_PASSWORD => <DBPW>
    //DB_2_NAME => <DB>

    // DB_n ...
    // ...

    // LDAP_1_RDN => 'ou=Admin,ou=example,dc=com'
    // LDAP_1_PASSWORD => 'mySecurePassword'
];

Extension Manager: QFQ Configuration

Keyword Default / Example Description
Config
flagProduction yes yes|no: used to differentiate production and development site.
maxFileSize 10M If empty, take minimum of ‘post_max_size’ and ‘upload_max_filesize’.
baseUrl http://example.com URL where wkhtmltopdf will fetch the HTML (no parameter, those comes later)
dateFormat yyyy-mm-dd Possible options: yyyy-mm-dd, dd.mm.yyyy.
thumbnailDirSecure fileadmin/protected/qfqThumbnail Important: secure directory ‘protected’ (recursive) against direct access.
thumbnailDirPublic typo3temp/qfqThumbnail Both thumbnail directories will be created if not existing.
cmdInkscape inkscape If inkscape is not available, specify an empty string.
cmdConvert convert GraphicsMagics ‘convert’ is recommended.
cmdWkhtmltopdf /usr/bin/wkhtmltopdf PathFilename of wkhtmltopdf. Optional variables like LD_LIBRARY_PATH=…
sendEMailOptions -o tls=yes General options. Check: http://caspian.dotconf.net/menu/Software/SendEmail
documentation http://docs.typo3.org Link to the online documentation of QFQ. Every QFQ installation also contains a local copy: typo3conf/ext/qfq/Documentation/html/Manual.html
Dynamic
fillStoreSystemBySql1/2/3 SELECT s.id AS … Specific values read from the database to fill the system store during QFQ load. See fillStoreSystemBySql for a usecase.
fillStoreSystemBySqlErrorMsg1/2/3 No current period found Only define an error message, if QFQ should stop running in case of an SQL error or not exact one record.
Debug
throwExceptionGeneralError auto
yes : ‘general errors’ in QFQ (PHP) will throw an exception.
auto : becomes ‘yes’, if ‘flagProduction’!=’yes’, else ‘no’.
no : ‘general errors’ in QFQ (PHP) will be silently ignored.
formSubmitLogMode all
all : every form submission will be logged.
none : no logging.
See form-submit-log-page for example QFQ code to display the log.
redirectAllMailTo john @ doe . com If set, redirect all QFQ generated mails (Form, Report) to the specified.
sqlLogMode modify
all : every statement will be logged - this might a lot.
modify : log only statements who change data. error : log only DB errors.
none : no SQL log at all.
sqlLog fileadmin/protected/log/sql.log Filename to log SQL commands: relative to <site path> or absolute. If the directory does not exist, create it.
qfqLog fileadmin/protected/log/qfq.log Filename to log general QFQ events:relative to <site path> or absolute. If the directory does not exist, create it.
mailLog fileadmin/protected/log/mail.log Filename to log sendEmail commands: relative to <site path> or absolute. If the directory does not exist, create it.
showDebugInfo auto FE - Possible values: yes|no|auto|download. For ‘auto’: If a BE User is logged in, a debug information will be shown on the FE.
Database
init init=SET names utf8; SET sql_mode = “NO_ENGINE_SUBSTITUTION” Global init for using the database. For ‘sql_mode=”NO_ENGINE_SUBSTITUTION”’ see #7407.
update auto
auto : apply DB Updates only if there is a newer version.
always : apply DB Updates always, especially play formEditor.sql every time QFQ is called - not recommended!
never : never apply DB Updates.
indexData 1 Optional. Default: 1. Retrieve the current setting via {{dbNameData:Y}}.
indexQfq 1 Optional. Default: 1. Retrieve the current setting via {{dbNameQfq:Y}}.
Security
escapeTypeDefault m All variables {{…}} get this escape class by default. See variable-escape .
securityVarsHoneypot email,username,password If empty: no check. All named variables will rendered as INPUT elements.
securityAttackDelay 5 If an attack is detected, sleep ‘x’ seconds and exit PHP process.
securityShowMessage true If an attack is detected, show a message.
securityGetMaxLength 50 GET vars longer than ‘x’ chars triggers an attack-recognized . ExceptionMaxLength .
securityFailedAuthDelay 3 If REST authorization fails, sleep ‘x’ seconds before answering.
Form-Config
recordLockTimeoutSeconds 900 Timeout for record locking. After this time, a record will be replaced.
sessionTimeoutSeconds 1800 Timeout for FE User session. See sessionTimeoutSeconds
enterAsSubmit enterAsSubmit = 1 0: off, 1: Pressing enter in a form means save and close .
editFormPage form T3 Pagealias to edit a form.
formDataPatternError please check pattern error Customizable error message used in validator.js. ‘pattern’ violation.
formDataRequiredError missing value Customizable error message used in validator.js. ‘required’ fields.
formDataMatchError type error Customizable error message used in validator.js. ‘match’ retype mismatch.
formDataError generic error Customizable error message used in validator.js. ‘no specific’ given.
Form-Layout
labelAlign left Label align (left/center/right)/ Default: left. Will be inherited to Form.
cssClassQfqContainer container
QFQ with own Bootstrap: ‘container’.
QFQ already nested in Bootstrap of mainpage: <empty>.
cssClassQfqForm qfq-color-base Wrap around QFQ ‘Form’.
cssClassQfqFormPill qfq-color-grey-1 Wrap around title bar for pills: CSS Class, typically a background color.
cssClassQfqFormBody qfq-color-grey-2 Wrap around FormElements: CSS Class, typically a background color.
formBsColumns col-md-12 col-lg-10 The whole form will be wrapped. See bs-custom-field-width
formBsLabelColumns col-md-3 col-lg-3 The column get the width. See bs-custom-field-width
formBsInputColumns col-md-6 col-lg-6
formBsNoteColumns col-md-3 col-lg-3
extraButtonInfoInline <img src=”info.png”> Image for extraButtonInfo (inline).
extraButtonInfoBelow <img src=”info.png”> Image for extraButtonInfo (below).
extraButtonInfoPosition below ‘auto’ (default) or ‘below’. See extraButtonInfo .
extraButtonInfoClass pull-right ‘’ (default) or ‘pull-right’. See extraButtonInfo .
Form-Language
formLanguage[ABCD]Id E.g.: 1 In Typo3 configured pageLanguage id. The number after the ‘L’ parameter.
formLanguage[ABCD]Label E.G.: english Label shown in Form editor , on the ‘basic’ tab.
saveButtonText Text on the form save button. Typically none.
saveButtonTooltip Save Tooltip on the form save button.
saveButtonClass btn btn-default navbar-btn Bootstrap CSS class for save button on top of the form.
buttonOnChangeClass alert-info btn-info Bootstrap CSS class for save button showing ‘data changed’.
saveButtonGlyphIcon glyphicon-ok Icon for the form save button.
closeButtonText Text on the form close button. Typically none.
closeButtonTooltip close Tooltip on the form close button.
closeButtonClass btn btn-default navbar-btn Bootstrap CSS class for close button on top of the form.
closeButtonGlyphIcon glyphicon-remove Icon for the form close button.
deleteButtonText Text on the form delete button. Typically none.
deleteButtonTooltip delete Tooltip on the form delete button.
deleteButtonClass btn btn-default navbar-btn Bootstrap CSS class for delete button on top of the form.
deleteButtonGlyphIcon glyphicon-trash Icon for the form delete button.
newButtonText Text on the form new button. Typically none.
newButtonTooltip new Tooltip on the form new button.
newButtonClass btn btn-default navbar-btn Bootstrap CSS class for new button on top of the form.
newButtonGlyphIcon glyphicon-plus Icon for the form new button.
showIdInFormTitle 0 (off), 1 (on) Append at the form title the current record id.
cssClassColumnId text-muted A column in a subrecord with the name id|ID|Id gets this class.

After parsing the configuration, the following variables will be set automatically in STORE_SYSTEM:

Keyword Description
dbNameData Name of the ‘data’-database. ‘{{dbNameData:Y}}
dbNameQfq Name of the ‘QFQ’-database. ‘{{dbNameQfq:Y}}
dbNameT3 Name of the ‘T3’-database. ‘{{dbNameT3:Y}}
sitePath Absolute path of the current T3 instance. ‘{{sitePath:Y}}
extPath Absolute path of the QFQ extension. ‘{{extPath:Y}}

Custom variables

Up to 30 custom variables can be defined in configuration .

E.g. to setup a contact address and reuse the information inside your installation do:

  custom1: ADMINISTRATIVE_CONTACT = john@doe.com
  custom2: ADMINISTRATIVE_ADDRESS = John Doe, Hollywood Blvd. 1, L.A.
  custom3: ADMINISTRATIVE_NAME = John Doe

* Somewhere in a `Form` or in `Report`::

     {{ADMINISTRATIVE_CONTACT:Y}}, {{ADMINISTRATIVE_ADDRESS:Y}}, {{ADMINISTRATIVE_NAME}}

It’s also possible to configure such variables directly in config.qfq.php .

Fill STORE_SYSTEM by SQL

A specified SELECT statement in configuration in variable fillStoreSystemBySql1 (or 2 , or 3 ) will be fired. The query should have 0 (nothing happens) or 1 row. All columns will be added to the existing STORE_SYSTEM. Existing variables will be overwritten. Be careful not to overwrite system values.

This option is useful to make generic custom values, saved in the database, accessible to all QFQ Report and Forms. Access such variables via {{<varname>:Y}} .

In case QFQ should stop working if a given query does not select exact one record (e.g. a missing period), define an error message:

fillStoreSystemBySql1: SELECT name FROM Person WHERE name='Doe'
fillStoreSystemBySqlErrorMsg1: Too many or to few "Doe's" in our database

periodId

This is

  • a usecase, implemented via fillStoreSystemBySql ,
  • a way to access Period.id with respect to the current period (the period itself is custom defined).

After a full QFQ installation:

  • a table Period (extend / change it to your needs, fill them with your periods),
  • one sample record in table Period ,

Websites, delivering semester data, school year schedules, or any other type or periods, often need an index to the current period.

In configuration :

fillStoreSystemBySql1: SELECT id AS periodId FROM Period WHERE start<=NOW() ORDER BY start DESC LIMIT 1

a variable ‘periodId’ will automatically computed and filled in STORE SYSTEM. Access it via {{periodId:Y0}} . To get the name and current period:

SELECT name, ' / ', start FROM Period WHERE id={{periodId:Y0}}

Typically, it’s necessary to offer a ‘previous’ / ‘next’ link. In this example, the STORE SIP holds the new periodId:

SELECT CONCAT('p:{{pageAlias:T}}&periodId=', {{periodId:SY0}}-1, '|Next') AS _page, ' ', name, ' ',
  CONCAT('p:{{pageAlias:T}}&periodId=', {{periodId:SY0}}+1, '|Next') AS _page FROM Period AS s WHERE s.id={{periodId:SY0}}

Take care for minimum and maximum indexes (do not render the links if out of range).

DB USER privileges

The specified DB User needs privileges to the database of at least: SELECT / INSERT / UPDATE / DELETE / SHOW.

To apply automatically QFQ-‘DB UPDATE’ the following rights are mandatory too: CREATE / ALTER

To get access to the Typo3 installation, ‘dbuser’ should also have access to the Typo3 Database with at least SELECT / INSERT / UPDATE / DELETE.

Exception for SECURITY_GET_MAX_LENGTH

If it is necessary to use a GET variable which exceeds securityGetMaxLength limit, name the variable with ‘_<num>’ at the end. E.g. my_long_variable_130 . Such a variable has an allowed length of 130 chars. Access the variable as usual with the variable name: {{my_long_variable_130:C:…}} .

FE-User: Session timeout seconds

There is no timeout for website users who are not logged in (but typically those users don’t have access to protected content).

For logged in users, the default timeout is the php.ini settings for session.cookie_lifetime and session.gc_maxlifetime (minimum of both). These timeout only affects QFQ related content and can be specified a) globally (QFQ configuration) and b) specific per Form.

The maximum timeout depends on the minimal value of php.ini session.cookie_lifetime and session.gc_maxlifetime . Specifying a higher value produces an error in the front end.

Every access to QFQ related content resets the timeout.

After FE login, the next access to QFQ related content starts the timeout counter.

Local Documentation

A HTML rendered version is available under: <your site>/typo3conf/ext/qfq/Documentation/html/Index.html

If you get a ‘Page forbidden / not found’ there might be some Webserver restrictions. E.g. the Typo3 example of .htaccess in the Typo3 installation folder will forbid access to any extension documentation (which is a good idea on a productive server). For a development server instead, deactivate the forbid rule of ‘documentation’. In .htaccess (snippet from Typo3 7.6 _.htaccess):

production:   RewriteRule (?:typo3conf/ext|typo3/sysext|typo3/ext)/[^/]+/(?:Configuration|Resources/Private|Tests?|Documentation|docs?)/ - [F]
development:  RewriteRule (?:typo3conf/ext|typo3/sysext|typo3/ext)/[^/]+/(?:Configuration|Resources/Private|Tests?|docs?)/ - [F]

Concept

SIPs

The following is a technical background information. Not needed to just use QFQ.

The SIPs (=Server Id Pairs) are uniq timestamps, created/registered on the fly for a specific browser session (=user). Every SIP is registered on the server (= stored in a browser session) and contains one or more key/value pairs. The key/value pairs never leave the server. The SIPs will be used:

  • to protect values not to be spoofed by anyone,
  • to protect values not to be altered by anyone,
  • to grant access, e.g.:
    • load and save forms,
    • upload files,
    • download files,
    • PHP AJAX pages.

SIPs becomes invalid, as soon as the current browser session is destroyed. The client (= user) can’t manipulate the content of SIPs - it’s only possible to reuse already registered SIPs by the user, who already owns the session.

Access privileges

The Typo3 FE Groups can be used to implement access privileges. Such groups are assigned to

  • Typo3 FE users,
  • Typo3 pages,
  • and/or Typo3 content records (e.g. QFQ records).

This will be used for general page structure privileges.

A record base privileges controlling (e.g. which user can edit which person record) will be implicit configured, by the way that records are viewable / editable (or not) through SQL in the specific QFQ tt-content statements.

Typo3 QFQ content element

Insert one or more QFQ content elements on a Typo3 page. Specify column and language per content record as wished.

The title of the QFQ content element will not be rendered on the frontend. It’s only visible to the webmaster in the backend for orientation.

QFQ Keywords (Bodytext)

All of the named parameter are optional.

Name Explanation
form
Formname.
Static: form = person
By SIP: form = {{form:SE}}
By SQL: form = {{SELECT c.form FROM config AS c WHERE c.id={{a:C}} }}
r
<record id>. The form will load the record with the specified id.
Static: r = 123
By SQL: r = {{SELECT …}}
If not specified, the SIP parameter ‘r’ is used.
dbIndex E.g. dbIndex = {{indexQfq:Y}} Select a DB index. Only necessary if a different than the standard DB should be used.
debugShowBodyText If=‘1’ and configuration : showDebugInfo: yes , shows a tooltip with bodytext
sqlLog Overwrites configuration : SQL_LOG . Only affects Report , not Form .
sqlLogMode Overwrites configuration : SQL_LOG_MODE . Only affects Report , not Form .
<level>.fbeg Start token for every field (=column)
<level>.fend End token for every field (=column)
<level>.fsep Separator token between fields (=columns)
<level>.fskipwrap Comma separated list of column id’s. Skip wrapping of indexed columns.
<level>.shead Static start token for whole <level>, independent if records are selected Shown before head .
<level>.stail Static end token for whole <level>, independent if records are selected. Shown after tail .
<level>.head Dynamic start token for whole <level>. Only if at least one record is select.
<level>.tail Dynamic end token for whole <level>. Only if at least one record is select.
<level>.rbeg Start token for row.
<level>.rbgd Alternating (per row) token.
<level>.rend End token for row. Will be rendered before subsequent levels are processed
<level>.renr End token for row. Will be rendered after subsequent levels are processed
<level>.rsep Seperator token between rows
<level>.sql SQL Query
<level>.althead If <level>.sql is empty, these token will be rendered.
<level>.altsql If <level>.sql is empty, these query will be fired. No sub queries. Shown after althead
<level>.content
show (default): content of current and sub level are directly shown.
hide : content of current and sub levels are stored and not shown.
store : content of current and sub levels are stored and shown.
To retrieve the content: {{<level>.line.content}} . See syntax-of-report

QFQ Database

Recommended setup for Typo3 & QFQ Installation is with two databases. One for the Typo3 installation and one for QFQ. A good practice is to name both databases equal, appending the suffix ‘_t3’ and ‘_db’.

When QFQ is called, it checks for QFQ system tables. If they do not exist or have a lower version than the installed QFQ version, the system tables will be automatically installed or updated.

System tables

Name Use Database
Clipboard Temporary QFQ
Cron Persistent QFQ
Dirty Temporary QFQ | Data
Form Persistent QFQ
FormElement Persistent QFQ
FormSubmitLog Persistent QFQ | Data
MailLog Persistent QFQ | Data
Period Persistent Data
Split Persistent Data

See mail-log-page and form-submit-log-page for some Frontend views for these tables.

  • Check Bug #5459 / support of system tables in different DBs not supported.

Multi Database

Base: T3 & QFQ

QFQ typically interacts with one database, the QFQ database. The database used by Typo3 is typically a separate one. Theoretically it might be the same (never tested), but it’s strongly recommended to use a separated QFQ database to have no problems on Typo3 updates and to have a clean separation between Typo3 and QFQ.

QFQ: System & Data

QFQ itself can be separated in ‘QFQ system’ (see system-tables ) and ‘QFQ data’ databases (even more than one are possible). The ‘QFQ system’ stores the forms, record locking, log tables and so on - QFQ data is for the rest.

A Multi Database setup is given, if ‘QFQ system’ is different from ‘QFQ data’.

Data: Data1, Data2, …, Data n

Every database needs to be configured via configuration with it’s own index .

QFQ data might switch between different ‘data’-databases. In configuration one main QFQ data index will be specified in indexQfq . If specific forms or reports should use a different database than that, dbIndex might change indexData temporarily.

dbIndex : A Report (field dbIndex ) as well as a Form (field parameter .`dbIndex`) can operate on a specific database.

A Form will:

  • load the form-definition from indexQfq (table Form and FormElement ),
  • loads and save data from/in indexData (config.qfq.php) / dbIndex (form.parameter.dbIndex),
  • retrieve extra information via dbIndexExtra - this is useful to offer information from a database and save them in a different one.

The simplest setup, QFQ system & data in the same database, needs no indexQfq / indexData definition in configuration or one or both of them set to ‘1’

To separate QFQ system and data, indexQfq and indexData will have different indexes.

A Multi Database setup might be useful for:

  • several independent Typo3 QFQ installations (each have it’s own form repository) and one central database, or
  • one QFQ installation which should display / load /save records from different databases, or
  • a combination of the above two.

Note:

  • Option ‘A’ is the most simple and commonly used.
  • Option ‘B’ separate the T3 and QFQ databases on two database hosts.
  • Option ‘C’ is like ‘B’ but with a shared ‘QFQ data’-database between three ‘Typo3 / QFQ’ instances.
  • Further variants are possible.
Domain Website Host T3 QFQ system QFQ data
A standalone.edu ‘w’ <dbHost>, <dbname>_t3, <dbnameSingle>_db
B appB1.edu ‘wApp’ <dbHostApp>, <dbnameB1>_t3 <dbHostB1>, <dbnameApp>_db
B appB2.edu ‘wApp’ <dbHostApp>, <dbnameB2>_t3 <dbHostB2>, <dbnameApp>_db
C appC1.edu ‘wAppC’ <dbHostAppC>, <dbnameC1>_t3 <dbHostC>, <dbnameSysC1>_db <dbHostData>_db, <dbNameData>_db
C appC2.edu ‘wAppC’ <dbHostAppC>, <dbnameC2>_t3 <dbHostC>, <dbnameSysC2>_db <dbHostData>_db, <dbNameData>_db
C appC3.edu ‘wAppC3’ <dbHostAppC3>, <dbnameC3>_t3 <dbHostC3>, <dbnameSysC3>_db <dbHostData>_db, <dbNameData>_db

In config-qfq-php mutliple database credentials can be prepared. Mandatory is at least one credential setup like DB_1_USER , DB_1_SERVER , DB_1_PASSWORD , DB_1_NAME . The number ‘1’ indicates the dbIndex . Increment the number to specify further database credential setups.

Typically the credentials for DB_1 also have access to the T3 database.

Different QFQ versions, shared database

When using different QFQ versions and a shared ‘QFQ data’-database, there is some risk of conflicting ‘QFQ system’ tables. Best is to always use the same QFQ version on all instances or use a Multi Database setup.

Debug

SQL Logging

Setup in configuration

  • sqlLog
    • Filename where to log SQL queries and statistical data.
    • File is relative to the <site path> or absolute (starting with ‘/’).
    • Content: SQL queries and timestamp, formName/formId, fe_user, success, affected rows, newly created record id’s and accessed from IP.
    • The global setting can be overwritten by defining sqlLog inside of a QFQ tt-content record.
  • sqlLogMode: all|modify|error|none

    • all : logs every SQL statement.
    • modify : logs only statements who might potentially change data.
    • error : logs only queries which generate SQL errors.
    • none : no query logging at all.
    • The global setting can be overwritten by defining sqlLogMode inside of a QFQ tt-content record.
  • showDebugInfo = [yes|no|auto],[download]

    If active, displays additional information in the Frontend (FE). This is typically helpful during development.

    • yes :

      • Form:

        • For every internal link/button, show tooltips with decoded SIP on mouseover.
        • Shows an ‘Edit form’-button (wrench symbol) on a form. The link points to the T3 page with the FormEditor .
      • Report: Will be configured per tt-content record.

        debugShowBodyText = 1

    • no : No debug info.

    • auto : Depending if there is a Typo3 BE session, set internally:

      • showDebugInfo = yes (BE session exist)
      • showDebugInfo = no (no BE session)
    • download :

      • During a download (especially by using wkhtml), temporary files are not deleted automatically. Also the wkhtmltopdf and pdfunite command lines will be logged to SQL_LOG . Use this only to debug problems on download.

Redirect all mail to (catch all)

Setup in configuration

  • redirectAllMailTo=john@doe.com
    • During the development, it might be helpful to configure a ‘catch all’ email address, which QFQ uses as the final receiver instead of the original intended one.
    • The setting will:
      • Replace the ‘To’ with the configured one.
      • Clear ‘CC’ and ‘Bcc’
      • Write a note and the original configured receiver at the top of the email body.

mail-log-page

Mail Log page

For debugging purposes you may like to add a Mail Log page in the frontend. The following QFQ code could be used for that purpose (put it in a QFQ PageContent element):

# Page parameters
1.sql = SELECT @grId := '{{grId:C0:digit}}' AS _grId
2.sql = SELECT @summary := IF('{{summary:CE:alnumx}}' = 'true', 'true', 'false') AS _s

# Filters
10 {
  sql = SELECT gr.id, IF(gr.id = @grId, "' selected>", "'>"), gr.value, ' (Id: ', gr.id, ')'
           FROM gGroup AS gr
           INNER JOIN MailLog AS ml ON ml.grId = gr.id
           GROUP BY gr.id
  head = <form onchange='this.submit();' class='form-inline'><input type='hidden' name='id' value='{{pageAlias:T0}}'>
           Filter By Group: <select name='grId' class='form-control'><option value=''></option>
  rbeg = <option value='
  rend = </option>
  tail = </select>
}
20 {
  sql = SELECT IF(@summary = 'true', ' checked', '')
  head = <div class='checkbox'><label><input type='checkbox' name='summary' value='true'
  tail = >Summary</label></div></form>
}

# Mail Log
50 {
  sql = SELECT id, '</td><td>', grId, '</td><td>', xId, '</td><td>',
            REPLACE(receiver, ',', '<br>'), '</td><td>', REPLACE(sender, ',', '<br>'), '</td><td>',
            DATE_FORMAT(modified, '%d.%m.%Y<br>%H:%i:%s'), '</td><td style="word-break:break-word;">',
            CONCAT('<b>', subject, '</b><br>', IF(@summary = 'true', CONCAT(SUBSTR(body, 1,
                   LEAST(IF(INSTR(body, '\n') = 0, 50, INSTR(body, '\n')), IF(INSTR(body, '<br>') = 0, 50,
                   INSTR(body, '<br>')))-1), ' ...'), CONCAT('<br>', REPLACE(body, '\n', '<br>'))) )
          FROM MailLog WHERE (grId = @grId OR @grId = 0)
          ORDER BY modified DESC
          LIMIT 100
  head = <table class="table table-condensed table-hover"><tr>
             <th>Id</th><th>grId</th><th>xId</th><th>To</th><th>From</th><th>Date</th><th>E-Mail</th></tr>
  tail = </table>
  rbeg = <tr><td>
  rend = </td></tr>
}

form-submit-log-page

Form Submit Log page

For debugging purposes you may like to add a Form Submit Log page in the frontend. The following QFQ code could be used for that purpose (put it in a QFQ PageContent element):

# Filters
20.shead = <form onchange='this.submit()' class='form-inline'><input type='hidden' name='id' value='{{pageAlias:T0}}'>
20 {
  sql = SELECT id, IF(id = '{{formId:SC0}}', "' selected>", "'>"), name
        FROM Form ORDER BY name
  head = <label for='formId'>Form:</label> <select name='formId' id='formId' class='form-control'><option value=0></option>
  tail = </select>
  rbeg = <option value='
  rend = </option>
}
30 {
  sql = SELECT feUser, IF(feUser = '{{feUser:SCE:alnumx}}', "' selected>", "'>"), feUser
        FROM FormSubmitLog GROUP BY feUser ORDER BY feUser
  head = <label for='feUser'>FE User:</label> <select name='feUser' id='feUser' class='form-control'><option value=''></option>
  tail = </select>
  rbeg = <option value='
  rend = </option>
}
30.stail = </form>

# Show Log
50 {
  sql = SELECT l.id,
      CONCAT('<b>Form</b>: ', f.name,
        '<br><b>Record Id</b>: ', l.recordId,
        '<br><b>Fe User</b>: ', l.feUser,
        '<br><b>Date</b>: ', l.created,
        '<br><b>Page Id</b>: ', l.pageId,
        '<br><b>Session Id</b>: ', l.sessionId,
        '<br><b>IP Address</b>: ', l.clientIp,
        '<br><b>User Agent</b>: ', l.userAgent,
        '<br><b>SIP Data</b>: <div style="margin-left:20px;">', "<script>var data = JSON.parse('", l.sipData,
          "'); for (var key in data) {
          document.write('<b>' + key + '</b>: ' + data[key] + '<br>'); }</script>", '</div>'),
      CONCAT("<script>var data = JSON.parse('", l.formData,
        "'); for (var key in data) {
          document.write('<b>' + key + '</b>: ' + data[key] + '<br>'); }</script>")
      FROM FormSubmitLog AS l
      LEFT JOIN Form AS f ON f.id = l.formId
      WHERE (l.formId = '{{formId:SC0}}' OR '{{formId:SC0}}' = 0)
        AND (l.feUser = '{{feUser:SCE:alnumx}}' OR '{{feUser:SCE:alnumx}}' = '')
      ORDER BY l.created DESC LIMIT 100
  head = <table class="table table-hover">
         <tr><th>Id</th><th style="min-width:250px;">Environment</th><th>Submitted Data</th>
  tail = </table>
  rbeg = <tr>
  renr = </tr>
  fbeg = <td>
  fend = </td>
}

Variable

Variables in QFQ are surrounded by double curly braces. Four different types of variable substitution functionality is provided. Access to:

Some examples, including nesting:

# Store
#---------------------------------------------
{{r}}
{{index:FS}}
{{name:FS:alnumx:s:my default}}

# SQL
#---------------------------------------------
{{SELECT name FROM person WHERE id=1234}}

# Row columns
#---------------------------------------------
{{10.pId}}
{{10.20.pId}}

# Nesting
#---------------------------------------------
{{SELECT name FROM person WHERE id={{r}} }}
{{SELECT name FROM person WHERE id={{key1:C:alnumx}} }} # explained below
{{SELECT name FROM person WHERE id={{SELECT id FROM pf LIMIT 1}} }} # it's more efficient to use only one query

# Link Columns
{{p:form=Person&r=1|t:Edit Person|E|s AS link}}

Leading and trailing spaces inside curly braces are removed.

  • {{ SELECT “Hello World” }} becomes {{SELECT “Hello World”}}
  • {{ varname }} becomes {{varname}}

Types

Store variables

Syntax: {{VarName[:<store(s)[:<sanitize class>[:<escape>[:<default value>[:type violate message]]]]]}}

See also:

  • Example:

    {{pId}}
    {{pId:FSE}}
    {{pId:FSE:digit}}
    {{pId::digit}}
    {{name:FSE:alnumx:m}}
    {{name:::m}}
    {{name:FSE:alnumx:m:John Doe}}
    {{name::::John Doe}}
    {{name:FSE:alnumx:m:John Doe:forbidden characters}}
    {{name:::::forbidden characters}}
    
  • Zero or more stores might be specified to be searched for the given VarName.

  • If no store is specified, the default for the searched stores are: FSRVD (=FORM > SIP > RECORD > VARS > DEFAULT).

  • If the VarName is not found in one store, the next store is searched, up to the last specified store.

  • If the VarName is not found and a default value is given, the default is returned.

  • If no value is found, nothing is replaced - the string ‘{{<VarName>}}’ remains.

  • If anywhere along the line an empty string is found, this is a value: therefore, the search will stop.

Sanitize class

Values in STORE_CLIENT C (Client=Browser) and STORE_FORM F (Form, HTTP ‘post’) are checked against a sanitize class. Values from other stores are not checked against any sanitize class, even if a sanitize class is specified.

  • Variables get by default the sanitize class defined in the corresponding FormElement . If not defined, the default class is ‘digit’.
  • A default sanitize class can be overwritten by individual definition: {{a:C:alnumx}}
  • If a value violates the specific sanitize class, see variable-type-message-violate for default or customized message. By default the value becomes !!<name of sanitize class>!! . E.g. !!digit!! .

For QFQ variables and FormElements:

Name Form Query Pattern
alnumx Form Query [A-Za-z][0-9]@-_.,;: /() ÀÈÌÒÙàèìòùÁÉÍÓÚÝáéíóúýÂÊÎÔÛâêîôûÃÑÕãñõÄËÏÖÜŸäëïöüÿç
digit Form Query [0-9]
numerical Form Query [0-9.-+]
allbut Form Query All characters allowed, but not [ ] { } % #. The used regexp: ‘^[^[]{}%\#]+$’,
all Form Query no sanitizing

Only in FormElement:

Rules for CheckType Auto (by priority):

  • TypeAheadSQL or TypeAheadLDAP defined: alnumx
  • Table definition * integer type: digit * floating point number: numerical
  • FE Type * ‘password’, ‘note’: all * ‘editor’, ‘text’ and encode = ‘specialchar’: all
  • None of the above: alnumx

Escape/Action class

The following escape & action types are available:

  • The escape/action class is defined by the fourth parameter of the variable. E.g.: {{name:FE:alnumx:m}} (m = mysql).
  • It’s possible to combine multiple escape/action classes, they will be processed in the order given. E.g. {{name:FE:alnumx:Ls}} (L, s).
  • Escaping is typically necessary for all user supplied content, especially if they are processed via SQL or LDAP queries.
  • Be careful when escaping nested variables. Best is to escape only the most outer variable.
  • In configuration a global escapeTypeDefault can be defined. The configured escape/action class applies to all substituted variables, who do not contain a specific escape/action class.
  • Additionally a defaultEscapeType can be defined per Form (separate field in the Form editor ). This overwrites the global definition of configuration . By default, every Form.defaultEscapeType = ‘c’ (=config), which means the setting in configuration .
  • To suppress an escape type, define the escape type = ‘-‘ on the specific variable. E.g.: {{name:FE:alnumx:-}} .

Escape

To ‘escape’ a character typically means: a character, which have a special meaning/function, should not treated as a special character. E.g. a string is surrounded by single ticks ‘. If such a string should contain the same single tick inside, the inside single tick has to be escaped - if not, the string end’s at the second tick, not the third. This is typically done by a backlash: \

QFQ offers different ways of escaping. Which of them to use, depends on the situation.

Especially variables used in SQL statements might cause trouble when using: NUL (ASCII 0), \n, \r, \, ‘, “, or Control-Z.

Action

  • password - ‘p’: transforms the value of the variable into a Typo3 salted password hash. The hash function is the one used by Typo3 to encrypt and salt a password. This is useful to manipulate FE user passwords via QFQ. See setFeUserPassword
  • stop replace - ‘S’: typically QFQ will replace nested variables as long as there are variables to replace. This options
    stops this
  • exception - ‘X’: If a variable is not found in any given store, it’s replace by a default value or an error message. In special situation it might be useful to do a full stop on all current actions (no further procession). A custom message can be defined via: variable-type-message-violate .
  • wipe - ‘w’: In special cases it might be useful to get a value via SIP only one time and after retrieving the value it will be deleted in STORE SIP . Further access to the variable will return ‘variable undefined’. At time of writing only the STORE SIP supports the feature ‘wipe’. This is useful to suppress any repeating events by using the browser history. The following example will send a mail only the first when it is called with a given SIP:

    10.sql = SELECT ‘…’ AS _sendmail FROM Person AS p WHERE ‘{{action:S::w}}’=’send’ AND p.id={{pId:S}}

Default

  • Any string can be given to define a default value.
  • If a default value is given, it makes no sense to define more than one store: with a default value given, only the first store is considered.
  • If the default value contains a ‘:’, that one needs to be escaped by \

Type message violate

If a value violates the sanitize class, the following actions are possible:

  • ‘c’ - The violated class will be set as content, surrounded by ‘!!’. E.g. ‘!!digit!!’. This is the default.
  • ‘e’ - Instead of the value an empty string will be set as content.
  • ‘0’ - Instead of the value the string ‘0’ will be set as content.
  • ‘custom text …’ - Instead of the value, the custom text will be set as content. If the text contains a ‘:’, that one needs to be escaped by \ . Check variable-escape qualifier ‘C’ to let QFQ do the colon escaping.

SQL variables

  • The detection of an SQL command is case insensitive .
  • Leading whitespace will be skipped.
  • The following commands are interpreted as SQL commands:
    • SELECT
    • INSERT, UPDATE, DELETE, REPLACE, TRUNCATE
    • SHOW, DESCRIBE, EXPLAIN, SET
  • A SQL Statement might contain variables, including additional SQL statements. Inner SQL queries will be executed first.
  • All variables will be substituted one by one from inner to outer.
  • The number of variables inside an input field or a SQL statement is not limited.

Result: string

A result of a SQL statement will be imploded over all: concat all columns of a row, concat all rows - there is no glue string.

Result: row

A few functions needs more than a returned string, instead separate columns are necessary. To indicate an array result, specify those with an ‘!’:

{{!SELECT ...}}

This manual will specify the individual QFQ elements, who needs an array instead of a string. It’s an error to return a string where an array is needed and vice versa.

Database index

To access different databases in a multi-database setup, the database index can be specified after the opening curly braces.

{{[1]SELECT ... }}

For using the indexData and indexQfq ( configuration ), it’s a good practice to specify the variable name instead of the numeric index.

{{[{{indexData:Y}}]SELECT ...}}

If no dbIndex is given, {{indexData:Y}} is used.

Example

{{SELECT id, name FROM Person}}
{{SELECT id, name, IF({{feUser:T0}}=0,'Yes','No')  FROM Person WHERE id={{r:S}} }}
{{SELECT id, city FROM Address AS adr WHERE adr.accId={{SELECT id FROM Account AS acc WHERE acc.name={{feUser:T0}} }} }}
{{!SELECT id, name FROM Person}}
{{[2]SELECT id, name FROM Form}}
{{[{{indexQfq:Y}}]SELECT id, name FROM Form}}

Row column variables

Syntax: {{<level>.<column>}}

Only used in report to access outer columns. See access-column-values and syntax-of-report .

There might be name conflicts between VarName / SQL keywords and <line identifier>. QFQ checks first for ‘<level>’, than for ‘SQL keywords’ and than for ‘VarNames’ in stores.

All types might be nested with each other. There is no limit of nesting variables.

Very specific: Also, it’s possible that the content of a variable is again (including curly braces) a variable - this is sometimes used in text templates, where the template is retrieved from a record and specific locations in the text will be (automatically by QFQ) replaced by values from other sources.

General note: using this type of variables is only the second choice. First choice is {{column:R}} (see access-column-values ) - using the STORE_RECORD is more portable cause no renumbering is needed if the level keys change.

Security

All values passed to QFQ will be:

  • Checked against max. length and allowed content, on the client and on the server side. On the server side, the check happens before any further processing. The ‘length’ and ‘allowed’ content is specified per FormElement . ‘digit’ or ‘alnumx’ is the default. Violating the rules will stop the ‘save record’ process (Form) or result in an empty value (Report). If a variable is not replaced, check the default sanitize class.
  • Only elements defined in the Form definition or requested by Report will be processed.
  • UTF8 normalized (normalizer::normalize) to unify different ways of composing characters. It’s more a database interest, to work with unified data.

SQL statements are typically fired as prepared statements with separated variables. Further custom SQL statements will be defined by the webmaster - those do not use prepared statements and might be affected by SQL injection. To prevent SQL injection, every variable is by default escaped with mysqli::real_escape_string .

QFQ notice :

  • Variables passed by the client (=Browser) are untrusted and use the default sanitize class ‘digit’ (if nothing else is specified). If alpha characters are submitted, the content violates digit and becomes therefore !!<name of sanitize class>!! - there is no error message. Best is to always use SIP (value is trustful) or at least digits for GET (=client) parameter (user might change those and therefore those are not trustful).

Get Parameter

QFQ security restriction :

  • GET parameter might contain urlencoded content (%xx). Therefore all GET parameter will be processed by ‘urldecode()’. As a result a text like ‘%nn’ in GET variables will always be decoded. It’s not possible to transfer ‘%nn’ itself.
  • GET values are limited to securityGetMaxLength ( extension-manager-qfq-configuration ) chars - any violation will stop QFQ. Individual exceptions are defined via ExceptionMaxLength .
  • GET parameter ‘type’ and ‘L’ might affected by (T3, configuration dependent) cache poisoning. If they contain non digit values, only the first character is used (if this is a digit) or completely cleaned (else).

Post Parameter

Per FormElement (HTML input) the default is to htmlspecialchars() the input. This means &<>’” will be encoded as htmlentity and saved as a htmlentity in the database. In case any of these characters (e.g. for HTML tags) are required, the encoding can be disabled per FormElement: encode=none (default is specialchar ).

During Form load, htmlentities are decoded again.

$_SERVER

All $_SERVER vars are htmlentities encoded (all, not only specialchars!) .

Honeypot

Every QFQ Form contains ‘honeypot’-HTML input elements (HTML: hidden & readonly). Which of them to use is configured in configuration (default: ‘username’, ‘password’ and ‘email’). On every start of QFQ (form, report, save, …), these variables are tested if they are non-empty. In such a case a probably malicious bot has send the request and the request will not be processed.

If any of the default configured variable names are needed (which will never be the case for QFQ), an explicit variable name list have to be configured in configuration .

QFQ security restriction :

  • The honeypot variables can’t be used in GET or POST as regular HTML input elements - any values of them will terminate QFQ.

Violation

On any violation, QFQ will sleep securityAttackDelaySeconds ( configuration ) and than exit the running PHP process. A detected attack leads to a complete white (=empty) page.

If securityShowMessage : true ( configuration ), at least a message is displayed after the delay.

Client Parameter via SIP

Links with URL parameters, targeting to the local website, are typically SIP encoded. Instead of transferring the parameter as part of the URL, only one unique GET parameter ‘s’ is appended at the link. The parameter ‘s’ is unique (equal to a timestamp) for the user. Assigned variables are stored as a part of the PHP user session on the server. Two users might have the same value of parameter ‘s’, but the content is completely independent.

Variables needed by Typo3 remains on the link and are not ‘sip-encoded’.

Secure direct file access

If the application uploads files, mostly it’s not necessary and often a security issue, to offer a direct download of the uploaded files. Best is to create a directory, e.g. <site path>/fileadmin/protected and deny direct access via webbrowser to it. E.g. for Apache set a rule:

<Directory "/var/www/html/fileadmin/protected">
    Require all denied
</Directory>

If you only have access to .htaccess , create a file <site path>/fileadmin/protected/.htaccess with:

<IfModule mod_authz_core.c>
     Require all denied
</IfModule>

Important

All QFQ uploads should save files only in/below such a protected directory.

To offer download of those files, use the reserved column name ‘_download’ (see download ) or variants.

Important

To protect the installation against executing of uploaded malicious script code, disable PHP for the final upload directory. E.g. fileadmin (Apache):

<Directory "/var/www/html/fileadmin">
  php_admin_flag engine Off
</Directory>

This is in general a good security improvement for directories with user supplied content.

File upload

By default the mime type of every uploaded file is checked against a white list of allowed mime types. The mime type of a file can be (easily) faked by an attacker. This check is good to handle regular user file upload for specific file types but won’t help to prevent attacks against uploading and executing malicious code.

Instead prohibit the execution of user contributed files by the webserver config ( SecureDirectFileAccess ).

Typo3 Setup - best practice

  • Activate notification emails for every BE login (if there are only few BE users). In case the backend has been hacked, unusual login’s (time or username) will appear:

    [BE][warning_email_addr] = <your email>
    [BE][warning_mode] = 1
    

Store

Only variables that are known in a specified store can be substituted.

Name Description Content
B Store: BEFORE - B : Record - the current record loaded in the form before any update. All columns of the current record from the current table. See STORE_BEFORE .
C Store: CLIENT - C : POST variable, if not found: GET variable. Parameter sent from the Client (=Browser). See STORE_CLIENT .
D Default values column : The table.column specified default value .
E Empty - always an empty string, might be helpful if a variable is empty or undefined and will be used in an SQL statement. Any key
F Store: FORM - F : data not saved in database yet. All native FormElements . Recent values from the Browser. See: STORE_FORM
L Store: LDAP - L : Will be filled on demand during processing of a FormElement . Custom specified list of LDAP attributes. See STORE_LDAP .
M Column type: The table.column specified type .
P Parent record. E.g.: on multi & copy forms the current record of the outer query. All columns of the MultiSQL Statement from the table for the current row
R Store: RECORD - R : Record - the current record loaded in the form. All columns of the current record from the current table. See STORE_RECORD .
S Store: SIP - S : Client parameter ‘s’ will indicate the current SIP, which will be loaded from the SESSION repo to the SIP-Store. sip, r (recordId), form. See STORE_SIP .
T Store: TYPO3 (Bodytext) - T : a) Bodytext (ttcontent record), b) Typo3 internal variables. See Typo3 tt_content record configuration. See STORE_TYPO3 .
U Store: USER - U : per user variables, valid as long as the browser session lives. Set via report: ‘…’ AS ‘_=<var name>’ See: STORE_USER , store_user_examples
V Store: VARS - V : Generic variables. See STORE_VARS .
Y Store: SYSTEM - Y : a) Database, b) helper vars for logging/debugging: SYSTEM_SQL_RAW … SYSTEM_FORM_ELEMENT_COLUMN, c) Any custom fields: CONTACT, HELP, … See STORE_SYSTEM .
0 Zero - always value: 0, might be helpful if a variable is empty or undefined and will be used in an SQL statement. Any key
  • Default <prio> : FSRVD - Form / SIP / Record / Vars / Table definition.
  • Hint: Preferable, parameter should be submitted by SIP, not by Client (=URL).
    • Warning: Data submitted via ‘Client’ can be easily spoofed and altered.
    • Best: Data submitted via SIP never leaves the server, cannot be spoofed or altered by the user.
    • SIPs can _only_ be defined by using Report . Inside of Report use columns ‘Link’ (with attribute ‘s’), ‘page?’ or ‘Page?’.

Store: FORM - F

  • Sanitized: yes
  • Represents the values in the form, typically before saving them.
  • Used for:
    • FormElements which will be rerendered, after a parent FormElement has been changed by the user.
    • FormElement actions, before saving the form.
    • Values will be sanitized by the class configured in corresponding the FormElement . By default, the sanitize class is alnumx .
Name Explanation
<FormElement name> Name of native FormElement . To get, exactly and only, the specified FormElement (for ‘pId’): {{pId:F}}

Store: SIP - S

  • Sanitized: no
  • Filled automatically by creating links. E.g.:
    • in Report by using _page? or _link (with active ‘s’)
    • in Form by using subrecords: ‘new’, ‘edit’, ‘delete’ links (system) or by column type _page? , _link .
Name Explanation
sip 13 char uniqid
r current record id
form current form name
table current table name
urlparam all non Typo3 parameter in one string
<user defined> additional user defined link parameter

Store: RECORD - R

Name Type | Explanation
<column name> Form Name of a column of the primary table (as defined in the current form). Example: {{pId:R}}
<column name> Report Name of a column of a previous fired SQL query. Example: {{pId:R}}
&<column name> Report (final) Name of a column of a previous fired SQL query, typically used by columns with a special-column-names . Final value. Example: ‘{{link:R}}’ returns ‘p:home&form=Person|s|b:success|t:Edit’. Whereas ‘{{&link:R}}’ returns ‘<span class=”btn btn-success”><a href=”?home&s=badcaffee1234”>Edit</a></span>

Store: BEFORE - B

  • Sanitized: no
  • Current record loaded in Form without any modification.
  • If r=0, all values are empty.

This store is handy to compare new and old values of a form.

Name Explanation
<column name> Name of a column of the primary table (as defined in the current form). To get, exactly and only, the specified form FormElement : {{pId:B}}

Store: CLIENT - C

  • Sanitized: yes
Name Explanation
s =SIP
r record id. Only if specified as GET parameter - typically stored in SIP (=STORE_SIP)
form Name of form to load. Only if specified as GET parameter - typically stored in SIP (=STORE_SIP)
HTTP_HOST current HTTP HOST
REMOTE_ADDR Client IP address
‘$_SERVER[*]’ All other variables accessible by $_SERVER[] . Only the often used have a pre-defined sanitize class.
Authorization Value of the HTTP Header ‘Authorization’. This is typically not set. Mostly used for authentication of REST requests

Store: TYPO3 (Bodytext) - T

  • Sanitized: no
Name Explanation Note
form
Formname defined in ttcontent record bodytext
* Fix. E.g. form = person
* via SIP. E.g. form = {{form:SE}}
see note
pageId Record id of current Typo3 page see note
pageAlias Alias of current Typo3 page. If empty, take pageId. see note
pageTitle Title of current Typo3 page see note
pageType Current selected page type (typically URL parameter ‘type’) see note
pageLanguage Current selected page language (typically URL parameter ‘L’) see note
ttcontentUid Record id of current Typo3 content element see note
feUser Logged in Typo3 FE User
feUserUid Logged in Typo3 FE User uid
feUserGroup FE groups of logged in Typo3 FE User
beUser Logged in Typo3 BE User
beUserLoggedIn ‘yes’ | ‘no’ - Status if a BE-User is logged in
  • note : not available:
    • in Dynamic Update or
    • by FormElement class ‘action’ with type ‘beforeSave’, ‘afterSave’, ‘beforeDelete’, ‘afterDelete’.

Store: VARS - V

  • Sanitized: no
Name Explanation
random Random string with length of 32 alphanum chars (lower & upper case). This is variable is always filled.
slaveId see FormElement action
  • FormElement ‘upload’:
Name Explanation
filename Original filename of an uploaded file via an ‘upload’-FormElement. Valid only during processing of the current ‘upload’-formElement.
filenameOnly Like filename, but without path.
filenameBase Like filename , but without an optional extension. E.g. filename=’image.png’ comes to filenameBase=’image’
filenameExt Like filename , but only the optional extension. E.g. filename=’image.png’ comes to filenameExt=’png’
fileDestination Destination (path & filename) for an uploaded file. Defined in an ‘upload’-FormElement.parameter. Valid: same as ‘filename’.
fileSize Size of the uploaded file.
mimeType Mime type of the uploaded file.

The directive fillStoreVar will fill the store VARS with custom values. Existing Store VARS values will be merged together with them. E.g.:

fillStoreVar = {{SELECT p.name, p.email FROM Person AS p WHERE p.id={{pId:S}} }}
  • After filling the store, the values can be retrieved via {{name:V}} and {{email:V}} .
  • Be careful by specifying general purpose variables like id , r , pageId and so on. This might conflict with existing variables.
  • fillStoreVar can be used in form-parameter and fe-parameter-attributes

Store: LDAP - L

  • Sanitized: yes
  • See also LDAP :
Name Explanation
<custom defined> See ldapAttributes

Store: SYSTEM - Y

  • Sanitized: no

See configuration for a list of all settings.

Store: USER - U

  • Sanitized: no

At start of a new browser session (=user calls the website the first time or was logged out before) the store is empty. As soon as a value is set in the store, it remains as long as the browser session is alive (=until user logs out).

Values can be set via report ‘… AS “_=<var name>”’

See also: store_user_examples

LDAP

A form can retrieve data from LDAP server(s) to display or to save them. Configuration options for LDAP will be specified in the parameter field of the Form and/or the FormElement . Definitions of the FormElement will overwrite definitions of the Form . One LDAP Server can be configured per FormElement . Multiple FormElements might use individual LDAP Server configurations.

To decide which Parameter should be placed on Form.parameter and which on FormElement.parameter : If LDAP access is …

  • only necessary in one FormElement , most useful setup is to specify all values in that specific FormElement ,
  • needed on multiple FormElement*s (of the same *Form , e.g. one input with typeAhead , one note and one action ), it’s more efficient to specify the base parameter ldapServer , ldapBaseDn in Form.parameter and the rest on the current FormElement .
Parameter Example Description Form FormElement Used for
ldapServer directory.example.com Hostname. For LDAPS: ldaps://directory.example.com:636 x x TA, FSL
ldapBaseDn ou=Addressbook,dc=example,dc=com Base DN to start the search x x TA, FSL
ldapAttributes cn, email List of attributes to save in STORE_LDAP x x FSL
ldapSearch ( mail=john . doe @ example . com ) Regular LDAP search expression x x FSL
ldapTimeLimit 3 (default) Maximum time to wait for an answer of the LDAP Server x x TA, FSL
ldapUseBindCredentials ldapUseBindCredentials=1 Use LDAP_1_* credentials from config-qfq-php for ldap_bind() x x TA, FSL
typeAheadLdap Enable LDAP as ‘Typeahead’ data source x TA
typeAheadLdapSearch (|(cn=*?*)(mail=*?*)) Regular LDAP search expression, returns upto typeAheadLimit x x TA
typeAheadLdapSearchPrefetch (mail=?) Regular LDAP search expression, typically return one record x x TA
typeAheadLdapSearchPerToken
Split search value in token and OR-combine every search with
the individual tokens
x x TA
typeAheadLdapValuePrintf ‘%s / %s’, cn, mail Custom format to display attributes, as value x x TA
typeAheadLdapIdPrintf ‘%s’, mail Custom format to display attributes, as id x x TA
typeAheadLimit 20 (default) Result will be limited to this number of entries x x TA
typeAheadPedantic typeAheadPedantic=0 Turn off ‘pedantic’ mode - allow any values (see below) x x TA
typeAheadMinLength 2 (default) Minimum number of characters before starting the search x x TA
fillStoreLdap Activate Fill STORE LDAP with the first retrieved record x FSL
  • typeAheadLimit : there might be a hard limit on the server side (e.g. 100) - which can’t be extended.
  • ldapUseBindCredentials is only necessary if anonymous access is not possible. RDN and password has to be configured in config-qfq-php .

Typeahead (TA)

Typeahead offers continous searching of a LDAP directoy by using a regular FormElement of type text . The FormElement.parameter*=*typeAheadLdap will trigger LDAP searches on every user keystroke (starting after typeAheadMinLength keystrokes) for the current FormElement - this is different from dynamicUpdate (triggered by leaving focus of an input element). Typeahead delivers a list of elements.

  • FormElement.parameter.typeAheadLdap - activate the mode Typeahead - no value is needed, the existence is suffucient.
  • Form.parameter or FormElement.parameter :
    • ldapServer = directory.example.com
    • ldapBaseDn = ou=Addressbook,dc=example,dc=com
    • typeAheadLdapSearch = (|(cn=*?*)(mail=*?*))
    • typeAheadLdapValuePrintf = ‘%s / %s’, cn, email
    • typeAheadLdapIdPrintf = ‘%s’, email
    • Optional: ldapUseBindCredentials = 1

All fetched LDAP values will be formatted with:

  • typeAheadLdapValuePrintf , shown to the user in a drop-down box and
  • typeAheadLdapIdPrintf , which represents the final data to save.

The id/value translation is compareable to a regular select drop-down box with id/value pairs. Only attributes, defined in typeAheadLdapValuePrintf / typeAheadLdapIdPrintf will be fetched from the LDAP directory. To examine all possible values of an LDAP server, use the commandline tool ldapsearch . E.g.:

ldapsearch -x -h directory.example.com -L -b ou=Addressbook,dc=example,dc=com "(mail=john.doe@example.com)"

All occurrences of a ‘?’ in ldapSearch will be replaced by the user data typed in via the text- FormElement . The typed data will be escaped to fulfill LDAP search limitations. Regular Form variables might be used on all parameter and will be evaluated during form load (!) - not at the time when the user types something.

Pedantic

The typeAheadPedantic mode ensures that the typed value (technically this is the value of the id , latest in the moment when loosing the focus) is valid (= exists on the LDAP server or is defined in typeAheadSql ). If the user typed something and that is not a valid id , the client (=browser) will delete the input when losing the focus. To identify the exact id , an additional search filter is necessary: typeAheadLdapSearchPrefetch - see next topic.

typeAheadPedantic is active by default when typeAheadLdap or typeAheadSql is defined, but can be turned off with typeAheadPedantic=0 .

  • Form.parameter or FormElement.parameter :
    • typeAheadPedantic=0

Prefetch

After ‘form load’ with an existing record, the user expects to see the previous saved data. In case there is an id to value translation, the value does not exist in the database, instead it has to be fetched again dynamically. A precise LDAP or SQL query has to be defined to force this:

  • Form.parameter or FormElement.parameter :
    • typeAheadLdapSearchPrefetch = (mail=?)
    • typeAheadSqlPrefetch = SELECT firstName, ‘ ‘, lastName FROM person WHERE id = ?

This situation also applies in pedantic mode to verify the user input after each change.

PerToken

Sometimes a LDAP server only provides attributes like ‘sn’ and ‘givenName’, but not ‘displayName’ or another practial combination of multiple attributes - than it is difficult to search for ‘firstname’ and (=human AND) ‘lastname’. E.g. ‘John Doe’, results to search like (|(sn=*John Doe*)(givenName=*John Doe*)) which will be probably always be empty. Instead, the user input has to be split in token and the search string has to repeated for every token.

  • Form.parameter or FormElement.parameter :
    • typeAheadLdapSearchPerToken - no value needed.

This will repeat the search string per token.

E.g.:

User search string: X Y
Ldap search string: (|(a=*?*)(b=*?*))

Result: (& (|(a=*X*)(b=*X*)) (|(a=*Y*)(b=*Y*))

Attention: this option is only useful in specific environments. Only use it, if it is really needed. The query becomes much more cpu / IO intensive.

Fill STORE LDAP (FSL)

Before processing a FormElement , an optional configured FSL-action loads one record from a LDAP directory and stores the named attributes in STORE_LDAP. If the LDAP search query selects more than one record, only the first record is processed. The attributes names always becomes lowercase (PHP implentation detail on get_ldap_entries()) in the store. To make accessing STORE_LDAP easily, the keys are implemented case insensitive for this specific store. FLS is triggered during Form -…

  • load,
  • dynamic update,
  • save.

The FLS happens before the FormElement processing starts. Therefore the fetched LDAP data (specified by ldapAttributes ), are available via {{<attributename>:L:allbut:s}} during the regular FormElement processing. Take care to specify a sanitize class and optional escaping on further processing of those data.

Also, the STORE LDAP remains filled, during the whole form processing time. E.g. if the values are needed for a person name and email, it’s sufficient to fire one FSL on the first FormElement Action element, and use the same values during further FormElement Action Elements.

Important

LDAP access might slow down the Form processing on load, update or save! The timeout (default: 3 seconds) have to be multiplied by the number of accesses. E.g. a broken LDAP connection and 3 FormElements with FSL results to 9 seconds delay on save. Also be prepared not to receive the expected data.

  • FormElement.parameter.fillStoreLdap - activate the mode Fill S - no value is needed, the existence is sufficient.
  • Form.parameter or FormElement.parameter :
    • ldapServer = directory.example.com
    • ldapBaseDn = ou=Addressbook,dc=example,dc=com
    • typeAheadLdapSearch = (|(cn=*?*)(mail=*?*))
    • ldapAttributes = givenName, sn, telephoneNumber, email
    • ldapSearch = (mail={{email:F0:alnumx:l}})
    • Optional: ldapUseBindCredentials = 1

After filling the store, access the content via {{<attributename>:L:allbut:s}} .

Form

General

  • Forms will be created by using the Form Editor on the Typo3 frontend (HTML form).

  • The Form editor itself consist of two predefined QFQ forms: form and formElement - these forms are often updated during the installation of new QFQ versions.

  • Every form consist of a) a Form record and b) multiple FormElement records.

  • A form is assigned to a table . Such a table is called the primary table for this form.

  • Forms can roughly categorized into:

    • Simple form: the form acts on one record, stored in one table.
      • The form will create necessary SQL commands for insert, update and delete (only primary record) automatically.
    • Advanced form: the form acts on multiple records, stored in more than one table.
      • Fields of the primary table acts like a simple form, all other fields have to be specified with action/afterSave records.
    • Multi form: the form acts simultaneously on more than one record. All records use the same FormElements .
      • The FormElements are defined as a regular simple / or advanced form, plus a SQL Query, which selects and iterates over all records. Those records will be loaded at the same time.
    • Delete form: any form can be used as a form to delete-record .
  • Form mode: The parameter ‘r’ (given via URL or via SIP) decide if the form is in mode:

    • New :

      • Missing parameter ‘r’ or ‘r=0’
      • On form load, no record is displayed.
      • Saving the form creates a new primary record.
      • E.g.: http://example.com/index.php?id=home&form=Person or http://example.com/index.php?id=home&form=Person&r=0
    • Edit :

      • Parameter ‘r>0’
      • On form load, the specified record (<tablename>.id= <r>) will be loaded and displayed.
      • Saving the form will update the existing record.
      • E.g.: http://example.com/index.php?id=home&form=Person&r=123
    • Providing additional parameter:

      Often, it is necessary to store additional, for the user not visible, parameter in a record. See form-magic .

  • Display a form:

    • Create a QFQ tt_content record on a Typo 3 page.
    • Inside the QFQ record: form = <formname> . E.g.:
      • Static: form = Person
      • Dynamic: form = {{form:SE}} (the left form is a keyword for QFQ, the right form is a free chooseable variable name)
    • With the Dynamic option, it’s easily possible to use one Typo3 page and display different forms on that specific page. This is nice to configure few Typo 3 pages. The disadvantage is that the user might loose the navigation.

Record locking

Forms and ‘record delete’-function support basic record locking. A user opens a form: starting with the first change of content, a record lock will be acquired in the background. If the lock is denied (another user already owns a lock on the record) an alert is shown. By default the record lock mode is ‘exclusive’ and the default timeout is 15 minutes. Both can be configured per form. The general timeout can also be configured in configuration (will be copied to the form during creating the form).

The lock timeout counts from the first change, not from the last modification on the form.

If a timeout expires, the lock becomes invalid. During the next change in a form, the lock is acquired again.

A lock is assigned to a record of a table. Multiple forms, with the same primary table, uses the same lock for a given record.

If a Form acts on further records (e.g. via FE action), those records are not protected by this basic record locking.

If a user tries to delete a record and another user already owns a lock on that record, the delete action is denied.

If there are different locking modes in multiple forms, the most restricting mode applies for the current lock.

Exclusive

An existing lock on a record forbids any write action on that record.

Advisory

An existing lock on a record informs the user that another user is currently working on that record. Nevertheless, writing is allowed.

None

No locking at all.

Comment- and space-character

The following applies to the fields Form.parameter and FormElement.parameter :

  • Lines will be trimmed - leading and trailing spaces will be removed.
  • If a leading and/or trailing space is needed, escape it: \ hello world \ > ‘ hello world ‘.
  • Lines starting with a ‘#’ are treated as a comment and will not be parsed. Such lines are treated as ‘empty lines’.
  • The comment sign can be escaped with \ .

Definition

Name Description
Name Unique and speaking name of the Form . Form will be identified by this name. Use only ‘[a-zA-Z0-9._+-]’. form-name
Title Title, shown on/above the form. form-title
Note Personal editor notes. form-note
Table Primary table of the form. form-tablename
Primary Key Primary key of the indicated table. Only needed if != ‘id’. form-primary-key
Required Parameter NEW Name of required SIP parameter to create a new record (r=0), separated by comma. ‘#’ as comment delimiter. See form-requiredParameter
Required Parameter EDIT Name of required SIP parameter to edit an existing record (r>0), separated by comma. ‘#’ as comment delimiter. See form-requiredParameter
Permit New ‘sip, logged_in, logged_out, always, never’ (Default: sip): See form-permitNewEdit
Permit Edit ‘sip, logged_in, logged_out, always, never’ (Default: sip): See form-permitNewEdit
Escape Type Default See variable-escape .
Show button ‘new, delete, close, save’ (Default: ‘new,delete,close,save’): Shown named buttons in the upper right corner of the form. See form-showButton
Forward Mode ‘auto | close | no | url | url-skip-history | url-sip | url-sip-skip-history’ (Default: auto): See form-forward .
Forward (Mode) Page
  1. URL / Typo3 page id/alias or b) Forward Mode (via ‘{{…}}’) or combination of a) & b). See form-forward .
labelAlign Label align (default/left/center/right)/ Default: ‘default’ (defined by Config).
Parameter Misc additional parameters. See form-parameter .
BS Label Columns The bootstrap grid system is based on 12 columns. The sum of bsLabelColumns , bsInputColumns and bsNoteColumns should be 12. These values here are the base values for all FormElements . Exceptions per FormElement can be specified per FormElement . Default: label=col-md-3, input=col-md-6, note=col-md-3. See form-layout .
BS Input Columns
BS Note Columns
multiMode NOT IMPLEMENTED - ‘none, horizontal, vertical’ (Default ‘none’)
multiSql NOT IMPLEMENTED - Optional. SQL Query which selects all records to edit.
multiDetailForm NOT IMPLEMENTED - Optional. Form to open, if a record is selected to edit (double click on record line)
multiDetailFormParameter NOT IMPLEMENTED - Optional. Translated Parameter submitted to detailform (like subrecord parameter)

permitNew & permitEdit

Depending on r , the following access permission will be taken:

  • r=0 - permitNew
  • r>0 - permitEdit
Option Description
sip The parameter ‘form’ and ‘r’ must be supplied via SIP or hard coded in the QFQ tt_content record.
logged_in The form will only be shown if the current User is logged in as a FE User
logged_out The form will only be shown if the current User is not logged in as a FE User
always No access restriction, the form will always be shown
never The form will never be shown
  • sip
    • is always the preferred way. With ‘sip’ it’s not necessary to differ between logged in or not, cause the SIP only exist and is only valid, if it’s created via QFQ/report earlier. This means ‘creating’ the SIP implies ‘access granted’. The grant will be revoked when the QFQ session is destroyed - this happens when a user loggs out or the web browser is closed.
  • logged_in / logged_out : for forms which might be displayed without a SIP, but maybe on a protected or even unprotected page. The option is probably not often used.
  • always : such a form is always allowed to be loaded.
    • permitNew=always : Public accessible forms (e.g. for registration) will allow users to fill and save the form.
    • permitEdit=always : Public accessible forms will allow users to update existing data. This is dangerous, cause the URL parameter (recordId) ‘r’ might be changed by the user (URL manipulating) and therefore the user might see and/or change data from other users. The option is probably not often used.
  • never : such a form is not allowed to be loaded.
    • permitNew=never : Public accessible forms. It’s not possible to create new records.
    • permitEdit=none : Public accessible forms. It’s not possible to update records.

Required Parameter New|Edit

Comma separated list of variable names. On form load, an error message will be shown in case of missing parameters. The parameters must be given by SIP.

The list of required parameter has to be defined for New (r=0, create a new record) and for Edit (r>0, edit existing record).

Optional a comment might be attached after the parameter definition.

E.g.:

New: grId, pId # Always specify a person, grId2
Edit: pId

showButton

Display or hide the button new , delete , close , save .

  • new : Creates a new record. If the form needs any special parameter via SIP or Client (=browser), hide this ‘new’ button - the necessary parameter are not provided.
  • delete : This either deletes the current record only, or (if defined via action FormElement ‘beforeDelete’ ) any specified subrecords.
  • close : Close the current form. If there are changes, a popup opens and ask to save / close / cancel. The last page from the history will be shown.
  • save : Save the form.
  • Default: show all buttons.

Forward: Save / Close

Forward (=forwardMode)

After the user presses Save , Close , Delete or New , different actions are possible where the browser redirects to.

  • auto (default) - the QFQ browser Javascript logic, decides to stay on the page or to force a redirection to a previous page. The decision depends on:
    • Close goes back (feels like close) to the previous page. Note: if there is no history, QFQ won’t close the tab,
      instead a message is shown.
    • Save stays on the current page.
  • close - goes back (feels like close) to the previous page. Note: if there is no history, QFQ won’t close the tab,
    instead a message is shown.
  • no - no change, the browser remains on the current side. Close does not close the page. It just triggers a save if there are modified data.
  • url - the browser redirects to the URL or T3 page named in Forward URL / Page . Independent if the user presses save or close .
  • url-skip-history - same as url , but the current location won’t saved in the browser history.
  • url-sip - like url , but any given parameter will be SIP encoded. Only useful if url points to current web instance.
  • url-sip-skip-history - like url-sip , but skips the Browser history.

Only with Forward == url | url-skip-history , the definition of Forward URL / Page becomes active.

Forward URL / Page (=forwardPage)

Format: [<url>] or [<mode>|<url>]

  • <url> :
    • http://www.example.com/index.html?a=123#bottom
    • website.html?a=123#bottom
    • ?id=<T3 Alias pageid>&a=123#bottom, ?id=<T3 page id>&a=123#bottom
    • {{SELECT …}}
    • <mode>|<url>
  • <mode> - Valid keywords are as above: auto|close|no|url|url-skip-history|url-sip|url-sip-skip-history

Specifying the mode in forwardPage overwrites formMode (but only if formMode is url… ).

Also regular QFQ statements like {{var}} or {{SELECT …}} are possible in forwardPage . This is useful to dynamically redirect to different targets, depending on user input or any other dependencies.

If a forwardMode ‘url…’ is specified and there is no forwardPage , QFQ falls down to auto mode.

On a form, the user might click ‘save’ or ‘save,close’ or ‘close’ (with modified data this leads to ‘save,close’). The CLIENT submit_reason shows the user action:

  • {{submit_reason:CE:alnumx}} = save or save,close

Example forwardPage

  • {{SELECT IF(‘{{formModeGlobal:S}}’=’requiredOff’, ‘no’, ‘auto’) }}
  • {{SELECT IF(‘{{submit_reason:CE:alnumx}}’=’save’, ‘no’, ‘url’), ‘|http://example.com’ }}

Type: combined dynamic mode & URL/page

Syntax: forwardPage=<mode>|<page>

  • forwardPage={{SELECT IF(a.url=’‘,’no’,’url’), ‘|’, a.url FROM address AS a }}

Parameter

  • The following parameter are optional and can be configured in the Form.parameter field.
Name Type Description
dbIndex int Database credential index, given via config-qfq-php to let the current Form operate on the database.
bsColumns string Wrap the whole form in ‘<div class=”col-md-.. col-lg-..”>. See bs-custom-field-width .
maxVisiblePill int Show pills upto <maxVisiblePill> as button, all further in a drop-down menu. Eg.: maxVisiblePill=3.
class string HTML div with given class, surrounding the whole form. Eg.: class=container-fluid.
classTitle string CSS class inside of the title div. Default ‘qfq-form-title’.
classPill string HTML div with given class, surrounding the pill title line.
classBody string HTML div with given class, surrounding all FormElement .
extraDeleteForm string Name of a form which specifies how to delete the primary record and optional slave records.
data-pattern-error string Pattern violation: Text for error message used for all FormElements of current form.
data-required-error string Required violation: Text for error message used for all FormElements of current form.
data-match-error string Match violation: Text for error message used for all FormElements of current form.
data-error string If none specific is defined: Text for error message used for all FormElements of current form.
buttonOnChangeClass string Color for save button after user modified some content or current form. E.g.: ‘btn-info alert-info’.
ldapServer string FQDN Ldap Server. E.g.: directory.example.com.
ldapBaseDn string E.g.: ou=Addressbook,dc=example,dc=com .
ldapAttributes string List of attributes to fill STORE_LDAP with. E.g.: cn, email.
ldapSearch string E.g.: (mail={{email::alnumx:l}})
ldapTimeLimit int Maximum time to wait for an answer of the LDAP Server.
typeAheadLdap Enable LDAP as ‘Typeahead’ data source.
typeAheadLdapSearch string Regular LDAP search expresssion. E.g.: (|(cn=*?*)(mail=*?*))
typeAheadLdapValuePrintf string Value formatting of LDAP result, per entry. E.g.: ‘%s / %s / %s’, mail, roomnumber, telephonenumber
typeAheadLdapIdPrintf string Key formatting of LDAP result, per entry. E.g.: ‘%s’, mail
typeAheadLdapSearchPerToken Split search value in token and OR-combine every search with the individual tokens.
typeAheadLimit int Maximum number of entries. The limit is applied to the server (LDAP or SQL) and the Client.
typeAheadMinLength int Minimum number of characters which have to typed to start the search.
mode string The value readonly will activate a global readonly mode of the form - the user can’t change any data. See Form mode global
enterAsSubmit digit 0: off, 1: Pressing enter in a form means save and close . Takes default from configuration .
submitButtonText string Show a save button at the bottom of the form, with <submitButtonText> . See submitButtonText .
saveButtonActive 0: off, 1: Make the ‘save’-button active on Form load (instead of waiting for the first user change). The save button is still ‘gray’ (record not dirty), but the user can click ‘save’.
saveButtonText string Overwrite default from configuration
saveButtonTooltip string Overwrite default from configuration
saveButtonClass string Overwrite default from configuration
saveButtonGlyphIcon string Overwrite default from configuration
closeButtonText string Overwrite default from configuration
closeButtonTooltip string Overwrite default from configuration
closeButtonClass string Overwrite default from configuration
closeButtonGlyphIcon string Overwrite default from configuration
deleteButtonText string Overwrite default from configuration
deleteButtonTooltip string Overwrite default from configuration
deleteButtonClass string Overwrite default from configuration
deleteButtonGlyphIcon string Overwrite default from configuration
newButtonText string Overwrite default from configuration
newButtonTooltip string Overwrite default from configuration
newButtonClass string Overwrite default from configuration
newButtonGlyphIcon string Overwrite default from configuration
extraButtonInfoClass string Overwrite default from configuration
fillStoreVar string Fill the STORE_VAR with custom values. See STORE_VARS .
showIdInFormTitle string Overwrite default from configuration
formSubmitLogMode string Overwrite default from configuration
sessionTimeoutSeconds int Overwrite default from configuration . See sessionTimeoutSeconds .
maxFileSize int Overwrite default from configuration .
requiredPosition int See requiredPosition .
  • Example:
    • maxVisiblePill = 5
    • class = container-fluid
    • classBody = qfq-form-right

submitButtonText

If specified and non empty, display a regular submit button at the bottom of the page with the given text. This gives the form a ordinary HTML-form look’n’ feel. With this option, the standard buttons on the top right border should be hided to not confuse the user.

  • Optional.
  • Default: Empty

class

  • Optional.
  • Default: container
  • Any CSS class name(s) can be specified.
  • Check typo3conf/ext/qfq/Resources/Public/Css/qfq-bs.css for predefined classes.
  • Typical use: adjust the floating rules of the form.

classPill

  • Optional.
  • Default: qfq-color-grey-1
  • Any CSS class name(s) can be specified.
  • Check typo3conf/ext/qfq/Resources/Public/Css/qfq-bs.css for predefined classes.
  • Typical use: adjust the background color of the pill title area.
  • Predefined background colors: qfq-color-white , qfq-color-grey-1 (dark), qfq-color-grey-2 (light), qfq-color-blue-1 (dark), qfq-color-blue-2 . (light)
  • classPill is only visible on forms with container elements of type ‘Pill’.

classBody

  • Optional.
  • Default: qfq-color-grey-2
  • Any CSS class name(s) can be specified.
  • Check typo3conf/ext/qfq/Resources/Public/Css/qfq-bs.css for predefined classes.
  • Typical use:
    • adjust the background color of the FormElement area.
    • make all form labels right align: qfq-form-right .
  • Predefined background colors: qfq-color-white , qfq-color-grey-1 (dark), qfq-color-grey-2 (light), qfq-color-blue-1 (dark), qfq-color-blue-2 . (light)

extraDeleteForm

Depending on the database definition, it might be necessary to delete the primary record and corresponding slave records. To not repeat such ‘slave record delete definition’, an ‘extraDeleteForm’ can be specified. If the user opens a record in a form and clicks on the ‘delete’ button, a defined ‘extraDeleteForm’-form will be used to delete primary and slave records instead of using the current form. E.g. if there are multiple different forms to work on the same table, all of theses forms might reference to the same ‘extraDeleteForm’-form. This simplifies the maintenance.

The ‘extraDeleteForm’ parameter might be specified for a ‘form’ and/or for ‘subrecords’

See also: delete-record .

Form mode global

The Form global mode mode is given by default with {{formModeGlobal:SE:alnumx}} .

Optional it might be defined via Form.parameter

mode=readonly|requiredOff
  • readonly : all FormElement of the whole form are temporarily in readonly mode. This is a fast way to use an existing Form just to display the form data, without a possibility for the user to change any data of the form.
  • requiredOff : all FormElement of the whole, with mode=required , will temporarily switch to mode=show . In this mode, the user might save the form without providing all necessary data. Later on, when application logic requires a final submit, this mode is not used any longer (call the form as regular without the ‘formModeGlobal’ parameter) and the form can only be saved with all data given. Than, e.g. an action-FormElement ‘afterSave’ can be used to detect the final submit situation and do some extra stuff, necessary for the final submit.

The following shows the same Form in the regular , readonly and requiredOff mode:

10.sql = SELECT CONCAT('p:{{pageAlias:T}}&form=person&r=', p.id, '|Regular') as _pagee,
                CONCAT('p:{{pageAlias:T}}&form=person&formModeGlobal=readonly&r=', p.id, '|Readonly') as _pagee,
                CONCAT('p:{{pageAlias:T}}&form=person&formModeGlobal=requiredOff&r=', p.id, '|Required off') as _pagee
                FROM Person AS p

FormElements

  • Each form contains one or more FormElement .
  • The FormElements are divided in three categories:
  • Ordering and grouping: Native FormElements and Container-Elements (both with feIdContainer=0) will be ordered by ‘ord’.
  • Inside of a container, all nested elements will be displayed.
  • Technical, it’s not necessary to configure a FormElement for the primary index column id .
  • Additional options to a FormElement will be configured via the FormElement.parameter field (analog to Form.parameter for Forms ).

Class: Container

  • Pills are containers for ‘fieldset’ and / or ‘native’ FormElements .
  • Fieldsets are containers for ‘native’ FormElements .
  • TemplateGroups are containers for ‘fieldset’ and / or ‘native’ FormElements .

Type: fieldset

  • Native FormElements can be assigned to a fieldset.
  • FormElement settings:
    • name : technical name, used as HTML identifier.
    • label : Shown title of the fieldset.

Type: pill (tab)

  • Pill is synonymous for a tab and looks like a tab.
  • If there is at least one pill defined:
    • every native FormElement needs to be assigned to a pill or to a fieldset.
    • every fieldset needs to be assigned to a pill.
  • Mode:
    • show : all child elements will be shown.
    • required : same as ‘show’. This mode has no other meaning than ‘show’.
    • readonly : technical it’s like HTML/CSS disabled .
      • The pill title is shown, but not clickable.
      • The FormElements on the pill still exist, but are not reachable for the user via UI.
    • hidden :
      • The pill is invisible.
      • The FormElements on the pill still exist, but are not reachable for the user via UI.
    • Note: Independent of the mode , all child elements are always rendered and processed by the client/server.
  • Pills are ‘dynamicUpdate’ aware. title and mode are optional recalculated during ‘dynamicUpdate’.
  • FormElement settings:
    • name : technical name, used as HTML identifier.
    • label : Label shown on the corresponding pill button or inside the drop-down menu.
    • mode :
      • show , required : regular mode. The pill will be shown.
      • readonly : the pill and it’s name is visible, but not clickable.
      • hidden : the pill is not shown at all.
    • modeSql :
    • type : pill
    • feIdContainer : 0 - Pill’s can’t be nested.
    • tooltip : Optional tooltip on hover. Especially helpful if the pill is set to readonly .
    • parameter :
      • maxVisiblePill : <nr> - Number of Pill-Buttons shown. Undefined means unlimited. Excess Pill buttons will be displayed as a drop-down menu.

Type: templateGroup

TemplateGroups will be used to create a series of grouped (by the given templateGroup ) FormElements .

FormElements can be assigned to a templateGroup . These templateGroup will be rendered upto n -times. On ‘form load’ only a single (=first) copy of the templateGroup will be shown. Below the last copy of the templateGroup an ‘add’-button is shown. If the user click on it, an additional copy of the templateGroup is displayed. This can be repeated up to templateGroup.maxLength times. Also, the user can ‘remove’ previously created copies by clicking on a remove button near beside every templateGroup . The first copy of a templateGroup can’t be removed.

  • FormElement settings:
    • label : Shown in the FormElement-editor container field.
    • maxLength : Maximum number of copies of the current templateGroup . Default: 5.
    • bsLabelColumn , bsInputColumn , bsNoteColumn : column widths for row with the ‘Add’ button.
    • parameter :
      • tgAddClass : Class of the ‘add’ button. Default: btn btn-default .
      • tgAddText : Text shown on the button. Default: Add .
      • tgRemoveClass : Class of the ‘remove’ button. Default: btn btn-default .
      • tgRemoveText : Text shown on the button. Default: Remove .
      • tgClass : Class wrapped around every copy of the templateGroup . E.g. the class qfq-child-margin-top adds a margin between two copies of the templateGroup . Default: empty

Multiple templateGroups per form are allowed.

The name of the native FormElements, inside the templateGroup, which represents the effective table columns, uses the placeholder %d . E.g. the columns grade1 , grade2 , grade3 needs a FormElement.name = grade%d . The counting will always start with 1. The placeholder %d can also be used in the FormElement.label

Example of styling the Add/ Delete Button: Icons Template Group

Column: primary record

If the columns <name>%d are real columns on the primary table, saving and delete (=empty string) are done automatically. E.g. if there are up to five elements grade1, …, grade5 and the user inputs only the first three, the remaining will be set to an empty string.

Column: non primary record

Additional logic is required to load, update, insert and/or delete slave records.

Load

On each native FormElement of the templateGroup define a SQL query in the FormElement.value field. The query has to select all values of all possible existing copies of the FormElement - therefore the exclamation mark is necessary. Also define the order. E.g. FormElement.value :

{{!SELECT street FROM Address WHERE personId={{id}} ORDER BY id}}
Insert / Update / Delete

Add an action record, type=’afterSave’, and assign the record to the given templateGroup .

In the parameter field define:

slaveId = {{SELECT id FROM Address WHERE personId={{id}} ORDER BY id LIMIT %D,1}}
sqlHonorFormElements = city%d, street%d
sqlUpdate = {{UPDATE Address SET city='{{city%d:FE:alnumx:s}}', street='{{street%d:FE:alnumx:s}}'  WHERE id={{slaveId}} LIMIT 1}}
sqlInsert = {{INSERT INTO Address (`personId`, `city`, `street`) VALUES ({{id}}, '{{city%d:FE:alnumx:s}}' , '{{street%d:FE:alnumx:s}}') }}
sqlDelete = {{DELETE FROM Address WHERE id={{slaveId}} LIMIT 1}}

The slaveId needs attention: the placeholder %d starts always at 1. The LIMIT directive starts at 0 - therefore use %D instead of %d , cause %D is always one below %d - but can only be used on the action element.

Class: Native

Fields:

Name Type Description
Container int 0 or FormElement .id of container element (pill, fieldSet, templateGroup) part the current Form
Enabled enum(‘yes’|’no’) Process the current FormElement
Dynamic Update enum(‘yes’|’no’) In the browser, FormElements with “dynamicUpdate=’yes’” will be updated depending on user input. Dynamic Update
Name string
Label string Label of FormElement . Depending on layout model, left or on top of the FormElement Additional label description can be added by wrapping in HTML tag ‘<small>’
Mode enum(‘show’, ‘readonly’, ‘required’, ‘hidden’ )
Show : regular user input field. This is the default.
Required : User has to specify a value. Typically, an <empty string> represents ‘no value’.
Readonly : User can’t change. Data is not saved, except for FormElement with ‘processReadOnly’
Hidden : FormElement is not visible.
Mode sql SELECT statement with a value like in mode A value given here overwrites the setting from mode . Most useful with Dynamic Update . E.g.: {{SELECT IF( ‘{{otherFunding:FR:alnumx}}’=’yes’ ,’show’, ‘hidden’ }}
Class enum(‘native’, ‘action’, ‘container’) Details below.
Type enum(‘checkbox’, ‘date’, ‘time’, ‘datetime’, ‘dateJQW’, ‘datetimeJQW’, ‘extra’, ‘gridJQW’, ‘text’, ‘editor’, ‘annotate’, ‘imageCut’, ‘note’, ‘password’, ‘radio’, ‘select’, ‘subrecord’, ‘upload’, ‘fieldset’, ‘pill’, ‘beforeLoad’, ‘beforeSave’, ‘beforeInsert’, ‘beforeUpdate’, ‘beforeDelete’, ‘afterLoad’, ‘afterSave’, ‘afterInsert’, ‘afterUpdate’, ‘afterDelete’, ‘sendMail’)
Encode ‘none’, ‘specialchar’ With ‘specialchar’ (default) the chars <>”’& will be encoded to their htmlentity. field-encode
Check Type enum(‘auto’, ‘alnumx’, ‘digit’, ‘numerical’, ‘email’, ‘pattern’, ‘allbut’, ‘all’) See: sanitize-class
Check Pattern ‘regexp’ field-checktype : If $checkType==’pattern’: pattern to match
Order string Display order of FormElements (‘order’ is a reserved keyword) field-ord
tabindex string HTML tabindex attribute field-tabindex
labelAlign left Label align (default/left/center/right)/ Default: ‘default’ (defined by Form).
Size string Visible length of input element. Might be omitted, depending on the chosen form layout. Format: <width>,<height> (in characters) field-size
BS Label Columns string Number of bootstrap grid columns. By default empty, value inherits from the form. field-bsLabelColumns . See bs-custom-field-width
BS Input Columns string
BS Note Columns string
Label / Input / Note enum(…) Switch on/off opening|closing of bootstrap form classes field-rowLabelInputNote
Maxlength string Maximum characters for input. field-maxLength
Note string Note of FormElement . Depending on layout model, right or below of the FormElement . Report syntax can also be used, see report-notation
Tooltip text Display this text as tooltip on mouse over. field-tooltip
Placeholder string Text, displayed inside the input element in light grey. field-placeholder
value text Default value: See field-value
sql1 text SQL query. See individual FormEelement . sql1
Parameter text Might contain misc parameter. See fe-parameter-attributes
feGroup string Comma-separated list of Typo3 FE Group ID. NOT SURE IF THIS WILL BE IMPLEMENTED. Native FormElements , fieldsets and pills can be assigned to feGroups. Group status: show, hidden, hidden. Group Access: FE-Groups. User will be assigned to FE-Groups and the form definition reference such FE-groups. Easy way of granting permission.
Deleted string ‘yes’|’no’.

FE: Value

By default this field is empty: QFQ will fill it with the corresponding existing column value on form load. For a customized default value define:

{{SELECT IF('{{column:RE}}'='','custom default', '{{column:R}}') }}

For non primary records, this is the place to load an existing value. E.g. we’re on a ‘Person’ detail form and would like to edit, on the same form, a corresponding person email address (which is in a separate table):

{{SELECT a.email FROM Address AS a WHERE a.pId={{id:R0}} ORDER BY a.id LIMIT 1}}

Report syntax can also be used, see report-notation .

FE: ‘Report’ notation

The FE fields ‘value’ and ‘note’ understand the Report syntax. Nested SQL queries as well as links with SIP encoding are possible. To distinguish between ‘Form’ and ‘Report’ syntax, the first line has to be #!report :

#!report

10.sql = SELECT ...

20 {
  sql = SELECT ...
  5.sql = SELECT ...
}

Attributes defined in the parameter field

See also at specific FormElement definitions.

Name Type Note
acceptZeroAsRequired string 0|1 - Accept a ‘0’ as a valid input. Default ‘0’ (=0 is not a valid input)
autofocus string See input-option-autofocus
capture, accept, maxFileSize, fileDestination, fileReplace, autoOrient, autoOrientCmd, autoOrientMimeType, chmodFile, chmodDir, slaveId, sqlBefore, sqlInsert, sqlUpdate, sqlDelete, sqlAfter, importToTable, importToColumns, importRegion, importMode, importType, importNamedSheetsOnly, importSetReadDataOnly, importListSheetNames, string See input-upload
checkBoxMode checked unchecked label2 itemList emptyHide emptyItemAtStart emptyItemAtEnd buttonClass string See input-checkbox , input-radio , input-select
dateFormat string yyyy-mm-dd / dd.mm.yyyy
data-pattern-error string Pattern violation: Text for error message
data-required-error string Required violation: Text for error message
data-match-error string Match violation: Text for error message
data-error string Violation of ‘check-type’: Text for error message
decimalFormat string [precision,scale] Limits and formats input to a decimal number with the specified precision and scale. If no precision and scale are specified, the decimal format is pulled from the table definition.
htmlAfter string HTML Code wrapped after the complete FormElement
htmlBefore string HTML Code wrapped before the complete FormElement
extraButtonLock none No value. Show a ‘lock’ on the right side of the input element. See extraButtonLock
extraButtonPassword none No value. Show an ‘eye’ on the right side of the input element. See extraButtonPassword
extraButtonInfo string Text. Show an ‘i’ on the right side of the input element. See extraButtonInfo
extraButtonInfoClass string By default empty. Specify any class to be assigned to wrap extraButtonInfo
editor-plugins, editor-toolbar, editor-statusbar, string See input-editor
fileButtonText string Overwrite default ‘Choose File’
fillStoreVar string Fill the STORE_VAR with custom values. See STORE_VARS .
form, page, title, extraDeleteForm, detail, subrecordTableClass, string See subrecord-option
min s/d/n

Minimum and/or maximum allowed values for input field. Can be used for numbers, dates, or strings.

Always use the international format ‘yyyy-mm-dd[ hh:mm[:ss]]

max s/d/n
processReadOnly [n] [0|1] By default FE’s with type=’readonly’ are not processed during ‘save’. This option forces to process them during ‘save’ as well.
retype, retypeLabel, retypeNote, characterCountWrap, hideZero, emptyMeansNull, string See input-text
showSeconds string 0|1 - Shows the seconds on form load. Default: 0
showZero string 0|1 - Empty timestamp: ‘0’(default) - nothing shown, ‘1’ - the string ‘0000-00-00 00:00:00’ is displayed
timeIsOptional string 0|1 - Used for datetime input. 0 (default): Time is required - 1: Entering a time is optional (defaults to 00:00:00 if none entered).
typeAheadLimit, typeAheadMinLength, typeAheadSql, typeAheadSqlPrefetch, string See input-typeahead
wrapRow string If specified, skip default wrapping ( <div class=’col-md-?’> ). Instead the given string is used.
wrapLabel string
wrapInput string
wrapNote string
trim string By default, whitespace is trimmed. To disable, use ‘trim=none’. You can also specify custom trim characters: ‘trim=\ ‘ only trims spaces.
sqlValidate string See sqlValidate
expectRecords int
messageFail string
dataReference string Optional. See applicationTest
requiredPosition int See requiredPosition .
  • s/d/n : string or date or number.

Native FormElements

  • Like ‘input’, ‘checkbox’, …
autofocus

The first FormElement with this attribute will get the focus after form load. If there is no such attribute given to any FormElement , the attribute will be automatically assigned to the first editable FormElement .

To disable ‘autofocus’ on a form, set ‘autofocus=0’ on the first editable FormElement .

Note: If there are multiple pills defined on a form, only the first pill will be set with ‘autofocus’.

extraButtonLock
  • The user has to click on the lock, before it’s possible to change the value. This will protect data against unwanted modification.
  • After Form load, the value is shown, but not editable.
  • Shows a ‘lock’ on the right side of an input element of type text , date , time or datetime .
  • This option is not available for FormElements with mode=readonly .
  • There is no value needed for this parameter.
extraButtonPassword
  • The user has to click on the eye (unhide) to see the value.
  • After Form load, the data is hided by asteriks.
  • Shows an ‘eye’ on the right side of an input element of type text , date , time or datetime .
  • There is no value needed for this parameter.
extraButtonInfo
  • After Form load, the info button/icon is shown but the information message is hidden.
  • The user has to click on the info button/icon to see an additional message.
  • The value of this parameter is the text shown to the user.
  • Shows an info button/icon, depending of extraButtonInfoPosition in configuration
    • auto , depending on FormElement type:
      • on the right side of an input element for type text , date , time or datetime ,
      • below the FormElement for all other types.
    • below : below the FormElement for all types.
  • For FormElement with mode below , a span element with the given class in extraButtonInfoClass (FE, F, configuration ) will be applied. E.g. this might be pull-right to align the info button/icon on the right side below the input element.

Required Position

By default, input elements with Mode=required will be displayed with a ‘red asterix’ right beside the label. The position of the ‘red asterix’ can be choosen via the parameter field:

requiredPosition = label-left|label-right|input-left|input-right|note-left|note-right

The default is ‘label-right’.

The definition can be set per Form (=affects all FormElements) or per FormElement.

Type: checkbox

Checkboxes can be rendered in mode:

  • single :

    • One column in a table corresponds to one checkbox.
    • The value for statuses checked and unchecked are free to choose.
    • This mode is selected, if a) checkBoxMode = single, or b) checkBoxMode is missing and the number of fields of the column definition is <3.
    • FormElement.parameter :
      • checkBoxMode = single (optional)
      • checked = <value> (optional, the value which represents ‘checked’)
        • If checked is empty or missing: If type = ‘enum’ or ‘set’, get first item of the definition. If type = string, get default.
      • unchecked = <value> (optional, the value which represents ‘unchecked’)
        • If unchecked is empty or missing: If type = ‘enum’ or ‘set’, get second item of checked. If type = ‘string’, get ‘’.
      • label2 = <value> (Text right beside checkbox) (optional)
  • multi :

    • One column in a table represents multiple checkboxes. This is typically useful for the column type set .

    • The value for status checked are free to choose, the value for status unchecked is always the empty string.

    • Each field key (or the corresponding value from the key/value pair) will be rendered right beside the checkbox.

    • FormElement.parameter

      • checkBoxMode = multi
      • itemList - E.g.:
        • itemList=red,blue,orange
        • itemList=1:red,2:blue,3:orange
        • If ‘:’ or ‘,’ are part of key or value, it needs to escaped by \ . E.g.: itemList=1:red\: (with colon),2:blue\, (with comma),3:orange`
    • FormElement.sql1 = {{!SELECT id, value FROM someTable}}

    • FormElement.maxlength - vertical or horizontal alignment:

      • Value: ‘’, 0, 1 - The check boxes will be aligned vertical.
      • Value: >1 - The check boxes will be aligned horizontal, with a linebreak every ‘value’ elements.
  • FormElement.parameter :

    • emptyHide : Existence of this item hides an entry with an empty string. This is useful for e.g. Enums, which have an empty entry, but the empty value should not be selectable.

    • emptyItemAtStart : Existence of this item inserts an empty entry at the beginning of the selectlist.

    • emptyItemAtEnd : Existence of this item inserts an empty entry at the end of the selectlist.

    • buttonClass : Instead of the plain HTML checkbox fields, Bootstrap buttons . are rendered as checkbox elements. Use one of the following classes :

      • btn-default (default, grey),
      • btn-primary (blue),
      • btn-success (green),
      • btn-info (light blue),
      • btn-warning (orange),
      • btn-danger (red).

      With a given buttonClass , all buttons (=radios) are rendered horizontal. A value in FormElement.maxlength has no effect.

  • No preselection :

    • If a form is in ‘new’ mode and if there is a default value configured on a table column, such a value is shown by default. There might be situations, where the user should be forced to select a value (e.g. specifying the gender). An unwanted default value can be suppressed by specifying an explicit definition on the FormElement field value :

      {{<columnName>:RZ}}
      

      For existing records the shown value is as expected the value of the record. For new records, it’s the value 0 , which is typically not one of the ENUM / SET values and therefore nothing is selected.

Type: date

Type: datetime

  • Range datetime: ‘1000-01-01 00:00:00’ to ‘9999-12-31 23:59:59’ or ‘0000-00-00 00:00:00’. ( http://dev.mysql.com/doc/refman/5.5/en/datetime.html )
  • Optional:
    • FormElement.parameter :
      • dateFormat = yyyy-mm-dd | dd.mm.yyyy
      • showSeconds = 0|1 - shows the seconds. Independent if the user specifies seconds, they are displayed ‘1’ or not ‘0’.
      • showZero = 0|1 - For an empty timestamp, With ‘0’ nothing is displayed. With ‘1’ the string ‘0000-00-00 00:00:00’ is displayed.

Type: extra

  • The element behaves like, and can be used as, a HTML hidden input element - with the difference & advantage, that the element never leaves the server and therefore can’t be manipulated by a user.
  • The following names are reserved and can’t be used to name ‘extra’ FormElements: ‘id’, ‘type’, ‘L’.
  • The element is not transferred to the the browser.
  • The element can be used to define / pre calculate values for a column, which do not already exist as a native FormElement .
  • The element is build / computed on form load and saved alongside with the SIP parameter of the current form.
  • The element is not recalculated during save - it’s stored during ‘Form Load’ inside the current form SIP handle.
  • Access the value without specifying any store (default store priority is sufficient).

Type: text

  • General input for text and number.
  • FormElement.size =
    • <number>: width of input element in characters. Lineheight = 1.
    • <cols>,<rows>: input element = textarea, width=<cols>, height=<rows>
  • FormElement.parameter :
    • retype = 1 (optional): Current input element will be rendered twice. The form can only submitted if both elements are equal.
      • retypeLabel = <text> (optional): The label of the second element.
      • retypeNote = <text> (optional): The note of the second element.
    • characterCountWrap = <span class=”qfq-cc-style”>Count: | </span> (optional). Displays a character counter below the input/textarea element.
    • Also check the fe-parameter-attributes data-…-error to customize error messages shown by the validator.
    • hideZero = 0|1 (optional): with hideZero=1 a ‘0’ in the value will be replaced by an empty string.
    • emptyMeansNull = [0|1] (optional): with emptyMeansNull or emptyMeansNull=1 a NULL value will be written if the value is an empty string
    • inputType = number (optional). Typically the HTML tag ‘type’ will be ‘text’, ‘textarea’ or ‘number’ (detected automatically). If necessary, the HTML tag ‘type’ might be forced to a specific given value.
    • step = Step size of the up/down buttons which increase/decrease the number of in the input field. Optional. Default 1. Only useful with inputType=number (defined explicit via inputType or detected automatically).

Type Ahead

Activating typeahead functionality offers an instant lookup of data and displaying them to the user, while the user is typing, a drop-down box offers the results. As datasource the regular SQL connection or a LDAP query can be used. With every keystroke (starting from the typeAheadMinLength characters), the already typed value will be transmitted to the server, the lookup will be performed and the result, upto typeAheadLimit entries, are displayed as a drop-down box.

  • FormElement.parameter :
    • typeAheadLimit = <number>. Max numbers of result records to be shown. Default is 20.
    • typeAheadMinLength = <number>. Minimum length to type before the first lookup starts. Default is 2.

Depending of the typeahead setup, the given FormElement will contain the displayed value or id (if an id/value dict is configured).

Configuration via Form / FormElement

All of the typeAhead* (except typeAheadLdap ) and ldap* parameter can be specified either in Form.parameter or in FormElement.parameter .

SQL
  • FormElement.parameter :
    • typeAheadSql = SELECT … AS ‘id’, … AS ‘value’ WHERE name LIKE ? OR firstName LIKE ? LIMIT 100
      • If there is only one column in the SELECT statement, that one will be used and there is no dict (key/value pair).
      • If there is no column id or no column value , then the first column becomes id and the second column becomes value .
      • The query will be fired as a ‘prepared statement’.
      • The value, typed by the user, will be replaced on all places where a ? appears.
      • All ? will be automatically surrounded by ‘%’. Therefore wildcard search is implemented: … LIKE ‘%<?>%’ …
    • typeAheadSqlPrefetch = SELECT firstName, ‘ ‘, lastName FROM person WHERE id = ?
      • If the query returns several results, only the first one is returned and displayed.
      • If the query selects multiple columns, the columns are concatenated.

Type: editor

  • TinyMCE ( https://www.tinymce.com , community edition) is used as the QFQ Rich Text Editor.

  • The content will be saved as HTML inside the database.

  • All configuration and plugins will be configured via the ‘parameter’ field. Just prepend the word ‘editor-‘ in front of each TinyMCE keyword. Check possible options under:

  • Bars:

    • Top: menubar - by default hidden.
    • Top: toolbar - by default visible.
    • Bottom: statusbar - by default hidden, exception: min_height and max_height are given via size parameter.
  • The default setting in FormElement.parameter is:

    editor-plugins=code link lists searchreplace table textcolor textpattern visualchars
    editor-toolbar=code searchreplace undo redo | styleselect link table | fontselect fontsizeselect | bullist numlist outdent indent | forecolor backcolor bold italic editor-menubar=false
    editor-statusbar=false
    
  • To deactivate the surrouding <p> tag, configure in FormElement.parameter :

    editor-forced_root_block = false
    

    This might have impacts on the editor. See https://www.tinymce.com/docs/configure/content-filtering/#forced_root_block

  • Set ‘extended_valid_elements’ to enable HTML tags and their attributes. Example:

    editor-extended_valid_elements = span[class|style]
    
  • Set ‘editor-content_css’ to use a custom CSS to style elements inside the editor. Example:

    editor-content_css = fileadmin/custom.css
    
  • FormElement.size = <min_height>,<max_height>: in pixels, including top and bottom bars. E.g.: 300,600

Type: annotate

Annotate image or text. Typically the image or text has been uploaded during a previous step. The annotation will be saved in FormElement.name column of the current record. The uploaded file itself will not be modified. The annotations can be shown in edit (and might be modified) or in readonly mode.

Two modes are available:

grafic
A simple grafic editor to paint on top of the image (best by a tablet with pen or grafic tablet). The uploaded image is shown in the background. All drawings are saved as a JSON fabric.js data string. Supported file types: png, svg . PDF files can be easily divided into per page SVG files during upload - see split-pdf-upload
text
Per code line, annotation(s) can be added. Different users can add individual annotations. A user can only edit the own annotations. The annotations are saved as QFQ internal JSON string.

Note

Drawing with fabric.js might produce a lot data. Take care the column type/size is big enough (>=64kB).

Grafic

An image, specified by FormElement.parameter.imageSource={{pathFileName}} , will be displayed in the background. On form load, both, the image and an optional already given grafical annotations, will be displayed. The image is SIP protected and will be loaded on demand.

Form.parameter

Attribute Value Description
annotateType grafic grafic|text . Default is grafic . Select mode.
imageSource <path filename> Background image. E.g. fileadmin/images/scan.png
defaultPenColor <rgb hex value> | Pen default color, after loading the fabric element. Default is ‘0000FF’ (blue).

Note

By using the the FormElement annotate , the JS code fabric.min.js and qfq.fabric.min.js has to be included. See setup-css-js .

Code

Form.parameter

Attribute Value Description
annotateType text grafic|text . Default is grafic . Select mode.
textSource <path filename> Text file to annotate.
annotateUserName <john doe> Will be shown at annotation line.
annotateUserUid <123> Will be shown at annotation line.
annotateUserAvatar < https://gravatar… > Will be shown at annotation line.
highlight auto off,auto,javascript,qfq,python,matlab

Type: imageCut

Uploaded images can be cut or rotate via QFQ (via fabric.js). The modified image is saved under the given pathFileName.

  • The ‘value’ of the FormElement has to be a valid PathFileName to an image.
  • Valid image file formats are SVG, PNG, JPG, GIF.
  • Invalid or missing filenames results to an empty ‘imageCut’ element.
  • FormElement.parameter :
    • resizeWidth = <empty>|[width in pixel] - the final width of the modified image. If empty (or not given), no change.
    • keepOriginal = <empty>|[string] - By default: ‘.save’. If empty (no string given), don’t keep the original. If an extension is given and if there is not already a <pathFileName><.extension>, than the original file is to copied to it.

Type: note

An FormElement without any ‘input’ functionality -just to show some text. Use the typical fields ‘label’, ‘value’ and ‘note’ to be displayed in the corresponding three standard columns.

Type: password

  • Like a text element, but every character is shown as an asterisk.

Type: radio

  • Radio Buttons will be built from one of three sources:

    1. ‘sql1’: E.g. {{!SELECT type AS label FROM car }} or {{!SELECT type AS label, typeNr AS id FROM car}} or {{!SHOW tables}} .
    • Resultset format ‘named’: column ‘label’ and optional a column ‘id’.
    • Resultset format ‘index’:
      • One column in resultset >> first column represents label
      • Two or more columns in resultset >> first column represents id and second column represents label .
    1. FormElement.parameter :
    • itemList = <attribute> E.g.: itemList=red,blue,orange or itemList=1:red,2:blue,3:orange
    • If ‘:’ or ‘,’ are part of key or value, it needs to escaped by \ . E.g.: itemList=1:red\: (with colon),2:blue\, (with comma),3:orange
    1. Definition of the enum or set field (only labels, ids are not possible).
  • FormElement.maxlength = <value>

    • Applies only to ‘plain’ radio elements (not the Bootstrap ‘buttonClass’ from below)
    • vertical or horizontal alignment:
      • <value> : ‘’, 0, 1 - The radios will be aligned vertical .
      • <value> : >1 - The readios will be aligned horizontal , with a linebreak every ‘value’ elements.
  • FormElement.parameter :

    • emptyHide : Existence of this item hides an entry with an empty string. This is useful for e.g. Enums, which have an empty entry, but the empty value should not be selectable.

    • emptyItemAtStart : Existence of this item inserts an empty entry at the beginning of the selectlist.

    • emptyItemAtEnd : Existence of this item inserts an empty entry at the end of the selectlist.

    • buttonClass = <class> - Instead of the plain radio fields, Bootstrap buttons . are rendered as radio elements. Use one of the following classes :

      • btn-default (default, grey),
      • btn-primary (blue),
      • btn-success (green),
      • btn-info (light blue),
      • btn-warning (orange),
      • btn-danger (red).

      With a given buttonClass , all buttons (=radios) are rendered horizontal. A value in FormElement.maxlength has no effect.

  • No preselection :

    • If there is a default configured on a table column, such a value is selected by default. If the user should actively choose an option, the ‘preselection’ can be omitted by specifying an explicit definition on the FormElement field value :

      {{<columnName>:RZ}}
      

      For existing records the shown value is as expected the value of the record. For new records, it’s the value 0 , which is typically not one of the ENUM values and therefore nothing is selected.

Type: select

  • Select lists will be built from one of three sources:
    • FormElement.sql1 = {{!<SQL Query}}
      • E.g. {{!SELECT type AS label FROM car }} or {{!SELECT type AS label, typeNr AS id FROM car}} or {{!SHOW tables}} .
      • Resultset format ‘named’: column ‘label’ and optional a column ‘id’.
      • Resultset format ‘index’:
        • One column in resultset >> first column represents label
        • Two or more columns in resultset >> first column represents id and second column represents label .
    • FormElement.parameter :
      • itemList = <attribute> - E.g.: itemList=red,blue,orange or itemList=1:red,2:blue:3:orange
      • If ‘:’ or ‘,’ are part of key or value, it needs to escaped by \ . E.g.: itemList=1:red\: (with colon),2:blue\, (with comma),3:orange
    • Definition of the enum or set field (only labels, ids are not possible).
  • FormElement.size = <value>
    • <value> : <empty>|0|1: drop-down list.
    • <value> : >1: Select field with size rows height. Multiple selection of items is possible.
  • FormElement.parameter :
    • emptyItemAtStart : Existence of this item inserts an empty entry at the beginning of the selectlist.
    • emptyItemAtEnd : Existence of this item inserts an empty entry at the end of the selectlist.
    • emptyHide : Existence of this item hides the empty entry. This is useful for e.g. Enums, which have an empty entry and the empty value should not be an option to be selected.
    • datalist : Similar to ‘typeAhead’. Enables the user to select a predefined option (sql1, itemList) or supply any
      free text. Attention: Safari (and some other) browsers do not support this fully - https://caniuse.com/#search=datalist .

Type: subrecord

The FormElement type ‘subrecord’ renders a list of records (so called secondary records), typically to show, edit, delete or add new records. The list is defined as a SQL query. The number of records shown is not limited. These FormElement will be rendered inside the form as a HTML table.

  • mode / modeSql = <type/value>

    • show / required : the regular mode to show the subrecords
    • readonly : New / Edit / Delete Buttons are disabled
    • hidden : The FormElement is rendered, but hidden with display=’none’ .
  • dynamicUpdate - not supported at the moment.

  • sql1 = {{!SQL Query}}

    • SQL query to select records. E.g.:

      {{!SELECT addr.id AS id, CONCAT(addr.street, addr.streetnumber) AS a, addr.city AS b, addr.zip AS c FROM Address AS addr}}
      
    • Notice the exclamation mark after ‘{{‘ - this is necessary to return an array of elements, instead of a single string.

    • Exactly one column ‘id’ has to exist; it specifies the primary record for the target form. In case the id should not be visible to the user, it has to be named ‘_id’ .

    • Column name: [title=]<title>[|[maxLength=]<number>][|nostrip][|icon][|link][|url][|mailto][|_rowClass][|_rowTooltip]

      • If the keyword is used, all parameter are position independent.

      • Parameter are separated by ‘|’.

      • [title=]<text> : Title of the column. The keyword ‘title=’ is optional. Columns with a title starting with ‘_’ won’t be rendered.

      • [maxLength=]<number> : Max. number of characters displayed per cell. The keyword ‘maxLength=’ is optional. Default

        maxLength ‘20’. A value of ‘0’ means no limit. This setting also affects the title of the column.

      • nostrip : by default, html tags will be stripped off the cell content before rendering. This protects the table layout. ‘nostrip’ deactivates the cleaning to make pure html possible.

      • icon : the cell value contains the name of an icon in typo3conf/ext/qfq/Resources/Public/icons . Empty cell values will omit an html image tag (=nothing rendered in the cell).

      • link : value will be rendered as described under Column: _link

      • url : value will be rendered as a href url.

      • mailto : value will be rendered as a href mailto.

      • _rowClass

      • _rowTooltip

        • Defines the title attribute (=tooltip) of a subrecord table row.
      • Examples:

        {{!SELECT id, note1 AS 'Comment', note2 AS 'Comment|50' , note3 AS 'title=Comment|maxLength=100|nostrip', note4 AS '50|Comment',
        'checked.png' AS 'Status|icon', email AS 'mailto', CONCAT(homepage, '|Homepage') AS 'url',
        CONCAT('d|s|F:', pathFileName) AS 'Download|link',
        ELT(status,'info','warning','danger') AS '_rowClass', help AS '_rowTooltip' ...}}
        
  • FormElement.parameter

    • form = <form name> - Target form, e.g. form=person

    • page = <T3 page alias or id> - Target page with detail form. If none specified, use the current page.

    • extraDeleteForm : Optional. The per row delete Button will reference the form specified here (for deleting) instead of the default ( form ).

    • detail = <string> - Mapping of values from

        1. the primary form,
        1. the current row,
        1. any constant or ‘{{…}}’ -

      to the target form (defined via form=… ).

      • Syntax:

        <source table column name 1|&constant 1>:<target column name 1>[,<source table column name 2|&constant 2>:<target column name 2>][...]
        
      • Example: detail=id:personId,rowId:secId,&12:xId,&{{a}}:personId (rowId is a column of the current selected row defined by sql1)

      • By default, the given value will overwrite values on the target record. In most situations, this is the wished behaviour.

      • Exceptions of the default behaviour have to be defined on the target form in the corresponding FormElement in the field value by changing the default Store priority definition. E.g. {{<columnName>:RS0}} - For existing records, the store R will provide a value. For new records, store R is empty and store S will be searched for a value: the value defined in detail will be choosen. At last the store ‘0’ is defined as a fallback.

      • source table column name : E.g. A person form is opened with person.id=5 (r=5). The definition detail=id:personId and form=address maps person.id to address.personId. On the target record, the column personId becomes ‘5’.

      • Constant ‘&’ : Indicate a ‘constant’ value. E.g. &12:xId or {{…}} (all possibilities, incl. further SELECT statements) might be used.

    • subrecordTableClass : Optional. Default: ‘table table-hover qfq-subrecord-table’. If given, the default will be

      overwritten. Example:

      subrecordTableClass = table table-hover qfq-subrecord-table qfq-table-50
      
    • Tablesorter in Subrecord:

      subrecordTableClass = table table-hover qfq-subrecord-table tablesorter tablesorter-pager tablesorter-filter

    • subrecordColumnTitleEdit : Optional. Will be rendered as the column title for the new/edit column.

    • subrecordColumnTitleDelete : Optional. Will be rendered as the column title for the delete column.

Subrecord DragAndDrop

Subrecords inherently support drag-and-drop, see also drag_and_drop . The following parameters can be used in the parameter field to customize/activate drag-and-drop:

  • orderInterval : The order interval to be used, default is 10.
  • dndTable : The table that contains the records to be ordered. If not given, the table name of the form specified via form=… is used.
  • orderColumn : The dedicated order column in the specified dndTable (needs to match a column in the table definition). Default is ord .

If dndTable is an actual table with a column orderColumn , QFQ automatically applies drag-and-drop logic to the rendered subrecord. It does so by using the subrecord field sql1 . The sql1 query should therefore include both a column id (or _id) and ord (or _ord).

Tips:

  • If you want to deactivate a drag-and-drop that QFQ automatically renders, set the orderColumn to a non-existing column. E.g., orderColumn = nonExistingColumn . This will deactivate drag-and-drop.
  • In order to evaluate the sql1 query dynamically during a drag-and-drop event, the fill store R (with the current form) is loaded. Currently, SIP parameters and other variables are not supplied to be evaluated during a drag-and-drop event. This may be added later upon request.
  • If the subrecord is rendered with drag-and-drop active, but the order is not affected upon reload, there is most likely a problem with evaluating the sql1 query at runtime.

Type: time

  • Range time: ‘00:00:00’ to ‘23:59:59’ or ‘00:00:00’. ( http://dev.mysql.com/doc/refman/5.5/en/datetime.html )
  • Optional:
  • FormElement.parameter
    • showSeconds = 0|1 - shows the seconds. Independent if the user specifies seconds, they are displayed ‘1’ or not ‘0’.
    • showZero = 0|1 - For an empty timestamp, With ‘0’ nothing is displayed. With ‘1’ the string ‘00:00[:00]’ is displayed.

Type: upload

An upload element is based on a ‘file browse’-button and a ‘trash’-button (=delete). Only one of them is shown at a time. The ‘file browse’-button is displayed, if there is no file uploaded already. The ‘trash’-button is displayed, if there is a file uploaded already.

After clicking on the browse button, the user select a file from the local filesystem. After choosing the file, the upload starts immediately, shown by a turning wheel. When the server received the whole file and accepts (see below) the file, the ‘file browse’-button disappears and the filename is shown, followed by a ‘trash’-button. Either the user is satisfied now or the user can delete the uploaded file (and maybe upload another one).

Until this point, the file is cached on the server but not copied to the fileDestination . The user have to save the current record, either to finalize the upload and/or to delete a previously uploaded file.

The FormElement behaves like a

  • ‘native FormElement’ (showing controls/text on the form) as well as an
  • ‘action FormElement’ by firing queries and doing some additional actions during form save.

Inside the Form editor it’s shown as a ‘native FormElement’. During saving the current record, it behaves like an action FormElement and will be processed after saving the primary record and before any action FormElements are processed.

  • FormElement.value = <string> - By default, the full path of any already uploaded file is shown. To show something different, e.g. only the filename, define:

    a) {{filenameBase:V}}
    b) {{SELECT SUBSTRING_INDEX( '{{pathFileName:R}}', '/', -1)  }}
    

See also downloadButton to offer a download of an uploaded file.

  • FormElement.parameter :

    • capture = camera - On a smartphone, after pressing the ‘open file’ button, the camera will be opened and a choosen picture will be uploaded. Automatically set/overwrite accept=image/* .

    • accept = <mime type>,image/*,video/*,audio/*,.doc,.docx,.pdf

      • List of mime types (also known as ‘media types’): http://www.iana.org/assignments/media-types/media-types.xhtml
      • If none mime type is specified, ‘application/pdf’ is set. This forces that always (!) one type is specified.
      • To allow any type, specify * or */* or *.* .
      • One or more media types might be specified, separated by ‘,’.
      • Different browser respect the given definitions in different ways. Typically the ‘file choose’ dialog offer:
        • the specified mime type (some browers only show ‘custom’, if more than one mime type is given),
        • the option ‘All files’ (the user is always free to try to upload other file types) - but the server won’t accept them,
        • the ‘file choose’ dialog only offers files of the selected (in the dialog) type.
      • If for a specific file type is no mime type available, the definition of file extension(s) is possible. This is less secure , cause there is no content check on the server after the upload.
    • maxFileSize = <size> - max filesize in bytes (no unit), kilobytes (k/K) or megabytes (m/M) for an uploaded file. If empty or not given, take value from Form, System or System default.

    • fileDestination = <pathFileName> - Destination where to copy the file. A good practice is to specify a relative fileDestination - such an installation (filesystem and database) are moveable.

      • If the original filename should be part of fileDestination , the variable {{filename}} (STORE_VARS) can be used. Example

        fileDestination={{SELECT 'fileadmin/user/pictures/', p.name, '-{{filename}}' FROM Person AS p WHERE p.id={{id:R0}} }}
        
        • Several more variants of the filename and also mimetype and filesize are available. See store_vars_form_element_upload .
        • The original filename will be sanitized: only ‘<alnum>’, ‘.’ and ‘_’ characters are allowed. German ‘umlaut’ will be replaced by ‘ae’, ‘ue’, ‘oe’. All non valid characters will be replaced by ‘_’.
      • If a file already exist under fileDestination , an error message is shown and ‘save’ is aborted. The user has no possibility to overwrite the already existing file. If the whole workflow is correct, this situation should no arise. Check also fileReplace below.

      • All necessary subdirectories in fileDestination are automatically created.

      • Using the current record id in the fileDestination : Using {{r}} is problematic for a ‘new’ primary record: that one is still ‘0’ at the time of saving. Use {{id:R0}} instead.

      • Uploading of malicious code (e.g. PHP files) is hard to detect. The default mime type check can be easily faked by an attacker. Therefore it’s recommended to use a fileDestination -directory, which is secured against script execution (even if the file has been uploaded, the webserver won’t execute it) - see SecureDirectFileAccess .

    • sqlBefore , sqlAfter : available in Upload simple mode and Upload advanced mode .

    • slaveId , sqlInsert , sqlUpdate , sqlDelete , sqlUpdate : available only in Upload advanced mode .

    • fileSize / mimeType

      • In Upload simple mode the information of fileSize and mimeType will be automatically updated on the current record, if table columns fileSize and/or mimeType exist.

        • If there are more than one Upload FormElement in a form, the automatically update for fileSize and/or mimeType are not done automatically.
      • In Upload advanced mode the fileSize and / or mimeType have to be updated with an explicit SQL statement:

        sqlAfter = {{UPDATE Data SET mimeType='{{mimeType:V}}', fileSize={{fileSize:V}} WHERE id={{id:R}} }}
        
    • fileReplace = always - If fileDestination exist - replace it by the new one.

    • chmodFile = <unix file permission mode> - e.g. 660 for owner and group read and writeable. Only the numeric mode is allowed.

    • chmodDir = <unix file permission mode> - e.g. 770 for owner and group read, writeable and executable. Only the numeric mode is allowed. Will be applied to all new created directories.

    • autoOrient: images might contain EXIF data (e.g. captured via mobile phones) incl. an orientation tag like TopLeft, BottomRight and so on. Web-Browser and other grafic programs often understand and respect those information and rotate such images automatically. If not, the image might be displayed in an unwanted oritentation. With active option ‘autoOrient’, QFQ tries to normalize such images via ‘convert’ (part of ImageMagick). Especially if images are processed by the QFQ internal ‘Fabric’-JS it’s recommended to normalize images first. The normalization process does not solve all orientation problems.

      • autoOrient = [0|1]
      • autoOrientCmd = ‘convert -auto-orient {{fileDestination:V}} {{fileDestination:V}}.new; mv {{fileDestination:V}}.new {{fileDestination:V}}’
      • autoOrientMimeType = image/jpeg,image/png,image/tiff

      If the defaults for autoOrientCmd and autoOrientMimeType are sufficient, it’s not necessary to specify them.

  • downloadButton = t:<string> - If given, shows a button to download the previous uploaded file - instead of the string given in fe.value . The button is only shown if fe.value points to a readable file on the server.
    • If downloadButton is empty, just shows the regular download glyph.
    • To just show the filename: downloadButton = t:{{filenameOnly:V}}
    • Additional attributes might be given like downloadButton = t:Download|o:check file . Please check download .
      • The following attributes are hard coded (can’t be changed): s|M:file|d|F
  • fileSplit, fileDestinationSplit, tableNameSplit: see split-pdf-upload
  • Excel Import: QFQ offers functionality to directly import excel data into the database. This functionality can optionally be combined with saving the file by using the above parameters like fileDestination . The data is imported without formatting. Please note that this means Excel dates will be imported as a number (e.g. 43214), which is the serial value date in Excel. To convert such a number to a MariaDb date, use: DATE_ADD(‘1899-12-30’, INTERVAL serialValue DAY) .
    • importToTable = <[db.]tablename> - Required . Providing this parameter activates the import. If the table doesn’t exist, it will be created.
    • importToColumns = <col1>,<col2>,… - If none provided, the Excel column names A, B, … are used. Note: These have to match the table’s column names if the table already exists.
    • importRegion = [tab],[startColumn],[startRow],[endColumn],[endRow]|… - All parts are optional (default: entire 1st sheet). Tab can either be given as an index (1-based) or a name. start/endColumn can be given either numerically (1, 2, …) or by column name (A, B, …). Note that you can specify several regions to import.
    • importMode = append (default) | replace - The data is either appended or replace in the specified table.
    • importType = auto (default) | xls | xlsx | ods | csv - Define what kind of data should be expected by the Spreadsheet Reader.
    • importNamedSheetsOnly = <comma separated list of sheet names>. Use this option if specific sheets cause problems during import and should be skipped, by naming only those sheets, who will be read. This will also reduce the memory usage.
    • importSetReadDataOnly = 0|1. Read only cell data, not the cell formatting. Warning: cell types other than numerical will be misinterpreted.
    • importListSheetNames = 0|1. For debug use only. Will open a dialog and report all found worksheet names.

Immediately after the upload finished (before the user press save), the file will be checked on the server for it’s content or file extension (see ‘accept’).

The maximum size is defined by the minimum of upload_max_filesize , post_max_size and memory_limit (PHP script) in the php.ini.

In case of broken uploads, please also check max_input_time in php.ini.

Deleting a record and the referenced file

If the user deletes a record (e.g. pressing the delete button on a form) which contains reference(s) to files, such files are deleted too. Slave records, which might be also deleted through a ‘delete’-form, are not checked for file references and therefore such files are not deleted on the filesystem.

Only column(name)s which contains pathFileName as part of their name, are checked for file references.

If there are other records, which references the same file, such files are not deleted. It’s a very basic check: just the current column of the current table is compared. In general it’s not a good idea to have multiple references to a single file. Therefore this check is just a fallback.

Upload simple mode

Requires: ‘upload’-FormElement.name = ‘column name’ of an column in the primary table.

After moving the file to fileDestination , the current record/column will be updated to fileDestination . The database definition of the named column has to be a string variant (varchar, text but not numeric or else). On form load, the column value will be displayed as the whole value (pathFileName)

Deleting an uploaded file in the form (by clicking on the trash near beside) will delete the file on the filesystem as well. The column will be updated to an empty string.

This happens automatically without any further definiton in the ‘upload’-FormElement.

Multiple ‘upload’-FormElements per form are possible. Each of it needs an own table column.

Upload advanced mode

Requires: ‘upload’-FormElement.name is unknown as a column in the primary table.

This mode will serve further database structure scenarios.

A typical name for such an ‘upload’-FormElement, to show that the name does not exist in the table, might start with ‘my’, e.g. ‘myUpload1’.

  • FormElement.value = <string> - The path/filename, shown during ‘form load’ to indicate a previous uploaded file, has to be queried with this field. E.g.:

    {{SELECT pathFileNamePicture FROM Note WHERE id={{slaveId}} }}
    
  • FormElement.parameter :

    • fileDestination = <pathFileName> - determine the path/filename. E.g.:

      fileDestination=fileadmin/person/{{name:R0}}_{{id:R}}/uploads/picture_{{filename}}
      
    • slaveId = <id> - Defines the target record where to retrieve and store the path/filename of the uploaded file. Check also Parameter: slaveId . E.g.:

      slaveId={{SELECT id FROM Note WHERE pId={{id:R0}} AND type='picture' LIMIT 1}}
      
    • sqlBefore = {{<query>}} - fired during a form save, before the following queries are fired.

    • sqlInsert = {{<query>}} - fired if slaveId=0 and an upload exist (user has choosen a file):

      sqlInsert={{INSERT INTO Note (pId, type, pathFileName) VALUE ({{id:R0}}, 'image', '{{fileDestination}}') }}
      
    • sqlUpdate = {{<query>}} - fired if slaveId>0 and an upload exist (user has choosen a file). E.g.:

      sqlUpdate={{UPDATE Note SET pathFileName = '{{fileDestination}}' WHERE id={{slaveId}} LIMIT 1}}
      
    • sqlDelete = {{<query>}} - fired if slaveId>0 and no upload exist (user has not choosen a file). E.g.:

      sqlDelete={{DELETE FROM Note WHERE id={{slaveId:V}}  LIMIT 1}}
      
    • sqlAfter = {{<query>}} - fired after all previous queries have been fired. Might update the new created id to a primary record. E.g.:

      sqlAfter={{UPDATE Person SET noteIdPicture = {{slaveId}} WHERE id={{id:R0}} LIMIT 1 }}
      

Split PDF Upload

Additional to the upload, it’s possible to split the uploaded file (only PDF files) into several SVG or JPEG files, one file per PDF page. The split is done via a) http://www.cityinthesky.co.uk/opensource/pdf2svg/ or b) Image Magick convert .

Currently, QFQ can only split PDF files.

If the source file is not of type PDF, activating fileSplit has no impact: no split and NO complain about invalid file type.

  • FormElement.parameter :

    • fileSplit = <type> - Activate the splitting process. Possible values: svg or jpeg . No default.

    • fileSplitOptions = <command line options> .

      • [svg] - no default
      • [jpeg] - default: -density 150 -quality 90
    • fileDestinationSplit = <pathFileName (pattern)> - Target directory and filename pattern for the created &

      split’ed files. Default <fileDestination>.split/split.<nr>.<fileSplit> If explicit given, respect that SVG needs a printf style for <nr>, whereas JPEG is numbered automatically. E.g.

      [svg] fileDestinationSplit = fileadmin/protected/{{id:R}}.{{filenameBase:V}}.%02d.svg
      [jpeg] fileDestinationSplit = fileadmin/protected/{{id:R}}.{{filenameBase:V}}.jpg
      
    • tableNameSplit = <tablename> - Default: name of table of current form. This name will be saved in table Split

The splitting happens immediately after the user pressed save .

To easily access the split files via QFQ, per file one record is created in table ‘Split’.

Table ‘Split’:

Column Description
id Uniq auto increment index
tableName Name of the table, where the reference to the original file (multipage PDF file) is saved.
xId Primary id of the reference record.
pathFileName Path/filename reference to one of the created files

One usecase why to split an upload: annotate individual pages by using the FormElement .type=`annotate`.

Class: Action

Type: before… | after…

These type of ‘action’ FormElements will be used to implement data validation or creating/updating additional records.

Types:

  • beforeLoad (e.g. good to check access permission)
  • afterLoad
  • beforeSave (e.g. to prohibit creating of duplicate records)
  • afterSave (e.g. to to create & update additional records)
  • beforeInsert
  • afterInsert
  • beforeUpdate
  • afterUpdate
  • beforeDelete (e.g. to delete slave records)
  • afterDelete
  • paste (configure copy/paste forms)

Parameter: sqlValidate

Perform checks by fireing a SQL query and expecting a predefined number of selected records.

  • OK: the expectRecords number of records has been selected. Continue processing the next FormElement .
  • Fail: the expectRecords number of records has not been selected (less or more): Display the error message messageFail and abort the whole (!) current form load or save.

FormElement.parameter :

  • requiredList = <fe.name[s]> - List of native - FormElement names: only if all of those elements are filled (!=0 and !=’‘), the current action - FormElement will be processed. This will enable or disable the check, based on the user input! If no native - FormElement names are given, the specified check will always be performed.
  • sqlValidate = {{<query>}} - validation query. E.g.: sqlValidate={{SELECT id FROM Person AS p WHERE p.name LIKE {{name:F:all}} AND p.firstname LIKE {{firstname:F:all}} }}
  • expectRecords = <value> - number of expected records.
    • expectRecords = 0 or expectRecords = 0,1 or expectRecords = {{SELECT COUNT(id) FROM Person}}
    • Separate multiple valid record numbers by ‘,’. If at least one of those matches, the check will pass successfully.
  • messageFail = <string> - Message to show. E.g.: messageFail = There is already a person called {{firstname:F:all}} {{name:F:all}}

Parameter: slaveId

FormElement.parameter :

  • slaveId = <id> :
    • Auto fill: name the action action - FormElement equal to an existing column (table from the current form definition). slaveId will be automatically filled with the value of the named column.
      • If there is no such named column name, set slaveId = 0 .
    • Explicit definition: slaveId = 123 or slaveId = {{SELECT id …}}

Note:

  • {{slaveId}} can be used in any query of the current FormElement .
  • If the action - FormElement name exist as a column in the master record: Update that column automatically with the recent slaveId
  • After an INSERT the last_insert_id() becomes the slaveId ).

Parameter: sqlBefore / sqlInsert / sqlUpdate / sqlDelete / sqlAfter

  • Save values of a form to different record(s), optionally on different table(s).
  • Typically useful on ‘afterSave’ - be careful when using it earlier, e.g. beforeLoad.

FormElement.parameter :

  • requiredList = <fe.name[s]> - List of native - FormElement : only if all of those elements are filled, the current action - FormElement will be processed.
  • sqlBefore = {{<query>}} - always fired (before any sqlInsert , sqlUpdate , ..)
  • sqlInsert = {{<query>}} - fired if slaveId == 0 or slaveId == ‘’ .
  • sqlUpdate = {{<query>}} - fired if slaveId > 0 .
  • sqlDelete = {{<query>}} - fired if slaveId > 0 , after sqlInsert or sqlUpdate . Be careful not to delete filled records! Always add a check, if values given, not to delete the record! sqlHonorFormElements helps to skip such checks.
  • sqlAfter = {{<query>}} - always fired (after sqlInsert , sqlUpdate or sqlDelete ).
  • sqlHonorFormElements = <fe.name[s]> list of FormElement names (this parameter is optional).
    • If one of the named FormElements is not empty:
      • fire sqlInsert if slaveId == 0 ,
      • fire sqlUpdate if slaveId > 0
    • If all of the named FormElements are empty:
      • fire sqlDelete if slaveId > 0

Example

Situation 1: master.xId=slave.id (1:1)

  • Name the action element ‘xId’: than {{slaveId}} will be automatically set to the value of ‘master.xId’

    • {{slaveId}} == 0 ? ‘sqlInsert’ will be fired.
    • {{slaveId}} != 0 ? ‘sqlUpdate’ will be fired.
  • In case of firing ‘sqlInsert’, the ‘slave.id’ of the new created record are copied to master.xId (the database will be updated automatically).

  • If the automatic update of the master record is not suitable, the action element should have no name or a name which does not exist as a column of the master record. Define slaveId={{SELECT id …}}

  • Two FormElements myStreet and myCity :

    • Without sqlHonorFormElements . Parameter:

      sqlInsert = {{INSERT INTO address (`street`, `city`) VALUES ('{{myStreet:FE:alnumx:s}}', '{{myCity:FE:alnumx:s}}') }}
      sqlUpdate = {{UPDATE address SET `street` = '{{myStreet:FE:alnumx:s}}', `city` = '{{myCity:FE:alnumx:s}}'  WHERE id={{slaveId}} LIMIT 1 }}
      sqlDelete = {{DELETE FROM address WHERE id={{slaveId}} AND '{{myStreet:FE:alnumx:s}}'='' AND '{{myCity:FE:alnumx:s}}'='' LIMIT 1 }}
      
    • With sqlHonorFormElements . Parameter:

      sqlHonorFormElements = myStreet, myCity     # Non Templategroup
      sqlInsert = {{INSERT INTO address (`street`, `city`) VALUES ('{{myStreet:FE:alnumx:s}}', '{{myCity:FE:alnumx:s}}') }}
      sqlUpdate = {{UPDATE address SET `street` = '{{myStreet:FE:alnumx:s}}', `city` = '{{myCity:FE:alnumx:s}}'  WHERE id={{slaveId}} LIMIT 1 }}
      sqlDelete = {{DELETE FROM address WHERE id={{slaveId}} LIMIT 1 }}
      
      # For Templategroups: sqlHonorFormElements = myStreet%d, myCity%d
      

Situation 2: master.id=slave.xId (1:n)

  • Name the action element different to any column name of the master record (or no name).

  • Determine the slaveId: slaveId={{SELECT id FROM slave WHERE slave.xxx={{…}} LIMIT 1}}

    • {{slaveId}} == 0 ? ‘sqlInsert’ will be fired.
    • {{slaveId}} != 0 ? ‘sqlUpdate’ will be fired.
  • Two FormElements myStreet and myCity . The person is the master record, address is the slave:

    • Without sqlHonorFormElements . Parameter:

      slaveId = {{SELECT id FROM address WHERE personId={{id}} ORDER BY id LIMIT 1 }}
      sqlInsert = {{INSERT INTO address (`personId`, `street`, `city`) VALUES ({{id}}, '{{myStreet:FE:alnumx:s}}', '{{myCity:FE:alnumx:s}}') }}
      sqlUpdate = {{UPDATE address SET `street` = '{{myStreet:FE:alnumx:s}}', `city` = '{{myCity:FE:alnumx:s}}'  WHERE id={{slaveId}} LIMIT 1 }}
      sqlDelete = {{DELETE FROM address WHERE id={{slaveId}} AND '{{myStreet:FE:alnumx:s}}'='' AND '{{myCity:FE:alnumx:s}}'='' LIMIT 1 }}
      
    • With sqlHonorFormElements . Parameter:

      slaveId = {{SELECT id FROM address WHERE personId={{id}} ORDER BY id LIMIT 1 }}
      sqlHonorFormElements = myStreet, myCity       # Non Templategroup
      sqlInsert = {{INSERT INTO address (`personId`, `street`, `city`) VALUES ({{id}}, '{{myStreet:FE:alnumx:s}}', '{{myCity:FE:alnumx:s}}') }}
      sqlUpdate = {{UPDATE address SET `street` = '{{myStreet:FE:alnumx:s}}', `city` = '{{myCity:FE:alnumx:s}}'  WHERE id={{slaveId}} LIMIT 1 }}
      sqlDelete = {{DELETE FROM address WHERE id={{slaveId}} LIMIT 1 }}
      
      # For Templategroups: sqlHonorFormElements = myStreet%d, myCity%d
      

Type: sendmail

  • Send mail(s) will be processed after:

    • saving the record ,
    • processing all uploads,
    • processing afterSave action FormElements .
  • FormElement.value = <string> - Body of the email. See also: html-formatting

  • FormElement.parameter :

    • sendMailTo = <string> - Comma-separated list of receiver email addresses. Optional: ‘realname < john @ doe . com >. If there is no recipient email address, no mail will be sent.
    • sendMailCc = <string> - Comma-separated list of receiver email addresses. Optional: ‘realname < john @ doe . com >.
    • sendMailBcc = <string> - Comma-separated list of receiver email addresses. Optional: ‘realname < john @ doe . com >.
    • sendMailFrom = <string> - Sender of the email. Optional: ‘realname < john @ doe . com >’. Mandatory .
    • sendMailSubject = <string> - Subject of the email.
    • sendMailReplyTo = <string> - Reply this email address. Optional: ‘realname < john @ doe . com >’.
    • sendMailAttachment = <string> - List of ‘sources’ to attach to the mail as files. Check attachment for options.
    • sendMailHeader = <string> - Specify custom header.
    • sendMailFlagAutoSubmit = <string> - on|off - If ‘on’ (default), the mail contains the header ‘Auto-Submitted: auto-send’ - this suppress a) OoO replies, b) forwarding of emails.
    • sendMailGrId = <string> - Will be copied to the mailLog record. Helps to setup specific logfile queries.
    • sendMailXId = <string> - Will be copied to the mailLog record. Helps to setup specific logfile queries.
    • sendMailXId2 = <string> - Will be copied to the mailLog record. Helps to setup specific logfile queries.
    • sendMailXId3 = <string> - Will be copied to the mailLog record. Helps to setup specific logfile queries.
    • sendMailMode = <string> - html - if set, the e-mail body will be rendered as html.
    • sendMailSubjectHtmlEntity = <string> - encode|decode|none - the mail subject will be htmlspecialchar() encoded / decoded (default) or none (untouched).
    • sendMailBodyHtmlEntity*= `<string>` - **encode|decode|none* - the mail body will be htmlspecialchar() encoded, decoded (default) or none (untouched).
    • sqlBefore / sqlAfter = <string> - can be used like with other action elements (will be fired before/after sending the e-mail).
  • To use values of the submitted form, use the STORE_FORM. E.g. {{name:F:allbut}}

  • To use the id of a new created or already existing primary record, use the STORE_RECORD. E.g. {{id:R}} .

  • 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 sendMailSubjectHtmlEntity=none and/or sendMailBodyHtmlEntity=none .

  • For debugging, please check REDIRECT_ALL_MAIL_TO .

Example to attach one file1.pdf (with the attachment filename ‘readme.pdf’) and concatenate two PDF, created on the fly from the www.example.com and ?export (with the attachment filename ‘personal.pdf’):

sendMailAttachmemt = F:fileadmin/file1.pdf|d:readme.pdf|C|u:http://www.example.com|p:?id=export&r=123&_sip=1|d:personal.pdf

Type: paste

See also copy-form .

  • sql1 = {{<query>}} - e.g. {{!SELECT {{id:P}} AS id, ‘{{myNewName:FE:allbut}}’ AS name}} (only one record) or {{!SELECT i.id AS id, {{basketId:P}} AS basketId FROM Item AS i WHERE i.basketId={{id:P}} }} (multiple records)
    • Pay attention to ‘!’.
    • For every row, a new record is created in recordDestinationTable .
    • Column ‘id’ is not copied.
    • The recordSourceTable together with column id will identify the source record.
    • Columns not specified, will be copied 1:1 from source to destination.
    • Columns specified, will overwrite the source value.
  • FormElement.parameter :
    • recordSourceTable = <tableName> - Optional: table from where the records will be copied. Default: <recordDestinationTable>
    • recordDestinationTable = <tableName> - table where the new records will be copied to.
    • translateIdColumn = <column name> - column name to update references of newly created id’s.

Form Magic

Parameter

  • Table column id : QFQ expect that each table, which will be loaded in a form, contains an autoincrement column of name id . It’s not necessary to create a FormElement id in a form - but it won’t disturb.

  • Parameter (one or more) in the SIP url, which exist as a column in the form table (SIP parameter name is equal to a table column name), will be automatically saved in the record. This acts as ‘hidden magic’.

    Example: A slave record (e.g. an address of a person) has to be assigned to a master record (a person). Just give the pId in the link who calls the address form. The following creates a ‘new’ button for an address for all persons, and the pId will be automatically saved in the address table:

    SELECT CONCAT('p:{{pageAlias:T}}&form=address&r=0&pId=', p.id) AS _pagen FROM Person AS p
    

    Such parameter, which the form expects to be in the SIP url, should be specified in Form.permitNew and/or Form.permitEdit. It’s only a check for the webmaster, not to forgot a parameter in a SIP url.

  • FormElement.type = subrecord

    Subrecord’s will automatically create new , edit and delete links. To inject parameter in those automatically created links, use FormElement.parameter.detail . See subrecord-option .

  • FormElement.type = extra

    If a table column should be saved with a specific value, and the value should not be shown to the user, the FE.type=’extra’ will do the job. The value could be static or calculated on the fly. Often it’s easier to specify such a parameter/value in the SIP url, but if the form is called from multiple places, an extra element is more suitable.

Variables

  • Form.parameter.fillStoreVar / FormElement.parameter.fillStoreVar

    A SQL statement will fill STORE_VARS. Such values can be used during form load and/or save.

Action

  • Action FE

    Via FormElement.parameter.requiredList an element can be enabled / disabled, depending of a user provided input in one of the specified required FEs.

Multi Language Form

QFQ Forms might be configured for up to 5 different languages. Per language there is one extra field in the Form editor . Which field represents which language is configured in configuration .

  • The Typo3 installation needs to be configured to handle different languages - this is independent of QFQ and not covered here. QFQ will use the Typo3 internal variable ‘pageLanguage’, which typically correlates to the URL parameter ‘L’ in the URL.
  • In configuration the Typo3 language index (value of ‘L’) and a language label have to be configured for each language. Only than, the additional language fields in the Form editor will be shown.

Example

Assuming the Typo3 page has the

  • default language, L=0
  • English, L=1
  • Spanish, L=2

Configuration in configuration :

formLanguageAId = 1
formLanguageALabel = English

formLanguageBId = 2
formLanguageBLabel = Spanish

The default language is not covered in configuration .

The Form editor now shows on the pill ‘Basic’ (Form and FormEditor) for both languages each an additional parameter input field. Any input field in the Form editor can be redeclared in the corresponding language parameter field. Any missing definition means ‘take the default’. E.g.:

  • Form: ‘person’

    Column Value
    title Eingabe Person
    languageParameterA title=Input Person
    languageParameterB title=Persona de entrada
  • FormElement ‘firstname’ in Form ‘person’:

    Column Value
    title Vorname
    note Bitte alle Vornamen erfassen
    languageParameterA
    title=Firstname
    note=Please give all firstnames
    languageParameterB
    title=Persona de entrada
    note=Por favor, introduzca todos los nombres

The following fields are possible:

  • Form: title, showButton, forwardMode, forwardPage, bsLabelColumns, bsInputColumns, bsNoteColumns, recordLockTimeoutSeconds
  • FormElement: label, mode, modeSql, class, type, subrecordOption, encode, checkType, ord, tabindex, size, maxLength, bsLabelColumns, bsInputColumns, bsNoteColumns,rowLabelInputNote, note, tooltip, placeholder, value, sql1, feGroup

Dynamic Update

The ‘Dynamic Update’ feature makes a form more interactive. If a user changes a FormElement who is tagged with ‘dynamicUpdate’, all elements who are tagged with ‘dynamicUpdate’, will be recalculated and rerendered.

The following fields will be recalculated during ‘Dynamic Update’

  • ‘modeSql’ - Possible values: ‘show’, ‘required’, ‘readonly’, ‘hidden’
  • ‘label’
  • ‘value’
  • ‘note’
  • ‘parameter.*’ - especially ‘itemList’

To make a form dynamic:

  • Mark all FormElements with dynamic update`=`enabled , which should initiate or receive updates.

See #3426 / Dynamic Update: Inputs loose the new content and shows the old value:

  • On all dynamic update FormElements an explicit definition of value , including a sanitize class, is necessary (except the field is numeric). A missing definition let’s the content overwrite all the time with the old value . A typical definition for value looks like (default store priority is: FSRVD):

    {{<FormElement name>::alnumx}}
    
  • Define the receiving FormElements in a way, that they will interpret the recent user change! The form variable of the specific sender FormElement {{<sender element>:F:<sanitize>}} should be part of one of the above fields to get an impact. E.g.:

    [receiving *FormElement*].parameter: itemList={{ SELECT IF({{carPriceRange:FE:alnumx}}='expensive','Ferrari,Tesla,Jaguar','General Motors,Honda,Seat,Fiat') }}
    

    Remember to specify a ‘sanitize’ class - a missing sanitize class means ‘digit’, every content, which is not numeric, violates the sanitize class and becomes therefore an empty string!

  • If the dynamic update should work on existing and new records, it’s important to guarantee that the query result is not empty! even if the primary record does not exist! E.g. use a LEFT JOIN . The following query is ok for new and edit .

    {{SELECT IF( IFNULL(adr.type,'') LIKE '%token%','show','hidden') FROM (SELECT 1) AS fake LEFT JOIN Address AS adr ON adr.type='{{type:FR0}}' LIMIT 1}}
    

Examples

  • Master FormElement ‘music’ is a radio/enum of ‘classic’, ‘jazz’, ‘pop’.

Content of a select list

  • Slave FormElement ‘interpret’ is ‘select’-list, depending of ‘music’
sql={{!SELECT name FROM interpret WHERE music={{music:FE:alnumx}} ORDER BY name}}

Show / Hide a FormElement

  • Slave ‘interpret’ is displayed only for ‘pop’. Field ‘modeSql’:
{{SELECT IF( '{{music:FR:alnumx}}'='pop' ,'show', 'hidden' ) }}

Form Layout

The forms will be rendered with Bootstrap CSS classes, based on the 12 column grid model (Bootstrap 3.x). Generally a 3 column layout for label columns on the left side, an input field column in the middle and a note column on the right side will be rendered.

The used default column (=bootstrap grid) width is 3,6,3 (col-md , col-lg) for label, input, note .

  • The system wide defaults can be changed via configuration .
  • Per Form settings can be done in the Form parameter field. They overwrite the system wide default.
  • Per FormElement settings can be done in the FormElement parameter field. They overwrite the Form setting.

A column will be switched off (no wrapping via <div class=’col-md-?> ) by setting a 0 on the respective column.

Custom field width

Per FormElement set BS Label Columns , BS Input Columns or BS Note Columns to customize an individual width. If only a number is specified, it’s used as col-md-<number> . Else the whole text string is used as CSS class, e.g. col-md-3 col-lg-2 .

Multiple Elements per row

Every row is by default wrapped in a <div class=’form-group’> and every column is wrapped in a <div class=’col-md-?’> . To display multiple input elements in one row, the wrapping of the FormElement row and of the three columns can be customized via the checkboxes of Label / Input / Note . Every open and every close tag can be individually switched on or off.

E.g. to display 2 FormElements in a row with one label (first FormElement ) and one note (last FormElement ) we need the following (switch off all non named):

  • First FormElement
    • open row tag: row ,
    • open and close label tag: label , /label ,
    • open and close field tag: input , /input ,
  • Second FormElement
    • open and close field tag: input , /input ,
    • open and close note tag: note , /note ,
    • close row tag: /row ,

Copy Form

Records (=master) and child records can be duplicated (=copied) by a regular Form , extended by FormElements of type ‘paste’. A ‘copy form’ works either in:

  • ‘copy and paste now’ mode: the ‘select’ and ‘paste’ Form is merged in one form, only one master record is possible,
  • ‘copy now, paste later’ mode: the ‘select’ Form selects master record(s), the ‘paste’ Form paste’s them later.

Concept

A ‘select action’ (e.g. a Form or a button click) creates record(s) in the table Clipboard . Each clipboard record contains:

  • the ‘id(s)’ of the record(s) to duplicate,
  • the ‘paste’ form id (that Form defines, to which table the master records belongs to, as well as rules of how to duplicate any slave records) and where to copy the new records
  • user identifier (QFQ cookie) to separate clipboard records of different users inside the Clipboard table.

The ‘select action’ is also responsible to delete old clipboard records of the current user, before new clipboard records are created.

The ‘paste form’ iterates over all master record id(s) in the Clipboard table. For each master record id, all FormElements of type paste are fired (incl. the creating of slave records).

E.g. if there is a basket with different items and you want to duplicate the whole basket including new items, create a form with the following parameter

  • Form
    • Name: copyBasket
    • Table: Clipboard
    • Show Button: only close and save
  • FormElement 1: Record id of the source record.
    • Name: idSrc
    • Lable: Source Form
    • Class: native
    • Type: select
    • sql1: {{! SELECT id, title FROM Basket }}
  • FormElement 2: New name of the copied record.
    • Name: myNewName
    • Class: native
    • Type: text
  • FormElement 3: a) Check that there is no name conflict. b)Purge any old clipboard content of the current user.
    • Name: clearClipboard
    • Class: action
    • Type: beforeSave
    • Parameter:
      • sqlValidate={{SELECT f.id FROM Form AS f WHERE f.name LIKE ‘{{myName:FE:alnumx}}’ LIMIT 1}}
      • expectRecords = 0
      • messageFail = There is already a form with this name
      • sqlAfter={{DELETE FROM Clipboard WHERE cookie=’{{cookieQfq:C0:alnumx}}’ }}
  • FormElement 4: Update the clipboard source reference, with current {{cookieQfq:C}} identifier.
    • Name: updateClipboardRecord
    • Class: action
    • Type: afterSave
    • Parameter: sqlAfter={{UPDATE Clipboard SET cookie=’{{cookieQfq:C0:alnumx}}’, formIdPaste={{formId:S0}} /* PasteForm */ WHERE id={{id:R}} LIMIT 1 }}
  • FormElement 5: Copy basket identifier.
    • Name: basketId
    • Class: action
    • Type: paste
    • sql1: {{!SELECT {{id:P}} AS id, ‘{{myNewName:FE:allbut}}’ AS name}}
    • Parameter: recordDestinationTable=Basket
  • FormElement 6: Copy items of basket.
    • Name: itemId
    • Class: action
    • Type: paste
    • sql1: {{!SELECT i.id AS id, {{basketId:P}} AS basketId FROM Item AS i WHERE i.basketId={{id:P}} }}
    • Parameter: recordDestinationTable=Item

Table self referencing records

Records might contain references to other records in the same table. E.g. native FormElements might assigned to a fieldSet, templateGroup or pill, a fieldSet might assigned to other fieldsets or pills and so on. When duplicating a Form and the corresponding FormElements all internal references needs to be updated as well.

On each FormElement.type=`paste` record, the column to be updated is defined via:

  • parameter: translateIdColumn = <column name>

For the ‘copyForm’ this would be ‘feIdContainer’.

The update of the records is started after all records have been copied (of the specific FormElement.type=`paste` record).

Delete Record

Deleting record(s) via QFQ might be solved by either:

  • using the delete button on a form on the top right corner.
  • by letting report creating a special link (see below). The link contains the record id and:
    • a form name, or
    • a table name.

Deleting a record just by specifying a table name, will only delete the defined record (no slave records).

  • By using a delete button via report or in a subrecord row, a ajax request is send.
  • By using a delete button on the top right corner of the form, the form will be closed after deleting the record.

Example for report:

SELECT p.name, CONCAT('U:form=person&r=', p.id) AS _paged FROM Person AS p
SELECT p.name, CONCAT('U:table=Person&r=', p.id) AS _paged FROM Person AS p

To automatically delete slave records, use a form and create beforeDelete FormElement(s) on the form:

  • class: action
  • type: beforeDelete
  • parameter: sqlAfter={{DELETE FROM <slaveTable> WHERE <slaveTable>.<masteId>={{id:R}} }}

You might also check the form ‘form’ how the slave records ‘FormElement’ will be deleted.

Locking Record / Form

Support for record locking is given with mode:

  • exclusive : user can’t force a write.
    • Including a timeout (default 15 mins recordLockTimeoutSeconds in configuration ) for maximum lock time.
  • advisory : user is only warned, but allowed to overwrite.
  • none : no bookkeeping about locks.

For ‘new’ records (r=0) there is no locking at all.

The record locking protection is based on the tablename and the record id . Different Forms , with the same primary table, will be protected by record locking. On the other side, action- FormElements updating non primary table records are not protected by ‘record locking’: the QFQ record locking is NOT 100% .

The ‘record locking’ mode will be specified per Form . If there are multiple Forms with different modes, and there is already a lock for a tablename / record id pair, the most restrictive will be applied.

Best practice

Custom default value only for ‘new records’

Method 1

On Form.parameter define a fillStoreVar query with a column name equal to a form field. That’s all.

Example:

FormElement.name = technicalContact
Form.parameter.fillStoreVar = {{SELECT CONCAT(p.firstName, ' ', p.name) AS technicalContact FROM Person AS p WHERE p.account='{{feUser:T}}' }}

What we use here is the default STORE prio FSRVD. If the form loads with r=0, ‘F’, ‘S’ and ‘R’ are empty. ‘V’ is filled. If r>0, than ‘F’ and ‘S’ are empty and ‘R’ is filled.

Method 2

In the specific FormElement set value={{columnName:RSE}} . The link to the form should be rendered with ‘”…&columnName=<data>&…” AS _page’. The trick is that the STORE_RECORD is empty for new records, and therefore the corresponding value from STORE_SIP will be returned. Existing records will use the already saved value.

Central configured values

Any variable in configuration can be used by {{<varname>:Y}} in form or report statements.

E.g.

TECHNICAL_CONTACT = jane . doe @ example . net

Could be used in an FormElement.type = sendmail with parameter setting sendMailFrom={{TECHNICAL_CONTACT:Y}} .

Debug Report

Writing “report’s” in the nested notation or long queries broken over several lines, might not interpreted as wished. Best for debugging is to specify in the tt-content record:

debugShowBodyText = 1

Note: Debug information is only display if it’s enabled in configuration by

  • showDebugInfo: yes or
  • showDebugInfo: auto and logged in in the same Browser as a Typo3 backend user.

More detailed error messages

If showDebugInfo is enabled, a full stacktrace and variable contents are displayed in case of an error.

Form: compute next free ‘ord’ automatically

Requirement: new records should automatically get the highest number plus 10 for their ‘ord’ value. Existing records should not be altered.

Version 1

Compute the next ‘ord’ in advance in the subrecord field of the primary form. Submit that value to the new record via SIP parameter to the secondary form.

On the secondary form: for ‘new’ records choose the computed value, for existing records leave the value unchanged.

  • Master form, subrecord FormElement , field parameter : set

    detail=id:formId,{{SELECT '&', IFNULL(fe.ord,0)+10 FROM Form AS f LEFT JOIN *FormElement* AS fe ON fe.formId=f.id WHERE
    f.id={{r:S0}} ORDER BY fe.ord DESC LIMIT 1}}:ord
    
  • Slave form, ord FormElement , field value : set

    `{{ord:RS0}}`.
    

Version 2

Compute the next ‘ord’ as default value direct inside the secondary form. No change is needed for the primary form.

  • Secondary form, ord FormElement , field value : set {{SELECT IF({{ord:R0}}=0, MAX(IFNULL(fe.ord,0))+10,{{ord:R0}}) FROM (SELECT 1) AS a LEFT JOIN FormElement AS fe ON fe.formId={{formId:S0}} GROUP BY fe.formId}} .

Form: Person Wizard - firstname, city

Requirement: A form that displays the column ‘firstname’ from table ‘Person’ and ‘city’ from table ‘Address’. If the records not exist, the form should create it.

Form primary table: Person

Form slave table: Address

Relation: Person.id = Address.personId

  • Form: wizard
    • Name: wizard
    • Title: Person Wizard
    • Table: Person
    • Render: bootstrap
  • FormElement : firstname
    • Class: native
    • Type: text
    • Name: firstname
    • Label: Firstname
  • FormElement : email, text, 20
    • Class: native
    • Type: text
    • Name: city
    • Label: City
    • Value: {{SELECT city FROM Address WHERE personId={{r}} ORDER BY id LIMIT 1}}
  • FormElement : insert/update address record
    • Class: action
    • Type: afterSave
    • Label: Manage Address
    • Parameter:
      • slaveId={{SELECT id FROM Address WHERE personId={{r}} ORDER BY id LIMIT 1}}
      • sqlInsert={{INSERT INTO Address (personId, city) VALUES ({{r}}, ‘{{city:F:allbut:s}}’) }}
      • sqlUpdate={{UPDATE Address SET city=’{{city:F:allbut:s}}’ WHERE id={{slaveId:V}} }}
      • sqlDelete={{DELETE FROM Address WHERE id={{slaveId:V}} AND ‘’=’{{city:F:allbut:s}}’ LIMIT 1}}

Form: Person Wizard - firstname, single note

Requirement: A form that displays the column ‘firstname’ from table ‘Person’ and ‘note’ from table ‘Note’. If the records don’t exist, the form should create it. Column Person.noteId points to Note.id

Form primary table: Person

Form slave table: Address

Relation: Person.id = Address.personId

  • Form: wizard
    • Name: wizard
    • Title: Person Wizard
    • Table: Person
    • Render: bootstrap
  • FormElement : firstname
    • Class: native
    • Type: text
    • Name: firstname
    • Label: Firstname
  • FormElement : email, text, 20
    • Class: native
    • Type: text
    • Name: note
    • Label: Note
    • Value: {{SELECT Note FROM Note AS n, Person AS p WHERE p.id={{r}} AND p.noteId=n.id ORDER BY id }}
  • FormElement : insert/update address record
    • Class: action
    • Type: afterSave
    • Name: noteId
    • Label: Manage Note
    • Parameter:
      • sqlInsert={{INSERT INTO Note (note) VALUES (‘{{note:F:allbut:s}}’) }}
      • sqlUpdate={{UPDATE Note SET note=’{{note:F:allbut:s}}’ WHERE id={{slaveId:V}} }}

Icons Template Group

This example will display graphics instead of text ‘add’ and ‘remove’. Also there is a distance between the templateGroups.

  • FormElement.parameter:

    tgClass = qfq-child-margin-top
    tgAddClass = btn alert-success
    tgAddText = <span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
    tgRemoveClass = btn btn-danger alert-danger
    tgRemoveText = <span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
    

Chart

  • QFQ delivers a chart JavaScript lib: https://github.com/nnnick/Chart.js.git . Docs: http://www.chartjs.org/docs/

  • The library is not sourced in the HTML page automatically. To do it, either include the lib typo3conf/ext/qfq/Resources/Public/JavaScript/Chart.min.js :

    • in the specific tt_content record (shown below in the example) or
    • system wide via Typo3 Template record.
  • By splitting HTML and JavaScript code over several lines, take care not accidentally to create a ‘nesting’-end token. Check the line after 10.tail = . It’s ‘}’ alone on one line. This is a valid ‘nesting’-end token!. There are two options to circumvent this:

    • Don’t nest the HTML & JavaScript code - bad workaround, this is not human readable.

    • Select different nesting token, e.g. ‘<’ (check the first line on the following example).

      # <
      
      10.sql = SELECT '_'
      10.head =
        <div style="height: 1024px; width: 640px;">
          <h3>Distribution of FormElement types over all forms</h3>
          <canvas id="barchart" width="1240" height="640"></canvas>
        </div>
        <script src="typo3conf/ext/qfq/Resources/Public/JavaScript/Chart.min.js"></script>
        <script>
          $(function () {
            var ctx = document.getElementById("barchart");
            var barChart = new Chart(ctx, {
              type: 'bar',
                data: {
      
      10.tail =
                }
            });
          });
        </script>
      
      # Labels
      10.10 <
        sql = SELECT "'", fe.type, "'" FROM FormElement AS fe GROUP BY fe.type ORDER BY fe.type
        head = labels: [
        tail = ],
        rsep = ,
      >
      
      # Data
      10.20 <
        sql = SELECT COUNT(fe.id) FROM FormElement AS fe GROUP BY fe.type ORDER BY fe.type
        head = datasets: [ {   data: [
        tail = ],  backgroundColor: "steelblue", label: "FormElements" } ]
        rsep = ,
      >
      

Upload Form Simple

Table Person

Name Type
id int
name varchar(255)
pathFileNamePicture varchar(255)
pathFileNameAvatar varchar(255)
  • Form:

    • Name: UploadSimple
    • Table: Person
  • FormElements:

    • Name: name

      • Type: text
      • Label: Name
    • Name: pathFileNamePicture

      • Type: upload

      • Label: Picture

      • Parameter:

        fileDestination=fileadmin/user/{{id:R0}}-picture-{{filename}}
        
    • Name: pathFileNameAvatar

      • Type: upload

      • Label: Avatar

      • Parameter:

        fileDestination=fileadmin/user/{{id:R0}}-avatar-{{filename}}
        

Upload Form Advanced 1

Table: Person

Name Type
id int
name varchar(255)

Table: Note

Name Type
id int
pId int
type varchar(255)
pathFileName varchar(255)
  • Form:

    • Name: UploadAdvanced1
    • Table: Person
  • FormElements

    • Name: name

      • Type: text
      • Label: Name
    • Name: mypathFileNamePicture

      • Type: upload

      • Label: Picture

      • Value: {{SELECT pathFileName FROM Note WHERE id={{slaveId}} }}

      • Parameter:

        fileDestination=fileadmin/user/{{id:R0}}-picture-{{filename}}
        slaveId={{SELECT id FROM Note WHERE pId={{id:R0}} AND type='picture' LIMIT 1}}
        sqlInsert={{INSERT INTO Note (pathFileName, type, pId) VALUE ('{{fileDestination}}', 'picture', {{id:R0}}) }}
        sqlUpdate={{UPDATE Note SET pathFileName = '{{fileDestination}}' WHERE id={{slaveId}} LIMIT 1}}
        sqlDelete={{DELETE FROM Note WHERE id={{slaveId}}  LIMIT 1}}
        
    • Name: mypathFileNameAvatar

      • Type: upload

      • Label: Avatar

      • Value: {{SELECT pathFileName FROM Note WHERE id={{slaveId}} }}

      • Parameter:

        fileDestination=fileadmin/user/{{id:R0}}-avatar-{{filename}}
        slaveId={{SELECT id FROM Note WHERE pId={{id:R0}} AND type='avatar' LIMIT 1}}
        sqlInsert={{INSERT INTO Note (pathFileName, type, pId) VALUE ('{{fileDestination}}', 'avatar', {{id:R0}}) }}
        sqlUpdate={{UPDATE Note SET pathFileName = '{{fileDestination}}' WHERE id={{slaveId}} LIMIT 1}}
        sqlDelete={{DELETE FROM Note WHERE id={{slaveId}}  LIMIT 1}}
        

Upload Form Advanced 2

Table: Person

Name Type
id int
name varchar(255)
noteIdPicture int
noteIdAvatar int

Table: Note

Name Type
id int
pathFileName varchar(255)
  • Form:

    • Name: UploadAdvanced2
    • Table: Person
  • FormElements

    • Name: name

      • Type: text
      • Label: Name
    • Name: mypathFileNamePicture

      • Type: upload

      • Label: Picture

      • Value: {{SELECT pathFileName FROM Note WHERE id={{slaveId}} }}

      • Parameter:

        fileDestination=fileadmin/user/{{id:R0}}-picture-{{filename}}
        slaveId={{SELECT id FROM Note WHERE id={{noteIdPicture}} LIMIT 1}}
        sqlInsert={{INSERT INTO Note (pathFileName) VALUE ('{{fileDestination}}') }}
        sqlUpdate={{UPDATE Note SET pathFileName = '{{fileDestination}}' WHERE id={{slaveId}} LIMIT 1}}
        sqlDelete={{DELETE FROM Note WHERE id={{slaveId}}  LIMIT 1}}
        sqlAfter={{UPDATE Person SET noteIdPicture={{slaveId}} WHERE id={{id:R0}} LIMIT 1
        
    • Name: mypathFileNameAvatar

      • Type: upload

      • Label: Avatar

      • Value: {{SELECT pathFileName FROM Note WHERE id={{slaveId}} }}

      • Parameter:

        fileDestination=fileadmin/user/{{id:R0}}-avatar-{{filename}}
        slaveId={{SELECT id FROM Note WHERE id={{noteIdAvatar}} LIMIT 1}}
        sqlInsert={{INSERT INTO Note (pathFileName) VALUE ('{{fileDestination}}') }}
        sqlUpdate={{UPDATE Note SET pathFileName = '{{fileDestination}}' WHERE id={{slaveId}} LIMIT 1}}
        sqlDelete={{DELETE FROM Note WHERE id={{slaveId}}  LIMIT 1}}
        sqlAfter={{UPDATE Person SET noteIdAvatar={{slaveId}} WHERE id={{id:R0}} LIMIT 1
        

Typeahead: SQL

Table: Person

Name Type
id int
name varchar(255)
  • Form:
    • Name: PersonNameTypeahead
    • Table: Person
  • FormElements
    • Name: name
      • Type: text
      • Label: Name
      • Parameter: typeAheadSql = SELECT name FROM Person WHERE name LIKE ? OR firstName LIKE ? LIMIT 100

Typeahead: LDAP with additional values

Table: Person

Name Type
id int
name varchar(255)
firstname varchar(255)
email varchar(255)
  • Form:

    • Name: PersonNameTypeaheadSetNames

    • Table: Person

    • Parameter:

      ldapServer = directory.example.com
      ldapBaseDn = ou=Addressbook,dc=example,dc=com
      
  • FormElements

    • Name: email

      • Class: native

      • Type: text

      • Label: Email

      • Note: Name: {{cn:LE}}<br>Email: {{mail:LE}}

      • dynamicUpdate: checked

      • Parameter:

        # Typeahead
        typeAheadLdapSearch = (|(cn=*?*)(mail=*?*))
        typeAheadLdapValuePrintf %s / %s’, cn, email
        typeAheadLdapIdPrintf  %s’, email
        
        # dynamicUpdate: show note
        fillStoreLdap
        ldapSearch = (mail={{email::alnumx}})
        ldapAttributes = cn, email
        
    • Name: fillLdapValues

      • Class: action

      • Type: afterSave

      • Parameter:

        fillStoreLdap
        ldapSearch = (mail={{email::alnumx}})
        ldapAttributes = cn, email
        
        slaveId={{id:R0}}
        sqlUpdate={{ UPDATE Person AS p SET p.name='{{cn:L:alnumx:s}}' WHERE p.id={{slaveId}} LIMIT 1 }}
        

Import/merge form

The form copyFormFromExt copies a form from table ExtForm / ExtFormElement to Form / FormElement . The import/merge form:

  • offers a drop down list with all forms of ExtForm ,
  • an input element for the new form name,
  • create new Form.id
  • copied FormElements get the new Form.id.
  • the copied form will be opened in the FormEditor.

Installation:

  • Play (do all sql statements on your QFQ database, e.g. via mysql <dbname> < copyFormFromExt.sql or phpMyAdmin ) the file <ext_dir>/Classes/Sql/copyFormFromExt.sql .

  • Insert a link/button ‘Copy form from ExtForm’ to open the import/merge form. A good place is the list of all forms (see form-editor ). E.g.:

    10.head = {{'b|p:id={{pageAlias:T}}&form=copyFormFromExt|t:Copy form from ExtForm' AS _link }} ...
    

If there are several T3/QFQ instances and if forms should be imported frequently/easily, set up a one shot ‘import Forms from db xyz’ like:

10.sql = CREATE OR REPLACE table ExtForm SELECT * FROM <db xyz>.Form
20.sql = CREATE OR REPLACE table ExtFormElement SELECT * FROM <db xyz>.FormElement

Report

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:

10.sql = SELECT id AS pId, CONCAT(firstName, " ", lastName, " ") AS name FROM person

The ‘10’ indicates a root level of the report (see section Structure ). The expresssion ‘10.sql’ defines a 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:

John DoeJane MillerFrank Star

I.e., QFQ will simply output the content of the SQL result row after row for each single level.

However, we can modify (wrap) the output by setting the values of various keys for each level: 10.rsep=<br/> for example tells QFQ to separate the rows of the result by a HTML-line break. The final result in this case is:

10.sql = SELECT id AS personId, CONCAT(firstName, " ", lastName, " ") AS name FROM person
10.rsep = <br>

HTML output:

John Doe<br>Jane Miller<br>Frank Star

Syntax of report

All root level queries will be fired in the order specified by ‘level’ (Integer value).

For each row of a query (this means all queries), all subqueries will be fired once.

  • E.g. if the outer query selects 5 rows, and a nested query select 3 rows, than the total number of rows are 5 x 3 = 15 rows.

There is a set of variables that will get replaced before (‘count’ also after) the SQL-Query gets executed:

{{<name>[:<store/s>[:...]]}}
Variables from specific stores.

{{<name>:R}} - use case of the above generic definition. See also access-column-values .

{{<level>.<columnName>}}
Similar to {{<name>:R}} but more specific. There is no sanitize class, escape mode or default value.
{{<level>.line.count}} - Current row index
This variable is specific, as it will be replaced before the query is fired in case of <level> is an outer/previous level or it will be replaced after a query is fired in case <level> is the current level.
{{<level>.line.total}}
Total rows (MySQL num_rows for SELECT and SHOW , MySQL affected_rows for UPDATE and INSERT .
{{<level>.line.insertId}}
Last insert id for INSERT .
{{<level>.line.content}}
If the content of <level> have been stored, e.g. <level>.content=hide .

{{<level>.line.altCount}} - Like ‘line.count’ but for ‘alt’ query.

{{<level>.line.altTotal}} - Like ‘line.total’ but for ‘alt’ query.

{{<level>.line.altInsertId}} - Like ‘line.insertId’ but for ‘alt’ query.

See Variable for a full list of all available variables.

Different types of SQL queries are possible: SELECT, INSERT, UPDATE, DELETE, SHOW, REPLACE

Only SELECT and SHOW queries will fire subqueries.

Processing of the resulting rows and columns:

  • In general, all columns of all rows will be 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

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 .

Inline Report editing

For quick changes it can be bothersome to go to the TYPO3 backend to update the page content and reload the page. For this reason, QFQ offers an inline report editing feature whenever there is a TYPO3 BE user logged in. A small link symbol will appear on the right-hand side of each report record. Please note that the TYPO3 Frontend cache is also deleted upon each inline report save.

In order for the inline report editing to work, QFQ needs to be able to access the T3 database. This database is assumed to be accessible with the same credentials as specified with indexQfq.

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:

10.sql = SELECT id AS _pId, CONCAT(firstName, " ", lastName, " ") AS name FROM person
10.rsep = <br>

10.10.sql = SELECT CONCAT(postal_code, " ", city) FROM address WHERE pId = {{10.pId}}
10.10.rbeg = (
10.10.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:

  • <level>.<keyword> =
  • {
  • <level>[.<sub level] {

Example:

10.sql = SELECT 'hello world'
           FROM mastertable
10.tail = End

20.sql = SELECT 'a warm welcome'
           'some additional', 'columns'
           FROM another_table
           WHERE id>100

20.head = <h3>
20.tail = </h3>

Join mode: SQL

This is the default. All lines are joined with a space in between. E.g.:

10.sql = SELECT 'hello world'
           FROM mastertable

Results to: 10.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.:

10.sql = SELECT 'hello world', 'd:final.pdf \
                                |p:id=export  \
                                |t:Download' AS _pdf \

Results to: 10.sql = SELECT 'hello world', 'd:final.pdf|p:id=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:

10.sql = SELECT 'hello world', CONCAT('d:final.pdf'
                                '|p:id=export',
                                '|t:Download') AS _pdf

Nesting of levels

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:

10.sql = SELECT name FROM Person
10.rsep = ' '
10.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. >
10 <
  sql = SELECT
  head = <script>
         data = [
           {
             10, 20
           }
         ]
         </script>
>

Per QFQ tt-content record, only one type of nesting braces can be used.

Be careful to:

  • write nothing else than whitespaces/newline behind an open brace

  • the closing brace has to be alone on a line:

    10.sql = SELECT 'Yearly Report'
    
    20 {
          sql = SELECT companyName FORM Company LIMIT 1
          head = <h1>
          tail = </h1>
    }
    
    30 {
          sql = SELECT depName FROM Department
          head = <p>
          tail = </p>
          5 {
                sql = SELECT 'detailed information for department'
                1.sql = SELECT name FROM Person LIMIT 7
                1.head = Employees:
          }
    }
    
    30.5.tail = More will follow
    
    50
    
    {
           sql = SELECT 'A query with braces on their own'
    }
    

Access column values

Columns of the upper / outer level result can be accessed via variables in two ways

  • STORE_RECORD: {{pId:R}}
  • Level Key: {{10.pId}}

The STORE_RECORD will always be merged with previous content. The Level Keys are unique.

Multiple columns, with the same column name, can’t be accessed individually. Only the last column is available.

Retrieving the final value of special-column-names is possible via ‘{{&<column>:R}}. Example:

10.sql = SELECT 'p:home&form=Person|s|b:success|t:Edit' AS _link
10.20.sql = SELECT '{{link:R}}', '{{&link:R}}'

The first column of row 10.20 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:

10.sql= SELECT p.id AS _pId, p.name FROM Person AS p
10.5.sql = SELECT adr.city, 'dummy' AS _pId FROM Address AS adr WHERE adr.pId={{pId:R}}
10.5.20.sql = SELECT '{{pId:R}}'
10.10.sql = SELECT '{{pId:R}}'

The line ‘10.10’ 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 ‘level’:

10.sql= SELECT p.id AS _pId, p.name FROM Person AS p
10.5.sql = SELECT adr.city, 'dummy' AS _pId FROM Address AS adr WHERE adr.pId={{10.pId}}
10.5.20.sql = SELECT '{{10.pId}}'
10.10.sql = SELECT '{{10.pId}}'

Notes to the level:

Levels A report is divided into levels. The Example has levels 10 , 20 , 30 , 30.5 , 30.5.1 , 50
Qualifier A level is divided into qualifiers 30.5.1 has 3 qualifiers 30 , 5 , 1
Root levels Is a level with one qualifier. E.g.: 10
Sub levels Is a level with more than one qualifier. E.g. levels 30.5 or 30.5.1
Child The level 30 has one child and child child: 30.5 and 30.5.1
Example 10 , 20 , 30 , 50* are root level and will be completely processed one after each other. 30.5 will be executed as many times as 30 has row numbers. 30.5.1 will be executed as many times as 30.5 has row numbers.

Report Example 1:

# Displays current date
10.sql = SELECT CURDATE()

# Show all students from the person table
20.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
20.25.sql = SELECT e.mark FROM exam AS e WHERE e.pId={{20.pId}} ORDER BY e.date

# This query will never be fired, cause there is no direct parent called 20.30.
20.30.10.sql = SELECT 'never fired'

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 .
  • A key sql is necessary in order to process a level.

See all QFQ Keywords (Bodytext) .

Processing of columns in the SQL result

  • The content of all columns of all rows will be printed sequentially, without separator (except one is defined).
  • Rows with special-column-names will be rendered internally by QFQ and the QFQ output of such processing (if there is any) is taken as the content.

Special column names

QFQ typically don’t care about the content of any SQL-Query - it just copy the content to the output (=Browser).

One exception are columns, whose name starts with ‘_’. E.g.:

10.sql = SELECT 'All', 'cats' AS red, 'are' AS _green, 'grey in the night' AS _link
  • The first and second column are regular columns. No QFQ processing.
  • The third column (alias name ‘green’) is no QFQ special column name, but has an ‘_’ at the beginning: this column content will be hidden.
  • The fourth column (alias name ‘link’) uses a QFQ special column name. Here, only in this example, it has no further meaning.

Summary:

  • Special column names always start with ‘_’.
  • Columns starting with a ‘_’ but not defined as as QFQ special column name are hidden(!) - in other words: they are not printed as output.
  • SQL hint: If there is no column alias defined, the column name becomes the value of the first row. E.g. if the first selected row contains a ‘_’ as the first character, 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.
  • Important: to reference a column, the name has to be given without ‘_’. E.g. SELECT ‘cat’ AS _pet will be used with {{pet:R}} (notice the missing ‘_’).
  • For ‘special column names’: the column name together with the value controls how QFQ processes the column.
Reserved column name Purpose
_link column-link - Build links with different features.
_pageX or _PageX column_pageX - 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 - 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-save-pdf - pre render PDF files
_excel excel-export - creates Excel exports based on QFQ Report queries, optional with pre uploaded Excel template files
_yank copyToClipboard . 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.
_vertical column_vertical - Render Text vertically. This is useful for tables with limited column width.
_img column_img - Display images.
_bullet Display a blue/gray/green/pink/red/yellow bullet. If none color specified, show nothing.
_check Display a blue/gray/green/pink/red/yellow checked sign. If none color specified, show nothing.
_nl2br All newline characters will be converted to <br> .
_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 .
_+<html-tag attributes> The content will be wrapped with ‘<html-tag attributes>’. Example: SELECT ‘example’ AS ‘_+a href=” http://example.com ”’ creates ‘<a href=” http://example.com ”>example</a>’
_=<varname> The content will be saved in store ‘user’ under ‘varname’. Retrieve it later via {{varname:U}}. Example: ‘SELECT “Hello world” AS _=text’. See STORE_USER , store_user_examples
_<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.

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
no link, only text or image, incl. Optional tooltip. For Bootstrap buttons
r:3 will set the button to disable and no link/sip is rendered.
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

Example:

10.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.

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 success, info, warning, danger
Positive button text Default: ‘Ok’
Negative button text Default: ‘Cancel’. To hide the second button: ‘-‘
Timeout in seconds 0: no timeout, >0: after the specified time in seconds, the alert will dissapear and behaves like ‘negative answer’
Flag modal 0: Alert behaves not modal. 1: (default) Alert behaves modal.

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:No way” AS _link Instead of ‘Ok’ and ‘Cancel’, the button text will be ‘I do’ and ‘No way’
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.

Columns: _page[X]

The colum name is composed of the string page and a trailing character to specify the type of the link.

Syntax :

10.sql = SELECT "[options]" AS _page[<link type>]

with: [options] = [p:<page & param>][|t:<text>][|o:<tooltip>][|q:<question parameter>][|c:<class>][|g:<target>][|r:<render mode>]

<link type> = c,d,e,h,i,n,s
column name Purpose default value of question parameter Mandatory parameters
_page Internal link without a grafic empty p:<pageId/pageAlias>[&param]
_pagec Internal link without a grafic, with question Please confirm! p:<pageId/pageAlias>[&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:<pageId/pageAlias>[&param]
_pageh Internal link with help icon (question mark) empty p:<pageId/pageAlias>[&param]
_pagei Internal link with information icon (i) empty p:<pageId/pageAlias>[&param]
_pagen Internal link with new icon (sheet) empty p:<pageId/pageAlias>[&param]
_pages Internal link with show icon (magnifier) empty p:<pageId/pageAlias>[&param]
  • All parameter are optional.
  • Optional set of predefined icons.
  • Optional set of dialog boxes.
Parameter Description Default value Example
<page> TYPO3 page id or page alias. The current page: {{pageId}} 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 Expected “=” to follow “see”
<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

10.sql = SELECT "U:table=<tablename>&r=<record id>|q:<question>|..." AS _paged
10.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 colummnnames or tablenames.

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

10.sql = SELECT 'U:table=Person&r=123|q:Do you want delete John Doe?' AS _paged
10.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!
"[<page id|alias>[&param=value&...]] | [text] | [tooltip] | [question parameter] | [class] | [target] | [render mode]" as _Pagee.

Column: _Paged

  • Similar to _paged
  • Parameter are position dependent and therefore without a qualifier!
"[table=<table name>&r-<record id>[&param=value&...] | [text] | [tooltip] | [question parameter] | [class] | [render mode]" as _Paged.
"[form=<form name>&r-<record id>[&param=value&...] | [text] | [tooltip] | [question parameter] | [class] | [render mode]" as _Paged.

Column: _vertical

Render text vertically. This is useful for tables with limited column width. The vertical rendering is achieved via CSS tranformations (rotation) defined in the style attribute of the wrapping tag. You can optionally specify the rotation angle.

Syntax

10.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

10.sql = SELECT "Hallo" AS _vertical
20.sql = SELECT "Hallo|90" AS _vertical
20.sql = SELECT "Hallo|-75" AS _vertical

Column: _mailto

Easily create Email links.

Syntax

10.sql = SELECT "<email address>|[<link text>]" AS _mailto
Parameter Description Default value
<emailaddress> The email address where the link should point to. none
<linktext> The text that should be displayed on the website and be linked to the email address. This will typically be the name of the recipient. If this parameter is omitted, the email address will be displayed as link text. none

Minimal Example

10.sql = SELECT "john.doe@example.com" AS _mailto

Advanced Example

10.sql = SELECT "john.doe@example.com|John Doe" AS _mailto

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

10.sql = SELECT "t:john@doe.com|f:jane@doe.com|s:Reminder tomorrow|b:Please dont miss the meeting tomorrow" AS _sendmail
10.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
Token
short / long
Parameter Description Required
f
from
email FROM : Sender of the email. Optional: ‘realname < john @ doe . com >’ yes
t
to
email[,email] TO : Comma separated list of receiver email addresses. Optional: realname <john@doe.com> yes
c
cc
email[,email] CC : Comma separated list of receiver email addresses. Optional: ‘realname < john @ doe . com >’ yes
B
bcc
email[,email] BCC : Comma separated list of receiver email addresses. Optional: ‘realname < john @ doe . com >’ 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
Attachment : All following (until the next ‘C’) ‘F|p|u’ concatenated to one attachment.
Repeatable.
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=none and/or E=none .

Minimal Example

10.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

10.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 .

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 F:fileadmin/file3.pdf Single file to attach
u u:www.example.com/index.html?key=value&… A URL, will be converted to a PDF and than attached.
p p:?id=export&r=123&_sip=1 A SIP protected local T3 page. Will be converted to a PDF and than attached.
d d:myfile.pdf Name of the attachment in the email.
C C|u: http://www.example .com|F:file1.pdf|C|F:file2.pdf Concatenate all named sources to one PDF file. The souces has 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.
10.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.
10.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.
10.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.
10.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.
10.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.
10.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

10.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

10.sql = SELECT "fileadmin/img/img.jpg" AS _img

Advanced Examples

10.sql = SELECT "fileadmin/img/img.jpg|Aternative Text" AS _img            # alt="Alternative Text, no title
20.sql = SELECT "fileadmin/img/img.jpg|Aternative Text|" AS _img           # alt="Alternative Text, no title
30.sql = SELECT "fileadmin/img/img.jpg|Aternative Text|Title Text" AS _img # alt="Alternative Text, title="Title Text"
40.sql = SELECT "fileadmin/img/img.jpg|Alternative Text" AS _img           # alt="Alternative Text", no title
50.sql = SELECT "fileadmin/img/img.jpg" AS _img                            # empty alt, no title
60.sql = SELECT "fileadmin/img/img.jpg|" AS _img                           # empty alt, no title
70.sql = SELECT "fileadmin/img/img.jpg||Title Text" AS _img                # empty alt, title="Title Text"
80.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 true to fake rc of 0 (only the last rc will be reported):

    SELECT 'touch /root; true' AS _exec
    

Syntax

<command>
Parameter Description Default value
<command> The command that should be executed on the server. none

Minimal Examples

10.sql = SELECT "ls -s" AS _exec
20.sql = SELECT "./batchfile.sh" AS _exec

Column: _pdf | _file | _zip

Detailed explanation: download

Most of the other Link-Class attributes can be used to customize the link.

10.sql = SELECT "[options]" AS _pdf, "[options]" AS _file, "[options]" AS _zip

with: [options] = [d:<exportFilename][|p:<params>][|U:<params>][|u:<url>][|F:file][|t:<text>][|a:<message>][|o:<tooltip>][|c:<class>][|r:<render mode>]
  • Parameter are position independent.

  • <params> : see download-parameter-files

  • For column _pdf and _zip , the element sources p:… , U:… , u:… , F:… might repeated multiple times.

  • Example:

    10.sql = SELECT "F:fileadmin/test.pdf" as _pdf,  "F:fileadmin/test.pdf" as _file,  "F:fileadmin/test.pdf" as _zip
    10.sql = SELECT "p:id=export&r=1" as _pdf,  "p:id=export&r=1" as _file,  "p:id=export&r=1" as _zip
    
    10.sql = SELECT "t:Download PDF|F:fileadmin/test.pdf" as _pdf,  "t:Download PDF|F:fileadmin/test.pdf" as _file,  "t:Download ZIP|F:fileadmin/test.pdf" as _zip
    10.sql = SELECT "t:Download PDF|p:id=export&r=1" as _pdf,  "t:Download PDF|p:id=export&r=1" as _file,  "t:Download ZIP|p:id=export&r=1" as _zip
    
    10.sql = SELECT "d:complete.pdf|t:Download PDF|F:fileadmin/test1.pdf|F:fileadmin/test2.pdf" as _pdf, "d:complete.zip|t:Download ZIP|F:fileadmin/test1.pdf|F:fileadmin/test2.pdf" as _zip
    
    10.sql = SELECT "d:complete.pdf|t:Download PDF|F:fileadmin/test.pdf|p:id=export&r=1|u:www.example.com" AS _pdf
    

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 download-parameter-files ), including possible wkhtmltopdf parameters
  • The export filename and path as d: - for security reasons, this path has to start with fileadmin/

Tips:

  • 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 overwriten. To save individual files, choose a new filename, for example by adding a timestamp.

Example:

SELECT "d:fileadmin/result.pdf|F:fileadmin/_temp_/test.pdf" AS _savePdf
SELECT "d:fileadmin/result.pdf|F:fileadmin/_temp_/test.pdf|U:id=test&--orientation=landscape" AS _savePdf

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 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 T:fileadmin/file3.pdf File render a thumbnail
W W:200x, W:x100, W:200x100 Dimension of the thumbnail: ‘<width>x<height>. Both parameter are otional. If non is given the default is W:150x
s s:1, s:0 Optional. Default: s:1 . If SIP is enabled, the rendered URL is a link via api/download.php?.. . Else a direct pathFileName.
r r:7 Render Mode. Default ‘r:0’. With ‘r:7’ only the url will be delivered.

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
10.sql = SELECT 'T:fileadmin/file3.pdf' AS _thumbnail

# SIP protected, IMG tag, thumbnail width 50px
20.sql = SELECT 'T:fileadmin/file3.pdf|W:50' AS _thumbnail

# No SIP protection, IMG tag, thumbnail width 150px
30.sql = SELECT 'T:fileadmin/file3.pdf|s:0' AS _thumbnail

# SIP protected, only the URL to the image, thumbnail width 150px
40.sql = SELECT 'T:fileadmin/file3.pdf|s:1|r:7' AS _thumbnail

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 asynchonous 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

10.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

Copy to clipboard

Token Example Comment
y[:<content>] y, y:some content Initiates ‘copy to clipboard’ mode. Source might given text or page or url
F:<pathFileName> F:fileadmin/protected/data.R pathFileName in DocumentRoot

Example:

10.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

Special SQL Functions

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:

10.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() .

QMORE: Truncate Long Text - more/less

The SQL function QMORE(text, n) truncates text if it is longer than n characters and adds a “more..” button. If the “more…” button is clicked, the whole text is displayed. The stored procedure QMORE() will inject some HTML/CSS code.

Example:

10.sql = SELECT QMORE("This is a text which is longer than 10 characters", 10)

Output:

This is a more..

Download

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.

The downloads are SIP protected. Only the current user can use the link to download files.

By using the _link column name:

  • the option d:… initiate creating the download link and optional specifies an export filename,
  • the optional M:… (Mode) specifies the export type (file, pdf, zip, export),
  • setting s:1 is recommended for the download function (file / path name is hidden to the user),
  • 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 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.

In case the download needs a persistant URL (no SIP, no user session), a regular link, pointing directly to a file, have to be used - the download functionality described here is not appropriate for such a scenario. If necessary, column-save-pdf can be used to generate such a file.

Parameter and (element) sources

  • download : d[:<exportFilename>]

    • exportFilename = <filename for save as> - Name, offered in the ‘File save as’ browser dialog. Default: ‘output.<ext>’.

      If there is no exportFilename defined, 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.

  • 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 | zip | excel>

      • 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:zip is used together with p:… , U:… or u:.. , 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 or M: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:<tt-content record id> - the tt_content.uid of a QFQ PageContent record (shown on hover in the backend). This will render only the specified QFQ content record, 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. Parameters can be passed: uid:<tt-content record id>[?arg1=value1][&arg2=value2][…] and will be available in the SIP store for the QFQ PageContent, or passed as wkhtmltopdf arguments, if applicable.

    • 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:… or U:… 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 in p:… , u:… or U:… has to be separated by ‘=’. Please see last example below.
      • If an option contains an ‘&’ it must be escaped with double \ . See example.

    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

# 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 pagesize 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}}?indexp.php?id=head\\&L=1|F:fileadmin/pdf/test.pdf" AS _link

Example _pdf , _zip :

# File 1: p:id=1&--orientation=Landscape&--page-size=A3
# File 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

# File 1: p:id=1
# File 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

# File 1: p:id=1
# File 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.

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:

  1. 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]
    
  2. Create a T3 body page (e.g. page alias: ‘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>
    
  3. Create a T3 letter- header page (e.g. page alias: ‘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>
    
  4. 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&--margin-bottom=20mm&',
                             '--header-html={{BASE_URL_PRINT:Y}}?id=letterheader&',
                             '--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:

10.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.

Hint: For up/downloading of excel files (without modification), check the generic Form input-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 _link
    
  • Create 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: