diff --git a/doc/CODING.md b/doc/CODING.md index c532510757c0b0bc87c1b6489cde2ddde729d2c6..ebba160ce9d07bbfb2c5d2add6d51eed373862ee 100644 --- a/doc/CODING.md +++ b/doc/CODING.md @@ -30,17 +30,23 @@ LOAD * (Report) * Process all <number>.[<number.>].sql statements -* Access code variables: +* Access variables: * active/valid formname: [$this->store->setVar(SYSTEM_FORM, $formName, STORE_SYSTEM);] * SIP: [$this->store->getVar('form', STORE_SIP)] * All parameters from active SIP: [$this->store->getStore(STORE_SIP)] - * Check Contstants.php for known Store members: + * Check Contstants.php for known Store *members*. * In QuickFormQuery.php the whole Form will be copied to `$this->formSpec` and depending on further processing, the - elements are available in `$this->feNative` and `$this->feAction`. - * The Form specificaton (table form) will be evaluated direct after loading. - * The FormElement specification will be evaluated later on in BuildForm*.php - + elements are available in `$this->feSpecNative` and `$this->feSpecAction`. + * The Form specificaton (table form) will be evaluated direct after loading (no `dynamicUpdate`). + * The FormElement specification will be evaluated later in BuildForm*.php: + * $formSpec, and root elements of $feSpecAction & $feSpecNative will be read in QuickFormQuery.php. + * AbstractBuildForm.php/BuildFormBootstrap.php receives a copy during instantiating of that class. + * Processing of FormElements in AbstractBuildForm.php/BuildFormBootstrap.php typically do not change values, + especially there is no evaluation, in $feSpecAction & $feSpecNative. + * *Dynamic Update*: remember, an update(=Form load) is a complete new rendering of the form in that moment. All + values/elements/notes/debug are fresh with the latest form content. + * If a form is called without a SIP (form.permitNew='always'), than a SIP is created on the fly (as a parameter in the form). * Uniq SIP for mutliple tabs with r=0 @@ -59,6 +65,13 @@ LOAD AbstractBuildForm.php: process() > prepareT3VarsForSave() > Store.php: copyT3VarsToSip(); * *Form save*: FillStoreForm.php: process() > Store: fillTypo3StoreFromSip() +* Store: STORE_ADDITIONAL_FORM_ELEMENTS + * HTML 'hidden' elements, inside of a checkbox or radio input definition, might disturb Bootstrap CSS classes. + * Therefore HTML elements like 'hidden' can be collected in STORE_ADDITIONAL_FORM_ELEMENTS. + * When the form will be composed of all single parts, the STORE_ADDITIONAL_FORM_ELEMENTS content will be arranged before + the regular INPUT Elements. + + * Formular zusammenbauen * QuickFormQuery: doForm > loadFormSpecification - laedt den Form Record alle Form Elemente die nicht genested sind: native, pill, fieldset, templateGroup >> $his->formNative. diff --git a/extension/Documentation/AdministratorManual/Index.rst b/extension/Documentation/AdministratorManual/Index.rst index 47d1ce3d45ede5efebcc15194f99082d583fe669..e6384cffcc56ee9ded663c41b1e328ba31cf9dad 100644 --- a/extension/Documentation/AdministratorManual/Index.rst +++ b/extension/Documentation/AdministratorManual/Index.rst @@ -14,6 +14,9 @@ Administrator Manual Preparation ----------- +Report & Form +^^^^^^^^^^^^^ + The QFQ extension needs in PHP 5.x the PHP MySQL native driver. The following functions are used and are only available with the native driver (see also: http://dev.mysql.com/downloads/connector/php-mysqlnd/): @@ -34,6 +37,34 @@ Preparation steps for Ubuntu 16.04:: sudo apt install php7.0-intl +Print page via wkhtmltopdf +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Different browser prints the same page in different variations. To prevent this, QFQ implements a small PHP wrapper `print.php` +which uses http://wkhtmltopdf.org/ (webkit based) to convert HTML to PDF. The converter is not included in QFQ and has +to be manually installed. + +Hint: 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 config.qfq.ini specify the: +* installed `wkhtmltopdf` binary, +* the site base URL. + +Provide a `print this page`-link (replace {current pageId}):: + + <a href="typo3conf/ext/qfq/qfq/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/qfq/api/print.php?id=|&type=2"><span class="glyphicon glyphicon-print" aria-hidden="true"></span> Printview</a> + data = page:uid + } + Setup ----- @@ -162,7 +193,10 @@ config.qfq.ini +-----------------------------+-----------------------------------------+----------------------------------------------------------------------------+ | FORM_BUTTON_ON_CHANGE_CLASS | FORM_BUTTON_ON_CHANGE_CLASS=alert-info btn-info | Color for save button after modification | +-----------------------------+-----------------------------------------+----------------------------------------------------------------------------+ - +| BASE_URL_PRINT | BASE_URL_PRINT=http://example.com | URL where wkhtmltopdf will fetch the HTML (no parameter, those comes later)| ++-----------------------------+-----------------------------------------+----------------------------------------------------------------------------+ +| WKHTMLTOPDF | WKHTMLTOPDF=/usr/bin/wkhtmltopdf | Binary where to find wkhtmltopdf | ++-----------------------------+-----------------------------------------+----------------------------------------------------------------------------+ Example: *typo3conf/config.qfq.ini* @@ -190,6 +224,8 @@ Example: *typo3conf/config.qfq.ini* ;FORM_BS_LABEL_COLUMNS = 3 ;FORM_BS_INPUT_COLUMNS = 6 ;FORM_BS_NOTE_COLUMNS = 3 + BASE_URL_PRINT=http://example.com + WKHTMLTOPDF=/usr/bin/wkhtmltopdf Documentation ------------- diff --git a/extension/Documentation/UsersManual/Index.rst b/extension/Documentation/UsersManual/Index.rst index c85937386ce0cb82506813c6d4157e3f8b19587e..c766c13cc20f66f87c51796f347376676cd09840 100644 --- a/extension/Documentation/UsersManual/Index.rst +++ b/extension/Documentation/UsersManual/Index.rst @@ -234,7 +234,7 @@ Only variables that are known in a specified store can be substituted. +=====+========================================================================================+============================================================================+ | F | :ref:`STORE_FORM`: data not saved in database yet. | All native *FormElements*. Recent values from the Browser. | +-----+----------------------------------------------------------------------------------------+----------------------------------------------------------------------------+ - | S | :ref:`STORE_SIP`: Client parameter 's' will indicate the current SIP, which will be | sip, r (record_id), form | + | S | :ref:`STORE_SIP`: Client parameter 's' will indicate the current SIP, which will be | sip, r (recordId), form | | | loaded from the SESSION repo to the SIP-Store. | | +-----+----------------------------------------------------------------------------------------+----------------------------------------------------------------------------+ | R | :ref:`STORE_RECORD`: Record - the current record loaded in the form | All columns of the current record from the current table | @@ -291,7 +291,7 @@ Store: *FORM* - F +---------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------+ | Name | Explanation | +=================================+============================================================================================================================================+ - | <FormElement name> | Name of native *FormElement*. To get, exactly and only, the specified *FormElement* (for 'p_id'): *{{p_id:F}}* | + | <FormElement name> | Name of native *FormElement*. To get, exactly and only, the specified *FormElement* (for 'pId'): *{{pId:F}}* | +---------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------+ .. _STORE_SIP: @@ -333,7 +333,7 @@ Store: *RECORD* - R +------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------+ | 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*: *{{p_id:R}}* | + | <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:R}}* | +------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------+ .. _STORE_BEFORE: @@ -350,7 +350,7 @@ 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*: *{{p_id:R}}* | + | <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:R}}* | +------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------+ .. _STORE_CLIENT: @@ -522,7 +522,7 @@ SQL Statement {{SELECT id, name FROM Person}} {{SELECT id, name, IF({{feUser}}=0,'Yes','No') FROM Vorlesung WHERE sem_id={{keySemId:Y}} }} - {{SELECT id, city FROM Address AS adr WHERE adr.p_id={{SELECT id FROM Account AS acc WHERE acc.name={{feUser}} }} }} + {{SELECT id, city FROM Address AS adr WHERE adr.pId={{SELECT id FROM Account AS acc WHERE acc.name={{feUser}} }} }} * Special case for SELECT input fields. To deliver a result array specify an '!' before the SELECT: :: @@ -834,7 +834,7 @@ Fields: |checkType | enum('min|max', 'pattern', | | | | 'number', 'email') | | +---------------+-----------------------------+---------------------------------------------------------------------------------------------------+ -|checkPattern | 'regexp' |If $check_type=='pattern': pattern to match | +|checkPattern | 'regexp' |If $checkType=='pattern': pattern to match | +---------------+-----------------------------+---------------------------------------------------------------------------------------------------+ |onChange | string |List of *FormElement*-names of current form, separated by ', ', If one of the named *FormElements* | | | | change, reload own data / status / mode | @@ -1561,20 +1561,20 @@ sqlBefore / sqlInsert / sqlUpdate / sqlDelete / sqlAfter Example ''''''' -Situation 1: master.x_id=slave.id (1:1) +Situation 1: master.xId=slave.id (1:1) - * Name the action element 'x_id': than {{slaveId}} will be automatically set to the value of 'master.x_id' + * 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 fireing 'sqlInsert', the 'slave.id' of the new created record are copied to master.x_id (the database will + * In case of fireing '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 ...}}` -Situation 2: master.id=slave.x_id (1:n) +Situation 2: master.id=slave.xId (1:n) * Name the action element different to any columnname of the master record (or no name). * Determine the slaveId: `slaveId={{SELECT id FROM slave WHERE slave.xxx={{...}} LIMIT 1}}` @@ -1716,10 +1716,10 @@ To display a report on any given TYPO3 page, create a content element of type 'Q A simple example ^^^^^^^^^^^^^^^^ -Assume that the database has a table person with columns first_name and last_name. To create a simple list of all persons, we can do the following: +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 person_id, CONCAT(first_name, " ", last_name, " ") AS name FROM person + 10.sql = SELECT id AS pId, CONCAT(firstName, " ", lastName, " ") AS name FROM person 10 Stands for a *root level* of the report (see section `Structure`_). 10.sql defines a SQL query for this 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. @@ -1739,7 +1739,7 @@ However, we can modify (wrap) the output by setting the values of various keys f :: - 10.sql = SELECT id AS person_id, CONCAT(first_name, " ", last_name, " ") AS name FROM person + 10.sql = SELECT id AS personId, CONCAT(firstName, " ", lastName, " ") AS name FROM person 10.sep = <br /> HTML output: @@ -1817,10 +1817,10 @@ See the example below: :: - 10.sql = SELECT id AS _person_id, CONCAT(first_name, " ", last_name, " ") AS name FROM person + 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 p_id = {{10.person_id}} + 10.10.sql = SELECT CONCAT(postal_code, " ", city) FROM address WHERE pId = {{10.pId}} 10.10.rbeg = ( 10.10.rend = ) @@ -1934,7 +1934,7 @@ Be careful to: Access to upper column values ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Columns of the upper level result can be accessed via variables, eg. {{10.person_id}} will be replaced by the value in the person_id column. +Columns of the upper level result can be accessed via variables, eg. {{10.pId}} will be replaced by the value in the pId column. +-------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ |**Levels** |A report is divided into levels. Example 1 has 3 levels **10**, **20.25**, **20.25.10** | @@ -1965,10 +1965,10 @@ Report Example 1: 10.sql = SELECT CURDATE() # Show all students from the person table - 20.sql = SELECT p.id AS p_id, p.first_name, " - ", p.last_name FROM person AS p WHERE p.typ LIKE "student" + 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.p_id={{20.p_id}} ORDER BY e.date + 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' @@ -2857,7 +2857,7 @@ Accessing the database :: - 10.sql = SELECT p.first_name FROM exp_person AS p + 10.sql = SELECT p.firstName FROM exp_person AS p .. @@ -2879,7 +2879,7 @@ Accessing the database :: - 10.sql = SELECT p.first_name, p.last_name FROM exp_person AS p + 10.sql = SELECT p.firstName, p.lastName FROM exp_person AS p .. @@ -2913,7 +2913,7 @@ Two columns # Add the formating information as a coloum - 10.sql = SELECT p.first_name, " " , p.last_name, "'<br /'>" FROM exp_person AS p + 10.sql = SELECT p.firstName, " " , p.lastName, "'<br /'>" FROM exp_person AS p .. @@ -3014,29 +3014,29 @@ Two queries: nested with variables :: 10.rend = <br /> # inner query - 10.10.sql = SELECT a.street FROM exp_address AS a WHERE a.pid='{{10.id}}' + 10.10.sql = SELECT a.street FROM exp_address AS a WHERE a.pId='{{10.id}}' 10.10.rend = <br /> * For every record of '10', all assigned records of 10.10 will be printed. Two queries: nested with hidden variables in a table :: - 10.sql = SELECT p.id AS _p_id, p.name FROM exp_person AS p + 10.sql = SELECT p.id AS _pId, p.name FROM exp_person AS p 10.rend = <br /> # inner query - 10.10.sql = SELECT a.street FROM exp_address AS a WHERE a.p_id='{{10.p_id}}' + 10.10.sql = SELECT a.street FROM exp_address AS a WHERE a.pId='{{10.pId}}' 10.10.rend = <br /> Same as above, but written in the nested notation :: 10 { - sql = SELECT p.id AS _p_id, p.name FROM exp_person AS p + sql = SELECT p.id AS _pId, p.name FROM exp_person AS p rend = <br /> 10 { # inner query - sql = SELECT a.street FROM exp_address AS a WHERE a.p_id='{{10.p_id}}' + sql = SELECT a.street FROM exp_address AS a WHERE a.pId='{{10.pId}}' rend = <br /> } } diff --git a/extension/RELEASE.txt b/extension/RELEASE.txt index 4c829aa2551cc3e58e18f48be499677df7d434a2..5e252a995fd1ce3b6ab21ed9bcc9182a4c573cd6 100644 --- a/extension/RELEASE.txt +++ b/extension/RELEASE.txt @@ -12,6 +12,15 @@ Changes 'pattern','allbut','all') NOT NULL DEFAULT 'alnumx' +Features +-------- + + * print.php + + * Install `wkhtmltopdf` on the webserver (http://wkhtmltopdf.org/). + * In config.qfq.ini setup BASE_URL_PRINT, WKHTMLTOPDF. + + Bug Fixes --------- diff --git a/extension/config.qfq.example.ini b/extension/config.qfq.example.ini index 56ad6f37e6317563b8a703be05d8c0284beb98e3..9d1e9415a877cdb459b874b77f66914651cf49ea 100644 --- a/extension/config.qfq.example.ini +++ b/extension/config.qfq.example.ini @@ -41,3 +41,7 @@ DATE_FORMAT = yyyy-mm-dd ;FORM_BS_LABEL_COLUMNS = 3 ;FORM_BS_INPUT_COLUMNS = 6 ;FORM_BS_NOTE_COLUMNS = 3 + +; Configure URL where `wkhtmltopdf` fetches pages and produces PDFs +BASE_URL_PRINT = http://example.com/ +WKHTMLTOPDF = /opt/wkhtmltox/bin/wkhtmltopdf diff --git a/extension/qfq/api/load.php b/extension/qfq/api/load.php index c1ed3c1ba4559db9373e501ba3a26276429945bd..fcc474ec8b725e574484d8f13a3a5679ca2a3f3d 100644 --- a/extension/qfq/api/load.php +++ b/extension/qfq/api/load.php @@ -65,6 +65,8 @@ try { $answer[API_STATUS] = API_ANSWER_STATUS_SUCCESS; $answer[API_MESSAGE] = 'load: success'; $answer[API_FORM_UPDATE] = $data; + $answer[API_ELEMENT_UPDATE] = $data[API_ELEMENT_UPDATE]; + unset($data[API_ELEMENT_UPDATE]); } catch (qfq\UserFormException $e) { $answer[API_MESSAGE] = $e->formatMessage(); diff --git a/extension/qfq/api/print.php b/extension/qfq/api/print.php new file mode 100644 index 0000000000000000000000000000000000000000..080a9c58d6fc9cb692335ce3daabc09770f87a2b --- /dev/null +++ b/extension/qfq/api/print.php @@ -0,0 +1,137 @@ +<?php +/** + * Created by PhpStorm. + * User: ep + * Date: 12/23/15 + * Time: 6:17 PM + */ + + +namespace qfq; + +use qfq; + +require_once(__DIR__ . '/../qfq/store/Config.php'); +require_once(__DIR__ . '/../qfq/Constants.php'); +require_once(__DIR__ . '/../qfq/helper/KeyValueStringParser.php'); + +const PAGEID = 'id'; +const PARAM_GET = 'paramGet'; +const URL_PRINT = 'urlPrint'; + +/** + * Create a temporary file. + * + * @return string + */ +function createEmptyFile() { + return tempnam(sys_get_temp_dir(), "webkitPrintPdf"); +} + +/** + * Set HTML Header to initiate PDF download. + * + * @param $filename + */ +function setHeader($filename) { + + header("Content-Disposition: inline; filename=\"$filename\""); + header("Content-Type: application/pdf"); + header("Content-Transfer-Encoding: binary"); +} + +/** + * Read QFQ config. Only SYSTEM_BASE_URL_PRINT and SYSTEM_WKHTMLTOPDF will be used. + * Check and get all clean _GET Parameter. Build a URL based on SYSTEM_BASE_URL_PRINT and the delivered URL params. + * + * @return array + * @throws UserFormException + * @throws \exception + */ +function init() { + $cfg = new Config(); + + $config = $cfg->readConfig(''); + $param = readCleanGetParam(); + + if (!isset($config[SYSTEM_BASE_URL_PRINT]) || $config[SYSTEM_BASE_URL_PRINT] == '') { + throw new \exception(CONFIG_INI . ' - Missing ' . SYSTEM_BASE_URL_PRINT); + } + + if (!isset($config[SYSTEM_WKHTMLTOPDF]) || $config[SYSTEM_WKHTMLTOPDF] == '') { + throw new \exception(CONFIG_INI . ' - Missing ' . SYSTEM_WKHTMLTOPDF); + } + + if (!is_executable($config[SYSTEM_WKHTMLTOPDF])) { + throw new \exception(CONFIG_INI . ' - ' . SYSTEM_WKHTMLTOPDF . '=' . $config[SYSTEM_WKHTMLTOPDF] . ' - not found or not executable.'); + } + + if (!isset($param[PAGEID]) || $param[PAGEID] == '') { + throw new \exception("Missing GET Parameter '" . PAGEID . "'."); + } + + $setup = array(); + $setup[URL_PRINT] = $config[SYSTEM_BASE_URL_PRINT] . '/?' . KeyValueStringParser::unparse($param, '=', '&'); + $setup[PAGEID] = $param[PAGEID]; + $setup[SYSTEM_WKHTMLTOPDF] = $config[SYSTEM_WKHTMLTOPDF]; + + return $setup; +} + +; + +/** + * Return an array with GET params who are clean - they do not violate $pattern. + * + * @return array + */ +function readCleanGetParam() { + + $param = array(); + $pattern = '^[\-_\.,;:\/a-zA-Z0-9]*$'; // ':alnum:' does not work here in FF + + foreach ($_GET as $key => $value) { + if (preg_match("/$pattern/", $value) === 1) { + $param[$key] = $value; + } + } + + return $param; +} + +/** + * @param array $setup + * @throws \exception + */ +function page2pdf($setup) { + + $rc = 0; + $file = createEmptyFile(); + + $cmd = $setup[SYSTEM_WKHTMLTOPDF] . " '" . $setup[URL_PRINT] . "' " . $file . " > $file.log 2>&1"; + + $line = system($cmd, $rc); + + if ($rc == 0) { + setHeader('print.' . $setup[PAGEID] . '.pdf'); + @readfile($file); + @unlink($file); + @unlink($file . '.log'); + exit; // Do an extremely hard exit here to make sure there are no more additional bytes sent (makes the delivered PDF unusable). + } else { + throw new \exception("Error [RC=$rc] $line: $cmd"); + } +} + + +/** + * Main + */ +try { + $setup = init(); + + page2pdf($setup); + +} catch (\Exception $e) { + echo "Exception: " . $e->getMessage(); +} diff --git a/extension/qfq/qfq/AbstractBuildForm.php b/extension/qfq/qfq/AbstractBuildForm.php index d1272a7bbe0e83a8bee8e91150ac88bc2508ce97..686236080eb4ed116b55fc3b02673a81c95674bd 100644 --- a/extension/qfq/qfq/AbstractBuildForm.php +++ b/extension/qfq/qfq/AbstractBuildForm.php @@ -23,7 +23,6 @@ require_once(__DIR__ . '/../qfq/helper/Support.php'); require_once(__DIR__ . '/../qfq/helper/OnArray.php'); require_once(__DIR__ . '/../qfq/report/Link.php'); - /** * Class AbstractBuildForm * @package qfq @@ -186,10 +185,11 @@ abstract class AbstractBuildForm { $htmlTail = $this->tail(); $htmlSubrecords = $this->doSubrecords(); } + $htmlHidden = $this->buildAdditionalFormElements(); $htmlSip = $this->buildHiddenSip($json); - return ($mode === FORM_LOAD) ? $htmlHead . $htmlElements . $htmlSip . $htmlT3vars . $htmlTail . $htmlSubrecords : $json; + return ($mode === FORM_LOAD) ? $htmlHead . $htmlHidden . $htmlElements . $htmlSip . $htmlT3vars . $htmlTail . $htmlSubrecords : $json; } /** @@ -330,7 +330,7 @@ abstract class AbstractBuildForm { abstract public function getProcessFilter(); /** - * Process all FormElements: build corresponding HTML code. Collect and return all HTML code & JSON. + * Process all FormElements: Collect and return all HTML code & JSON. * * @param $recordId * @param string $filter FORM_ELEMENTS_NATIVE | FORM_ELEMENTS_SUBRECORD | FORM_ELEMENTS_NATIVE_SUBRECORD @@ -351,7 +351,7 @@ abstract class AbstractBuildForm { $html = ''; $flagOutput = false; - // The following 'FormElement.parameter' will never be used during load (fe.type='upload'). + // The following 'FormElement.parameter' will never be used during load (fe.type='upload'). FE_PARAMETER has been already expanded. $skip = [FE_SQL_UPDATE, FE_SQL_INSERT, FE_SQL_DELETE, FE_SQL_AFTER, FE_SQL_BEFORE, FE_PARAMETER]; // get current data record @@ -420,6 +420,7 @@ abstract class AbstractBuildForm { $buildElementFunctionName = 'build' . $this->buildElementFunctionName[$formElement[FE_TYPE]]; $jsonElement = array(); + $elementExtra = ''; // Render pure element $elementHtml = $this->$buildElementFunctionName($formElement, $htmlFormElementName, $value, $jsonElement, $mode); @@ -539,6 +540,20 @@ abstract class AbstractBuildForm { abstract public function doSubrecords(); + /** + * Get all elements from STORE_ADDITIONAL_FORM_ELEMENTS and return them as a string. + * + * @return string + * @throws CodeException + * @throws \qfq\UserFormException + */ + private function buildAdditionalFormElements() { + + $data = $this->store->getStore(STORE_ADDITIONAL_FORM_ELEMENTS); + + return is_array($data) ? implode('', $data) : ''; + } + /** * Create a hidden sip, based on latest STORE_SIP Values. Return complete HTML 'hidden' element. * @@ -560,26 +575,38 @@ abstract class AbstractBuildForm { $sipValue = $sip->queryStringToSip($queryString, RETURN_SIP); - $json[] = $this->getJsonElementUpdate(CLIENT_SIP, $sipValue, FE_MODE_SHOW); + $json[] = $this->getFormElementForJson(CLIENT_SIP, $sipValue, [FE_MODE => FE_MODE_SHOW]); return $this->buildNativeHidden(CLIENT_SIP, $sipValue); } /** - * Create an array with standard elements and add 'form-element', 'value'. + * Create an array with standard elements for 'mode' (hidden, disabled, required) and add 'form-element', 'value'. + * 'Generic Element Update': add via API_ELEMENT_UPDATE 'label' and 'note'. + * All collected data as array - will be later converted to JSON. * - * @param $htmlFormElementName + * @param string $htmlFormElementName * @param string|array $value - * @param string $feMode disabled|readonly|'' + * @param array $formElement * @return array */ - private function getJsonElementUpdate($htmlFormElementName, $value, $feMode) { + private function getFormElementForJson($htmlFormElementName, $value, array $formElement) { - $json = $this->getJsonFeMode($feMode); + $json = $this->getJsonFeMode($formElement[FE_MODE]); $json['form-element'] = $htmlFormElementName; $json['value'] = $value; + if (isset($formElement[FE_LABEL])) { + $key = $formElement[FE_HTML_ID] . HTML_ID_EXTENSION_LABEL; + $json[API_ELEMENT_UPDATE][$key][API_ELEMENT_CONTENT] = $this->buildLabel($htmlFormElementName, $formElement[FE_LABEL]); + } + + if (isset($formElement[FE_NOTE])) { + $key = $formElement[FE_HTML_ID] . HTML_ID_EXTENSION_NOTE; + $json[API_ELEMENT_UPDATE][$key][API_ELEMENT_CONTENT] = $formElement[FE_NOTE]; + } + return $json; } @@ -629,6 +656,22 @@ abstract class AbstractBuildForm { } } + /** + * Builds a label, typically for an html-'<input>'-element. + * + * @param string $htmlFormElementName + * @param string $label + * @return string + */ + public function buildLabel($htmlFormElementName, $label) { + $attributes = Support::doAttribute('for', $htmlFormElementName); + $attributes .= Support::doAttribute('class', 'control-label'); + + $html = Support::wrapTag("<label $attributes>", $label); + + return $html; + } + /** * Takes the current SIP ('form' and additional parameter), set SIP_RECORD_ID=0 and create a new 'NewRecordUrl'. * @@ -664,22 +707,6 @@ abstract class AbstractBuildForm { abstract public function buildRowSubrecord(array $formElement, $elementHtml); - /** - * Builds a label, typically for an html-'<input>'-element. - * - * @param string $htmlFormElementName - * @param string $label - * @return string - */ - public function buildLabel($htmlFormElementName, $label) { - $attributes = Support::doAttribute('for', $htmlFormElementName); - $attributes .= Support::doAttribute('class', 'control-label'); - - $html = Support::wrapTag("<label $attributes>", $label); - - return $html; - } - /** * Builds HTML 'input' element. * Format: <input name="$htmlFormElementName" <type="email|input|password|url" [autocomplete="autocomplete"] [autofocus="autofocus"] @@ -741,7 +768,7 @@ abstract class AbstractBuildForm { $attribute .= $this->getAttributeFeMode($formElement[FE_MODE]); - $json = $this->getJsonElementUpdate($htmlFormElementName, $value, $formElement[FE_MODE]); + $json = $this->getFormElementForJson($htmlFormElementName, $value, $formElement); return "$htmlTag $attribute>$textarea" . $this->getHelpBlock(); @@ -1234,7 +1261,9 @@ abstract class AbstractBuildForm { $attribute .= $this->getAttributeList($formElement, ['autofocus']); $attribute .= $this->getAttributeList($formElement, [F_FE_DATA_PATTERN_ERROR, F_FE_DATA_REQUIRED_ERROR, F_FE_DATA_MATCH_ERROR, F_FE_DATA_ERROR]); - $html = $this->buildNativeHidden($htmlFormElementName, $formElement['unchecked']); + $htmlHidden = $this->buildNativeHidden($htmlFormElementName, $formElement['unchecked']); + $this->store->setVar($htmlFormElementName, $htmlHidden, STORE_ADDITIONAL_FORM_ELEMENTS, false); + $html = ''; $htmlElement = '<input ' . $attribute . '>'; if (isset($formElement['label2'])) { @@ -1247,7 +1276,7 @@ abstract class AbstractBuildForm { $htmlElement, true); $html = Support::wrapTag('<div class="btn-group" data-toggle="buttons">', $html); - $json = $this->getJsonElementUpdate($htmlFormElementName, $valueJson, $formElement[FE_MODE]); + $json = $this->getFormElementForJson($htmlFormElementName, $valueJson, $formElement); return $html; } @@ -1286,7 +1315,9 @@ abstract class AbstractBuildForm { $attribute .= $this->getAttributeList($formElement, ['autofocus']); $attribute .= $this->getAttributeList($formElement, [F_FE_DATA_PATTERN_ERROR, F_FE_DATA_REQUIRED_ERROR, F_FE_DATA_MATCH_ERROR, F_FE_DATA_ERROR]); - $html = $this->buildNativeHidden($htmlFormElementName, $formElement['unchecked']); + $htmlHidden = $this->buildNativeHidden($htmlFormElementName, $formElement['unchecked']); + $this->store->setVar($htmlFormElementName, $htmlHidden, STORE_ADDITIONAL_FORM_ELEMENTS, false); + $html = ''; $html .= '<input ' . $attribute . '>'; if (isset($formElement['label2'])) { @@ -1296,7 +1327,7 @@ abstract class AbstractBuildForm { $html = Support::wrapTag("<label>", $html, true); $html = Support::wrapTag("<div class='checkbox'>", $html, true); - $json = $this->getJsonElementUpdate($htmlFormElementName, $valueJson, $formElement[FE_MODE]); + $json = $this->getFormElementForJson($htmlFormElementName, $valueJson, $formElement); return $html; } @@ -1351,7 +1382,11 @@ abstract class AbstractBuildForm { $attributeBase .= Support::doAttribute('data-load', ($formElement[FE_DYNAMIC_UPDATE] === 'yes') ? 'data-load' : ''); $attributeBase .= $this->getAttributeList($formElement, [F_FE_DATA_PATTERN_ERROR, F_FE_DATA_REQUIRED_ERROR, F_FE_DATA_MATCH_ERROR, F_FE_DATA_ERROR]); - $html = $this->buildNativeHidden(HelperFormElement::prependFormElementNameCheckBoxMulti($htmlFormElementName, 'h'), ''); + $key = HelperFormElement::prependFormElementNameCheckBoxMulti($htmlFormElementName, 'h'); + $htmlHidden = $this->buildNativeHidden($key, ''); + $this->store->setVar($htmlFormElementName, $htmlHidden, STORE_ADDITIONAL_FORM_ELEMENTS, false); + + $html = ''; $attribute = $attributeBase; if (isset($formElement['autofocus'])) { @@ -1382,7 +1417,7 @@ abstract class AbstractBuildForm { $html .= Support::wrapTag("<label class='btn " . $formElement[FE_BUTTON_CLASS] . "$classActive'>", $htmlElement, true); - $json[] = $this->getJsonElementUpdate($htmlFormElementNameUniq, $jsonValue, $formElement[FE_MODE]); + $json[] = $this->getFormElementForJson($htmlFormElementNameUniq, $jsonValue, $formElement); // Init for the next checkbox $attribute = $attributeBase; @@ -1417,7 +1452,11 @@ abstract class AbstractBuildForm { $attributeBase .= Support::doAttribute('data-load', ($formElement[FE_DYNAMIC_UPDATE] === 'yes') ? 'data-load' : ''); $attributeBase .= $this->getAttributeList($formElement, [F_FE_DATA_PATTERN_ERROR, F_FE_DATA_REQUIRED_ERROR, F_FE_DATA_MATCH_ERROR, F_FE_DATA_ERROR]); - $html = $this->buildNativeHidden(HelperFormElement::prependFormElementNameCheckBoxMulti($htmlFormElementName, 'h'), ''); + $key = HelperFormElement::prependFormElementNameCheckBoxMulti($htmlFormElementName, 'h'); + $htmlHidden = $this->buildNativeHidden($key, ''); + $this->store->setVar($htmlFormElementName, $htmlHidden, STORE_ADDITIONAL_FORM_ELEMENTS, false); + + $html = ''; $orientation = ($formElement[FE_MAX_LENGTH] > 1) ? ALIGN_HORIZONTAL : ALIGN_VERTICAL; $checkboxClass = ($orientation === ALIGN_HORIZONTAL) ? 'checkbox-inline' : 'checkbox'; @@ -1470,7 +1509,7 @@ abstract class AbstractBuildForm { } $html .= $htmlElement . $br; - $json[] = $this->getJsonElementUpdate($htmlFormElementNameUniq, $jsonValue, $formElement[FE_MODE]); + $json[] = $this->getFormElementForJson($htmlFormElementNameUniq, $jsonValue, $formElement); } @@ -1582,8 +1621,10 @@ abstract class AbstractBuildForm { $attribute .= Support::doAttribute('autofocus', $formElement['autofocus']); } - $html = $this->buildNativeHidden($htmlFormElementName, $value); + $htmlHidden = $this->buildNativeHidden($htmlFormElementName, $value); + $this->store->setVar($htmlFormElementName, $htmlHidden, STORE_ADDITIONAL_FORM_ELEMENTS, false); + $html = ''; for ($ii = 0; $ii < count($itemValue); $ii++) { $classActive = ''; @@ -1607,7 +1648,7 @@ abstract class AbstractBuildForm { $html = Support::wrapTag('<div class="btn-group" data-toggle="buttons">', $html); - $json = $this->getJsonElementUpdate($htmlFormElementName, $value, $formElement[FE_MODE]); + $json = $this->getFormElementForJson($htmlFormElementName, $value, $formElement); return $html; } @@ -1660,6 +1701,7 @@ abstract class AbstractBuildForm { $attribute .= Support::doAttribute('autofocus', $formElement['autofocus']); } + $html = $this->buildNativeHidden($htmlFormElementName, $value); for ($ii = 0; $ii < count($itemValue); $ii++) { @@ -1699,7 +1741,7 @@ abstract class AbstractBuildForm { $attribute = $attributeBase; } - $json = $this->getJsonElementUpdate($htmlFormElementName, $value, $formElement[FE_MODE]); + $json = $this->getFormElementForJson($htmlFormElementName, $value, $formElement); return $html; } @@ -1760,7 +1802,7 @@ abstract class AbstractBuildForm { $option .= '>' . $itemValue[$ii] . '</option>'; } - $json = $this->getJsonElementUpdate($htmlFormElementName, $jsonValues, $formElement[FE_MODE]); + $json = $this->getFormElementForJson($htmlFormElementName, $jsonValues, $formElement); return '<select ' . $attribute . '>' . $option . '</select>' . $this->getHelpBlock(); } @@ -2226,7 +2268,7 @@ abstract class AbstractBuildForm { // <button type="button" class="file-delete" data-sip="571d1fc9e6974"><span class="glyphicon glyphicon-trash"></span></button> - $json = $this->getJsonElementUpdate($htmlFormElementName, $value, $formElement[FE_MODE]); + $json = $this->getFormElementForJson($htmlFormElementName, $value, $formElement); return $htmlTextDelete . $htmlInputFile . $hiddenSipUpload; } @@ -2329,7 +2371,7 @@ abstract class AbstractBuildForm { $attribute .= $this->getAttributeFeMode($formElement[FE_MODE]); - $json = $this->getJsonElementUpdate($htmlFormElementName, $value, $formElement[FE_MODE]); + $json = $this->getFormElementForJson($htmlFormElementName, $value, $formElement); return "<input $attribute>" . $this->getHelpBlock(); @@ -2461,7 +2503,7 @@ abstract class AbstractBuildForm { $attribute .= $this->getAttributeFeMode($formElement[FE_MODE]); $attribute .= $this->getAttributeList($formElement, [F_FE_DATA_PATTERN_ERROR, F_FE_DATA_REQUIRED_ERROR, F_FE_DATA_MATCH_ERROR, F_FE_DATA_ERROR]); - $json = $this->getJsonElementUpdate($htmlFormElementName, $value, $formElement[FE_MODE]); + $json = $this->getFormElementForJson($htmlFormElementName, $value, $formElement); $element = Support::wrapTag("<textarea $attribute>", htmlentities($value), false); @@ -2642,7 +2684,7 @@ abstract class AbstractBuildForm { // restore parent processed FE's $this->feSpecNative = $tmpStore; - $json = $this->getJsonElementUpdate($htmlFormElementName, $value, $formElement[FE_MODE]); + $json = $this->getFormElementForJson($htmlFormElementName, $value, $formElement); return $html; } diff --git a/extension/qfq/qfq/BuildFormBootstrap.php b/extension/qfq/qfq/BuildFormBootstrap.php index fcd0467d3e74558515be35c36d0bd460da71c5d0..5455a18122c23073166cc619bc9d2139f62afcbe 100644 --- a/extension/qfq/qfq/BuildFormBootstrap.php +++ b/extension/qfq/qfq/BuildFormBootstrap.php @@ -79,7 +79,7 @@ class BuildFormBootstrap extends AbstractBuildForm { $this->wrap[WRAP_SETUP_LABEL][WRAP_SETUP_END] = "</div>"; $this->wrap[WRAP_SETUP_INPUT][WRAP_SETUP_START] = "<div class='col-md-$input'>"; $this->wrap[WRAP_SETUP_INPUT][WRAP_SETUP_END] = "</div>"; - $this->wrap[WRAP_SETUP_NOTE][WRAP_SETUP_START] = "<div class='col-md-$note'>"; + $this->wrap[WRAP_SETUP_NOTE][WRAP_SETUP_START] = "<div class='col-md-$note qfq-note'>"; $this->wrap[WRAP_SETUP_NOTE][WRAP_SETUP_END] = "</div>"; } @@ -321,8 +321,8 @@ class BuildFormBootstrap extends AbstractBuildForm { * @return string */ public function tail() { + $html = ''; -// $html .= $this->buildNewSip(); $deleteUrl = ''; $formId = $this->getFormId(); @@ -416,6 +416,7 @@ EOF; $html = ''; $htmlLabel = ''; + // Label if ($formElement[FE_BS_LABEL_COLUMNS] > 0) { $htmlLabel = $this->buildLabel($htmlFormElementName, $formElement[FE_LABEL]); } @@ -423,10 +424,13 @@ EOF; $html .= $this->customWrap($formElement, $htmlLabel, FE_WRAP_LABEL, $formElement[FE_BS_LABEL_COLUMNS], [$this->wrap[WRAP_SETUP_LABEL][WRAP_SETUP_START], $this->wrap[WRAP_SETUP_LABEL][WRAP_SETUP_END]], $formElement[FE_HTML_ID] . HTML_ID_EXTENSION_LABEL); + // Input $html .= $this->customWrap($formElement, $htmlElement, FE_WRAP_INPUT, $formElement[FE_BS_INPUT_COLUMNS], [$this->wrap[WRAP_SETUP_INPUT][WRAP_SETUP_START], $this->wrap[WRAP_SETUP_INPUT][WRAP_SETUP_END]], $formElement[FE_HTML_ID] . HTML_ID_EXTENSION_INPUT); - $note = Support::wrapTag("<div class='qfq-note'>", $formElement[FE_NOTE], true); + // Note +// $note = Support::wrapTag("<div class='qfq-note'>", $formElement[FE_NOTE], true); + $note = $formElement[FE_NOTE]; $html .= $this->customWrap($formElement, $note, FE_WRAP_NOTE, $formElement[FE_BS_NOTE_COLUMNS], [$this->wrap[WRAP_SETUP_NOTE][WRAP_SETUP_START], $this->wrap[WRAP_SETUP_NOTE][WRAP_SETUP_END]], $formElement[FE_HTML_ID] . HTML_ID_EXTENSION_NOTE); diff --git a/extension/qfq/qfq/Constants.php b/extension/qfq/qfq/Constants.php index ac4df036e16bb4e4d294e67d676587fc7b189e5c..727f712eacb8b833e7464ae119f29c5110c690ee 100644 --- a/extension/qfq/qfq/Constants.php +++ b/extension/qfq/qfq/Constants.php @@ -238,8 +238,10 @@ const STORE_ZERO = "0"; // value: 0, might helpfull if variable is empty but use const STORE_EMPTY = "E"; // value: '', might helpfull if variable is not defined and should result in an empty string instead of {{...}} (cause not replaced) const STORE_SYSTEM = "Y"; // various system values like db connection credentials const STORE_EXTRA = 'X'; // Persistent Store: contains arrays! Not Usefull for user. Used by system. +const STORE_ADDITIONAL_FORM_ELEMENTS = 'A'; // Internal Store to collect FormElements. Typically for 'hidden' elements of radio and checkbox. Helps render those elements at the end of the whole form rendering. const STORE_USE_DEFAULT = "FSRVD"; + // // Store: Definitions / Members // @@ -320,6 +322,9 @@ const SYSTEM_FORM_BS_NOTE_COLUMNS = 'FORM_BS_NOTE_COLUMNS'; const SYSTEM_FORM_BUTTON_ON_CHANGE_CLASS = 'FORM_BUTTON_ON_CHANGE_CLASS'; +const SYSTEM_BASE_URL_PRINT = 'BASE_URL_PRINT'; +const SYSTEM_WKHTMLTOPDF = 'WKHTMLTOPDF'; + // computed automatically during runtime const SYSTEM_PATH_EXT = 'EXT_PATH'; const SYSTEM_SITE_PATH = 'SITE_PATH'; @@ -419,6 +424,8 @@ const API_FIELD_NAME = 'field-name'; const API_FIELD_MESSAGE = 'field-message'; const API_FORM_UPDATE = 'form-update'; const API_ELEMENT_UPDATE = 'element-update'; +const API_ELEMENT_ATTRIBUTE = 'attr'; +const API_ELEMENT_CONTENT = 'content'; const API_JSON_HIDDEN = 'hidden'; const API_JSON_DISABLED = 'disabled'; diff --git a/extension/qfq/qfq/QuickFormQuery.php b/extension/qfq/qfq/QuickFormQuery.php index a29eb279e42b13e49bac910b739157a51106976d..92d406d7b083e832b948c1f2dd711bfc67381198 100644 --- a/extension/qfq/qfq/QuickFormQuery.php +++ b/extension/qfq/qfq/QuickFormQuery.php @@ -117,7 +117,6 @@ class QuickFormQuery { * @param bool $phpUnit * @throws CodeException * @throws UserFormException - * @internal param string $bodytext */ public function __construct(array $t3data = array(), $phpUnit = false) { @@ -344,6 +343,9 @@ class QuickFormQuery { throw new CodeException("This statement should never be reached", ERROR_CODE_SHOULD_NOT_HAPPEN); } + if (is_array($data)) { + $data = $this->groupElementUpdateEntries($data); + } return $data; } @@ -697,6 +699,30 @@ class QuickFormQuery { } + /** + * Searches the whole array $dataArray on the second level for API_ELEMENT_UPDATE. + * All found elements collect under $collect[API_ELEMENT_UPDATE]... . Leave the rest unchanged. + * + * @param array $dataArray + * @return array to build JSON + */ + private function groupElementUpdateEntries(array $dataArray) { + $collect = array(); + + foreach ($dataArray as $data) { + + if (isset($data[API_ELEMENT_UPDATE])) { + foreach ($data[API_ELEMENT_UPDATE] as $key => $item) { + $collect[API_ELEMENT_UPDATE][$key] = $item; + } + unset($data[API_ELEMENT_UPDATE]); + } + $collect[] = $data; + } + + return $collect; + } + /** * Process the SQL Queries from bodytext. Return the output. * diff --git a/extension/qfq/qfq/store/Config.php b/extension/qfq/qfq/store/Config.php new file mode 100644 index 0000000000000000000000000000000000000000..fe646b5ebbc39cb0d2d9370da61e9aa758b14fb2 --- /dev/null +++ b/extension/qfq/qfq/store/Config.php @@ -0,0 +1,86 @@ +<?php +/** + * Created by PhpStorm. + * User: crose + * Date: 3/6/17 + * Time: 8:47 PM + */ + +namespace qfq; + +use qfq; + +require_once(__DIR__ . '/../../qfq/Constants.php'); + +class Config { + + /** + * Read config.qfq.ini. + * + * @throws CodeException + * @throws qfq\UserFormException + */ + + public function readConfig($fileConfigIni = '') { + + if ($fileConfigIni == '') { + // Production Path to CONFIG_INI + $fileConfigIni = __DIR__ . '/../../../../../' . CONFIG_INI; + if (!file_exists($fileConfigIni)) { + // PHPUnit Path to CONFIG_INI + $fileConfigIni = __DIR__ . '/../../../' . CONFIG_INI; + } + } + + try { + $config = parse_ini_file($fileConfigIni, false); + + } catch (\Exception $e) { + throw new qfq\UserFormException ("Error read file " . $fileConfigIni . ": " . $e->getMessage(), ERROR_IO_READ_FILE); + } + + $config = self::renameConfigElements($config); + + return $config; + } + + /** + * Rename Elements defined in config.qfq.ini to more appropriate in user interaction. + * E.g.: in config.qfq.ini everything is in upper case and word space is '_'. In Form.parameter it's lowercase and camel hook. + * + * @param array $config + * @return array + */ + private static function renameConfigElements(array $config) { + + // oldname > newname + $setting = [ + [SYSTEM_FORM_BS_LABEL_COLUMNS, F_BS_LABEL_COLUMNS], + [SYSTEM_FORM_BS_INPUT_COLUMNS, F_BS_INPUT_COLUMNS], + [SYSTEM_FORM_BS_NOTE_COLUMNS, F_BS_NOTE_COLUMNS], + [SYSTEM_FORM_DATA_PATTERN_ERROR, F_FE_DATA_PATTERN_ERROR], + [SYSTEM_FORM_DATA_REQUIRED_ERROR, F_FE_DATA_REQUIRED_ERROR], + [SYSTEM_FORM_DATA_MATCH_ERROR, F_FE_DATA_MATCH_ERROR], + [SYSTEM_FORM_DATA_ERROR, F_FE_DATA_ERROR], + [SYSTEM_CSS_CLASS_QFQ_FORM, F_CLASS], + [SYSTEM_CSS_CLASS_QFQ_FORM_PILL, F_CLASS_PILL], + [SYSTEM_CSS_CLASS_QFQ_FORM_BODY, F_CLASS_BODY], + [SYSTEM_FORM_BUTTON_ON_CHANGE_CLASS, F_BUTTON_ON_CHANGE_CLASS], + ]; + + foreach ($setting as $row) { + $oldName = $row[0]; + $newName = $row[1]; + + if (isset($config[$oldName])) { + $config[$newName] = $config[$oldName]; + if ($oldName != $newName) { + unset($config[$oldName]); + } + } + } + + return $config; + } + +} \ No newline at end of file diff --git a/extension/qfq/qfq/store/Store.php b/extension/qfq/qfq/store/Store.php index 9a4ebfbc2d368eb6d86ac4c1ee0e2931b81de2af..ea95f84f3f5b0faa6ae2310a6bf11d83540f3beb 100644 --- a/extension/qfq/qfq/store/Store.php +++ b/extension/qfq/qfq/store/Store.php @@ -19,6 +19,7 @@ require_once(__DIR__ . '/../../qfq/Constants.php'); require_once(__DIR__ . '/../../qfq/store/Sip.php'); //require_once(__DIR__ . '/../../qfq/store/Session.php'); require_once(__DIR__ . '/../../qfq/Database.php'); +require_once(__DIR__ . '/../../qfq/store/Config.php'); /* * Stores: @@ -163,7 +164,8 @@ class Store { STORE_ZERO => false, STORE_EMPTY => false, STORE_SYSTEM => false, - STORE_EXTRA => false + STORE_EXTRA => false, + STORE_ADDITIONAL_FORM_ELEMENTS => false ]; self::fillSystemStore($fileConfigIni); @@ -181,23 +183,8 @@ class Store { */ private static function fillSystemStore($fileConfigIni = '') { - if ($fileConfigIni == '') { - // Production Path to CONFIG_INI - $fileConfigIni = __DIR__ . '/../../../../../' . CONFIG_INI; - if (!file_exists($fileConfigIni)) { - // PHPUnit Path to CONFIG_INI - $fileConfigIni = __DIR__ . '/../../../' . CONFIG_INI; - } - } - - try { - $config = parse_ini_file($fileConfigIni, false); - - } catch (\Exception $e) { - throw new qfq\UserFormException ("Error read file " . $fileConfigIni . ": " . $e->getMessage(), ERROR_IO_READ_FILE); - } - - $config = self::renameConfigElements($config); + $cfg = new Config(); + $config = $cfg->readConfig($fileConfigIni); // Defaults Support::setIfNotSet($config, SYSTEM_DATE_FORMAT, 'yyyy-mm-dd'); @@ -218,45 +205,6 @@ class Store { self::setStore($config, STORE_SYSTEM, true); } - /** - * Rename Elements defined in config.qfq.ini to more appropriate in user interaction. - * E.g.: in config.qfq.ini everything is in upper case and word space is '_'. In Form.parameter it's lowercase and camel hook. - * - * @param array $config - * @return array - */ - private static function renameConfigElements(array $config) { - - // oldname > newname - $setting = [ - [SYSTEM_FORM_BS_LABEL_COLUMNS, F_BS_LABEL_COLUMNS], - [SYSTEM_FORM_BS_INPUT_COLUMNS, F_BS_INPUT_COLUMNS], - [SYSTEM_FORM_BS_NOTE_COLUMNS, F_BS_NOTE_COLUMNS], - [SYSTEM_FORM_DATA_PATTERN_ERROR, F_FE_DATA_PATTERN_ERROR], - [SYSTEM_FORM_DATA_REQUIRED_ERROR, F_FE_DATA_REQUIRED_ERROR], - [SYSTEM_FORM_DATA_MATCH_ERROR, F_FE_DATA_MATCH_ERROR], - [SYSTEM_FORM_DATA_ERROR, F_FE_DATA_ERROR], - [SYSTEM_CSS_CLASS_QFQ_FORM, F_CLASS], - [SYSTEM_CSS_CLASS_QFQ_FORM_PILL, F_CLASS_PILL], - [SYSTEM_CSS_CLASS_QFQ_FORM_BODY, F_CLASS_BODY], - [SYSTEM_FORM_BUTTON_ON_CHANGE_CLASS, F_BUTTON_ON_CHANGE_CLASS], - ]; - - foreach ($setting as $row) { - $oldName = $row[0]; - $newName = $row[1]; - - if (isset($config[$oldName])) { - $config[$newName] = $config[$oldName]; - if ($oldName != $newName) { - unset($config[$oldName]); - } - } - } - - return $config; - } - /** * QFQ might be called via Typo3 (index.php) or directly via AJAX (directory: api). The * @param array $config diff --git a/extension/qfq/tests/phpunit/BuildFormPlainTest.php b/extension/qfq/tests/phpunit/BuildFormPlainTest.php index f7cd55a6d3e8787f307bb30d5765646ee0874285..2546d02f120e43f0802fb743a3e7699e334b2bae 100644 --- a/extension/qfq/tests/phpunit/BuildFormPlainTest.php +++ b/extension/qfq/tests/phpunit/BuildFormPlainTest.php @@ -80,34 +80,36 @@ class BuildFormPlainTest extends AbstractDatabaseTest { $build = new \qfq\BuildFormPlain($form, array(), [$formElement]); + $label['123-l'][API_ELEMENT_CONTENT] = '<label for="name:1" class="control-label" >Name</label>'; + $result = $build->buildInput($formElement, 'name:1', '', $json); $this->assertEquals('<input id="123" name="name:1" class="form-control" type="input" maxlength="255" value="" data-hidden="no" data-disabled="no" data-required="no" ><div class="help-block with-errors hidden"></div>', $result); - $this->assertEquals([FE_MODE_HIDDEN => '', 'disabled' => false, FE_MODE_REQUIRED => '', 'form-element' => 'name:1', 'value' => ''], $json); + $this->assertEquals([FE_MODE_HIDDEN => '', 'disabled' => false, FE_MODE_REQUIRED => '', 'form-element' => 'name:1', 'value' => '', API_ELEMENT_UPDATE => $label], $json); // CheckType $formElement['checkType'] = SANITIZE_ALLOW_MIN_MAX; $formElement['checkPattern'] = '1|10'; $result = $build->buildInput($formElement, 'name:1', '', $json); $this->assertEquals('<input id="123" name="name:1" class="form-control" type="input" maxlength="255" value="" min="1" max="10" data-hidden="no" data-disabled="no" data-required="no" ><div class="help-block with-errors hidden"></div>', $result); - $this->assertEquals([FE_MODE_HIDDEN => '', 'disabled' => false, FE_MODE_REQUIRED => '', 'form-element' => 'name:1', 'value' => '', 'disabled' => false], $json); + $this->assertEquals([FE_MODE_HIDDEN => '', 'disabled' => false, FE_MODE_REQUIRED => '', 'form-element' => 'name:1', 'value' => '', 'disabled' => false, API_ELEMENT_UPDATE => $label], $json); $formElement['checkType'] = SANITIZE_ALLOW_PATTERN; $formElement['checkPattern'] = '^[a-z]*$'; $result = $build->buildInput($formElement, 'name:1', '', $json); $this->assertEquals('<input id="123" name="name:1" class="form-control" type="input" maxlength="255" value="" pattern="^[a-z]*$" data-hidden="no" data-disabled="no" data-required="no" ><div class="help-block with-errors hidden"></div>', $result); - $this->assertEquals([FE_MODE_HIDDEN => '', 'disabled' => false, FE_MODE_REQUIRED => '', 'form-element' => 'name:1', 'value' => '', 'disabled' => false], $json); + $this->assertEquals([FE_MODE_HIDDEN => '', 'disabled' => false, FE_MODE_REQUIRED => '', 'form-element' => 'name:1', 'value' => '', 'disabled' => false, API_ELEMENT_UPDATE => $label], $json); $formElement['checkType'] = SANITIZE_ALLOW_DIGIT; $formElement['checkPattern'] = ''; $result = $build->buildInput($formElement, 'name:1', '', $json); $this->assertEquals('<input id="123" name="name:1" class="form-control" type="input" maxlength="255" value="" pattern="^[\d]*$" data-hidden="no" data-disabled="no" data-required="no" ><div class="help-block with-errors hidden"></div>', $result); - $this->assertEquals([FE_MODE_HIDDEN => '', 'disabled' => false, FE_MODE_REQUIRED => '', 'form-element' => 'name:1', 'value' => '', 'disabled' => false], $json); + $this->assertEquals([FE_MODE_HIDDEN => '', 'disabled' => false, FE_MODE_REQUIRED => '', 'form-element' => 'name:1', 'value' => '', 'disabled' => false, API_ELEMENT_UPDATE => $label], $json); $formElement['checkType'] = SANITIZE_ALLOW_EMAIL; $formElement['checkPattern'] = ''; $result = $build->buildInput($formElement, 'name:1', '', $json); $this->assertEquals('<input id="123" name="name:1" class="form-control" type="input" maxlength="255" value="" pattern="^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$" data-hidden="no" data-disabled="no" data-required="no" ><div class="help-block with-errors hidden"></div>', $result); - $this->assertEquals([FE_MODE_HIDDEN => '', 'disabled' => false, FE_MODE_REQUIRED => '', 'form-element' => 'name:1', 'value' => '', 'disabled' => false], $json); + $this->assertEquals([FE_MODE_HIDDEN => '', 'disabled' => false, FE_MODE_REQUIRED => '', 'form-element' => 'name:1', 'value' => '', 'disabled' => false, API_ELEMENT_UPDATE => $label], $json); $formElement['checkType'] = ''; $formElement['checkPattern'] = ''; @@ -118,13 +120,13 @@ class BuildFormPlainTest extends AbstractDatabaseTest { $formElement['maxLength'] = 40; $result = $build->buildInput($formElement, 'name:1', '', $json); $this->assertEquals('<input id="123" name="name:1" class="form-control" type="input" size="40" maxlength="40" value="" data-hidden="no" data-disabled="no" data-required="no" ><div class="help-block with-errors hidden"></div>', $result); - $this->assertEquals([FE_MODE_HIDDEN => '', 'disabled' => false, FE_MODE_REQUIRED => '', 'form-element' => 'name:1', 'value' => '', 'disabled' => false], $json); + $this->assertEquals([FE_MODE_HIDDEN => '', 'disabled' => false, FE_MODE_REQUIRED => '', 'form-element' => 'name:1', 'value' => '', 'disabled' => false, API_ELEMENT_UPDATE => $label], $json); // maxlength bigger than physical spec: $formElement['maxLength'] = 1000; $result = $build->buildInput($formElement, 'name:1', '', $json); $this->assertEquals('<input id="123" name="name:1" class="form-control" type="input" size="40" maxlength="255" value="" data-hidden="no" data-disabled="no" data-required="no" ><div class="help-block with-errors hidden"></div>', $result); - $this->assertEquals([FE_MODE_HIDDEN => '', 'disabled' => false, FE_MODE_REQUIRED => '', 'form-element' => 'name:1', 'value' => '', 'disabled' => false], $json); + $this->assertEquals([FE_MODE_HIDDEN => '', 'disabled' => false, FE_MODE_REQUIRED => '', 'form-element' => 'name:1', 'value' => '', 'disabled' => false, API_ELEMENT_UPDATE => $label], $json); // no size, no maxlength and column not in primary table $formElement2 = $formElement; @@ -157,18 +159,18 @@ class BuildFormPlainTest extends AbstractDatabaseTest { $formElement['placeholder'] = 'Please type a name'; $result = $build->buildInput($formElement, 'name:1', 'Hello World', $json); $this->assertEquals('<input id="123" name="name:1" class="form-control" type="input" size="40" maxlength="255" value="Hello World" placeholder="Please type a name" title="Nice Tooltip" data-hidden="no" data-disabled="no" data-required="no" ><div class="help-block with-errors hidden"></div>', $result); - $this->assertEquals([FE_MODE_HIDDEN => '', 'disabled' => false, FE_MODE_REQUIRED => '', 'form-element' => 'name:1', 'value' => 'Hello World', 'disabled' => false], $json); + $this->assertEquals([FE_MODE_HIDDEN => '', 'disabled' => false, FE_MODE_REQUIRED => '', 'form-element' => 'name:1', 'value' => 'Hello World', 'disabled' => false, API_ELEMENT_UPDATE => $label], $json); // textarea $formElement['size'] = '40,10'; $result = $build->buildInput($formElement, 'name:1', 'Hello World', $json); $this->assertEquals('<textarea id="123" name="name:1" class="form-control" cols="40" rows="10" placeholder="Please type a name" title="Nice Tooltip" data-hidden="no" data-disabled="no" data-required="no" >Hello World</textarea><div class="help-block with-errors hidden"></div>', $result); - $this->assertEquals([FE_MODE_HIDDEN => '', 'disabled' => false, FE_MODE_REQUIRED => '', 'form-element' => 'name:1', 'value' => 'Hello World', 'disabled' => false], $json); + $this->assertEquals([FE_MODE_HIDDEN => '', 'disabled' => false, FE_MODE_REQUIRED => '', 'form-element' => 'name:1', 'value' => 'Hello World', 'disabled' => false, API_ELEMENT_UPDATE => $label], $json); $formElement['size'] = ' 40 , 10 '; $result = $build->buildInput($formElement, 'name:1', 'Hello World', $json); $this->assertEquals('<textarea id="123" name="name:1" class="form-control" cols="40" rows="10" placeholder="Please type a name" title="Nice Tooltip" data-hidden="no" data-disabled="no" data-required="no" >Hello World</textarea><div class="help-block with-errors hidden"></div>', $result); - $this->assertEquals([FE_MODE_HIDDEN => '', 'disabled' => false, FE_MODE_REQUIRED => '', 'form-element' => 'name:1', 'value' => 'Hello World', 'disabled' => false], $json); + $this->assertEquals([FE_MODE_HIDDEN => '', 'disabled' => false, FE_MODE_REQUIRED => '', 'form-element' => 'name:1', 'value' => 'Hello World', 'disabled' => false, API_ELEMENT_UPDATE => $label], $json); } /** @@ -493,6 +495,7 @@ class BuildFormPlainTest extends AbstractDatabaseTest { parent::setUp(); + $this->store->unsetStore(STORE_ADDITIONAL_FORM_ELEMENTS); $this->executeSQLFile(__DIR__ . '/fixtures/Generic.sql', true); // Defaults diff --git a/extension/qfq/tests/phpunit/FillStoreFormTest.php b/extension/qfq/tests/phpunit/FillStoreFormTest.php new file mode 100644 index 0000000000000000000000000000000000000000..d9a471bbbfef20c374742f76a5ebd07ea8b09abe --- /dev/null +++ b/extension/qfq/tests/phpunit/FillStoreFormTest.php @@ -0,0 +1,41 @@ +<?php +/** + * Created by PhpStorm. + * User: crose + * Date: 1/2/16 + * Time: 11:10 PM + */ + +namespace qfq; + +require_once(__DIR__ . '/../../qfq/store/FillStoreForm.php'); + +//require_once(__DIR__ . '/../../qfq/exceptions/CodeException.php'); + + +class FillStoreFormTest extends \PHPUnit_Framework_TestCase { + + /** + * @throws CodeException + * @throws UserFormException + */ + public function testFake() { + + # Violates SANITIZE class: SANITIZE string is always an empty string. + # Access are cached: use new variables for every test. + + # Check '' +// $this->assertEquals('', Sanitize::sanitize('', SANITIZE_ALLOW_ALNUMX), "SANITIZE_ALNUMX fails"); + $this->assertEquals('', ''); + + } +// +// /** +// * @expectedException \qfq\UserFormException +// */ +// public function testSanitizeExceptionMinMaxMissingMin() { +// Sanitize::sanitize(56, SANITIZE_ALLOW_MIN_MAX, '|45'); +// } +// + +}