Skip to content
Snippets Groups Projects
Manual.rst 499 KiB
Newer Older
A page will be called with several SIP variables, but not at all at the same time. To still get all variables at any time::
    10.sql = SELECT '{{order:USE:::sum}}' AS '_=order', '{{step:USE:::5}}' AS _step, '{{direction:USE:::ASC}}' AS _direction

    # Different links
    20.sql = SELECT 'p:{{pageAlias:T}}&order=count|t:Order by count|b|s' AS _link,
                    'p:{{pageAlias:T}}&order=sum|t:Order by sum|b|s' AS _link,
                    'p:{{pageAlias:T}}&step=10|t:Step=10|b|s' AS _link,
                    'p:{{pageAlias:T}}&step=50|t:Step=50|b|s' AS _link,
                    'p:{{pageAlias:T}}&direction=ASC|t:Order by up|b|s' AS _link,
                    'p:{{pageAlias:T}}&direction=DESC|t:Order by down|b|s' AS _link

    30.sql = SELECT * FROM items ORDER BY {{order:U}} {{direction:U}} LIMIT {{step:U}}

Simulate/switch user: feUser

Just set the STORE_USER variable 'feUser'.

All places with `{{feUser:T}}` has to be replaced by `{{feUser:UT}}`::
    10.sql = SELECT '{{feUser:UT}}' AS '_=feUser'

    # Offer switching feUser
    20.sql = SELECT 'p:{{pageAlias:T}}&feUser=account1|t:Become "account1"|b|s' AS _link,
                    'p:{{pageAlias:T}}&feUser={{feUser:T}}|t:Back to own identity|b|s' AS _link,


Semester switch (remember last choice)
A current semester is defined via configuration in STORE_SYSTEM '{{semId:Y}}'. The first column in 10.sql
`'{{semId:SUY}}' AS '_=semId'` saves
the semester to STORE_USER via '_=semId'. The priority 'SUY' takes either the latest choose (STORE_SIP) or reuse the
last used (STORE_USER) or (first time call during browser session) takes the default from config (STORE_SYSTEM)::

    # Semester switch
    10 {
      sql = SELECT '{{semId:SUY}}' AS '_=semId',
                   CONCAT('p:{{pageAlias:T}}&semId=', sp.id, '|t:', QBAR(sp.name), '|s|b|G:glyphicon-chevron-left') AS _link,
                           ' <button class="btn disabled ',   IF({{semId:Y0}}=sc.id, 'btn-success', 'btn-default'), '">',sc.name, '</button> ',
                           CONCAT('p:{{pageAlias:T}}&semId=', sn.id, '|t:', QBAR(sn.name), '|s|b|G:glyphicon-chevron-right|R') AS _link
                       FROM semester AS sc
                       LEFT JOIN semester AS sp ON sp.id=sc.id-1
                       LEFT JOIN semester AS sn ON sc.id+1=sn.id AND sn.show_semester_from<=CURDATE()
                       WHERE sc.id={{semId:SUY}} AND '{{form:SE}}'=''
                       ORDER BY sc.semester_von
      head = <div class="btn-group" style="position: absolute; top: 15px; right: 25px;">
      tail = </div><p></p>
    }

Carsten  Rose's avatar
Carsten Rose committed
.. _`restApi`:
Carsten  Rose's avatar
Carsten Rose committed
Via `REST <https://en.wikipedia.org/wiki/Representational_state_transfer>`_ it's possible to access the QFQ based
application. Each REST API endpoint has to be defined as a QFQ Form. The QFQ REST api implements the
four most used REST HTTP methods:

GET - Read
    Shows a list of database records or a single record. The QFQ form holds the definition which and what to show.

    List: ``curl -X GET "http://localhost/qfq/typo3conf/ext/qfq/Classes/Api/rest.php/person/``
    Data (id=123): ``curl -X GET "http://localhost/qfq/typo3conf/ext/qfq/Classes/Api/rest.php/person/123``

POST - Create new record
    The QFQ form defines wich columns will be written in which table. Most of QFQ Form functionality can be used. Example:

    ``curl -X POST "http://localhost/qfq/typo3conf/ext/qfq/Classes/Api/rest.php/person/" -d '{"name":"Miller","firstname":"Joe"}'``

PUT - Update a record
    Similar to POST, but a given record will be updated.

    ``curl -X PUT "http://localhost/qfq/typo3conf/ext/qfq/Classes/Api/rest.php/person/123" -d '{"name":"Miller","firstname":"Joe"}'``

DELETE - Delete a record
    Similar to a QFQ Delete form.

    ``curl -X DELETE "http://localhost/qfq/typo3conf/ext/qfq/Classes/Api/rest.php/person/123"``
All data will imported / exported in JSON notation.
Any QFQ form becomes a REST form via:  ``Form > Access > Permit REST: get / insert / update / delete``
    The basic REST API endpoint: ``<domain>/typo3conf/ext/qfq/Classes/Api/rest.php``
    ``<domain>/typo3conf/ext/qfq/Classes/Api/rest.php/<level1>/<id1>/<level2>/<id2>/.../?<var1>=<value1>&...``


Append level names and ids after `.../rest.php/`, each separated by '/' .

E.g.:

1. List of all persons: `<domain>/typo3conf/ext/qfq/Classes/Api/rest.php/person`
2. Data of person 123: `<domain>/typo3conf/ext/qfq/Classes/Api/rest.php/person/123`
3. Adresses of person 123: `<domain>/typo3conf/ext/qfq/Classes/Api/rest.php/person/123/address`
4. Adress details of address 45 from person 123: `<domain>/typo3conf/ext/qfq/Classes/Api/rest.php/person/123/address/45`

QFQ 'Forms' are used as a 'container' (to define all details).

.. tip::

    The QFQ ``form name`` represents  the level name.

Only the last <level> of an URI will be processed. The former ones are just to fulfil a good looking REST API.

.. note::

    Each level name (=form name) is available via STORE_CLIENT and name `_formX`. E.g. in example
    (1) `{{_form1:C:alnumx}}=person` and `{{_form2:C:alnumx}}=address`.

    Each level id is available via STORE_CLIENT and name `_idX`. E.g. in example
    (2) `{{_id1:C}}=123` and `{{_id2:C}}=45`.

    Also the `id` after the last `level` in the URI path, 123 in example (2) and 45 in example (4), is copied to
    variable `r` in STORE_TYPO3, access it via `{{r:T}}`.


GET - Read
Carsten  Rose's avatar
Carsten Rose committed
A REST (GET) form has two modes:

data
    Specific content to a given id. Defined via 'form.parameter.restSqlData'. This mode is selected if there is an
    id>0 given.

list
    A list of records will be exported.  Defined via 'form.parameter.restSqlList'. This mode is selected if there is no
    id or id=0.

Carsten  Rose's avatar
Carsten Rose committed
.. note::

    There are  *no* native-FormElements necessary or loaded. Action FormElements will be processed.

To simplify access to id parameter of the URI, a mapping is possible via 'form.parameter.restParam'.
E.g. `restParam=pId,adrId` with example d) makes `{{pId:C}}=123` and `{{adrId:C}}=45`. The order of variable
names corresponds to the position in the URI. `_id1` is always mapped to the first parameter name, `_id2` to
the second one and so on.

GET Variables provided via URL are available via STORE_CLIENT as usual.


+-------------------+------------------------------------------------------------------------------+
| Attribute         | Description                                                                  |
+===================+==============================================================================+
| name              | *<level>* Mandatory. Level name (Endpoint) in URI.                           |
+-------------------+------------------------------------------------------------------------------+
| table             | Mandatory. Name of the primary table                                         |
+-------------------+------------------------------------------------------------------------------+
| Permit REST       | *get* Mandatory. The form can be loaded in REST mode.                        |
+-------------------+------------------------------------------------------------------------------+


+-------------------+----------------------------------------------------------------------------------+
| Attribute         | Description                                                                      |
+===================+==================================================================================+
| restSqlData       | Mandatory. SQL query selects content shown in data mode.                         |
|                   | | ``restSqlData={{!SELECT id, name, gender FROM Person WHERE id='{{r:T0}}'' }}`` |
+-------------------+----------------------------------------------------------------------------------+
| restSqlList       | Mandatory. SQL query selects content shown in data mode.                         |
|                   | | ``restSqlData={{!SELECT id, name FROM Person }}``                              |
+-------------------+----------------------------------------------------------------------------------+
| restParam         | Optional. CSV list of variable names. E.g.: ``restParam=pId,adrId``              |
+-------------------+----------------------------------------------------------------------------------+
| restToken         | Optional. User defined string or dynamic token (see `restAuthorization`_).       |
+-------------------+----------------------------------------------------------------------------------+

Carsten  Rose's avatar
Carsten Rose committed
.. note::

    There are no `special-column-names`_ available in `restSqlData` or `restSqlList`. Also there are no
    SIPs possible, cause REST typically does not offer sessions/cookies (which are necessary for SIPs).


Carsten  Rose's avatar
Carsten Rose committed
.. important::

    If there is an ``ìd`` given, a record in the named primary with the specified table has to exist.
    If not, an error is thrown.

+-------------------+------------------------------------------------------------------------------+
| Attribute         | Description                                                                  |
+===================+==============================================================================+
| name              | *<level>* Mandatory. Level name (Endpoint) in URI.                           |
+-------------------+------------------------------------------------------------------------------+
| table             | Mandatory. Name of the primary table                                         |
+-------------------+------------------------------------------------------------------------------+
| Permit REST       | *insert* Mandatory. The form can be loaded in REST mode.                     |
+-------------------+------------------------------------------------------------------------------+
| id                | Missing or '0'.                                                              |
+-------------------+------------------------------------------------------------------------------+


+-------------------+----------------------------------------------------------------------------------+
| Attribute         | Description                                                                      |
+===================+==================================================================================+
| restParam         | Optional. CSV list of variable names. E.g.: ``restParam=pId,adrId``              |
+-------------------+----------------------------------------------------------------------------------+
| restToken         | Optional. User defined string or dynamic token (see `restAuthorization`_).       |
+-------------------+----------------------------------------------------------------------------------+

FormElement:

* For each column to save one FormElement with ``FE.name=<column>`` is necessary.
* A regular QFQ form can be used as REST Post endpoint


+-------------------+------------------------------------------------------------------------------+
| Attribute         | Description                                                                  |
+===================+==============================================================================+
| name              | *<level>* Mandatory. Level name (Endpoint) in URI.                           |
+-------------------+------------------------------------------------------------------------------+
| table             | Mandatory. Name of the primary table                                         |
+-------------------+------------------------------------------------------------------------------+
| Permit REST       | *update* Mandatory. The form can be loaded in REST mode.                     |
+-------------------+------------------------------------------------------------------------------+
| id                | >0                                                                           |
+-------------------+------------------------------------------------------------------------------+


+-------------------+----------------------------------------------------------------------------------+
| Attribute         | Description                                                                      |
+===================+==================================================================================+
| restParam         | Optional. CSV list of variable names. E.g.: ``restParam=pId,adrId``              |
+-------------------+----------------------------------------------------------------------------------+
| restToken         | Optional. User defined string or dynamic token (see `restAuthorization`_).       |
+-------------------+----------------------------------------------------------------------------------+

FormElement:

* For each column to save one FormElement with ``FE.name=<column>`` is necessary.
* A regular QFQ form can be used as REST Post endpoint

DELETE - Delete
---------------

+-------------------+------------------------------------------------------------------------------+
| Attribute         | Description                                                                  |
+===================+==============================================================================+
| name              | *<level>* Mandatory. Level name (Endpoint) in URI.                           |
+-------------------+------------------------------------------------------------------------------+
| table             | Mandatory. Name of the primary table                                         |
+-------------------+------------------------------------------------------------------------------+
| Permit REST       | *delete* Mandatory. The form can be loaded in REST mode.                     |
+-------------------+------------------------------------------------------------------------------+
| id                | >0                                                                           |
+-------------------+------------------------------------------------------------------------------+


+-------------------+----------------------------------------------------------------------------------+
| Attribute         | Description                                                                      |
+===================+==================================================================================+
| restParam         | Optional. CSV list of variable names. E.g.: ``restParam=pId,adrId``              |
+-------------------+----------------------------------------------------------------------------------+
| restToken         | Optional. User defined string or dynamic token (see `restAuthorization`_).       |
+-------------------+----------------------------------------------------------------------------------+

Carsten  Rose's avatar
Carsten Rose committed
.. note::

    There are  *no* native-FormElements necessary - but might exist for dependent records to delete. Action FormElements
    will be processed.


.. _`restAuthorization`:
Carsten  Rose's avatar
Carsten Rose committed
A QFQ form is only acessible via REST API, if ``Form.permitRest`` enables one of the HTTP Methods: **get, post, put, delete**

``Permit New`` or ``Permit Edit`` don't apply to QFQ forms called via REST.

.. important::

    By default, the REST API is public accessible.
If this is not wished:

* HTTP AUTH might be used (configured via webserver)
* Any other webserver based access restriction method
* or the QFQ internal 'HTTP header token based authorization'.

Token based authorization
^^^^^^^^^^^^^^^^^^^^^^^^^

A form will require a 'token based authorization', as soon as there is a ``form.parameter.restToken`` defined.
Therefore the HTTP Header 'Authorization' has to be set with ``token=<secret token>``. The 'secret token' will
be checked against the server.

Example: ::

  form.parameter.restToken=myCrypticString0123456789

  Test via commandline: curl -X GET -H 'Authorization: Token token=myCrypticString0123456789' "http://localhost/qfq/typo3conf/ext/qfq/Classes/Api/rest.php/person/123/address/"
Carsten  Rose's avatar
Carsten Rose committed
The static setup with ``form.parameter.restToken=myCrypticString0123456789`` is fine, as long as only one token
exist. In case of multiple tokens, replace the static string against a SQL query.

.. tip::

    The HTML Header Authorization token is available in STORE_CLIENT via '`{{Authorization:C:alnumx}}`.
Best Practice: For example all created tokens are saved in a table 'Auth' with a column 'token'. Define::

  form.parameter.restToken={{SELECT a.token FROM Auth AS a WHERE a.token='{{Authorization:C:alnumx}}' }}

To restrict access to a subset of data, just save the limitations inside the Auth record and update the query

  form.parameter.restToken={{SELECT a.token FROM Auth AS a WHERE a.token='{{Authorization:C:alnumx}}'}}
  form.parameter.restSqlList={{!SELECT p.id, p.name, p.email FROM Person AS p, Auth AS a WHERE a.token='{{Authorization:C:alnumx}}' AND a.attribute=p.attribute}}
  form.parameter.restSqlData={{!SELECT p.* FROM Person AS p, Auth AS a WHERE a.token='{{Authorization:C:alnumx}}' AND a.attribute=p.attribute AND p.id='{{r:T0}}' }}

If authorization is denied, the request will be answered with a delay of 3 seconds (configured via securityFailedAuthDelay).

Carsten  Rose's avatar
Carsten Rose committed
System
.. _form-editor-usage:

FormEditor with usage
---------------------

With a large number of forms, it's important to know how often a form has been used, on which pages it's used and when
has the form be used. The following report includes the regular `form-editor`_ as well::

   #
   # Form
   #
   # a) List of forms: {{form:S}}='', {{formIdHistory:S}}=''
   # b) Edit Form: {{form:S}} - Open form {{form:S}} with record {{r:S}} - typically the FormEditor
   # c) Use history of a given form: {{formIdHistory:S}}
   #
   # {{form:S}}
   # {{formIdHistory:S0}} - usage history of form '{{formIdHistory:S}}'

   form={{form:SE}}

     # List of Forms: Do not show this list of forms if there is a form given by SIP.
     # Table header.
     sql = SELECT '<th data-sorter="false" class="filter-false">'
Carsten  Rose's avatar
Carsten Rose committed
                  , CONCAT('p:{{pageAlias:T}}&form=Form&') as _pagen
                  , '</th><th>Name'
                  , '</th><th>Title'
                  , '</th><th>Table'
                  , '</th><th>#'
                  , '</th><th><em>First</em>'
                  , '</th><th><em>Last</em>'
                  , '</th><th><em>PageId</em></th>'
              FROM (SELECT '') AS fake
              WHERE  '{{form:SE}}'=''
                AND {{formIdHistory:S0}}=0
     head = <table class="table table-hover qfq-table-50 tablesorter tablesorter-filter" id="{{pageAlias:T}}-form">
     rend = </tr></thead><tbody>
     tail = </tbody></table>
       # All forms
       sql = SELECT CONCAT('p:{{pageAlias:T}}&form=Form&r=', f.id) as _pagee
                    , CONCAT(f.name, ' <span class="text-muted">(', f.id, ')</span>')
                    , QMORE(strip_tags(f.title),50)
                    , f.tableName
                    , CONCAT('p:{{pageAlias:T}}&formIdHistory=', f.id, '|s|b|t:<span class="badge">', COUNT(fsl.id), '</span>'
                             , IF(COUNT(fsl.id)=0, '|r:3','')  ) as _link
                    , CONCAT( '<em><span title="',MIN(fsl.created), '">', DATE_FORMAT( MIN( fsl.created), '%d.%m.%Y'), '</span></em>')
                    , CONCAT( '<em><span title="',MAX(fsl.created), '">', DATE_FORMAT( MAX( fsl.created), '%d.%m.%Y'), '</span></em>')
                    , CONCAT('<em>', GROUP_CONCAT(DISTINCT fsl.pageId ORDER BY fsl.pageId), '<em>')
                 LEFT JOIN FormSubmitLog AS fsl
                   ON fsl.formId=f.id
                 WHERE {{formIdHistory:S0}}=0
                 GROUP BY f.id
                 ORDER BY f.name
       rbeg = <tr>
       rend = </tr>
       fbeg = <td>
       fend = </td>
     }
   }

     # History of a Form {{formIdHistory:S0}}
     sql = SELECT f.name
                  , fsl.feUser
                  , fsl.recordId
                  , fsl.pageId
                  , fsl.created
              FROM FormSubmitLog AS fsl, Form AS f
              WHERE fsl.formId={{formIdHistory:S0}}
                AND fsl.formId=f.id
              ORDER BY fsl.created DESC

     head = <table class="table table-hover qfq-table-50 tablesorter tablesorter-filter" id="{{pageAlias:T}}-formHistory">
            <thead class="qfq-sticky"><tr><th>Form</th><th>feUser</th><th>recordId</th><th>pageId</th><th>Submit</th></tr></thead><tbody>
     tail = </tbody></table>
     rbeg = <tr>
     rend = </tr>
     fbeg = <td>
     fend = </td>
     altsql = SELECT IF('{{formIdHistory:S0}}'='0', '', '<div class="alert alert-warning">History is empty</div>')
AutoCron
--------
The `AutoCron` service fires periodically jobs like `open a webpage` (typically a QFQ page which does some database
actions) or `send mail`.
* AutoCron will be triggered via system cron. Minimal time distance therefore is 1 minute. If this is not sufficient,
Marc Egger's avatar
Marc Egger committed
  any process who starts `.../typo3conf/ext/qfq/Classes/External/autocron.php` via `/usr/bin/php` frequently might be used.
  * If a job still runs and receives the next trigger, the running job will be completed first.
  * If more than one trigger arrives during a run, only one trigger will be processed.
  * If the system misses a run, it will be played as soon as the system is online again.
  * If multiple runs are missed, only one run is fired as soon as the system is online again.

* Running and processed jobs can easily be monitored via *lastRun, lastStatus, nextRun, inProgress*.
* Setup a system cron entry, typically as the webserver user ('www-data' on debian).
* Necessary privileges:

  * Read for `.../typo3conf/ext/qfq/*`
  * Write, if a logfile should be written (specified per cron job) in the custom specified directory.

Cron task:  ::
Marc Egger's avatar
Marc Egger committed
  * * * * * /usr/bin/php /var/www/html/typo3conf/ext/qfq/Classes/External/autocron.php
AutoCron Jobs of type 'website' needs the php.ini setting: ::

  allow_url_fopen = On

Carsten  Rose's avatar
Carsten Rose committed
Remember: if a cron job fails for whatever reason, the cron daemon will send a mail to the userid who started the cron
job. E.g. www-data. Setup a email forward of such account to a frequently read email account.
Create / edit `AutoCron` jobs
Create a T3 page with a QFQ record (similar to the formeditor). Such page should be access restricted and is only needed
    dbIndex={{indexQfq:Y}}
    form={{form:S}}
    10 {
        # Table header.
        sql = SELECT CONCAT('p:{{pageAlias:T}}&form=cron') AS _pagen, 'id', 'Next run','Frequency','Comment',
                     'Last run','In progress', 'Status', 'Auto generated' FROM (SELECT 1) AS fake WHERE '{{form:SE}}'=''
        head = <table class='table table-hover qfq-table-50'>
        tail = </table>
        rbeg = <thead><tr>
        rend = </tr></thead>
        fbeg = <th>
        fend = </th>

        10 {
        # All Cron Jobs
        sql = SELECT CONCAT('<tr class="',
                            IF(c.lastStatus LIKE 'Error%','danger',''),
                            IF(c.inProgress!=0 AND DATE_ADD(c.inProgress, INTERVAL 10 MINUTE)<NOW(),' warning',''),
                            IF(c.status='enable','',' text-muted'),'" ',

                            IF(c.inProgress!=0 AND DATE_ADD(c.inProgress, INTERVAL 10 MINUTE)<NOW(),'title="inProgress > 10mins"',
                            IF(c.lastStatus LIKE 'Error%','title="Status: Error"','')),
                            '>'),
                        '<td>', CONCAT('p:{{pageAlias:T}}&form=cron&r=', c.id) AS _pagee, '</td><td>',
                        c.id, '</td><td>',
                        IF(c.nextrun=0,"", DATE_FORMAT(c.nextrun, "%d.%m.%y %H:%i:%s")), '</td><td>',
                        c.frequency, '</td><td>',
                        c.comment, '</td><td>',
                        IF(c.lastrun=0,"", DATE_FORMAT(c.lastrun,"%d.%m.%y %H:%i:%s")), '</td><td>',
                        IF(c.inProgress=0,"", DATE_FORMAT(c.inProgress,"%d.%m.%y %H:%i:%s")), '</td><td>',
                        LEFT(c.laststatus,40) AS '_+pre', '</td><td>',
                        CONCAT('U:form=cron&r=', c.id) AS _paged, '</td></tr>'
                FROM Cron AS c
        ORDER BY c.id
        }
    }
The system `cron` service will call the `QFQ AutoCron` every minute. `QFQ AutoCron` checks if there is a pending job, by looking
for jobs with `nextRun` <= `NOW()`. All found jobs will be fired - depending on their type, such jobs will send mail(s) or
open a `webpage`. A `webpage` will mostly be a local T3 page with at least one QFQ record on it. Such a QFQ record might
do some manipulation on the database or any other task.

A job with `nextRun`=0 or `inProgress`!=0 won't never be started.

Due to checking `inProgress`, jobs will never run in parallel, even if a job needs more than 1 minute (interval system
* frequency: '1 MINUTE', '2 DAY', '3 MONTH', ....

After finishing a job, `nextRun` will be increased by `frequency`. If `nextRun` still points in the past, it will be
increased by `frequency` again, until it points to the future.


Job: asynchronous
An 'AutoCron' job becomes 'asynchronous' if `frequency` is empty. Then, auto repeating is switched off.

If `nextRun` is > 0 and in the past, the job will be fired. After the job has been done, `nextRun` will be set to 0.
This is useful for jobs which have to be fired from time to time.

Carsten  Rose's avatar
Carsten Rose committed
To fire such an asynchronous job, just set `nextRun=NOW()` and wait for the next system cron run.
Carsten  Rose's avatar
Carsten Rose committed
If such a job is running and a new `nextRun=NOW()` is applied, the 'AutoCron' job will be fired again during the next
system cron run.

At the moment there is a special sendmail notation - this will change in the future.

  {{!SELECT 'john@doe.com' AS sendMailTo, 'Custom subject' AS sendMailSubject, 'jane@doe.com' AS sendMailFrom, 123 AS sendMailGrId, 456 AS sendMailXId}}
AutoCron will send as many mails as records are selected by the SQL query in field `Mail`. Field `Mail body` provides
The page specified in `URL` will be opened.
Optional the output of that page can be logged to a file (take care to have write permissions on that file).

* `Log output to file`=`output.log` - creates a file in the Typo3 host directory.
* `Log output to file`=`/var/log/output.log` - creates a file in `/var/log/` directory.


Also `overwrite` or `append` can be selected for the output file. In case of `append` a file rotation should be setup on
 OS level.
To check for a successful DB connection, it's a good practice to report a custom token on the T3 page / QFQ record like
Carsten  Rose's avatar
Carsten Rose committed
'DB Connect: ok'. Such a string can be checked via `Pattern to look for on output=/DB Connect: ok/`. The pattern
needs to be written in PHP PCRE syntax. For a simple search string, just surround them with '/'.
If the pattern is found on the page, the job get's 'Ok' - else 'Error - ...'.
Access restriction
;;;;;;;;;;;;;;;;;;

To protect AutoCron pages not to be triggered accidental or by unprivileged access, access to those page tree might be
limited to localhost. Some example Typoscript: ::

  # Access allowed for any logged in user or via 'localhost'
  [usergroup = *] || [IP = 127.0.0.1]
    page.10 < styles.content.get
  [else]
    # Error Message
    page.10 = TEXT
    page.10.value = <h2>Access denied</h2>Please log in or access this page from an authorized host. Your current IP address:&nbsp;
    page.20 = TEXT
    page.20.data = getenv : REMOTE_ADDR
  [global]



AutoCron / website: HTTPS protocol
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

* For `https` the PHP extension `php_openssl` has to be installed.
* All certificates are accepted, even self signed without a correct chain or hostnames, not listed in the certificate.
  This is useful if there is a general 'HTTP >> HTTPS' redirection configured and the website is accessed via `https://localhost/...`
With a framework like https://www.seleniumhq.org/ it's possible to play and verify unattended  test cases.

To assist such frameworks and to make the tests reliable, an individual tag might be assigned to HTML elements which have to
By default every FormElement contains an attribute 'data-reference=<value>', whereas the '<value>' is either the name
of the FormElement or a custom value, defined via 'FormElement.parameter.dataReference=<value>'.
Any HTML output can be extended by a tag - that's done by the webmaster. For QFQ generated links, an attribute like
'data-reference' might be injected via token 'A' (attribute). ::
   SELECT 'p:personedit&form=person&r=1|b|s|A:data-reference="person-edit"|t:Edit person' AS _link
Carsten  Rose's avatar
Carsten Rose committed
* Does the error happens on every *page* or only on a specific one?
* Does the error happens on every *form* or only on a specific one?
  * Always check the Javascript console of your browser, see `javascriptProblem`_.
  * Always check the Webserver log files.
Caching
-------

Content, generated by QFQ, is generally not cached. But the QFQ content records are cached by Typo3. This means if there is
a content element 'Insert Records' on a page 'A' which includes a QFQ record from page 'B' and such QFQ record is modified (the SQL
definition, not the delivered output), the new definition becomes by default only visible if the cached is cleared.

To simplify the situation, set on the page of the QFQ record (B) in Page TS Config: ::

  TCEMAIN.clearCacheCmd = pages

QFQ specific
------------

A variable {{<var>}} is empty
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Carsten  Rose's avatar
Carsten Rose committed
The sanitize rule is violated and therefore the value has been removed. Set {{<var>:<store>:all}} as a test.
Page is white: no HTML code at all
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

This should not happen.

The PHP process stopped at all. Check the Apache error logfile, look for a stacktrace to find the latest function. Send
a bug report.

Problem with query or variables
Carsten  Rose's avatar
Carsten Rose committed
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Specify the required sanitize class. Remember: for STORE_FORM and STORE_CLIENT the default is sanitize class is `digit`.
This means if the variable content is a string, this violates the sanitize class and the variable will not be replaced.

Tip on Form: put the problematic variable or SQL statement in the 'title' or note 'field' of a `FormElement`. This should show
the content. For SQL statements, remove the outer token (e.g. only one curly brace) to avoid triggering SQL: ::

  FE.title: Person { SELECT ... WHERE id={{buggyVar:alnumx}} }

Tip on Report: In case the query did not contain any double ticks, just wrap all but 'SELECT' in double ticks::
Carsten  Rose's avatar
Carsten Rose committed
 Buggy query:  10.sql = SELECT id, ... FROM myTable WHERE status={{myVar}} ORDER BY status
 Debug query:  10.sql = SELECT "id, ... FROM myTable WHERE status={{myVar}} ORDER BY status"
Error read file config.qfq.php: syntax error on line xx
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Ephraim Seidenberg's avatar
Ephraim Seidenberg committed
Check the given line number. If it's an SQL statement, enclose it in single or double ticks.

Output a text, substitute embedded QFQ variables
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The content will be copied to '_text'. In `10.tail` than the '{{text:R}}' will be substituted with all known variables.
Note the '-' in '{{text:RE::-}}', this will prevent that QFQ escapes any character from the content. ::

    10 {
      sql = SELECT no.text AS _text
               FROM Note AS no
               WHERE id=...
      tail = {{text:RE::-}}
    }

TypeAhead list with T3 page alias names - use of the T3 DB
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^


    FE.type = text
    FE.parameter.typeAheadSql = SELECT p.alias FROM {{dbNameT3:Y}}.pages AS p WHERE p.deleted=0 AND p.alias!='' AND p.alias LIKE ? ORDER BY p.alias LIMIT 20
    FE.parameter.typeAheadMinLength = 1

.. _setFeUserPassword:

Set FE-User password
^^^^^^^^^^^^^^^^^^^^

To offer an FE User the possibility to change the own T3 FE password, create a form: ::

    f.name = changeFePassword
    f.title = Change Password
    f.table = Person   (QFQ Table, not T3)
    f.permitNew = never
    f.permitEdit = sip

    fe[1].name = myPassword
    fe[1].title = Password
    fe[1].class = native
    fe[1].type = password
    fe[1].mode = required
    fe[1].parameter = retype

    fe[2].class = action
    fe[2].type = afterSave
Carsten  Rose's avatar
Carsten Rose committed
    fe[2].parameter = sqlAfter={{UPDATE {{dbNameT3:Y}}.fe_users SET password='{{myPassword:FE:all:p}}' WHERE username='{{feUser:T}}' AND deleted=0

Call the form via SIP on an existing record. Often QFQ has an own table for persons and also the current user exist in T3
fe_users table.

Logging
-------

General webserver error log
^^^^^^^^^^^^^^^^^^^^^^^^^^^

For apache: `/var/log/apache2/error_log`

Especially if you got a blank page (no rendering at all), this is typically an uncaught PHP error. Check the error message
Carsten  Rose's avatar
Carsten Rose committed
and report the bug (https://qfq.io > Contact).

Call to undefined function qfq\\mb_internal_encoding()
""""""""""""""""""""""""""""""""""""""""""""""""""""""

Check that all required php modules are installed. See `preparation`_.

Carsten  Rose's avatar
Carsten Rose committed
^^^^^^^^^^^^^^^^^^^^^

The browser shows a red popup with 'Internal Server Error'. The message is generated in the browser. Happens e.g. an AJAX
request response of QFQ (=Server) is broken. This might happen e.g. if PHP can't start successfully or PHP fails to run
due to  a missing php module or broken configuration.

Oops, an error occurred! Code: 20180612205917761fc593
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
You see this message on all places where a QFQ content record should produce some output. Typically the extension fails
to load. If the error message disappears when the QFQ extension is disabled (instead a message `qfq_qfq can't be rendered`
is shown), than QFQ is the problem.

Search the given code in `typo3temp/logs/*`, in this example 20180612205917761fc593. You'll should find a stacktrace with
a more detailed message.

The error might occur if there are problematic characters in config.qfq.php, like single or double ticks inside strings,
wich are not enclosed (correctly).
.. _`sendEmailProblem`:

sendEmail: Error => TLS setup failed
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Switch off the TLS encryption. In `configuration`_ specify for *config.sendEMailOptions*: ::

   -o tls=no

.. _`javascriptProblem`:

Javascript problem
------------------

Open the 'Webdeveloper Tools' (FF: F12, Chrome/Opera: Right mouse click > Inspect Element) in your browser, switch to
'console' and reload the page. Inspect the messages.

TinyMCE
-------

Glyph Icons in '<span>'
^^^^^^^^^^^^^^^^^^^^^^^

TinyMCE forbids by default HTML tag 'span' with 'class' attribute. E.g.: ::

    <span class="glyphicon glyphicon-user"></span>

To allow it, add 'span' to the valid elements in the FormElement.parameter field: ::

    editor-extended_valid_elements = span[class|style]
The HTML span tag has to be added via 'source' view. At least in TinyMCE 4.7.13, the glyph is still not shown in the
editor but saved.