.. ==================================================
.. ==================================================
.. ==================================================
.. Header hierarchy
.. ==
..  --
..   ^^
..    ""
..     ;;
..      ,,
..
.. --------------------------------------------used to the update the records specified ------
.. Best Practice T3 reST: https://docs.typo3.org/m/typo3/docs-how-to-document/master/en-us/WritingReST/CheatSheet.html
..             Reference: https://docs.typo3.org/m/typo3/docs-how-to-document/master/en-us/WritingReST/Index.html
.. Italic *italic*
.. Bold **bold**
.. Code ``text``
.. External Links: `Bootstrap <http://getbootstrap.com/>`_
.. Internal Link: :ref:`downloadButton` (default url text) or :ref:`download Button<downloadButton>` (explicit url text)
.. Add Images:    .. image:: ../Images/a4.jpg
..
..
.. Admonitions
..           .. note::   .. important::     .. tip::     .. warning::
.. Color:   (blue)       (orange)           (green)      (red)
..
.. Definition:
.. some text becomes strong (only one line)
..      description has to indented

.. -*- coding: utf-8 -*- with BOM.

.. include:: Includes.txt


.. _LDAP:

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                  | ldaps://directory.example.com:636| Hostname.                                                           | 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 :ref:`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        | x    | x           | TA       |
|                             |                                  |  the individual tokens                                              |      |             |          |
+-----------------------------+----------------------------------+---------------------------------------------------------------------+------+-------------+----------+
| 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
  :ref:`config-qfq-php`.

.. _LDAP_Typeahead:

Typeahead (TA) - LDAP
---------------------

See also :ref:`input-typeahead`

*Typeahead* offers continuous 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 sufficient.
* *Form.parameter* or *FormElement.parameter*:

  * *ldapServer* = `ldaps://directory.example.com:636`
  * *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 practical
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_LDAP_STORE:

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* = `ldaps://directory.example.com:636`
  * *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}}`.