From f287852c389bc881a79d2b4411852749c03d27c2 Mon Sep 17 00:00:00 2001 From: Carsten Rose <carsten.rose@math.uzh.ch> Date: Fri, 17 Mar 2017 16:43:45 +0100 Subject: [PATCH] Typeahead (#3369): Parameternames has changed, configuration is possible on the Form and on the FormElement. typeahead.php: if there is an exception, the message body is sent as regular 'content' for the dropdownbox. At the moment this is the only way to transmit any error messages. Ldap.php: missing FE_TYPEAHEAD_LDAP_KEY_PRINTF, FE_TYPEAHEAD_LDAP_VALUE_PRINTF will be substituted by the other. If both missing, an exception is thrown. --- extension/Documentation/Manual.rst | 82 ++++++++++++++++--------- extension/qfq/api/typeahead.php | 5 +- extension/qfq/qfq/AbstractBuildForm.php | 36 ++++++++--- extension/qfq/qfq/Constants.php | 23 ++++--- extension/qfq/qfq/form/TypeAhead.php | 2 +- extension/qfq/qfq/helper/Ldap.php | 24 ++++++-- 6 files changed, 118 insertions(+), 54 deletions(-) diff --git a/extension/Documentation/Manual.rst b/extension/Documentation/Manual.rst index b18082035..d5166fb73 100644 --- a/extension/Documentation/Manual.rst +++ b/extension/Documentation/Manual.rst @@ -862,31 +862,46 @@ parameter * Comments: lines starting with a '#' are treated as a comment and will not be parsed. -+------------------------+--------+----------------------------------------------------------------------------------------------------------+ -| Name | Type | Description | -+========================+========+==========================================================================================================+ -| maxVisiblePill | int | Show pills upto <maxVisiblePill> as button, all further in a dropdown menu. Eg.: maxVisiblePill=3 | -+------------------------+--------+----------------------------------------------------------------------------------------------------------+ -| class | string | HTML div with given class, surrounding the whole form. Eg.: class=container-fluid | -+------------------------+--------+----------------------------------------------------------------------------------------------------------+ -| classPill | string | HTML div with given class, surrounding the `pill` title line. | -+------------------------+--------+----------------------------------------------------------------------------------------------------------+ -| classBody | string | HTML div with given class, surrounding all *FormElement*. | -+------------------------+--------+----------------------------------------------------------------------------------------------------------+ -| submitButtonText | string | Show save button, with the <submitButtonText> at the bottom of the form | -+------------------------+--------+----------------------------------------------------------------------------------------------------------+ -| 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' + -+------------------------+--------+----------------------------------------------------------------------------------------------------------+ ++--------------------------+--------+----------------------------------------------------------------------------------------------------------+ +| Name | Type | Description | ++==========================+========+==========================================================================================================+ +| maxVisiblePill | int | Show pills upto <maxVisiblePill> as button, all further in a dropdown menu. Eg.: maxVisiblePill=3 | ++--------------------------+--------+----------------------------------------------------------------------------------------------------------+ +| class | string | HTML div with given class, surrounding the whole form. Eg.: class=container-fluid | ++--------------------------+--------+----------------------------------------------------------------------------------------------------------+ +| classPill | string | HTML div with given class, surrounding the `pill` title line. | ++--------------------------+--------+----------------------------------------------------------------------------------------------------------+ +| classBody | string | HTML div with given class, surrounding all *FormElement*. | ++--------------------------+--------+----------------------------------------------------------------------------------------------------------+ +| submitButtonText | string | Show save button, with the <submitButtonText> at the bottom of the form | ++--------------------------+--------+----------------------------------------------------------------------------------------------------------+ +| 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.: ldap.example.com | ++--------------------------+--------+----------------------------------------------------------------------------------------------------------+ +| ldapBaseDn | string | E.g.: ou=dept,dc=example,dc=com | ++--------------------------+--------+----------------------------------------------------------------------------------------------------------+ +| ldapSearch | string | E.g.: (|(cn=*?*)(mail=*?*)(ou=*?*)(roomNumber=*?*)(telephoneNumber=*?*)) | ++--------------------------+--------+----------------------------------------------------------------------------------------------------------+ +| 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. | ++--------------------------+--------+----------------------------------------------------------------------------------------------------------+ +| typeAheadLdapValuePrintf | string | Value formatting of LDAP result, per entry. E.g.: '%s / %s / %s', mail, roomnumber, telephonenumber | ++--------------------------+--------+----------------------------------------------------------------------------------------------------------+ +| typeAheadLdapKeyPrintf | string | Key formatting of LDAP result, per entry. E.g.: '%s', mail | ++--------------------------+--------+----------------------------------------------------------------------------------------------------------+ + * Example: @@ -1398,9 +1413,9 @@ Type Ahead '''''''''' Activating `typeahead` functionality offers an instant lookup of data and displaying them to the user, while the user is -typing. A dropdown box offers the results. As datasource the regular SQL connection or a LDAP query can be used. +typing a dropdown 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 is displayed as the dropdown box. +the server, the lookup will be performed and the result, upto *typeAheadLimit* entries, are displayed as the dropdown box. * *FormElement.parameter*: @@ -1410,6 +1425,11 @@ the server, the lookup will be performed and the result is displayed as the drop Depending of the `typeahead` setup, the given FormElement will contain the displayed `value` or `key` (if a key/value dict is configured). +Configuration via Form / FormElement +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +All of the `typeAhead*` (exception: `typeAheadLdap`) and `ldap*` parameter can be specified in either on the Form.parameter or on FormElement.parameter. + SQL ;;; @@ -1427,9 +1447,11 @@ LDAP * *FormElement.parameter*: - * *typeAheadLdapServer* = FQDN of the searched server. E.g.: `directory.uzh.ch` - * *typeAheadLdapBaseDn* = Base DN. E.g.: `ou=Addressbook,dc=uzh,dc=ch` - * *typeAheadLdapSearch* = LDAP search expression. E.g.: `(|(cn=*?*)(mail=*?*)(ou=*?*)(roomNumber=*?*)(telephoneNumber=*?*))` + * *typeAheadLdap* This entry activates the *LDAP* typeahead functionality. + * *ldapServer* = FQDN of the searched server. E.g.: `directory.uzh.ch` + * *ldapBaseDn* = Base DN. E.g.: `ou=Addressbook,dc=uzh,dc=ch` + * *ldapSearch* = LDAP search expression. Every '?' will be replaced with the current typed content. + E.g.: `(|(cn=*?*)(mail=*?*)(ou=*?*)(roomNumber=*?*)(telephoneNumber=*?*))` * *typeAheadLdapValuePrintf* = regular printf expression, LDAP attributenames will be used as variablenames. Will be shown in the dropdownbox. E.g.: `'%s / %s / %s', mail, roomNumber, telephoneNumber` * *typeAheadLdapKeyPrintf* = Same as `ldapValuePrintf` - on save, these content will be saved. E.g.: `'%s', mail` diff --git a/extension/qfq/api/typeahead.php b/extension/qfq/api/typeahead.php index e635c8ec7..cfd2e44e1 100644 --- a/extension/qfq/api/typeahead.php +++ b/extension/qfq/api/typeahead.php @@ -19,15 +19,14 @@ require_once(__DIR__ . '/../qfq/Constants.php'); * Return JSON encoded answer * */ - - try { $qfq = new \qfq\TypeAhead(); $answer = $qfq->process(); } catch (\Exception $e) { - $answer[API_MESSAGE] = "Generic Exception: " . $e->getMessage(); +// $answer[API_MESSAGE] = "Generic Exception: " . $e->getMessage(); + $answer[] = [API_TYPEAHEAD_KEY => 'errror', API_TYPEAHEAD_VALUE => "Error: " . $e->getMessage()]; } header("Content-Type: application/json"); diff --git a/extension/qfq/qfq/AbstractBuildForm.php b/extension/qfq/qfq/AbstractBuildForm.php index a3c72f4a9..f910a51b6 100644 --- a/extension/qfq/qfq/AbstractBuildForm.php +++ b/extension/qfq/qfq/AbstractBuildForm.php @@ -382,6 +382,10 @@ abstract class AbstractBuildForm { // Preparation for Log, Debug $this->store->setVar(SYSTEM_FORM_ELEMENT, Logger::formatFormElementName($fe), STORE_SYSTEM); + if (isset($fe[FE_FILL_STORE_LDAP]) || isset($fe[FE_TYPEAHEAD_LDAP])) { + $fe = $this->prepareLdapConfig($fe); + } + // for Upload FormElements, it's necessary to precalculate an optional given 'slaveId'. if ($fe[FE_TYPE] === FE_TYPE_UPLOAD) { Support::setIfNotSet($fe, FE_SLAVE_ID); @@ -461,6 +465,24 @@ abstract class AbstractBuildForm { return $html; } + /** + * Copy LDAP Server from $this->formSpec to $formElement if they are not specified on $formElement + * @param array $formElement + * @return array + */ + private function prepareLdapConfig(array $formElement) { + + foreach ([F_LDAP_SERVER, F_LDAP_BASE_DN, F_LDAP_SEARCH, F_TYPEAHEAD_LIMIT, + F_TYPEAHEAD_MINLENGTH, F_TYPEAHEAD_LDAP_VALUE_PRINTF, F_TYPEAHEAD_LDAP_KEY_PRINTF] as $key) { + if (!isset($formElement[$key])) { + if (isset($this->formSpec[$key])) { + $formElement[$key] = $this->formSpec[$key]; + } + } + } + + return $formElement; + } /** * Check if there is an explicit 'autofocus' definition in at least one FE. * Found: do nothing, it will be rendered at the correct position. @@ -824,17 +846,17 @@ abstract class AbstractBuildForm { if (isset($formElement[FE_TYPEAHEAD_SQL])) { $sql = $this->checkSqlAppendLimit($formElement[FE_TYPEAHEAD_SQL], $formElement[FE_TYPEAHEAD_LIMIT]); $urlParam = FE_TYPEAHEAD_SQL . '=' . $sql; - } elseif (isset($formElement[FE_TYPEAHEAD_LDAP_SERVER])) { - $formElement[FE_TYPEAHEAD_LDAP_SERVER] = Support::setIfNotSet($formElement, FE_TYPEAHEAD_LDAP_SERVER); - $formElement[FE_TYPEAHEAD_LDAP_BASE_DN] = Support::setIfNotSet($formElement, FE_TYPEAHEAD_LDAP_BASE_DN); - $formElement[FE_TYPEAHEAD_LDAP_SEARCH] = Support::setIfNotSet($formElement, FE_TYPEAHEAD_LDAP_SEARCH); + } elseif (isset($formElement[FE_LDAP_SERVER])) { + $formElement[FE_LDAP_SERVER] = Support::setIfNotSet($formElement, FE_LDAP_SERVER); + $formElement[FE_LDAP_BASE_DN] = Support::setIfNotSet($formElement, FE_LDAP_BASE_DN); + $formElement[FE_LDAP_SEARCH] = Support::setIfNotSet($formElement, FE_LDAP_SEARCH); $formElement[FE_TYPEAHEAD_LDAP_VALUE_PRINTF] = Support::setIfNotSet($formElement, FE_TYPEAHEAD_LDAP_VALUE_PRINTF); $formElement[FE_TYPEAHEAD_LDAP_KEY_PRINTF] = Support::setIfNotSet($formElement, FE_TYPEAHEAD_LDAP_KEY_PRINTF); $arr = [ - FE_TYPEAHEAD_LDAP_SERVER => $formElement[FE_TYPEAHEAD_LDAP_SERVER], - FE_TYPEAHEAD_LDAP_BASE_DN => $formElement[FE_TYPEAHEAD_LDAP_BASE_DN], - FE_TYPEAHEAD_LDAP_SEARCH => $formElement[FE_TYPEAHEAD_LDAP_SEARCH], + FE_LDAP_SERVER => $formElement[FE_LDAP_SERVER], + FE_LDAP_BASE_DN => $formElement[FE_LDAP_BASE_DN], + FE_LDAP_SEARCH => $formElement[FE_LDAP_SEARCH], FE_TYPEAHEAD_LDAP_VALUE_PRINTF => $formElement[FE_TYPEAHEAD_LDAP_VALUE_PRINTF], FE_TYPEAHEAD_LDAP_KEY_PRINTF => $formElement[FE_TYPEAHEAD_LDAP_KEY_PRINTF], FE_TYPEAHEAD_LIMIT => $formElement[FE_TYPEAHEAD_LIMIT], diff --git a/extension/qfq/qfq/Constants.php b/extension/qfq/qfq/Constants.php index 80d4f02c0..072b225be 100644 --- a/extension/qfq/qfq/Constants.php +++ b/extension/qfq/qfq/Constants.php @@ -531,6 +531,13 @@ const F_FE_DATA_ERROR = 'data-error'; const F_PARAMETER = 'parameter'; // valid for F_ and FE_ +const F_LDAP_SERVER = 'ldapServer'; +const F_LDAP_BASE_DN = 'ldapBaseDn'; +const F_LDAP_SEARCH = 'ldapSearch'; +const F_TYPEAHEAD_LIMIT = 'typeAheadLimit'; +const F_TYPEAHEAD_MINLENGTH = 'typeAheadMinLength'; +const F_TYPEAHEAD_LDAP_VALUE_PRINTF = 'typeAheadLdapValuePrintf'; +const F_TYPEAHEAD_LDAP_KEY_PRINTF = 'typeAheadLdapKeyPrintf'; // FORM_ELEMENT_STATI const FE_MODE_SHOW = 'show'; @@ -611,14 +618,16 @@ const FE_TEMPLATE_GROUP_CLASS = 'tgClass'; const FE_TEMPLATE_GROUP_DEFAULT_MAX_LENGTH = 5; const FE_TEMPLATE_GROUP_NAME_PATTERN = '%d'; const FE_BUTTON_CLASS = 'buttonClass'; -const FE_TYPEAHEAD_LIMIT = 'typeaheadLimit'; -const FE_TYPEAHEAD_MINLENGTH = 'typeaheadMinLength'; +const FE_LDAP_SERVER = F_LDAP_SERVER; +const FE_LDAP_BASE_DN = F_LDAP_BASE_DN; +const FE_LDAP_SEARCH = F_LDAP_SEARCH; +const FE_TYPEAHEAD_LIMIT = F_TYPEAHEAD_LIMIT; +const FE_TYPEAHEAD_MINLENGTH = F_TYPEAHEAD_MINLENGTH; const FE_TYPEAHEAD_SQL = 'typeAheadSql'; -const FE_TYPEAHEAD_LDAP_SERVER = 'typeAheadLdapServer'; -const FE_TYPEAHEAD_LDAP_BASE_DN = 'typeAheadLdapBaseDn'; -const FE_TYPEAHEAD_LDAP_SEARCH = 'typeAheadLdapSearch'; -const FE_TYPEAHEAD_LDAP_VALUE_PRINTF = 'typeAheadLdapValuePrintf'; -const FE_TYPEAHEAD_LDAP_KEY_PRINTF = 'typeAheadLdapKeyPrintf'; +const FE_TYPEAHEAD_LDAP_VALUE_PRINTF = F_TYPEAHEAD_LDAP_VALUE_PRINTF; +const FE_TYPEAHEAD_LDAP_KEY_PRINTF = F_TYPEAHEAD_LDAP_KEY_PRINTF; +const FE_TYPEAHEAD_LDAP = 'typeAheadLdap'; +const FE_FILL_STORE_LDAP = 'fillStoreLdap'; const FE_CHARACTER_COUNT_WRAP = 'characterCountWrap'; const RETYPE_FE_NAME_EXTENSION = 'RETYPE'; diff --git a/extension/qfq/qfq/form/TypeAhead.php b/extension/qfq/qfq/form/TypeAhead.php index 87545b6ca..fbfc280bc 100644 --- a/extension/qfq/qfq/form/TypeAhead.php +++ b/extension/qfq/qfq/form/TypeAhead.php @@ -80,7 +80,7 @@ class TypeAhead { if (isset($sipVars[FE_TYPEAHEAD_SQL])) { $arr = $this->typeAheadSql($sipVars, $this->vars[TYPEAHEAD_API_QUERY]); - } elseif (isset($sipVars[FE_TYPEAHEAD_LDAP_SERVER])) { + } elseif (isset($sipVars[FE_LDAP_SERVER])) { $ldap = new Ldap(); $arr = $ldap->process($sipVars, $this->vars[TYPEAHEAD_API_QUERY]); } diff --git a/extension/qfq/qfq/helper/Ldap.php b/extension/qfq/qfq/helper/Ldap.php index ced53b734..f69f38a69 100644 --- a/extension/qfq/qfq/helper/Ldap.php +++ b/extension/qfq/qfq/helper/Ldap.php @@ -23,9 +23,9 @@ class Ldap { public function process($config, $query) { $arr = array(); - $ldapServer = $config[FE_TYPEAHEAD_LDAP_SERVER]; - $ldapBaseDn = $config[FE_TYPEAHEAD_LDAP_BASE_DN]; - $ldapSearch = $config[FE_TYPEAHEAD_LDAP_SEARCH]; + $ldapServer = $config[FE_LDAP_SERVER]; + $ldapBaseDn = $config[FE_LDAP_BASE_DN]; + $ldapSearch = $config[FE_LDAP_SEARCH]; $ldapSearch = str_replace('?', $query, $ldapSearch); $ldapLimit = $config[FE_TYPEAHEAD_LIMIT]; @@ -34,6 +34,18 @@ class Ldap { throw new UserFormException("Unable to connect to LDAP server: $ldapServer", ERROR_LDAP_CONNECT); } + if ($config[FE_TYPEAHEAD_LDAP_KEY_PRINTF] == '') { + $config[FE_TYPEAHEAD_LDAP_KEY_PRINTF] = $config[FE_TYPEAHEAD_LDAP_VALUE_PRINTF]; + } + + if ($config[FE_TYPEAHEAD_LDAP_VALUE_PRINTF] == '') { + $config[FE_TYPEAHEAD_LDAP_VALUE_PRINTF] = $config[FE_TYPEAHEAD_LDAP_KEY_PRINTF]; + } + + if ($config[FE_TYPEAHEAD_LDAP_KEY_PRINTF] == '') { + throw new UserFormException("Missing parameter '" . FE_TYPEAHEAD_LDAP_KEY_PRINTF . "' and/or '" . FE_TYPEAHEAD_LDAP_VALUE_PRINTF); + } + $keyArr = $this->printfPrepare($config[FE_TYPEAHEAD_LDAP_KEY_PRINTF], $keyFormat); $valueArr = $this->printfPrepare($config[FE_TYPEAHEAD_LDAP_VALUE_PRINTF], $valueFormat); @@ -69,9 +81,9 @@ class Ldap { /** * Very specific function to prepare the later 'printfResult()'. * - * @param $fmtComplete - * @param $fmtFirst - * @return mixed + * @param $fmtComplete printf format string with all args. + * @param $fmtFirst returns the first part of $fmtComplete - the printf format string without any args. + * @return array Array with all requested keynames from $fmtComplete * @throws CodeException * @throws UserFormException */ -- GitLab