diff --git a/.gitignore b/.gitignore index f3634d5bdf0b1acfe44151cc8928fefe43a1f5cd..8b093469de16a3ff5b64c9f6ea98afcbe51247e0 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ .webprj .vscode .run +.phpunit.result.cache nbprojec nohup.out @@ -78,3 +79,4 @@ composer.lock __pycache__ package-lock.json +/extension/.phpunit.result.cache diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8061bfbf5356fbb5fc2dbef824c3786a9fd62caf..a8be0e21a3d3c10f537ea57a595e820867dea4e8 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -31,8 +31,8 @@ snapshot: script: - make VERSION=${VERSION} phpunit_snapshot - chmod a+r qfq_${VERSION}_*.zip - - echo "mv qfq_${VERSION}_*.zip qfq_${VERSION}_${RELDATE}-${CI_BUILD_REF_NAME}.zip" - - mv qfq_${VERSION}_*.zip qfq_${VERSION}_${RELDATE}-${CI_BUILD_REF_NAME}.zip + - echo "mv qfq_${VERSION}_*.zip qfq_${VERSION}_${RELDATE}-${CI_COMMIT_REF_NAME}.zip" + - mv qfq_${VERSION}_*.zip qfq_${VERSION}_${RELDATE}-${CI_COMMIT_REF_NAME}.zip - scp qfq_${VERSION}_*.zip w16:qfq/snapshots/ - mv qfq_${VERSION}_*.zip build/qfq.zip diff --git a/.readthedocs.yml b/.readthedocs.yml index b977cbddbf74da9c2923a377fb46909faa617843..7ad741af604aba0f9151d8273789ca3a85f11e6e 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -5,6 +5,12 @@ # Required version: 2 +# Set the OS, Python version and other tools you might need +build: + os: ubuntu-22.04 + tools: + python: "3.12" + # Build documentation in the docs/ directory with Sphinx sphinx: configuration: Documentation/conf.py @@ -14,8 +20,7 @@ formats: - pdf - epub -# Optionally set the version of Python and requirements required to build your docs +# Requirements to build your docs python: - version: 3.7 install: - requirements: Documentation/docker-sphinx-qfq/requirements.txt \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a50c7fa20a1cfe22b196bf39ce3e95e6d03c098..59dcd6135e7c87c4880813073631706d16427a41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,17 +1,30 @@ -.. ================================================== .. ================================================== .. -================================================== .. 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 +.. ================================================== +.. ================================================== +.. ================================================== +.. 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:: +.. Add Images: .. image:: ./Images/a4.jpg +.. +.. +.. Admonitions +.. .. note:: .. important:: .. tip:: .. warning:: .. Color: (blue) (orange) (green) (red) -.. .. Definition: +.. +.. Definition: .. some text becomes strong (only one line) .. description has to indented @@ -30,83 +43,308 @@ Version 23.x.x Date: <date> -Notes ^^^^^ +Notes +^^^^^ + +Features +^^^^^^^^ + +Bug Fixes +^^^^^^^^^ + +Version 23.10.1 +--------------- + +Date: 22.10.2023 + +Notes +^^^^^ + +Features +^^^^^^^^ + +* #15098 / Implemented qfqFunction in QFQ variable for usage in forms. +* Doc: Replace many places single back tick by double back tick. Add hint for 'Row size too large'. Added Enis & Jan + as Developer. Add hint use mysqldump with one row per record. + +Bug Fixes +^^^^^^^^^ + +* #17003 / inline edit - dark mode has wrong css path. +* #17075 / Fix broken '... AS _restClient'. +* #17091 / upload_Incorrect_integer_value_fileSize. +* RTD: Fix broken readthedocs rendering. + +Version 23.10.0 +--------------- + +Date: 05.10.2023 + +Features +^^^^^^^^ -Features ^^^^^^^^ +* #16350 / QFQ Table 'FormSubmiLog': update recordid after insert. +* #16350 / sql.log: reference to FormSubmitLog entry. All SQL statements generated by one HTTP Post (Form Submit) can + be identified. +* #16350 / Do not log Dirty. +* #16350 / If the T3 instance is behind a proxy, log HTTP_X_REAL_IP instead of REMOTE_ADDR in logfiles. +* #16584 / FormEditor Report: Default without statistics. +* #16589 / Implemented language configuration in backend for tt-content type qfq. +* #16798 / Report inline edit v2. Improved search inside inline edit report. Whole content will be searched. Added + ability to switch the editor to dark mode. +* Doc: Refactor description of {{random:V}}. +* Doc: Add config option 'protectedFolderCheck'. -Bug Fixes ^^^^^^^^^ +Bug Fixes +^^^^^^^^^ + +* #16573 / Fixed wrong built date and datetime string if default value was given. +* #16574 / Added multiple siteConfigurations compatibility for typo3 v10 and 11. +* #16616 / Fixed typeahead api query response problem if typeahead sql is not used. +* #16664 / Fix multi db user error. +* #16975 / Fix problem if a 'Form Submit' contains more than 64kB data. This can happen easily for 'fabric' elements. + +Version 23.6.4 +-------------- + +Date: 26.06.2023 + +Bug Fixes +^^^^^^^^^ + +* #16485 / TypeAhead: strpos() string, array given error. +* #16488 / Missing default values break saving records. New custom FE.parameter.defaultValue. +* #16491 / FE Typ Upload - JS failure: document.querySelector() is null. + +Version 23.6.3 +-------------- + +Date: 22.06.2023 + +Bug Fixes +^^^^^^^^^ + +* #16478 / Rest API: Fixed stream_get_contents failure in PHP8.1 Generic Error. + +Version 23.6.2 +-------------- + +Date: 21.06.2023 + +Bug Fixes +^^^^^^^^^ + +* #16475 / Spontaneous spaces in HTML emails. +* #16476 / SQL columns Text/Blog with default empty string becomes "''" + + +Version 23.6.1 +-------------- + +Date: 16.06.2023 + +Notes +^^^^^ + +* QFQ is Typo3 V11 compatible + +Bug Fixes +^^^^^^^^^ + +* #16372 / Upload Element 'Undefined index htmlDownloadButton' und 'unknown mode ID' +* #16381 / Form title dynamic update broken. +* #16392 / Reevaluate sanitize class for each store. +* Fix db column enum dropdown 'data truncated' +* DB Update 'alter index' pre check if exists +* FE: Upload - fix undefined index. + +Version 23.6.0 +-------------- + +Date: 09.06.2023 + +Features +^^^^^^^^ + +* Typo3 V11 compatible +* #9579 / Multiform with Process Row. +* #15770 / httpOrigin: Parameter missing in QFQ Config. +* #16285 / PHP V8 Migration. +* #16364 / T3 >=V10 pageSlug and ForwardPage. +* Add .phpunit.result.cache to .gitignore. +* Add bullet-orange.gif. +* Add index createdFeUserFormId to table FormSubmitLog. +* Doc: Add link to icons. Update use-case self-registration and add info in case of namesake. Unify word 'spam' to ' + junk'. + Add bootstrap links. +* Remove Form/FormElement parameter 'feGroup'. +* QFQ Typo3 Updatewizard: Sort output by page and tt-content ordering. Add page uid an title to be reported. + Inline Edit: add tt_content uid and header as tooltip. +* Update package.json. +* Update to PHPUnit version 9. + +Bug Fixes +^^^^^^^^^ + +* #12468 / Form: Dynamic Update Form.title after save. +* #14636 / 'Clear Me'-cross lighter and 2 px up. +* #15445 / Doc: JS Output Widged added. +* #15497 / Delete link: a) broken for 'table', b) broken with 'r:3'. +* #15527 / Form/subrecord: class 'qfq-table-50' no impact. +* #15654 / Form FE required 'Undefined index Error'. +* #15659 / Multi Form: Datetime Picker not aligned. +* #15691 / T3 V10: Export PDF (PDF Generator) invalid link causes error. +* #15726 / Formelement upload stays required when changed from required to hidden. +* #15729 / Form: text readonly input should show newline as '<br>'. +* #15747 / Typeahead does not work with special character. Added croatian special chars (Ć,ć,ÄŒ,Ä,Ä,Ä‘,Å ,Å¡,Ž,ž) to alnumx + whitelist. +* #15763 / Search/Refactor: a) search for ID, b) message if nothing is found - New highlighting for disabled content. + Added output for not found in search. +* #15773 / TypeAhead missing check type warning. +* #15813 / Pressing 'Enter' in Form asks: Do you really want to delete the record? +* #15875 / Unecessary qfq-config save. +* #15905 / FE type time undefined index. +* #15913 / Refactor: Footer displayed at the wrong place. +* #15921 / Frontend edit Report Codemirror resizeable. +* #16004 / Template Group Fields broken. +* #16046 / Datepicker not initialized in Template Group. +* #16051 / Dropdown menu: Download file broken with ZIP File. +* #16055 / Dropdown option with render mode 3 format problem. +* #16064 / Dynamic Update Checkbox and Form Store in parameter. +* #16073 / saveButtonActive is disabled after first save. +* #16201 / Character Count Class Quotation Mark. +* #16204 / TypeAhead Prefetch Expects String Array Given. +* #16228 / extraButtonPassword unhide broken. +* #16264 / Multi DB Broken since QFQ V23.2.0. +* #16273 / TinyMCE image upload path incorrect for T3 v10 and upwards. + +Version 23.3.1 +-------------- + +Date: 31.03.2023 + +Bug Fixes +^^^^^^^^^ + +* #15920 / QFQ variable evaluation broken: wrong variable name + +Version 23.3.0 +-------------- + +Date: 30.03.2023 + +Features +^^^^^^^^ + +* #15491 / Search refactor redesign and T3 V10 compatiblity: underscore character, counter fixed, page alias and slug + handling. +* #15529 / Form/subrecord: Design Titel / Box. +* #15570 / Changed DB handling in class FormAction. Fixed multi-db problem with this. Included change for check of + existing form editor report. +* #15579 / Ability for searching all possible characters, includes not replaced QFQ variables. +* #15627 / Added character count and maxLength feature for TinyMCE. +* Add various icons to documentation. +* Doc Form.rst: reference 'orderColumn'. +* Doc Report.rst: fix typo, add icons, improved example for tablesorter. +* Add indexes for table FormSubmitLog. +* FormEditor: Show Tablename in pill 'table definition'. +* FormEditor: FE subrecord > show container id below name. + +Bug Fixes +^^^^^^^^^ + +* #14754 / Using double quotes in tableview config caused sql error. +* #15483 / Protected folder check fixed. Changed default of wget, preventing errors. Changed handling from protected + folder check, new once a day. +* #15521 / FormEditor assigns always container, even none is selected. Change handling of form variables from type + select, + radio and checkbox. Expected 0 from client request instead of empty string. +* #15523 / Search/Refactor broken for Multi-DB. +* #15626 / Multi-DB: FormEditor save error. Version 23.2.0 -------------- Date: 05.02.2023 -Notes ^^^^^ +Notes +^^^^^ * QMANR, QMARK: new SQL prepared function to format Swiss Matrikelnumbers and mark found text. -* Refactoring: Search form to support refactoring. Search over tables FormElement,Form,tt_content,pages. On T3 page ' - form', replace `file=_formEditor` by `file={{file:SU:::_formEditor}}` +* Refactoring: Search form to support refactoring. Search over tables FormElement,Form,tt_content,pages. + On T3 page 'form', replace `file=_formEditor` by `file={{file:SU:::_formEditor}}` -Features ^^^^^^^^ +Features +^^^^^^^^ -* 7650 / For FormElement which are required: `indicateRequired=0|1` show or hide the 'required asterix'. -* 10013 / FormEditor: Several textarea inputs changed to 'editor' incl. SQL syntax highlighting. -* 14995 / Support for refactoring: search in QFQ Form/FormElement and Typo3 pages/tt-content tables. +* #7650 / For FormElement which are required: `indicateRequired=0|1` show or hide the 'required asterix'. +* #10013 / FormEditor: Several textarea inputs changed to 'editor' incl. SQL syntax highlighting. +* #14995 / Support for refactoring: search in QFQ Form/FormElement and Typo3 pages/tt-content tables. * QMANR: SQL prepared function to format Swiss Matrikelnumbers -Bug Fixes ^^^^^^^^^ +Bug Fixes +^^^^^^^^^ + +* #7899 / Fe.type=password / retype / required: always complain about missing value - bug has been fixed earlier. +* #8668 / Fix that if a pill becomes disabled, child FE should not respected during save. +* #15168 / Fixed language form save problem for t3 v >= 9. +* #15420 / TypeAhead Prefetch triggers 'form changed' after save. +* #15482 / Added missing password font file. -* 8668 / Fix that if a pill becomes disabled, child FE should not respected during save. -* 15168 / Fixed language form save problem for t3 v >= 9. -* 15420 / TypeAhead Prefetch triggers 'form changed' after save. -* 15482 / Added missing password font file. Version 23.1.1 -------------- Date: 22.01.2023 -Notes ^^^^^ +Notes +^^^^^ * Additional option to dynamically fill the STORE_SYSTEM: fillStoreSystemBySqlRow -Features ^^^^^^^^ +Features +^^^^^^^^ -* # 11980 / check protected folder for access from outside after new qfq installation or update. -* # 13566 / Cleanup: Delete config-example.qfq.php -* # 15053 / New dynamic QFQ config option: fillStoreSystemBySqlRow. +* #11980 / check protected folder for access from outside after new qfq installation or update. +* #13566 / Cleanup: Delete config-example.qfq.php +* #15053 / New dynamic QFQ config option: fillStoreSystemBySqlRow. * Update SANITIZE_ALLOW_EMAIL_MESSAGE to be more generic: 'Invalid email format' * Update Documentation/Form.rst: Added QFQ Function QMANR(manr), broken ref to restAuthorization, fixed typos. * Update copyright year -Bug Fixes ^^^^^^^^^ +Bug Fixes +^^^^^^^^^ -* # 2665 / Fixed not working dynamic update with tinymce and codemirror. Improved frontend edit visualization, elements in window will be resized automatically. -* # 11517 / Fixed Bug not showing extraInfoButton on certain FormElements like: TextArea, Select, Checkbox, Radio, Upload, Editor. -* # 12066 / Form: Enter as submit forward not working. +* #2665 / Fixed not working dynamic update with tinymce and codemirror. Improved frontend edit visualization, elements + in window will be resized automatically. +* #11517 / Fixed Bug not showing extraInfoButton on certain FormElements like: TextArea, Select, Checkbox, Radio, + Upload, Editor. +* #12066 / Form: Enter as submit forward not working. Version 23.1.0 -------------- Date: 05.01.2023 -Features ^^^^^^^^ +Features +^^^^^^^^ -* # 6250 / Enhance layout subrecord. -* # 10003 / Fieldset: stronger visualize group. -* # 15036 / FormElement subrecord summary additional row(s). -* # 15154 / FormElement input check via regexp: trim whitespace before regexp. +* #6250 / Enhance layout subrecord. +* #10003 / Fieldset: stronger visualize group. +* #15036 / FormElement subrecord summary additional row(s). +* #15154 / FormElement input check via regexp: trim whitespace before regexp. -Bug Fixes ^^^^^^^^^ +Bug Fixes +^^^^^^^^^ -* # 14305 / Bug "No form found with this id". -* # 14997 / Tablesorter: Filter value shown but no effect after 'Browser Back' -* # 15191 / Broken datepicker in template group. -* # 15214 / Inline report save history. -* # 15229 / Typeahead and qfq-clear-me will be initialized after adding new fields. -* # 15230 / Set own button class for dropdown. -* # 15314 / FE & BE Email now filled in 'sipForTypo3Vars' and therefore available in API calls. -* # 15316 / config.baseUrl might be overwritten if no scheme or multiple urls are given. +* #14305 / Bug "No form found with this id". +* #14997 / Tablesorter: Filter value shown but no effect after 'Browser Back' +* #15191 / Broken datepicker in template group. +* #15214 / Inline report save history. +* #15229 / Typeahead and qfq-clear-me will be initialized after adding new fields. +* #15230 / Set own button class for dropdown. +* #15314 / FE & BE Email now filled in 'sipForTypo3Vars' and therefore available in API calls. +* #15316 / config.baseUrl might be overwritten if no scheme or multiple urls are given. * Doc: Fix missing ! in doc: sqlValidate. Fix index undefined htmlAllow. Fix filemtime() Warning. * Fixed date to datetime convert bug. * Fixed error coming from typeahead when array is empty. @@ -117,27 +355,30 @@ Version 22.12.1 Date: 18.12.2022 -Notes ^^^^^ +Notes +^^^^^ * New Button Sizes: btn-tiny, btn-small. * Caching for on the fly rendered files (PDF, Excel, ZIP). * Inline Report edit saves history to T3. -Features ^^^^^^^^ +Features +^^^^^^^^ -* # 5715 / Caching for on the fly rendred files (PDF, Excel, ZIP). -* # 6250 / Enhance form layout: a) Subrecord, b) Subrecord-Title -* # 9927 / Do QFQ Update (config, table defintion) only if there is a BE user logged in the current session. -* # 12503 / Detect likely unwanted UPDATE statement with missing WHERE. +* #5715 / Caching for on the fly rendred files (PDF, Excel, ZIP). +* #6250 / Enhance form layout: a) Subrecord, b) Subrecord-Title +* #9927 / Do QFQ Update (config, table defintion) only if there is a BE user logged in the current session. +* #12503 / Detect likely unwanted UPDATE statement with missing WHERE. * New Button Sizes: btn-tiny, btn-small -Bug Fixes ^^^^^^^^^ +Bug Fixes +^^^^^^^^^ -* # 14305 / Inline Report edit saves history to Typo 3. -* # 14506 / Tablesorter: after form close, already defined filter criteria in tablesorter will be applied. -* # 15188 / Clean up config to T3 parameter. -* # 15193 / No form found with this id error in MultiDB Setup. -* # 15206 / Form Save: Internal Error in MultiDB Setup. +* #14305 / Inline Report edit saves history to Typo 3. +* #14506 / Tablesorter: after form close, already defined filter criteria in tablesorter will be applied. +* #15188 / Clean up config to T3 parameter. +* #15193 / No form found with this id error in MultiDB Setup. +* #15206 / Form Save: Internal Error in MultiDB Setup. * Fix Doc: latest CCS/JS includes. Version 22.12.0 @@ -145,7 +386,8 @@ Version 22.12.0 Date: 11.12.2022 -Notes ^^^^^ +Notes +^^^^^ * Customable list of FE names that won't be logged to SubmitFormLog. Default is 'password'. List of FE can be customized per QFQ installation and/or per form. @@ -165,33 +407,36 @@ Notes ^^^^^ * QFQ Config option: forceSmtpSender - force sendmail to use specified sender address. -Features ^^^^^^^^ +Features +^^^^^^^^ -* # 8187 / Subrecord: Dynamically computed subrecord 'new' button. Per row customizeable edit and delete button. -* # 11892 / Link/Buttons in HTML table column sort/filterable via tablesorter. Qualifier 'Y:...'. -* # 13945 / Link/Buttons: Text before and after link. -* # 14320 / TinyMCE: htmlAllow parameter - client and serverside check. Removes not allowed html tags. -* # 15075 / New QFQ config sendmail configuration: forceSmtpSender -* # 15111 / Special column name: _saveZip. Save zip on server. +* #8187 / Subrecord: Dynamically computed subrecord 'new' button. Per row customizeable edit and delete button. +* #11892 / Link/Buttons in HTML table column sort/filterable via tablesorter. Qualifier 'Y:...'. +* #13945 / Link/Buttons: Text before and after link. +* #14320 / TinyMCE: htmlAllow parameter - client and serverside check. Removes not allowed html tags. +* #15075 / New QFQ config sendmail configuration: forceSmtpSender +* #15111 / Special column name: _saveZip. Save zip on server. * Documentation-develop/*.md: CONFIG.md, Reformat * Fix undefined index in SendMail.php. Show message 'forceSmtpSender is active' in catch all mail. Skip forcing sender address in redirectAll mode. -Bug Fixes ^^^^^^^^^ -* # 8891 / doNotLogColumn: list of FE.name in qfq config and/or form parameter. Value of such column will be logged as +Bug Fixes +^^^^^^^^^ + +* #8891 / doNotLogColumn: list of FE.name in qfq config and/or form parameter. Value of such column will be logged as '*hide in log*'. List default is 'password'. -* # 13716 / Mask typed password: Added new (local) font file. -* # 14245 / After change via datetimepicker form save button keeps disabled. -* # 14303 / Datetime broken with picker -* # 14323 / Render mode: Fixed unit test. -* # 15013,#15014 / (Again) Excel Import: fix import of regions. -* # 15026 / Multiple checkbox dynamic update not working correctly. -* # 15027 / Excel Export error when BE user is logged in. -* # 15028 / Encryption with special column name broken. -* # 15079 / Dynamic Update / Readonly: lost value -* # 15090 / typeahead: fixed bug with given empty array. -* # 15091 / Hidden FormElement: After save, value in typeahead input will be refreshed with actual data. Usability +* #13716 / Mask typed password: Added new (local) font file. +* #14245 / After change via datetimepicker form save button keeps disabled. +* #14303 / Datetime broken with picker +* #14323 / Render mode: Fixed unit test. +* #15013,#15014 / (Again) Excel Import: fix import of regions. +* #15026 / Multiple checkbox dynamic update not working correctly. +* #15027 / Excel Export error when BE user is logged in. +* #15028 / Encryption with special column name broken. +* #15079 / Dynamic Update / Readonly: lost value +* #15090 / typeahead: fixed bug with given empty array. +* #15091 / Hidden FormElement: After save, value in typeahead input will be refreshed with actual data. Usability of processReadOnly is given even when dynamic update is active. Jquery serialize caused problems with checkbox setup. Version 22.11.0 @@ -199,46 +444,52 @@ Version 22.11.0 Date: 27.11.2022 -Features ^^^^^^^^ +Features +^^^^^^^^ -* # 15075 / sendmail: special option forceSmtpSender +* #15075 / sendmail: special option forceSmtpSender -Bug Fixes ^^^^^^^^^ +Bug Fixes +^^^^^^^^^ -* # 11325 / SQL CALL() - insert() and select() should be supported now. -* # 14622 / FormElement `unexpected error` on save. -* # 15005 / TypeAhead Dynamic Update Mode Hidden to Required -* # 15014 / Excel import broken if multiple regions defined. -* # 15048 / STORE_USER broken. +* #11325 / SQL CALL() - insert() and select() should be supported now. +* #14622 / FormElement `unexpected error` on save. +* #15005 / TypeAhead Dynamic Update Mode Hidden to Required +* #15014 / Excel import broken if multiple regions defined. +* #15048 / STORE_USER broken. Version 22.10.1 --------------- Date: 24.10.2022 -Notes ^^^^^ +Notes +^^^^^ * Migration of existing Typo3 V9 to V10: Check #12584, #12440 -Features ^^^^^^^^ +Features +^^^^^^^^ -* # 12584, #12440 / Typo3 V10 Migration Script Replace Alias Patterns -* # 14738 / Datetimepicker: verify various setups -* # 14802 / DateTime own class and unit tests +* #12584, #12440 / Typo3 V10 Migration Script Replace Alias Patterns +* #14738 / Datetimepicker: verify various setups +* #14802 / DateTime own class and unit tests * Add doc to table vertical column via CSS, * Add qfq-badge-* to doc -Bug Fixes ^^^^^^^^^ +Bug Fixes +^^^^^^^^^ -* # 14618 / Changed logic behind getting first value from array (check for typeahead array was already given but was +* #14618 / Changed logic behind getting first value from array (check for typeahead array was already given but was placed after the checkForEncryptedValue function). No exception possible. Function name changed to generic. Added new function is_multi_array to check if array is multidimensional and return true or false. -* # 14736 / Datetimepicker: FormElement.parameter.dateFormat - required. -* # 14755 / Fix for typeahead problem in t3 v9 and higher. Typeahead url will be setup same as save.php. Results to an absolute path. -* # 14813 / Report: dbIndex - missing per level definition -* # 14844 / Fixed bug of no working dynamic update and empty output in Form Store with Datetimepicker. -* # 14857 / String expected, array given. -* # 14885 / Fabric zoom problem. +* #14736 / Datetimepicker: FormElement.parameter.dateFormat - required. +* #14755 / Fix for typeahead problem in t3 v9 and higher. Typeahead url will be setup same as save.php. Results to an + absolute path. +* #14813 / Report: dbIndex - missing per level definition +* #14844 / Fixed bug of no working dynamic update and empty output in Form Store with Datetimepicker. +* #14857 / String expected, array given. +* #14885 / Fabric zoom problem. @@ -247,104 +498,117 @@ Version 22.10.0 Date: 04.10.2022 -Notes ^^^^^ +Notes +^^^^^ * TinyMCE Feature "upload images via drag'n'drop" now supported. -Features ^^^^^^^^ +Features +^^^^^^^^ -* # 14813 / Report: dbIndex - missing per level definition -* # 12474 / Check BaseConfigURL if it is given and the the last char is '/' -* # 12452 / baseUrl: add automatically '/' at end -* # 10782 / Tiny MCE: Image Upload & Image drag'n'drop +* #14813 / Report: dbIndex - missing per level definition +* #12474 / Check BaseConfigURL if it is given and the the last char is '/' +* #12452 / baseUrl: add automatically '/' at end +* #10782 / Tiny MCE: Image Upload & Image drag'n'drop -Bug Fixes ^^^^^^^^^ +Bug Fixes +^^^^^^^^^ -* # 14803 / MultiDB (different DB hosts): broken 'show table definition' in FormEditor - -* # 14791 / datetimepicker: undefined index: timeParts[2] -* # 14619 / Added note to missed bug fix. -* # 12630 / Added code commentary. Reformatted code. getUniqueFileName function changed. New logFileMessages implemented. -* # 14463 / llow statusbar (resize button included) to show as default. -* # 14455 / Drag and drop triggers record lock. -* # 13818 / htmlspecialchars_decode() - expects parameter 1 to be string, array given +* #14803 / MultiDB (different DB hosts): broken 'show table definition' in FormEditor - +* #14791 / datetimepicker: undefined index: timeParts[2] +* #14619 / Added note to missed bug fix. +* #12630 / Added code commentary. Reformatted code. getUniqueFileName function changed. New logFileMessages implemented. +* #14463 / llow statusbar (resize button included) to show as default. +* #14455 / Drag and drop triggers record lock. +* #13818 / htmlspecialchars_decode() - expects parameter 1 to be string, array given Version 22.9.2 -------------- Date: 22.09.2022 -Features ^^^^^^^^ +Features +^^^^^^^^ -* # 12630 / dateTimePicker refactored. Mode: 'qfq', 'browser', 'no'. Default: 'qfq' +* #12630 / dateTimePicker refactored. Mode: 'qfq', 'browser', 'no'. Default: 'qfq' * Add release notes to update UZH CD to latest version and to remove QFQ JS/CSS includes from custom typoscript template. -Bug Fixes ^^^^^^^^^ +Bug Fixes +^^^^^^^^^ -* # 14619 / dateTimePicher: missing popup on first click, record not dirty +* #14619 / dateTimePicher: missing popup on first click, record not dirty Version 22.9.1 -------------- Date: 18.09.2022 -Notes ^^^^^ +Notes +^^^^^ In 22.5.0 CodeMirror has been implemented for Report editing in the front end. QFQ delivers now the minimized Javascript version too. Please check http://docs.qfq.io/en/master/Installation.html#setup-css-js for updated JS includes. T3 extension 'UZH_CD' should be updated to latest version >=22.09.18! -Attention: since 22.09.07 UZH_CD includes all JS/CSS (exception: fabric). Remove all QFQ standard JS/CSS includes like -datetimepicker, codemirror, ... from your typoscript template(s). +Attention: since 22.09.07 UZH_CD includes all JS/CSS (exception: fabric). Remove all QFQ standard JS/CSS includes +like datetimepicker, codemirror, ... from your typoscript template(s). -Features ^^^^^^^^ -* # 14616 / Fabric updated to version 5. Annotation of images should now be supported again by modern browsers. -* # 10011,#10012 / RedirectAllMailto: optionally dynamically set by current logged in FE or BE User. -* # 14718 / Added prepared MySQL statement: QLEFT(), QRIGHT(). -* # 14635 / Add comment to update /etc/ImageMagick-6/policy.xml in case of 'convert-im6.q16: no images defined'. -* # 9052 / Report: CodeMirror with SQL Syntax Highlight in FE - minimized JS versions. -* # T3 QFQ extension config: change 'enter-as-submit' from string to boolean. +Features +^^^^^^^^ -Bug Fixes ^^^^^^^^^ +* #14616 / Fabric updated to version 5. Annotation of images should now be supported again by modern browsers. +* #10011,#10012 / RedirectAllMailto: optionally dynamically set by current logged in FE or BE User. +* #14718 / Added prepared MySQL statement: QLEFT(), QRIGHT(). +* #14635 / Add comment to update /etc/ImageMagick-6/policy.xml in case of 'convert-im6.q16: no images defined'. +* #9052 / Report: CodeMirror with SQL Syntax Highlight in FE - minimized JS versions. +* #T3 QFQ extension config: change 'enter-as-submit' from string to boolean. + +Bug Fixes +^^^^^^^^^ -* # 12452 / T3 QFQ extension setup: Fixes broken string in doc for 'baseUrl'. +* #12452 / T3 QFQ extension setup: Fixes broken string in doc for 'baseUrl'. Version 22.9.0 -------------- Date: 04.09.2022 -Features ^^^^^^^^ +Features +^^^^^^^^ * Report.rst: Add description details to tablesorter view saver. Fix Typo QESC_SQUOTE. -Bug Fixes ^^^^^^^^^ +Bug Fixes +^^^^^^^^^ -* # 14660 / Tablesorter View Saver: Broken in Multi-DB Setup -* # 14622 / Temp workaround for broken exception - Encrypt/Decrypt detection. +* #14660 / Tablesorter View Saver: Broken in Multi-DB Setup +* #14622 / Temp workaround for broken exception - Encrypt/Decrypt detection. Version 22.8.1 -------------- Date: 28.08.2022 -Features ^^^^^^^^ +Features +^^^^^^^^ -* # 9221 / If FE_TYPEAHEAD_LDAP or FE_TYPEAHEAD_SQL is set, skip forcing maxlength to table column definition. Instead +* #9221 / If FE_TYPEAHEAD_LDAP or FE_TYPEAHEAD_SQL is set, skip forcing maxlength to table column definition. Instead use custom supplied value. * Minor update form 'FormElement' * Refactor $buildElementFunctionName: remove indirect function calls from AbtractBuildForm.php to simplify debugging. * Extend Support::getColumnSize with further MariaDB column type sizes * Extension config description baseUrl: updated. -Bug Fixes ^^^^^^^^^ +Bug Fixes +^^^^^^^^^ -* # 14618 / string expected, null given. -* # 14611 / MultiDB: broken 'show table definition' in FormEditor. Add pill 'Table Definition' to form FormElement. -* # 14590 / emptyTypeAheadRequest'. -* # 9281 / QFQ system tables: Allow STRICT_TRANS_TABLES - Default '0' is now set for all INT columns. DB-Update will +* #14618 / string expected, null given. +* #14611 / MultiDB: broken 'show table definition' in FormEditor. Add pill 'Table Definition' to form FormElement. +* #14590 / emptyTypeAheadRequest'. +* #9281 / QFQ system tables: Allow STRICT_TRANS_TABLES - Default '0' is now set for all INT columns. DB-Update will change column definition. TEXT columns still don't have a default: Before MariaDB 10.2.1, BLOB and TEXT columns could not be assigned a DEFAULT value. This restriction was lifted in MariaDB 10.2.1. * Removed empty typeahead request at the beginning @@ -354,28 +618,33 @@ Version 22.8.0 Date: 22.08.2022 -Notes ^^^^^ +Notes +^^^^^ * Add Enis Nuredini as developer. -Features ^^^^^^^^ +Features +^^^^^^^^ -* # 11262 / stored Procedure: QNBSP - replace space by ' '. -* # 14270 / FormEditor: add pill 'Table Definition'. -* # 14321 / Remember last used pill: on/off via QFQ config or per form. -* # 14588 / FormEditor: Remove bell for debugging Form/Session mode. +* #11262 / stored Procedure: QNBSP - replace space by ' '. +* #14270 / FormEditor: add pill 'Table Definition'. +* #14321 / Remember last used pill: on/off via QFQ config or per form. +* #14588 / FormEditor: Remove bell for debugging Form/Session mode. * FormEditor: Column fe.mode shows now fe.modeSql by default. If empty, fe.mode will be shown. * QFQ_Encrypt_Decrypt - pre version - not finalized. -Bug Fixes ^^^^^^^^^ -* # 4018 / Typeahead Attack Detected: Implement renamed keywords _ta_query, _ta_prefetch. Fix broken detection of typeahead mode. -* # 13689 / Enter auf Eingabefeld mit ungültigem Wert führt zu blurry Seite. -* # 14288 / Upload: removed upload + save > triggers general error. -* # 14291 / Doc minor fix: Upload Doku table broken. -* # 14292 / Upload: mode=required broken for advanced upload (non primary column) after first save. -* # 14302 / Doc minor fix. SanitizeTest.php: minor const replacement. -* # 14587 / Use case: Self Registration - table definiton broken in doc: Changed Table Person, to include index and +Bug Fixes +^^^^^^^^^ + +* #4018 / Typeahead Attack Detected: Implement renamed keywords _ta_query, _ta_prefetch. Fix broken detection of + typeahead mode. +* #13689 / Enter auf Eingabefeld mit ungültigem Wert führt zu blurry Seite. +* #14288 / Upload: removed upload + save > triggers general error. +* #14291 / Doc minor fix: Upload Doku table broken. +* #14292 / Upload: mode=required broken for advanced upload (non primary column) after first save. +* #14302 / Doc minor fix. SanitizeTest.php: minor const replacement. +* #14587 / Use case: Self Registration - table definiton broken in doc: Changed Table Person, to include index and auto increment + authExpire default Null. * SYSTEM_SECURITY_GET_MAX_LENGTH: take care that minimum is 32. * Update Release.rst and Installation.rst: add missing Codemirror.css|js @@ -386,96 +655,104 @@ Version 22.5.0 Date: 16.05.2022 -Notes ^^^^^ +Notes +^^^^^ Please update your CSS and JS (T3 Main Template > Setup) to include the new DateTime Picker. Forms cannot save if the JS files are missing:: -page.includeCSS.file08 = typo3conf/ext/qfq/Resources/Public/Css/bootstrap-datetimepicker.min.css page.includeCSS.file09 -= typo3conf/ext/qfq/Resources/Public/Css/codemirror.css page.includeJS.file14 = -typo3conf/ext/qfq/Resources/Public/JavaScript/moment.min.js page.includeJS.file15 = -typo3conf/ext/qfq/Resources/Public/JavaScript/bootstrap-datetimepicker.min.js page.includeJS.file16 = -typo3conf/ext/qfq/Resources/Public/JavaScript/codemirror.min.js page.includeJS.file17 = -typo3conf/ext/qfq/Resources/Public/JavaScript/code-mirror-mode/sql/sql.min.js - -Features ^^^^^^^^ - -* # 9052 / Report frontend editing: CodeMirror with syntax highlight. -* # 10096 / Date time picker. -* # 10782 / TinyMCE: image upload via drag'n'drop. 'fileUploadPath=fileadmin/...'. -* # 13440 / Form: remember last used pill in localstore. -* # 13543 / System - base url: multiple base url can be configured. -* # 13562 / System - base url: if not set, set base url. -* # 13572 / Form Load: better error message on try to load non existent record. -* # 13581 / Copy to clipboard: small animation on icon. -* # 13657 / Added two new twig report files to system: FormEditorTwig and autoCronTwig. -* # 13679 / tablesorter/subrecord: view-saver added. -* # 13788 / Date time picker: selectable weekdays. +page.includeCSS.file08 = typo3conf/ext/qfq/Resources/Public/Css/bootstrap-datetimepicker.min.css +page.includeCSS.file09 = typo3conf/ext/qfq/Resources/Public/Css/codemirror.css +page.includeJS.file14 = typo3conf/ext/qfq/Resources/Public/JavaScript/moment.min.js +page.includeJS.file15 = typo3conf/ext/qfq/Resources/Public/JavaScript/bootstrap-datetimepicker.min.js +page.includeJS.file16 = typo3conf/ext/qfq/Resources/Public/JavaScript/codemirror.min.js +page.includeJS.file17 = typo3conf/ext/qfq/Resources/Public/JavaScript/code-mirror-mode/sql/sql.min.js + +Features +^^^^^^^^ + +* #9052 / Report frontend editing: CodeMirror with syntax highlight. +* #10096 / Date time picker. +* #10782 / TinyMCE: image upload via drag'n'drop. 'fileUploadPath=fileadmin/...'. +* #13440 / Form: remember last used pill in localstore. +* #13543 / System - base url: multiple base url can be configured. +* #13562 / System - base url: if not set, set base url. +* #13572 / Form Load: better error message on try to load non existent record. +* #13581 / Copy to clipboard: small animation on icon. +* #13657 / Added two new twig report files to system: FormEditorTwig and autoCronTwig. +* #13679 / tablesorter/subrecord: view-saver added. +* #13788 / Date time picker: selectable weekdays. * Update / doc: datetimepicker requires moment.js - put it in example code before datetimepicker. * Update / QFQ config note to baseUrl. * Skip selenium to speed up. * Move Marc Egger to further contributor. -Bug Fixes ^^^^^^^^^ - -* # 9520 / Button Save inside form. -* # 10646 / typeAhead: maxLength by default 512. -* # 11134 / Set samesite header for cookies. -* # 11325 / SQL CALL is handled seperate. All statements will execute. If only a SELECT statement is used in PROCEDURE then the output will shown, otherwise not. -* # 13658 / qfq.json: remove world readable. -* # 12483 / qfq.json - Doc: update all references of config.qfq.php to qfq.json -* # 12484 / config.render: old Both, new: Single. -* # 13084,9724 / Dropdown list on iOS not clickable. -* # 13527 / Form Multi: by default set r=0. -* # 13677 / FormEditor: Preview disabled if 'new.required' is given. -* # 13722 / Detect and fix pdfunite problems "Gen inside xref table too large (bigger than INT_MAX)" via pdf2ps, ps2pdf. -* # 13767 / Date time picker: up down button better design if required. -* # 13827 / Date time picker should not offer user credentials (like username / password). -* # 13751 / Date time picker dynamic update aware. -* # 13797 / Checkbox: dynamic update (single and multi) fixed. -* # 13818 / typeahead: fix for key/value problem: expects parameter 1 to be string, array given. -* # 13842 / Broken SIP: wkhtml and qfqpdf. -* # 13933 / Broken SIP: exception handling buggy. -* # 13860 / Safari: broken typeahead input. +Bug Fixes +^^^^^^^^^ + +* #9520 / Button Save inside form. +* #10646 / typeAhead: maxLength by default 512. +* #11134 / Set samesite header for cookies. +* #11325 / SQL CALL is handled seperate. All statements will execute. If only a SELECT statement is used in PROCEDURE + then the output will shown, otherwise not. +* #13658 / qfq.json: remove world readable. +* #12483 / qfq.json - Doc: update all references of config.qfq.php to qfq.json +* #12484 / config.render: old Both, new: Single. +* #13084,9724 / Dropdown list on iOS not clickable. +* #13527 / Form Multi: by default set r=0. +* #13677 / FormEditor: Preview disabled if 'new.required' is given. +* #13722 / Detect and fix pdfunite problems "Gen inside xref table too large (bigger than INT_MAX)" via pdf2ps, ps2pdf. +* #13767 / Date time picker: up down button better design if required. +* #13827 / Date time picker should not offer user credentials (like username / password). +* #13751 / Date time picker dynamic update aware. +* #13797 / Checkbox: dynamic update (single and multi) fixed. +* #13818 / typeahead: fix for key/value problem: expects parameter 1 to be string, array given. +* #13842 / Broken SIP: wkhtml and qfqpdf. +* #13933 / Broken SIP: exception handling buggy. +* #13860 / Safari: broken typeahead input. Version 21.12.0 -------------- Date: 13.12.2021 -Notes ^^^^^ +Notes +^^^^^ * New HTML to PDF renderer: puppeteer. Wrapped and used by qfqpdf. Solves various JS and CSS problems of wkhtml. Install: - mkdir /opt/qfqpdf; cd /opt/qfqpdf curl -L -o qfqpdf https://www.math.uzh.ch/repo/qfqpdf/current/qfqpdf-linux + mkdir /opt/qfqpdf; cd /opt/qfqpdf + curl -L -o qfqpdf https://www.math.uzh.ch/repo/qfqpdf/current/qfqpdf-linux chmod a+x qfqpdf * Module php-curl not needed anymore - has been replaced by php-stream. -Features ^^^^^^^^ - -* # 4812 / Subrecord - check for reserved Typo3 keywords (id, type, L) and throw exception. -* # 10145 / Typeahead Min Length = 0 is now possible. -* # 10715 / qfqpdf (puppeteer). -* # 13113 / Rewrite REST Client as php stream, add contentFile option. -* # 13242 / Apply given sanitize class to all defined stores. -* # 13330 / Multi Form: Upload. -* # 13333 / Option: Switch off attack detect. -* # 13496 / TinyMCEfontselect - Fontselect and fontsize are removed from the default configuration. -* # 12511 / Typo3 Store: new variables. -* # 13526 / QFQ tablesorter: Rename 'private view' to 'personal view' and 'public view' to 'group view'. -* # 12541 / Page and link without pageAlias. - -Bug Fixes ^^^^^^^^^ - -* # 3446 / Unknown permission mode: 'logged_in' -* # 9268 / SELECT with outer brackets not recognized as SELECT -* # 13030 / Max length cuts - line endings \r\n has been counted as two chars. During input they are counted as 1 and +Features +^^^^^^^^ + +* #4812 / Subrecord - check for reserved Typo3 keywords (id, type, L) and throw exception. +* #10145 / Typeahead Min Length = 0 is now possible. +* #10715 / qfqpdf (puppeteer). +* #13113 / Rewrite REST Client as php stream, add contentFile option. +* #13242 / Apply given sanitize class to all defined stores. +* #13330 / Multi Form: Upload. +* #13333 / Option: Switch off attack detect. +* #13496 / TinyMCEfontselect - Fontselect and fontsize are removed from the default configuration. +* #12511 / Typo3 Store: new variables. +* #13526 / QFQ tablesorter: Rename 'private view' to 'personal view' and 'public view' to 'group view'. +* #12541 / Page and link without pageAlias. + +Bug Fixes +^^^^^^^^^ + +* #3446 / Unknown permission mode: 'logged_in' +* #9268 / SELECT with outer brackets not recognized as SELECT +* #13030 / Max length cuts - line endings \r\n has been counted as two chars. During input they are counted as 1 and therefore on data load the string has been cutted. -* # 13139 / Tablesorter: some elements are in front of a sticky title row -* # 13507 / QFQ function should work without 'sql=' -* # 13525 / makefile adjusted for multiple users +* #13139 / Tablesorter: some elements are in front of a sticky title row +* #13507 / QFQ function should work without 'sql=' +* #13525 / makefile adjusted for multiple users Version 21.6.0 @@ -483,23 +760,26 @@ Version 21.6.0 Date: 29.06.2021 -Notes ^^^^^ +Notes +^^^^^ * For full image HEIC/HEIF support, please install package ``libheif-examples``. -Features ^^^^^^^^ +Features +^^^^^^^^ -* # 8945 / Best practice `self-registration`_ -* # 12551 / Add QFQ bootstrap diagram -* # 12615 / Implements silent HEIC/HEIF conversion to png. -* # 12636 / New FormElement.encoding = 'single tick' +* #8945 / Best practice `self-registration`_ +* #12551 / Add QFQ bootstrap diagram +* #12615 / Implements silent HEIC/HEIF conversion to png. +* #12636 / New FormElement.encoding = 'single tick' * Form.rst: Update doc of Multi-Form -Bug Fixes ^^^^^^^^^ +Bug Fixes +^^^^^^^^^ -* # 12701 / Bug 'spaces in mails'. Replaces '<br>' by '<br>\r\n' -* # 9371 / MultiSQL now accepts `_id` too. -* # 12674 / QFQ Function subheader: skip deleted +* #12701 / Bug 'spaces in mails'. Replaces '<br>' by '<br>\r\n' +* #9371 / MultiSQL now accepts `_id` too. +* #12674 / QFQ Function subheader: skip deleted * Remove keySemId and keySemIdUser - outdated / deprecated since 0.19.0 (pre 09.2017) * Download.php: Fix broken variable $this->$downloadDebugLog. @@ -508,40 +788,44 @@ Version 21.5.1 Date: 17.05.2021 -Notes ^^^^^ +Notes +^^^^^ * The `log` directory was moved into the `qfqProject` directory in version 21.2.0 for new installations. But if the directory `fileadmin/protected/log` already exists then QFQ keeps storing logs there. This was added to release notes of 21.2.0 in hindsight. -Features ^^^^^^^^ +Features +^^^^^^^^ -* # 12183 / Download table as csv -* # 12159 / Make url paths absolute (relative to baseUrl) +* #12183 / Download table as csv +* #12159 / Make url paths absolute (relative to baseUrl) -Bug Fixes ^^^^^^^^^ +Bug Fixes +^^^^^^^^^ -* # 12516 / Password Hashing Exception +* #12516 / Password Hashing Exception Version 21.5.0 -------------- Date: 02.05.2021 -Features ^^^^^^^^ +Features +^^^^^^^^ * CodingGuideline.rst: add Form Best practice Bug Fixes ^^^^^^^^^ -* # 10505 / Drag'n'Drop broken on Multi DB Instance - all checks done -* # 10754 / Clean up stale requirements.txt -* # 11769 / Missing description table in Form.rst -* # 12352 / Form As Json: copy via JSON in FormEditor broken. -* # 12398 / Fix check required for uploads -* # 12475 / During QFQ update take care that all system tables exist. -* # 12479 / Remove unwrap('p-tag') for TinyMCE - currently, this breaks regular consecutive <p> tags +* #10505 / Drag'n'Drop broken on Multi DB Instance - all checks done +* #10754 / Clean up stale requirements.txt +* #11769 / Missing description table in Form.rst +* #12352 / Form As Json: copy via JSON in FormEditor broken. +* #12398 / Fix check required for uploads +* #12475 / During QFQ update take care that all system tables exist. +* #12479 / Remove unwrap('p-tag') for TinyMCE - currently, this breaks regular consecutive <p> tags Version 21.4.0 @@ -668,9 +952,9 @@ Notes Features ^^^^^^^^ -* # 10286 / Download Links: Glyphicon selbst wählen/ausblenden -* # 11878 / Purge extension option config.documentation -* # 6793 / Source files for ZIP archives might now specified with a path/filename how they are called inside the ZIP. +* #10286 / Download Links: Glyphicon selbst wählen/ausblenden +* #11878 / Purge extension option config.documentation +* #6793 / Source files for ZIP archives might now specified with a path/filename how they are called inside the ZIP. * log directory was moved into qfqProject directory Bug Fixes @@ -747,8 +1031,8 @@ Date: 25.06.2020 Bug Fixes ^^^^^^^^^ - * #10641 / TypeAheadTag: Fehler beim gleichzeitigen anlegen mehrerer neuer Tags - * #10794 / Documentation: Crontab entry more clearly +* #10641 / TypeAheadTag: Fehler beim gleichzeitigen anlegen mehrerer neuer Tags +* #10794 / Documentation: Crontab entry more clearly Version 20.6.1 @@ -770,27 +1054,29 @@ Date: 14.06.2020 Notes ^^^^^ - * Add note in Installation.rst to start Apache with Locale en_US.UTF-8. This helps to support Umlaut and other characters - in filenames and wkhtml commandline options (like header/footer). - * Migrate documentation from T3 to ReadTheDocs.io - looks older but 'search' is much more better. New: chapters separated - in individual files. - * For the image to PDF feature, installation of `img2pdf` is required (please check :ref:`preparation`). +* Add note in Installation.rst to start Apache with Locale en_US.UTF-8. This helps to support Umlaut and other + characters + in filenames and wkhtml commandline options (like header/footer). +* Migrate documentation from T3 to ReadTheDocs.io - looks older but 'search' is much more better. New: chapters + separated + in individual files. +* For the image to PDF feature, installation of `img2pdf` is required (please check :ref:`preparation`). Features ^^^^^^^^ - * #10751 / Allow images to be concatenated for PDF download. - * Fontawesome updated 5.13. - * Extend FE.label size to 1023. - * Local documentation rendering directly via Sphinx. - * Manual: Search is working, table width not truncated anymore, PDF & epub export, redirect qfq.io/doc to docs.qfq.io. - * Update copyright notice. +* #10751 / Allow images to be concatenated for PDF download. +* Fontawesome updated 5.13. +* Extend FE.label size to 1023. +* Local documentation rendering directly via Sphinx. +* Manual: Search is working, table width not truncated anymore, PDF & epub export, redirect qfq.io/doc to docs.qfq.io. +* Update copyright notice. Bug Fixes ^^^^^^^^^ - * #10507 / FormElement.type: 'annotate' is defined two times in Enum - * #10705 / New function 'HelperFile::joinPathFilename($pre, $post)'. Joins only if $post is without leading slash. +* #10507 / FormElement.type: 'annotate' is defined two times in Enum +* #10705 / New function 'HelperFile::joinPathFilename($pre, $post)'. Joins only if $post is without leading slash. Version 20.4.1 @@ -912,9 +1198,9 @@ Notes Features ^^^^^^^^ -* # 9805 / Form.parameter.activateFirstRequiredTab. -* # 9858 / Form.parameter: replace 'mode' by 'formModeGlobal' -* # 9860 / SQL function QMORE(): change text '...' to '[...]'. +* #9805 / Form.parameter.activateFirstRequiredTab. +* #9858 / Form.parameter: replace 'mode' by 'formModeGlobal' +* #9860 / SQL function QMORE(): change text '...' to '[...]'. * Update Developer doc for record locking. * Mockup for error handling. @@ -1400,7 +1686,8 @@ Version 19.5.1 Date: 22.05.2019 -Notes ^^^^^ +Notes +^^^^^ * New dropdown menu, fully dynamic via '... AS _link' incl. SIP generation. * New SQL stored procedure for use directly in SQL queries: @@ -1640,7 +1927,8 @@ Version 19.1.3 Date: 28.01.2019 -Notes ^^^^^ +Notes +^^^^^ * If a variable violates a sanitize class, the substituted result can now be configured: a) !!<class>!!, b) '0', c) '', d) '<custom message>'. * Alerts (based on _link class), might now show only 'ok' (alone, without 'cancel'). @@ -1710,10 +1998,11 @@ Date: 04.01.2019 Bug Fixes ^^^^^^^^^ -* # 7600 / Path to sendEmail has changed and is updated now. -* # 7603 / Fix problem: formEditor.sql broken - missing semicolon. QFQ updates did not played formEditor.sql. +* #7600 / Path to sendEmail has changed and is updated now. +* #7603 / Fix problem: formEditor.sql broken - missing semicolon. QFQ updates did not played formEditor.sql. Unit test for DatabaseUpdate(). Especially that formEditor.sql is running fine. -* # 7594 / FE.type=extra: don't name it 'type' or 'id' or 'L' - more detailed error message and an explanation in Manual.rst. +* #7594 / FE.type=extra: don't name it 'type' or 'id' or 'L' - more detailed error message and an explanation in + Manual.rst. * Config.php: error message about to high session timeout now reports the PHP settings. Version 19.1.0 @@ -2094,7 +2383,8 @@ Version 18.8.2 Date: 28.08.2018 -Features ^^^^^^^^ +Features +^^^^^^^^ * #6563 / Accept 0 as required. @@ -2237,17 +2527,17 @@ a) dynamic calculated modeSql respected, b) formModeGlobal=requiredOff respected, c) dynamic FE with mode='hidden' are not saved anymore. -* # 6176 / Icon not aligned when error text: Buttons now wrapped in one 'input-group'. +* #6176 / Icon not aligned when error text: Buttons now wrapped in one 'input-group'. * Manual.rst: reformat autocron QFQ code. -* # 5880 / Skip Error Message during dynamicUpdate. -* # 5870 / Missing file config.qfq.ini: Clean QFQ message. -* # 5924 / config.qfq.ini/LocalConfiguration.php: several places in formEditor.sql still contained the 'dbIndex...'. -* # 6168 Configuration language setting ignored: Form and FormElement editor still used uppercase config values for +* #5880 / Skip Error Message during dynamicUpdate. +* #5870 / Missing file config.qfq.ini: Clean QFQ message. +* #5924 / config.qfq.ini/LocalConfiguration.php: several places in formEditor.sql still contained the 'dbIndex...'. +* #6168 Configuration language setting ignored: Form and FormElement editor still used uppercase config values for language configuration. Updated to the new camel case notation. -* # 5890 / config.qfq.ini is public readable. Renamed file to config.qfq.php. Implement a basic migration assistant to +* #5890 / config.qfq.ini is public readable. Renamed file to config.qfq.php. Implement a basic migration assistant to copy DB credentials to new config.qfq.php. All other values have to be copied to extmanager/qfq-configuration manually. -* # 6216 / Oops, an error occurred! Code - unhandled exception will be caught now. +* #6216 / Oops, an error occurred! Code - unhandled exception will be caught now. Version 18.4.4 @@ -2255,7 +2545,8 @@ Version 18.4.4 Date: 28.04.2018 -Bug Fixes ^^^^^^^^^ +Bug Fixes +^^^^^^^^^ * Fix broken ext_emconf.php @@ -2264,7 +2555,8 @@ Version 18.4.3 Date: 28.04.2018 -Bug Fixes ^^^^^^^^^ +Bug Fixes +^^^^^^^^^ * Version Number ...04... not supported by TE. Changing naming scheme to omit leading zero. @@ -2381,7 +2673,8 @@ Version 0.25.13 Date: 08.03.2018 -Features ^^^^^^^^ +Features +^^^^^^^^ * AutoCron: Added doc for autocron. Extend AutoCron.php to be MultiDB aware. Update der AutoCron form. * #4720 / Separate Database for Form & FormElement - Multi DB - fixed problem that 'Quick Edit Form / FormElement' has been broken in MultiDB Setup. @@ -2478,22 +2771,22 @@ Notes Features ^^^^^^^^ -* # 5022 / Variable violates santize class: 'msg' instead of empty string - new identifier "!!<sanitize class>!!". -* # 4813 / Exception during form load: show 'form edit link' if editor is logged in. +* #5022 / Variable violates santize class: 'msg' instead of empty string - new identifier "!!<sanitize class>!!". +* #4813 / Exception during form load: show 'form edit link' if editor is logged in. * formEditor.sql: Increas size of Form.title to give more room for SQL statements in. * Manual.rst: enhance debug tipps. -* # 5321 / Plain Link - render mode- only url - implemented. +* #5321 / Plain Link - render mode- only url - implemented. * Add regex101 link to checkPattern FormEditor. Bug Fixes ^^^^^^^^^ * Fixed some broken help links in formEditor.sql. -* # 5306 / Exception: tt_content_uid wrong - fixed. -* # 4303 / Download von doc/docx-Dateien / Download.php - Mime type wird nicht mehr an Dateiname angehängt. -* # 5316 / Help on how to send an E-Mail is wrong - several places fixed. -* # 5311 / Error Msg SLQ_RAW != SQL_FINAL: Debug message shows outdated SQL_RAW. -* # 5309 / min/max broken for date fields. Add min/max attributes to input and date input tag. +* #5306 / Exception: tt_content_uid wrong - fixed. +* #4303 / Download von doc/docx-Dateien / Download.php - Mime type wird nicht mehr an Dateiname angehängt. +* #5316 / Help on how to send an E-Mail is wrong - several places fixed. +* #5311 / Error Msg SLQ_RAW != SQL_FINAL: Debug message shows outdated SQL_RAW. +* #5309 / min/max broken for date fields. Add min/max attributes to input and date input tag. * Fabric now detects 'dirty'. * Manual.rst: Remove broken link to W3C file upload. @@ -2627,7 +2920,8 @@ Version 0.25.5 Date: 23.11.2017 -Bug Fixes ^^^^^^^^^ +Bug Fixes +^^^^^^^^^ * #4771: Workaround which switches off updates to SELECT lists, if they are part of a Multi-FE-Row. @@ -2699,7 +2993,8 @@ Version 0.25.2 Date: 08.11.2017 -Notes ^^^^^ +Notes +^^^^^ * Starting with this release, the default escape mode is 'm' (mysql_real_escape). @@ -2725,7 +3020,8 @@ Version 0.25.1 Date: 03.11.2017 -Bug Fixes ^^^^^^^^^ +Bug Fixes +^^^^^^^^^ * #4857 / broken (stale) download: multiple 'u:..' or 'u:...'. * #4212 / Broken JSON on response to save new record 'Unknown index' fixed by isset(). @@ -2786,7 +3082,8 @@ Version 0.23.1 Date: 23.09.2017 -Bug Fixes ^^^^^^^^^ +Bug Fixes +^^^^^^^^^ * #4620 / Easy Fix: saveButtonText / closeButtonText Formatierung. @@ -3276,8 +3573,9 @@ Changes * Play: - ALTER TABLE `FormElement` ADD INDEX `feIdContainer` (`feIdContainer`); ALTER TABLE `FormElement` ADD - INDEX `ord` (`ord`); ALTER TABLE `FormElement` ADD INDEX `feGroup` (`feGroup`); + ALTER TABLE `FormElement` ADD INDEX `feIdContainer` (`feIdContainer`); + ALTER TABLE `FormElement` ADD INDEX `ord` (`ord`); + ALTER TABLE `FormElement` ADD INDEX `feGroup` (`feGroup`); ALTER TABLE `FormElement` ADD `adminNote` TEXT NOT NULL AFTER `note`; @@ -3344,9 +3642,9 @@ Changes * Updated subrecord in 'Form' for 'FormElements' - columns 'size' and 'sql1' removed and 'dyn' inserted. Play formEditor.sql. -* # 3431, Parameter keyword 'typeAheadLdapKeyPrintf' changed to 'typeAheadLdapIdPrintf'.:: +* #3431, Parameter keyword 'typeAheadLdapKeyPrintf' changed to 'typeAheadLdapIdPrintf'.:: - UPDATE FormElement SET parameter = REPLACE(parameter, 'typeAheadLdapKeyPrintf', 'typeAheadLdapIdPrintf') + UPDATE FormElement SET parameter = REPLACE(parameter, 'typeAheadLdapKeyPrintf', 'typeAheadLdapIdPrintf') * Size 'placeholder' increased:: @@ -3419,7 +3717,7 @@ Features * Load foreign values in templatGroups - saving is not implemented yet. * Manual: Added howto prevent <p>-wrap in TinyMCE. * TemplateGroup: Add button now disabled if max. number of copies reached. -* # 3414 / QuickFormQuery.php: wrap whole form in 'col-md-XX' - User controls the width of an QFQ form. +* #3414 / QuickFormQuery.php: wrap whole form in 'col-md-XX' - User controls the width of an QFQ form. Bug Fixes ^^^^^^^^^ @@ -3587,11 +3885,11 @@ Bug fixes * User manual: - * Fixed double include of validator.js in T3 Typoscript template example. - * Fixed wrong store name SYSTEM: S > Y. - * Fixed wrong STORE_FORM variable names. - * Reformat FormElement.parameter description. - * Styling errors fixed. + * Fixed double include of validator.js in T3 Typoscript template example. + * Fixed wrong store name SYSTEM: S > Y. + * Fixed wrong STORE_FORM variable names. + * Reformat FormElement.parameter description. + * Styling errors fixed. * Use of 'decryptCurlyBraces()' to get better error messages. * Skip unwanted parameter expansion during save. @@ -3600,7 +3898,7 @@ Bug fixes * The defintion as 'editor' (not text) for FormElement 'note' has been lost - reinserted. * Fixed problem while playing SQL query - deleting old FormElements of Formeditor deleted also FormElements of other forms. -* # 3066 / help-text with-error - CSS class 'hidden' will be rendered by default (as long there is no error). +* #3066 / help-text with-error - CSS class 'hidden' will be rendered by default (as long there is no error). * Labels are skipped, if FormElement.bsLabelColumns=0. * Respect attribute `data-class-on-change` on save buttons. diff --git a/Documentation-develop/NewVersion.md b/Documentation-develop/NewVersion.md index 7224a82b1e45b6a504cf0ec85e93dd919c63e263..b6dc5c863603932fa60735c2eb696348eab4dcd4 100644 --- a/Documentation-develop/NewVersion.md +++ b/Documentation-develop/NewVersion.md @@ -44,7 +44,7 @@ Neue Versionsnummer **Achtung**: die Release Minor darf KEINE fuehrenden Nullen enthalten!!! Ansonsten funktioniert die Verteilung vie TER nicht. - **Auto**: ./setVersion.sh 23.2.0 + **Auto**: ./setVersion.sh 23.10.0 Manuell: @@ -56,20 +56,20 @@ Neue Versionsnummer * **Commit & Push** to develop branch: - New version 23.2.0 + New version v23.10.0 6) - * Merge 'Develop' to **Master**. + * Merge 'Develop' to **Master**: git.math.uzh.ch > QFQ > Merge Requests > New merge request > 'Develop >> Master' * Checkout **Master**. 7) **New Tag**: - # Neuen tag via Browser auf dem **master** branch zu setzen. + * Neuen tag via Browser auf dem **master** branch setzen: git.math.uzh.ch > QFQ > Repository > Tags > New tag - Tag: v23.2.0 + Tag: v23.10.0 - # Den tag mit diesem Command zu setzen scheint den Build Prozess nicht zu trigger. - git tag -a v23.2.0 -m 'New version v23.2.0' git push + # Den tag mit diesem Command zu setzen scheint den Build Prozess nicht zu triggern. + git tag -a v23.10.0 -m 'New version v23.10.0' git push 7) **Merge 'master' into 'develop'** diff --git a/Documentation-develop/PHPUNIT.md b/Documentation-develop/PHPUNIT.md new file mode 100644 index 0000000000000000000000000000000000000000..b4b13b8d5ee4bf26e34c37c19fa6ae7131080f65 --- /dev/null +++ b/Documentation-develop/PHPUNIT.md @@ -0,0 +1,73 @@ +# Phpunit + +* Versions: https://phpunit.de/supported-versions.html +* Installation: https://phpunit.de/getting-started/phpunit-9.html + +* Doc: https://docs.phpunit.de/ +* Doc um Exceptions zu testen: https://docs.phpunit.de/en/9.6/writing-tests-for-phpunit.html + +## TODO: if (!defined('PHPUNIT_QFQ')) {...} + +rewrite $phpUnit to: "if (!defined('PHPUNIT_QFQ')) {...} + +## Run unit tests from CLI: + +Make sure dev dependencies are installed: + +```shell script +# in extension directory +composer update --dev +``` + +Run all tests: + +```shell script +# in extension directory +vendor/bin/phpunit --configuration phpunit.xml +``` + +Run single test: + +```shell script +# in extension directory +vendor/bin/phpunit --configuration phpunit.xml --filter <test_name> +``` + +## Setup + +Requirements for running the php unittests: + +- `make bootstrap` was executed +- The following files exist at the same location (either `extension/` or `typo3conf/`): + * `qfq.json` + * `LocalConfiguration.php` +- The database credentials in `qfq.json` are correct +- The database with the name `DB_1_NAME` followed by `_phpunit` exists. E.g. `app_qfq_phpunit` where `DB_1_NAME=app_qfq` + +In Tests/Unit/ you may find a mockup of `LocalConfiguration.php` +and a template for `qfq.json`. + +## Run unit tests from commandline + +REMARK: Running the unit tests without specifying the configuration file `phpunit.xml` will not work. See section " +Autoloader" for explanation. + +From the extension folder run: + +`vendor/bin/phpunit --configuration phpunit.xml` + +## Phpunit configurations + +Phpunit configurations are stored in extension/phpunit.xml Running the tests without specifying these configurations +will not work. + +## Autoloader + +The test classes use the composer autoloader to reference to the source classes. The autoloader is loaded by phpunit +before each test as specified in phpunit.xml by the line `<phpunit bootstrap="vendor/autoload.php">` + +## Run tests without typo3 installation (e.g. gitlab runner) + +As defined in the phpunit command of projectRoot/Makefile. + +The files phpunit_qfq.json and phpunit_LocalConfiguration.php are copied outside the extension folder by the Makefile. diff --git a/Documentation/Form.rst b/Documentation/Form.rst index 69424f0377850600c5615624e1342f2e51008f22..6169c970a9c3a784dba9abe2d71a8b3c88ce7bda 100644 --- a/Documentation/Form.rst +++ b/Documentation/Form.rst @@ -926,11 +926,6 @@ Fields: +---------------------+-----------------------------+-----------------------------------------------------------------------------------------------------+ |Parameter | text | Might contain misc parameter. See :ref:`fe-parameter-attributes` | +---------------------+-----------------------------+-----------------------------------------------------------------------------------------------------+ -|feGroup | string | Comma-separated list of Typo3 FE Group ID. NOT SURE IF THIS WILL BE IMPLEMENTED. Native | -| | | *FormElements*, fieldsets and pills can be assigned to feGroups. Group status: show, hidden, | -| | | hidden. Group Access: FE-Groups. User will be assigned to FE-Groups and the form definition | -| | | reference such FE-groups. Easy way of granting permission. | -+---------------------+-----------------------------+-----------------------------------------------------------------------------------------------------+ |Deleted | string | 'yes'|'no'. | +---------------------+-----------------------------+-----------------------------------------------------------------------------------------------------+ @@ -1127,6 +1122,8 @@ FormElement.parameter | clearMe | 0 (off)|1(on) - Overwrite default from Form.parameter.clearMe or :ref:`configuration`. Show a small | | | 'x' in input or textarea fields to clear the input. | +---------------------------------+----------------------------------------------------------------------------------------------------------+ +| defaultValue | Set custom default value. If not set, db column default value will be taken. | ++---------------------------------+----------------------------------------------------------------------------------------------------------+ * `s/d/n`: string or date or number. @@ -1388,7 +1385,7 @@ General input for any text. * *retypeLabel* = <text> (optional): The label of the second element. * *retypeNote* = <text> (optional): The note of the second element. - * *characterCountWrap* = <span class="qfq-cc-style">Count: | </span> (optional). + * *characterCountWrap* = ``<span class="qfq-cc-style">Count: | </span>`` (optional). Displays a character counter below the input/textarea element. * Also check the :ref:`fe-parameter-attributes` *data-...-error* to customize error messages shown by the validator. * *hideZero* = 0|1 (optional): `with hideZero=1` a '0' in the value will be replaced by an empty string. @@ -1895,7 +1892,7 @@ will be rendered inside the form as a HTML table. * *nostrip*: by default, html tags will be stripped off the cell content before rendering. This protects the table layout. 'nostrip' deactivates the cleaning to make pure html possible. * *icon*: the cell value contains the name of an icon in *typo3conf/ext/qfq/Resources/Public/icons*. Empty cell values - will omit an html image tag (=nothing rendered in the cell). + will omit an html image tag (=nothing rendered in the cell). See :ref:`qfq-icons`. * *link*: value will be rendered as described under :ref:`column-link` * *url*: value will be rendered as a href url. * *mailto*: value will be rendered as a href mailto. @@ -2731,17 +2728,35 @@ record (defined by `multiSql`). The Form is shown as a HTML table. -* `multiSql`: Selects the records where the defined FormElements will work on each. +* *multiSql* = `<string>` - Selects the records where the defined FormElements will work on each. * A uniq column `id` or `_id` (not shown) is mandatory and has to reference an existing record in table `primary table`. * Additional columns, defined in `multiSql`, will be shown on the form on the same line, before the FormElements. * Per row, the STORE_PARENT is filled with the current record of the primary table. + * The optional special column `_processRow` will uncheck/check the processRow checkbox during form load. + +Process Row +^^^^^^^^^^^ + +Activating `processRow` adds a checkbox to every row of the `Multi Form`, including the header. +During `save`, only selected rows get processed. + +The checkbox in the header selects all/none rows at once. + +* `Form.parameter`: + + * *processRow* = `<string>` - the value displayed in table header next to the checkbox. + +* `Form.mulitSql`: If there is a column `_processRow`, value of 0/1 per row will control unchecked/checked during form load. + +Implicit Multi Form mode +^^^^^^^^^^^^^^^^^^^^^^^^ The following definition of *Simple* and *Advanced* is just for explanation, there is no *flag* or *mode* which has to be set. Also the *Simple* and *Advanced* variant can be mixed in the same Multi Form. Simple -^^^^^^ +"""""" General: @@ -2756,7 +2771,7 @@ FormElement: * No further definition (`sqlInsert`, `sqlUpdate`, ...) is required. Advanced -^^^^^^^^ +"""""""" To handle foreign records (insert/update/delete), use the :ref:`slave-id` concept. @@ -2863,7 +2878,7 @@ The following fields are possible: * Form: *title, showButton, forwardMode, forwardPage, bsLabelColumns, bsInputColumns, bsNoteColumns, recordLockTimeoutSeconds* * FormElement: *label, mode, modeSql, class, type, subrecordOption, encode, checkType, ord, size, maxLength,* - *bsLabelColumns, bsInputColumns, bsNoteColumns,rowLabelInputNote, note, tooltip, placeholder, value, sql1, feGroup* + *bsLabelColumns, bsInputColumns, bsNoteColumns,rowLabelInputNote, note, tooltip, placeholder, value, sql1* .. _dynamic-update: diff --git a/Documentation/GeneralTips.rst b/Documentation/GeneralTips.rst index 50d801696c2df6d1db517e4b41b30332219507a4..57643e80ef033f0a54d837b68c7d23a44a2c1194 100644 --- a/Documentation/GeneralTips.rst +++ b/Documentation/GeneralTips.rst @@ -292,3 +292,30 @@ The FE User record (table: fe_users) * Has to be assigned to a T3 page ``fe_users.pid``. * The T3 page has to be configured as ``record store`` on the T3 Plugin login box. * Access time has to be zero or a currently valid period. + +MariaDB +------- + +Row size too large +^^^^^^^^^^^^^^^^^^ + +* Details: https://mariadb.com/kb/en/troubleshooting-row-size-too-large-errors-with-innodb/ +* Typically too many columns per table on an InnoDB Table. +* First try is to check (and to change) ROW_FORMAT: :: + + ALTER TABLE tab ROW_FORMAT=DYNAMIC; + +* Bad workaround for creating a wide table: :: + + SET SESSION innodb_strict_mode=OFF; + CREATE TABLE ... + +* Best is to change as much columns as neccessary from ``varchar()`` to ``tinytext`` / ``text`` + +SQL Dump with one record per line +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Sometimes it's helpful to have SQL dumps with each record on a single line:: + + msyqldump --skip-opt <DB-Name> + diff --git a/Documentation/Images/bullet-orange.gif b/Documentation/Images/bullet-orange.gif new file mode 100644 index 0000000000000000000000000000000000000000..dde24273078c6ad8222744aa5893e1bfdf2274e2 Binary files /dev/null and b/Documentation/Images/bullet-orange.gif differ diff --git a/Documentation/Installation.rst b/Documentation/Installation.rst index 5e97050c8b750c943bac14b163530d79962c1e27..6336a6ce1a0423116d2a755e3d25005ea286c07b 100644 --- a/Documentation/Installation.rst +++ b/Documentation/Installation.rst @@ -284,6 +284,9 @@ Setup CSS & JS # Only needed in case FullCalendar is used. file22 = typo3conf/ext/qfq/Resources/Public/JavaScript/fullcalendar.min.js + + # Only needed in case widget-output is used. + file23 = typo3conf/ext/qfq/Resources/Public/JavaScript/widget-output.min.js } @@ -558,6 +561,7 @@ Extension Manager: QFQ Configuration +-----------------------------------+-------------------------------------------------------+----------------------------------------------------------------------------+ | formSubmitLogMode | all | | *all*: every form submission will be logged. | | | | | *none*: no logging. | +| | | | *modify*: prevent logging tables: FormSubmitLog and Dirty. | | | | | See :ref:`form-submit-log-page` for example QFQ code to display the log. | +-----------------------------------+-------------------------------------------------------+----------------------------------------------------------------------------+ | redirectAllMailTo | john@doe.com | If set, redirect all QFQ generated mails (Form, Report) to the specified. | @@ -624,6 +628,8 @@ Extension Manager: QFQ Configuration | encryptionMethod | AES-128 | Options: 'AES-128' (default) or 'AES-256'. | | | | Invalid option will be shown on page. | +-----------------------------------+-------------------------------------------------------+----------------------------------------------------------------------------+ +| protectedFolderCheck | on/off | on (default): check if fileadmin/protected is really protected. | ++-----------------------------------+-------------------------------------------------------+----------------------------------------------------------------------------+ | **Form-Config** | +-----------------------------------+-------------------------------------------------------+----------------------------------------------------------------------------+ | lockSameOwner | true | true/false: Should the same user that triggered the 'Dirty Record' | diff --git a/Documentation/Release.rst b/Documentation/Release.rst index 10b13198a8132ebae3d79d5483823f36a964d862..6a6d71277bbe1460e6fa2bc51227a485852e5155 100644 --- a/Documentation/Release.rst +++ b/Documentation/Release.rst @@ -12,6 +12,7 @@ .. --------------------------------------------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`` @@ -52,6 +53,213 @@ Features Bug Fixes ^^^^^^^^^ +Version 23.10.1 +--------------- + +Date: 22.10.2023 + +Notes +^^^^^ + +Features +^^^^^^^^ + +* #15098 / Implemented qfqFunction in QFQ variable for usage in forms. +* Doc: Replace many places single back tick by double back tick. Add hint for 'Row size too large'. Added Enis & Jan + as Developer. Add hint use mysqldump with one row per record. + +Bug Fixes +^^^^^^^^^ + +* #17003 / inline edit - dark mode has wrong css path. +* #17075 / Fix broken '... AS _restClient'. +* #17091 / upload_Incorrect_integer_value_fileSize. +* RTD: Fix broken readthedocs rendering. + +Version 23.10.0 +--------------- + +Date: 05.10.2023 + +Features +^^^^^^^^ + +* #16350 / QFQ Table 'FormSubmiLog': update recordid after insert. +* #16350 / sql.log: reference to FormSubmitLog entry. All SQL statements generated by one HTTP Post (Form Submit) can + be identified. +* #16350 / Do not log Dirty. +* #16350 / If the T3 instance is behind a proxy, log HTTP_X_REAL_IP instead of REMOTE_ADDR in logfiles. +* #16584 / FormEditor Report: Default without statistics. +* #16589 / Implemented language configuration in backend for tt-content type qfq. +* #16798 / Report inline edit v2. Improved search inside inline edit report. Whole content will be searched. Added + ability to switch the editor to dark mode. +* Doc: Refactor description of {{random:V}}. +* Doc: Add config option 'protectedFolderCheck'. + +Bug Fixes +^^^^^^^^^ + +* #16573 / Fixed wrong built date and datetime string if default value was given. +* #16574 / Added multiple siteConfigurations compatibility for typo3 v10 and 11. +* #16616 / Fixed typeahead api query response problem if typeahead sql is not used. +* #16664 / Fix multi db user error. +* #16975 / Fix problem if a 'Form Submit' contains more than 64kB data. This can happen easily for 'fabric' elements. + +Version 23.6.4 +-------------- + +Date: 26.06.2023 + +Bug Fixes +^^^^^^^^^ + +* #16485 / TypeAhead: strpos() string, array given error. +* #16488 / Missing default values break saving records. New custom FE.parameter.defaultValue. +* #16491 / FE Typ Upload - JS failure: document.querySelector() is null. + +Version 23.6.3 +-------------- + +Date: 22.06.2023 + +Bug Fixes +^^^^^^^^^ + +* #16478 / Rest API: Fixed stream_get_contents failure in PHP8.1 Generic Error. + +Version 23.6.2 +-------------- + +Date: 21.06.2023 + +Bug Fixes +^^^^^^^^^ + +* #16475 / Spontaneous spaces in HTML emails. +* #16476 / SQL columns Text/Blog with default empty string becomes "''" + + +Version 23.6.1 +-------------- + +Date: 16.06.2023 + +Notes +^^^^^ + +* QFQ is Typo3 V11 compatible + +Bug Fixes +^^^^^^^^^ + +* #16372 / Upload Element 'Undefined index htmlDownloadButton' und 'unknown mode ID' +* #16381 / Form title dynamic update broken. +* #16392 / Reevaluate sanitize class for each store. +* Fix db column enum dropdown 'data truncated' +* DB Update 'alter index' pre check if exists +* FE: Upload - fix undefined index. + +Version 23.6.0 +-------------- + +Date: 09.06.2023 + +Features +^^^^^^^^ + +* Typo3 V11 compatible +* #9579 / Multiform with Process Row. +* #15770 / httpOrigin: Parameter missing in QFQ Config. +* #16285 / PHP V8 Migration. +* #16364 / T3 >=V10 pageSlug and ForwardPage. +* Add .phpunit.result.cache to .gitignore. +* Add bullet-orange.gif. +* Add index createdFeUserFormId to table FormSubmitLog. +* Doc: Add link to icons. Update use-case self-registration and add info in case of namesake. Unify word 'spam' to 'junk'. + Add bootstrap links. +* Remove Form/FormElement parameter 'feGroup'. +* QFQ Typo3 Updatewizard: Sort output by page and tt-content ordering. Add page uid an title to be reported. + Inline Edit: add tt_content uid and header as tooltip. +* Update package.json. +* Update to PHPUnit version 9. + +Bug Fixes +^^^^^^^^^ + +* #12468 / Form: Dynamic Update Form.title after save. +* #14636 / 'Clear Me'-cross lighter and 2 px up. +* #15445 / Doc: JS Output Widged added. +* #15497 / Delete link: a) broken for 'table', b) broken with 'r:3'. +* #15527 / Form/subrecord: class 'qfq-table-50' no impact. +* #15654 / Form FE required 'Undefined index Error'. +* #15659 / Multi Form: Datetime Picker not aligned. +* #15691 / T3 V10: Export PDF (PDF Generator) invalid link causes error. +* #15726 / Formelement upload stays required when changed from required to hidden. +* #15729 / Form: text readonly input should show newline as '<br>'. +* #15747 / Typeahead does not work with special character. Added croatian special chars (Ć,ć,ÄŒ,Ä,Ä,Ä‘,Å ,Å¡,Ž,ž) to alnumx + whitelist. +* #15763 / Search/Refactor: a) search for ID, b) message if nothing is found - New highlighting for disabled content. + Added output for not found in search. +* #15773 / TypeAhead missing check type warning. +* #15813 / Pressing 'Enter' in Form asks: Do you really want to delete the record? +* #15875 / Unecessary qfq-config save. +* #15905 / FE type time undefined index. +* #15913 / Refactor: Footer displayed at the wrong place. +* #15921 / Frontend edit Report Codemirror resizeable. +* #16004 / Template Group Fields broken. +* #16046 / Datepicker not initialized in Template Group. +* #16051 / Dropdown menu: Download file broken with ZIP File. +* #16055 / Dropdown option with render mode 3 format problem. +* #16064 / Dynamic Update Checkbox and Form Store in parameter. +* #16073 / saveButtonActive is disabled after first save. +* #16201 / Character Count Class Quotation Mark. +* #16204 / TypeAhead Prefetch Expects String Array Given. +* #16228 / extraButtonPassword unhide broken. +* #16264 / Multi DB Broken since QFQ V23.2.0. +* #16273 / TinyMCE image upload path incorrect for T3 v10 and upwards. + +Version 23.3.1 +-------------- + +Date: 31.03.2023 + +Bug Fixes +^^^^^^^^^ + +* #15920 / QFQ variable evaluation broken: wrong variable name + +Version 23.3.0 +-------------- + +Date: 30.03.2023 + +Features +^^^^^^^^ + +* #15491 / Search refactor redesign and T3 V10 compatiblity: underscore character, counter fixed, page alias and slug handling. +* #15529 / Form/subrecord: Design Titel / Box. +* #15570 / Changed DB handling in class FormAction. Fixed multi-db problem with this. Included change for check of + existing form editor report. +* #15579 / Ability for searching all possible characters, includes not replaced QFQ variables. +* #15627 / Added character count and maxLength feature for TinyMCE. +* Add various icons to documentation. +* Doc Form.rst: reference 'orderColumn'. +* Doc Report.rst: fix typo, add icons, improved example for tablesorter. +* Add indexes for table FormSubmitLog. +* FormEditor: Show Tablename in pill 'table definition'. +* FormEditor: FE subrecord > show container id below name. + +Bug Fixes +^^^^^^^^^ + +* #14754 / Using double quotes in tableview config caused sql error. +* #15483 / Protected folder check fixed. Changed default of wget, preventing errors. Changed handling from protected + folder check, new once a day. +* #15521 / FormEditor assigns always container, even none is selected. Change handling of form variables from type select, + radio and checkbox. Expected 0 from client request instead of empty string. +* #15523 / Search/Refactor broken for Multi-DB. +* #15626 / Multi-DB: FormEditor save error. + Version 23.2.0 -------------- @@ -67,19 +275,19 @@ Notes Features ^^^^^^^^ - * 7650 / For FormElement which are required: `indicateRequired=0|1` show or hide the 'required asterix'. - * 10013 / FormEditor: Several textarea inputs changed to 'editor' incl. SQL syntax highlighting. - * 14995 / Support for refactoring: search in QFQ Form/FormElement and Typo3 pages/tt-content tables. - * QMANR: SQL prepared function to format Swiss Matrikelnumbers +* #7650 / For FormElement which are required: `indicateRequired=0|1` show or hide the 'required asterix'. +* #10013 / FormEditor: Several textarea inputs changed to 'editor' incl. SQL syntax highlighting. +* #14995 / Support for refactoring: search in QFQ Form/FormElement and Typo3 pages/tt-content tables. +* QMANR: SQL prepared function to format Swiss Matrikelnumbers Bug Fixes ^^^^^^^^^ - * 7899 / Fe.type=password / retype / required: always complain about missing value - bug has been fixed earlier. - * 8668 / Fix that if a pill becomes disabled, child FE should not respected during save. - * 15168 / Fixed language form save problem for t3 v >= 9. - * 15420 / TypeAhead Prefetch triggers 'form changed' after save. - * 15482 / Added missing password font file. +* #7899 / Fe.type=password / retype / required: always complain about missing value - bug has been fixed earlier. +* #8668 / Fix that if a pill becomes disabled, child FE should not respected during save. +* #15168 / Fixed language form save problem for t3 v >= 9. +* #15420 / TypeAhead Prefetch triggers 'form changed' after save. +* #15482 / Added missing password font file. Version 23.1.1 @@ -95,19 +303,19 @@ Notes Features ^^^^^^^^ - * #11980 / check protected folder for access from outside after new qfq installation or update. - * #13566 / Cleanup: Delete config-example.qfq.php - * #15053 / New dynamic QFQ config option: fillStoreSystemBySqlRow. - * Update SANITIZE_ALLOW_EMAIL_MESSAGE to be more generic: 'Invalid email format' - * Update Documentation/Form.rst: Added QFQ Function QMANR(manr), broken ref to restAuthorization, fixed typos. - * Update copyright year +* #11980 / check protected folder for access from outside after new qfq installation or update. +* #13566 / Cleanup: Delete config-example.qfq.php +* #15053 / New dynamic QFQ config option: fillStoreSystemBySqlRow. +* Update SANITIZE_ALLOW_EMAIL_MESSAGE to be more generic: 'Invalid email format' +* Update Documentation/Form.rst: Added QFQ Function QMANR(manr), broken ref to restAuthorization, fixed typos. +* Update copyright year Bug Fixes ^^^^^^^^^ - * #2665 / Fixed not working dynamic update with tinymce and codemirror. Improved frontend edit visualization, elements in window will be resized automatically. - * #11517 / Fixed Bug not showing extraInfoButton on certain FormElements like: TextArea, Select, Checkbox, Radio, Upload, Editor. - * #12066 / Form: Enter as submit forward not working. +* #2665 / Fixed not working dynamic update with tinymce and codemirror. Improved frontend edit visualization, elements in window will be resized automatically. +* #11517 / Fixed Bug not showing extraInfoButton on certain FormElements like: TextArea, Select, Checkbox, Radio, Upload, Editor. +* #12066 / Form: Enter as submit forward not working. Version 23.1.0 -------------- @@ -117,25 +325,25 @@ Date: 05.01.2023 Features ^^^^^^^^ - * #6250 / Enhance layout subrecord. - * #10003 / Fieldset: stronger visualize group. - * #15036 / FormElement subrecord summary additional row(s). - * #15154 / FormElement input check via regexp: trim whitespace before regexp. +* #6250 / Enhance layout subrecord. +* #10003 / Fieldset: stronger visualize group. +* #15036 / FormElement subrecord summary additional row(s). +* #15154 / FormElement input check via regexp: trim whitespace before regexp. Bug Fixes ^^^^^^^^^ - * #14305 / Bug "No form found with this id". - * #14997 / Tablesorter: Filter value shown but no effect after 'Browser Back' - * #15191 / Broken datepicker in template group. - * #15214 / Inline report save history. - * #15229 / Typeahead and qfq-clear-me will be initialized after adding new fields. - * #15230 / Set own button class for dropdown. - * #15314 / FE & BE Email now filled in 'sipForTypo3Vars' and therefore available in API calls. - * #15316 / config.baseUrl might be overwritten if no scheme or multiple urls are given. - * Doc: Fix missing ! in doc: sqlValidate. Fix index undefined htmlAllow. Fix filemtime() Warning. - * Fixed date to datetime convert bug. - * Fixed error coming from typeahead when array is empty. +* #14305 / Bug "No form found with this id". +* #14997 / Tablesorter: Filter value shown but no effect after 'Browser Back' +* #15191 / Broken datepicker in template group. +* #15214 / Inline report save history. +* #15229 / Typeahead and qfq-clear-me will be initialized after adding new fields. +* #15230 / Set own button class for dropdown. +* #15314 / FE & BE Email now filled in 'sipForTypo3Vars' and therefore available in API calls. +* #15316 / config.baseUrl might be overwritten if no scheme or multiple urls are given. +* Doc: Fix missing ! in doc: sqlValidate. Fix index undefined htmlAllow. Fix filemtime() Warning. +* Fixed date to datetime convert bug. +* Fixed error coming from typeahead when array is empty. Version 22.12.1 @@ -153,21 +361,21 @@ Notes Features ^^^^^^^^ - * #5715 / Caching for on the fly rendred files (PDF, Excel, ZIP). - * #6250 / Enhance form layout: a) Subrecord, b) Subrecord-Title - * #9927 / Do QFQ Update (config, table defintion) only if there is a BE user logged in the current session. - * #12503 / Detect likely unwanted UPDATE statement with missing WHERE. - * New Button Sizes: btn-tiny, btn-small +* #5715 / Caching for on the fly rendred files (PDF, Excel, ZIP). +* #6250 / Enhance form layout: a) Subrecord, b) Subrecord-Title +* #9927 / Do QFQ Update (config, table defintion) only if there is a BE user logged in the current session. +* #12503 / Detect likely unwanted UPDATE statement with missing WHERE. +* New Button Sizes: btn-tiny, btn-small Bug Fixes ^^^^^^^^^ - * #14305 / Inline Report edit saves history to Typo 3. - * #14506 / Tablesorter: after form close, already defined filter criteria in tablesorter will be applied. - * #15188 / Clean up config to T3 parameter. - * #15193 / No form found with this id error in MultiDB Setup. - * #15206 / Form Save: Internal Error in MultiDB Setup. - * Fix Doc: latest CCS/JS includes. +* #14305 / Inline Report edit saves history to Typo 3. +* #14506 / Tablesorter: after form close, already defined filter criteria in tablesorter will be applied. +* #15188 / Clean up config to T3 parameter. +* #15193 / No form found with this id error in MultiDB Setup. +* #15206 / Form Save: Internal Error in MultiDB Setup. +* Fix Doc: latest CCS/JS includes. Version 22.12.0 --------------- @@ -238,11 +446,11 @@ Features Bug Fixes ^^^^^^^^^ - * #11325 / SQL CALL() - insert() and select() should be supported now. - * #14622 / FormElement `unexpected error` on save. - * #15005 / TypeAhead Dynamic Update Mode Hidden to Required - * #15014 / Excel import broken if multiple regions defined. - * #15048 / STORE_USER broken. +* #11325 / SQL CALL() - insert() and select() should be supported now. +* #14622 / FormElement `unexpected error` on save. +* #15005 / TypeAhead Dynamic Update Mode Hidden to Required +* #15014 / Excel import broken if multiple regions defined. +* #15048 / STORE_USER broken. Version 22.10.1 --------------- @@ -252,29 +460,29 @@ Date: 24.10.2022 Notes ^^^^^ - * Migration of existing Typo3 V9 to V10: Check #12584, #12440 +* Migration of existing Typo3 V9 to V10: Check #12584, #12440 Features ^^^^^^^^ - * #12584, #12440 / Typo3 V10 Migration Script Replace Alias Patterns - * #14738 / Datetimepicker: verify various setups - * #14802 / DateTime own class and unit tests - * Add doc to table vertical column via CSS, - * Add qfq-badge-* to doc +* #12584, #12440 / Typo3 V10 Migration Script Replace Alias Patterns +* #14738 / Datetimepicker: verify various setups +* #14802 / DateTime own class and unit tests +* Add doc to table vertical column via CSS, +* Add qfq-badge-* to doc Bug Fixes ^^^^^^^^^ - * #14618 / Changed logic behind getting first value from array (check for typeahead array was already given but was - placed after the checkForEncryptedValue function). No exception possible. Function name changed to generic. Added new - function is_multi_array to check if array is multidimensional and return true or false. - * #14736 / Datetimepicker: FormElement.parameter.dateFormat - required. - * #14755 / Fix for typeahead problem in t3 v9 and higher. Typeahead url will be setup same as save.php. Results to an absolute path. - * #14813 / Report: dbIndex - missing per level definition - * #14844 / Fixed bug of no working dynamic update and empty output in Form Store with Datetimepicker. - * #14857 / String expected, array given. - * #14885 / Fabric zoom problem. +* #14618 / Changed logic behind getting first value from array (check for typeahead array was already given but was + placed after the checkForEncryptedValue function). No exception possible. Function name changed to generic. Added new + function is_multi_array to check if array is multidimensional and return true or false. +* #14736 / Datetimepicker: FormElement.parameter.dateFormat - required. +* #14755 / Fix for typeahead problem in t3 v9 and higher. Typeahead url will be setup same as save.php. Results to an absolute path. +* #14813 / Report: dbIndex - missing per level definition +* #14844 / Fixed bug of no working dynamic update and empty output in Form Store with Datetimepicker. +* #14857 / String expected, array given. +* #14885 / Fabric zoom problem. @@ -286,26 +494,26 @@ Date: 04.10.2022 Notes ^^^^^ - * TinyMCE Feature "upload images via drag'n'drop" now supported. +* TinyMCE Feature "upload images via drag'n'drop" now supported. Features ^^^^^^^^ - * #14813 / Report: dbIndex - missing per level definition - * #12474 / Check BaseConfigURL if it is given and the the last char is '/' - * #12452 / baseUrl: add automatically '/' at end - * #10782 / Tiny MCE: Image Upload & Image drag'n'drop +* #14813 / Report: dbIndex - missing per level definition +* #12474 / Check BaseConfigURL if it is given and the the last char is '/' +* #12452 / baseUrl: add automatically '/' at end +* #10782 / Tiny MCE: Image Upload & Image drag'n'drop Bug Fixes ^^^^^^^^^ - * #14803 / MultiDB (different DB hosts): broken 'show table definition' in FormEditor - - * #14791 / datetimepicker: undefined index: timeParts[2] - * #14619 / Added note to missed bug fix. - * #12630 / Added code commentary. Reformatted code. getUniqueFileName function changed. New logFileMessages implemented. - * #14463 / llow statusbar (resize button included) to show as default. - * #14455 / Drag and drop triggers record lock. - * #13818 / htmlspecialchars_decode() - expects parameter 1 to be string, array given +* #14803 / MultiDB (different DB hosts): broken 'show table definition' in FormEditor - +* #14791 / datetimepicker: undefined index: timeParts[2] +* #14619 / Added note to missed bug fix. +* #12630 / Added code commentary. Reformatted code. getUniqueFileName function changed. New logFileMessages implemented. +* #14463 / llow statusbar (resize button included) to show as default. +* #14455 / Drag and drop triggers record lock. +* #13818 / htmlspecialchars_decode() - expects parameter 1 to be string, array given Version 22.9.2 -------------- @@ -315,13 +523,13 @@ Date: 22.09.2022 Features ^^^^^^^^ - * #12630 / dateTimePicker refactored. Mode: 'qfq', 'browser', 'no'. Default: 'qfq' - * Add release notes to update UZH CD to latest version and to remove QFQ JS/CSS includes from custom typoscript template. +* #12630 / dateTimePicker refactored. Mode: 'qfq', 'browser', 'no'. Default: 'qfq' +* Add release notes to update UZH CD to latest version and to remove QFQ JS/CSS includes from custom typoscript template. Bug Fixes ^^^^^^^^^ - * #14619 / dateTimePicher: missing popup on first click, record not dirty +* #14619 / dateTimePicher: missing popup on first click, record not dirty Version 22.9.1 -------------- @@ -529,7 +737,7 @@ Bug Fixes * #3446 / Unknown permission mode: 'logged_in' * #9268 / SELECT with outer brackets not recognized as SELECT * #13030 / Max length cuts - line endings \r\n has been counted as two chars. During input they are counted as 1 and - therefore on data load the string has been cutted. + therefore on data load the string has been cutted. * #13139 / Tablesorter: some elements are in front of a sticky title row * #13507 / QFQ function should work without 'sql=' * #13525 / makefile adjusted for multiple users @@ -811,8 +1019,8 @@ Date: 25.06.2020 Bug Fixes ^^^^^^^^^ - * #10641 / TypeAheadTag: Fehler beim gleichzeitigen anlegen mehrerer neuer Tags - * #10794 / Documentation: Crontab entry more clearly +* #10641 / TypeAheadTag: Fehler beim gleichzeitigen anlegen mehrerer neuer Tags +* #10794 / Documentation: Crontab entry more clearly Version 20.6.1 @@ -834,27 +1042,27 @@ Date: 14.06.2020 Notes ^^^^^ - * Add note in Installation.rst to start Apache with Locale en_US.UTF-8. This helps to support Umlaut and other characters - in filenames and wkhtml commandline options (like header/footer). - * Migrate documentation from T3 to ReadTheDocs.io - looks older but 'search' is much more better. New: chapters separated - in individual files. - * For the image to PDF feature, installation of `img2pdf` is required (please check :ref:`preparation`). +* Add note in Installation.rst to start Apache with Locale en_US.UTF-8. This helps to support Umlaut and other characters + in filenames and wkhtml commandline options (like header/footer). +* Migrate documentation from T3 to ReadTheDocs.io - looks older but 'search' is much more better. New: chapters separated + in individual files. +* For the image to PDF feature, installation of `img2pdf` is required (please check :ref:`preparation`). Features ^^^^^^^^ - * #10751 / Allow images to be concatenated for PDF download. - * Fontawesome updated 5.13. - * Extend FE.label size to 1023. - * Local documentation rendering directly via Sphinx. - * Manual: Search is working, table width not truncated anymore, PDF & epub export, redirect qfq.io/doc to docs.qfq.io. - * Update copyright notice. +* #10751 / Allow images to be concatenated for PDF download. +* Fontawesome updated 5.13. +* Extend FE.label size to 1023. +* Local documentation rendering directly via Sphinx. +* Manual: Search is working, table width not truncated anymore, PDF & epub export, redirect qfq.io/doc to docs.qfq.io. +* Update copyright notice. Bug Fixes ^^^^^^^^^ - * #10507 / FormElement.type: 'annotate' is defined two times in Enum - * #10705 / New function 'HelperFile::joinPathFilename($pre, $post)'. Joins only if $post is without leading slash. +* #10507 / FormElement.type: 'annotate' is defined two times in Enum +* #10705 / New function 'HelperFile::joinPathFilename($pre, $post)'. Joins only if $post is without leading slash. Version 20.4.1 diff --git a/Documentation/Report.rst b/Documentation/Report.rst index c3c6cb230400ea73f88e5cea10941b112e1cbcfd..72dd4a5195931a9c47ae9a9fb6209cd4083d4d85 100644 --- a/Documentation/Report.rst +++ b/Documentation/Report.rst @@ -215,7 +215,7 @@ Links The link syntax described in :ref:`column-link` is available inside Twig templates using the `qfqlink` filter:: - {{ "u:http://www.example.com" | qfqlink }} + {{ "u:http://www.example.com" | qfqlink }} will render a link to *http://www.example.com*. @@ -223,11 +223,11 @@ JSON Decode ^^^^^^^^^^^ A String can be JSON decoded in Twig the following way:: - {% set decoded = '["this is one", "this is two"]' | json_decode%} + {% set decoded = '["this is one", "this is two"]' | json_decode%} This can then be used as a normal object in Twig:: - {{ decoded[0] }} + {{ decoded[0] }} will render *this is one*. @@ -237,17 +237,17 @@ Available Store Variables QFQ also provides access to the following stores via the template context. - * record - * sip - * typo3 - * user - * system - * var + - record + - sip + - typo3 + - user + - system + - var All stores are accessed using their lower case name as attribute of the context variable `store`. The active Typo3 front-end user is therefore available as:: - {{ store.typo3.feUser }} + {{ store.typo3.feUser }} Example ^^^^^^^ @@ -345,12 +345,12 @@ Text across several lines ^^^^^^^^^^^^^^^^^^^^^^^^^ To get better human readable SQL queries, it's possible to split a line across several lines. Lines -with keywords are on their own (:ref:`QFQ Keywords (Bodytext)<qfq_keywords>` start a new line). If a line is not a 'keyword' line, it will -be appended to the last keyword line. 'Keyword' lines are detected on: +with keywords are on their own (:ref:`QFQ Keywords (Bodytext)<qfq_keywords>` start a new line). If a line is not a +'keyword' line, it will be appended to the last keyword line. 'Keyword' lines are detected on:: -* <level>.<keyword> = -* { -* <level>[.<sub level] { + <level>.<keyword> = + { + <level>[.<sub level] { Example:: @@ -403,19 +403,19 @@ Nesting of levels Levels can be nested. E.g.:: - 10 { - sql = SELECT ... - 5 { - sql = SELECT ... - head = ... + 10 { + sql = SELECT ... + 5 { + sql = SELECT ... + head = ... + } } - } This is equal to:: - 10.sql = SELECT ... - 10.5.sql = SELECT ... - 10.5.head = ... + 10.sql = SELECT ... + 10.5.sql = SELECT ... + 10.5.head = ... Leading / trailing spaces ^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -423,9 +423,9 @@ Leading / trailing spaces By default, leading or trailing whitespaces are removed from strings behind '='. E.g. 'rend = test ' becomes 'test' for rend. To prevent any leading or trailing spaces, surround them by using single or double ticks. Example:: - 10.sql = SELECT name FROM Person - 10.rsep = ' ' - 10.head = "Names: " + 10.sql = SELECT name FROM Person + 10.rsep = ' ' + 10.head = "Names: " Braces character for nesting @@ -450,20 +450,24 @@ last character of that line must be one of '{[(<'. The corresponding braces are Per QFQ tt-content record, only one type of nesting braces can be used. +. important:: + Be careful to: -* write nothing else than whitespaces/newline behind an **open brace** -* the **closing brace** has to be alone on a line:: + - write nothing else than whitespaces/newline behind an **open brace** + - the **closing brace** has to be alone on a line + +Example:: - 10.sql = SELECT 'Yearly Report' + 10.sql = SELECT 'Yearly Report' - 20 { + 20 { sql = SELECT companyName FROM Company LIMIT 1 head = <h1> tail = </h1> - } + } - 30 { + 30 { sql = SELECT depName FROM Department head = <p> tail = </p> @@ -472,15 +476,15 @@ Be careful to: 1.sql = SELECT name FROM Person LIMIT 7 1.head = Employees: } - } + } - 30.5.tail = More will follow + 30.5.tail = More will follow - 50 + 50 - { + { sql = SELECT 'A query with braces on their own' - } + } .. _`access-column-values`: @@ -504,18 +508,18 @@ The STORE_RECORD will always be merged with previous content. The Level Keys are Example:: - 10.sql = SELECT 'p:/home?form=Person|s|b:success|t:Edit' AS _link - 10.20.sql = SELECT '{{link:R}}', '{{&link:R}}' + 10.sql = SELECT 'p:/home?form=Person|s|b:success|t:Edit' AS _link + 10.20.sql = SELECT '{{link:R}}', '{{&link:R}}' -The first column of row `10.20` returns `p:/home?form=Person|s|b:success|t:Edit`,the second column returns +The first column of row ``10.20`` returns ``p:/home?form=Person|s|b:success|t:Edit``,the second column returns '<span class="btn btn-success"><a href="?home&s=badcaffee1234">Edit</a></span>'. Example STORE_RECORD:: - 10.sql= SELECT p.id AS _pId, p.name FROM Person AS p - 10.5.sql = SELECT adr.city, 'dummy' AS _pId FROM Address AS adr WHERE adr.pId={{pId:R}} - 10.5.20.sql = SELECT '{{pId:R}}' - 10.10.sql = SELECT '{{pId:R}}' + 10.sql= SELECT p.id AS _pId, p.name FROM Person AS p + 10.5.sql = SELECT adr.city, 'dummy' AS _pId FROM Address AS adr WHERE adr.pId={{pId:R}} + 10.5.20.sql = SELECT '{{pId:R}}' + 10.10.sql = SELECT '{{pId:R}}' The line '10.10' will output 'dummy' in cases where there is at least one corresponding address. If there are no addresses (all persons) it reports the person id. @@ -523,10 +527,10 @@ If there is at least one address, it reports 'dummy', cause that's the last stor Example 'level':: - 10.sql= SELECT p.id AS _pId, p.name FROM Person AS p - 10.5.sql = SELECT adr.city, 'dummy' AS _pId FROM Address AS adr WHERE adr.pId={{10.pId}} - 10.5.20.sql = SELECT '{{10.pId}}' - 10.10.sql = SELECT '{{10.pId}}' + 10.sql= SELECT p.id AS _pId, p.name FROM Person AS p + 10.5.sql = SELECT adr.city, 'dummy' AS _pId FROM Address AS adr WHERE adr.pId={{10.pId}} + 10.5.20.sql = SELECT '{{10.pId}}' + 10.10.sql = SELECT '{{10.pId}}' Notes to the level: @@ -598,7 +602,7 @@ QFQ don't care about the content of any SQL-Query - it just copy the content to One exception are columns, whose name starts with '_'. E.g.:: - 10.sql = SELECT 'All', 'cats' AS red, 'are' AS _green, 'grey in the night' AS _link + 10.sql = SELECT 'All', 'cats' AS red, 'are' AS _green, 'grey in the night' AS _link * The first and second column are regular columns. No QFQ processing. * The third column (alias name 'green') is no QFQ special column name, but has an '_' at the beginning: this column @@ -663,11 +667,11 @@ Summary: +------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | _img | :ref:`column_img` - Display images. | +------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ -| _bullet |Display a blue/gray/green/pink/red/yellow bullet. If none color specified, show nothing. | +| _bullet |Display a blue/gray/green/orange/pink/red/yellow bullet. If none color specified, show nothing. | +------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | _check |Display a blue/gray/green/pink/red/yellow checked sign. If none color specified, show nothing. | +------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ -| _nl2br |All newline characters will be converted to `<br>`. | +| _nl2br |All newline characters will be converted to ``<br>``. | +------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | _striptags |HTML Tags will be stripped. | +------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ @@ -700,9 +704,9 @@ Summary: +------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ |_<nonReservedName> |Suppress output. Column names with leading underscore are used to select data from the database and make it available in other parts of the report without generating any output. | +------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ -|_formJson |System internal. Return form with given id as JSON string. (`SELECT 'fid:<formId>[\|reduce][\|b64]' AS _formJson`). | -| |Flag `reduce` filters out 'modified', 'created' as well as keys which hold default values. | -| |Flag `b64` encodes the JSON string in base 64. | +|_formJson |System internal. Return form with given id as JSON string. (``SELECT 'fid:<formId>[\|reduce][\|b64]' AS _formJson``). | +| |Flag ``reduce`` filters out 'modified', 'created' as well as keys which hold default values. | +| |Flag ``b64`` encodes the JSON string in base 64. | +------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ |_encrypt |:ref:`column-encrypt` - Encrypt value. | +------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ @@ -730,7 +734,7 @@ Column: _link +---+---+--------------+-----------------------------------+---------------------------+----------------------------------------------------------------------------------------------------------------------------------------+ |x | |Page |p:<pageSlug> |p:/impressum?foo=bar |Append optional GET parameters afeter '?', no hostname qualifier (automatically set by browser) | +---+---+--------------+-----------------------------------+---------------------------+----------------------------------------------------------------------------------------------------------------------------------------+ -|x | |Download |d:[<exportFilename>] |d:complete.pdf |Link points to `.../typo3conf/ext/qfq/Api/download.php`. Additional parameter SIP encoded. 'Download' needs SIP. See :ref:`download`. | +|x | |Download |d:[<exportFilename>] |d:complete.pdf |Link points to ``.../typo3conf/ext/qfq/Api/download.php``. Additional parameter SIP encoded. 'Download' needs SIP. See :ref:`download`. | +---+---+--------------+-----------------------------------+---------------------------+----------------------------------------------------------------------------------------------------------------------------------------+ |x | |Copy to |y:[some content] |y:this will be copied |Click on it copies the value of 'y:' to the clipboard. Optional a file ('F:...') might be specified as source. | | | |clipboard | | |See :ref:`copyToClipboard`. | @@ -760,9 +764,9 @@ Column: _link +---+---+--------------+-----------------------------------+---------------------------+----------------------------------------------------------------------------------------------------------------------------------------+ | |x |Show |S |S |Show 'show' icon as image | +---+---+--------------+-----------------------------------+---------------------------+----------------------------------------------------------------------------------------------------------------------------------------+ -| |x |Glyph |G:<glyphname> |G:glyphicon-envelope |Show <glyphname>. Check: https://getbootstrap.com/docs/3.4/components/ | +| |x |Glyph |G:<glyphname> |G:glyphicon-envelope |Show <glyphname>. Check: :ref:`bootstrap' | +---+---+--------------+-----------------------------------+---------------------------+----------------------------------------------------------------------------------------------------------------------------------------+ -| |x |Bullet |B:[<color>] |B:green |Show bullet with '<color>'. Colors: blue, gray, green, pink, red, yellow. Default Color: green. | +| |x |Bullet |B:[<color>] |B:green |Show bullet with '<color>'. Colors: blue, gray, green, orange, pink, red, yellow. Default Color: green. | +---+---+--------------+-----------------------------------+---------------------------+----------------------------------------------------------------------------------------------------------------------------------------+ | |x |Check |C:[<color>] |C:green |Show checked with '<color>'. Colors: blue, gray, green, pink, red, yellow. Default Color: green. | +---+---+--------------+-----------------------------------+---------------------------+----------------------------------------------------------------------------------------------------------------------------------------+ @@ -798,7 +802,7 @@ Column: _link +---+---+--------------+-----------------------------------+---------------------------+----------------------------------------------------------------------------------------------------------------------------------------+ | | |Text before |v:text before link |v:Some Text |Useful to show text before a link, delivered through `... AS _link`. See :ref:`linkTextBeforeAfter` | +---+---+--------------+-----------------------------------+---------------------------+----------------------------------------------------------------------------------------------------------------------------------------+ -| | |Text after |V:text after link |v:Some Text |Useful to show text after a link, delivered through `... AS _link`. See :ref:`linkTextBeforeAfter` | +| | |Text after |V:text after link |V:Some Text |Useful to show text after a link, delivered through `... AS _link`. See :ref:`linkTextBeforeAfter` | +---+---+--------------+-----------------------------------+---------------------------+----------------------------------------------------------------------------------------------------------------------------------------+ | | |File |F:<filename> |F:fileadmin/file.pdf |Element source for download mode file|pdf|zip. See :ref:`download`. | +---+---+--------------+-----------------------------------+---------------------------+----------------------------------------------------------------------------------------------------------------------------------------+ @@ -961,22 +965,22 @@ Text before / after link ^^^^^^^^^^^^^^^^^^^^^^^^ * Renders text before and/or after a link. -* Example: `SELECT 'p:{{pageAlias:T}}|t:Reload|v:Some text before |v: some text after' AS _link` -* A typical usecase is to get several `AS _link` columns in one HTML table cell, by still using `fbeg,fend`:: +* Example: ``SELECT 'p:{{pageAlias:T}}|t:Reload|v:Some text before |V: some text after' AS _link`` +* A typical usecase is to get several ``AS _link`` columns in one HTML table cell, by still using ``fbeg,fend``:: - 10 { - sql = SELECT p.id - , 'p:{{pageAlias:T}}|t:Reload 1|v:<td>Some text before |V: - ' AS '_link|_noWrap' - , 'p:{{pageAlias:T}}|t:Reload 2|V:</td>' AS '_link|_noWrap' - , p.name - FROM Person AS p - head = <table> - tail = </table> - rbeg = <tr> - rend = </tr> - fbeg = <td> - fend = </td> - } + 10 { + sql = SELECT p.id + , 'p:{{pageAlias:T}}|t:Reload 1|v:<td>Some text before |V: - ' AS '_link|_noWrap' + , 'p:{{pageAlias:T}}|t:Reload 2|V:</td>' AS '_link|_noWrap' + , p.name + FROM Person AS p + head = <table> + tail = </table> + rbeg = <tr> + rend = </tr> + fbeg = <td> + fend = </td> + } @@ -1019,10 +1023,9 @@ The colum name is composed of the string *page* and a trailing character to spec |_pages |Internal link with show icon (magnifier) |empty |p:<pageSlug>[?param] | +---------------+-----------------------------------------------+-------------------------------------+----------------------------------------------+ - -* All parameter are optional. -* Optional set of predefined icons. -* Optional set of dialog boxes. + * All parameter are optional. + * Optional set of predefined icons. + * Optional set of dialog boxes. +--------------+-------------------------------------------------------------------------------------------------+----------------------------------------------------------+---------------------------------------------------------------+ | Parameter | Description | Default value |Example | @@ -1061,7 +1064,7 @@ These column offers a link, with a confirmation question, to delete one record ( .. -If the record to delete contains column(s), whose column name match on `%pathFileName%` and such a +If the record to delete contains column(s), whose column name match on ``%pathFileName%`` and such a column points to a real existing file, such a file will be deleted too. If the table contains records where the specific file is multiple times referenced, than the file is not deleted (it would break the still existing references). Multiple references are not found, if they use different colummnnames or tablenames. @@ -1211,7 +1214,7 @@ The following parameters can also be written as complete words for ease of use:: [|autosubmit:<on/off>][|grid:<grid>][|xid:<xId>][|xid2:<xId2>][|xid3:<xId3>][|header:<mail header>] [|mode:html] -Send emails. Every mail will be logged in the table `mailLog`. Attachments are supported. +Send emails. Every mail will be logged in the table ``mailLog``. Attachments are supported. **Syntax** :: @@ -1227,13 +1230,13 @@ Send emails. Every mail will be logged in the table `mailLog`. Attachments are s | | f | email |**FROM**: Sender of the email. Optional: 'realname <john@doe.com>' | yes | | | from | | | | +--------------+----------------------------------------+--------------------------------------------------------------------------------------------------+------------+ -| | t | email[,email] |**TO**: Comma separated list of receiver email addresses. Optional: `realname <john@doe.com>` | yes | +| | t | email[,email] |**TO**: Comma separated list of receiver email addresses. Optional: ``realname <john@doe.com>`` | yes | | | to | | | | +--------------+----------------------------------------+--------------------------------------------------------------------------------------------------+------------+ -| | c | email[,email] |**CC**: Comma separated list of receiver email addresses. Optional: 'realname <john@doe.com>' | | +| | c | email[,email] |**CC**: Comma separated list of receiver email addresses. Optional: ``realname <john@doe.com>`` | | | | cc | | | yes | +--------------+----------------------------------------+--------------------------------------------------------------------------------------------------+------------+ -| | B | email[,email] |**BCC**: Comma separated list of receiver email addresses. Optional: 'realname <john@doe.com>' | | +| | B | email[,email] |**BCC**: Comma separated list of receiver email addresses. Optional: ``realname <john@doe.com>`` | | | | bcc | | | yes | +--------------+----------------------------------------+--------------------------------------------------------------------------------------------------+------------+ | | r | REPLY-TO:email |**Reply-to**: Email address to reply to (if different from sender) | | @@ -1283,7 +1286,7 @@ Send emails. Every mail will be logged in the table `mailLog`. Attachments are s * **e|E**: By default, QFQ stores values 'htmlspecialchars()' encoded. If such values have to send by email, the html entities are unwanted. Therefore the default setting for 'subject' und 'body' is to decode the values via 'htmlspecialchars_decode()'. - If this is not wished, it can be turned off by `e=none` and/or `E=none`. + If this is not wished, it can be turned off by ``e=none`` and/or ``E=none``. **Minimal Example** :: @@ -1340,23 +1343,23 @@ Optional any number of sources can be concatenated to a single PDF file: 'C|F:<f Examples in Report:: - # One file attached. - 10.sql = SELECT "t:john.doe@example.com|f:company@example.com|s:Latest News|b:The new version is now available.|F:fileadmin/summary.pdf" AS _sendmail + # One file attached. + 10.sql = SELECT "t:john.doe@example.com|f:company@example.com|s:Latest News|b:The new version is now available.|F:fileadmin/summary.pdf" AS _sendmail - # Two files attached. - 10.sql = SELECT "t:john.doe@example.com|f:company@example.com|s:Latest News|b:The new version is now available.|F:fileadmin/summary.pdf|F:fileadmin/detail.pdf" AS _sendmail + # Two files attached. + 10.sql = SELECT "t:john.doe@example.com|f:company@example.com|s:Latest News|b:The new version is now available.|F:fileadmin/summary.pdf|F:fileadmin/detail.pdf" AS _sendmail - # Two files and a webpage (converted to PDF) are attached. - 10.sql = SELECT "t:john.doe@example.com|f:company@example.com|s:Latest News|b:The new version is now available.|F:fileadmin/summary.pdf|F:fileadmin/detail.pdf|p:?id=export&r=123|d:person.pdf" AS _sendmail + # Two files and a webpage (converted to PDF) are attached. + 10.sql = SELECT "t:john.doe@example.com|f:company@example.com|s:Latest News|b:The new version is now available.|F:fileadmin/summary.pdf|F:fileadmin/detail.pdf|p:?id=export&r=123|d:person.pdf" AS _sendmail - # Two webpages (converted to PDF) are attached. - 10.sql = SELECT "t:john.doe@example.com|f:company@example.com|s:Latest News|b:The new version is now available.|p:?id=export&r=123|d:person123.pdf|p:?id=export&r=234|d:person234.pdf" AS _sendmail + # Two webpages (converted to PDF) are attached. + 10.sql = SELECT "t:john.doe@example.com|f:company@example.com|s:Latest News|b:The new version is now available.|p:?id=export&r=123|d:person123.pdf|p:?id=export&r=234|d:person234.pdf" AS _sendmail - # One file and two webpages (converted to PDF) are *concatenated* to one PDF and attached. - 10.sql = SELECT "t:john.doe@example.com|f:company@example.com|s:Latest News|b:The new version is now available.|C|F:fileadmin/summary.pdf|p:?id=export&r=123|p:?id=export&r=234|d:complete.pdf" AS _sendmail + # One file and two webpages (converted to PDF) are *concatenated* to one PDF and attached. + 10.sql = SELECT "t:john.doe@example.com|f:company@example.com|s:Latest News|b:The new version is now available.|C|F:fileadmin/summary.pdf|p:?id=export&r=123|p:?id=export&r=234|d:complete.pdf" AS _sendmail - # One T3 webpage, protected by a SIP, are attached. - 10.sql = SELECT "t:john.doe@example.com|f:company@example.com|s:Latest News|b:The new version is now available.|p:?id=export&r=123&_sip=1|d:person123.pdf" AS _sendmail + # One T3 webpage, protected by a SIP, are attached. + 10.sql = SELECT "t:john.doe@example.com|f:company@example.com|s:Latest News|b:The new version is now available.|p:?id=export&r=123&_sip=1|d:person123.pdf" AS _sendmail .. _column_img: @@ -1421,7 +1424,7 @@ Run any command on the web server. SELECT 'touch /tmp >/dev/null' AS _exec SELECT 'touch /root 2>&1 >/dev/null' AS _exec -* Multiple commands can be concatenated by `;`:: +* Multiple commands can be concatenated by ``;``:: SELECT 'date; date' AS _exec @@ -1465,7 +1468,7 @@ Run a php function defined in an external script. * The **current working directory** inside the function is the current web instance (e.g. location of index.php). * Hint: Inside the script ``dirname(__FILE__)`` gives the path of the script. * All **output (e.g. using echo) will be rendered** by the special column as is. -* If the function returns an associative array, then the **key-value pairs will be accessible via the VARS store `V`**. +* If the function returns an associative array, then the **key-value pairs will be accessible via the VARS store ``V``**. * If the function throws an **exception** then a standard QFQ error message is shown. * Text sent to 'stderr' by the php function is not returned at all. * The script has access to the following **qfq php functions** using the interface (see examples below): @@ -1510,13 +1513,13 @@ Run a php function defined in an external script. * QFQ report :: - 5.sql = SELECT "IAmInRecordStore" AS _savedInRecordStore - 10.sql = SELECT "F:fileadmin/scripts/my_script.php|call:my_function|arg:a1=Hello&a2=World" AS _script - 20.sql = SELECT "<br><br>Returened value: {{IAmInVarStore:V:alnumx}}" + 5.sql = SELECT "IAmInRecordStore" AS _savedInRecordStore + 10.sql = SELECT "F:fileadmin/scripts/my_script.php|call:my_function|arg:a1=Hello&a2=World" AS _script + 20.sql = SELECT "<br><br>Returened value: {{IAmInVarStore:V:alnumx}}" -* PHP script (`fileadmin/scripts/my_script.php`) :: +* PHP script (``fileadmin/scripts/my_script.php``) :: - <?php + <?php function my_function($param, $qfq) { echo 'The first argument contains all attributes including "F" and "c":<br>'; @@ -1563,8 +1566,8 @@ Most of the other Link-Class attributes can be used to customize the link. :: * Parameter are position independent. * *<params>*: see :ref:`download-parameter-files` * For column ``_pdf`` and ``_zip``, the element sources ``p:...``, ``U:...``, ``u:...``, ``F:...`` might repeated multiple times. -* For column ``_zip``, an optional parameter might define the path and filename inside the ZIP: `F:<orig filename>:<inside ZIP path and filename>` -* To only render the page content without menus add the parameter ``type=2``. For example: ``U:id=pageToPrint&type=2&_sip=1&r=', r.id`` +* For column ``_zip``, an optional parameter might define the path and filename inside the ZIP: ``F:<orig filename>:<inside ZIP path and filename>``. +* To only render the page content without menus add the parameter ``type=2``. For example: ``U:id=pageToPrint&type=2&_sip=1&r=', r.id``. * Example:: # ... AS _file @@ -1612,8 +1615,8 @@ Tips: Example:: - SELECT "d:fileadmin/result.pdf|F:fileadmin/_temp_/test.pdf" AS _savePdf - SELECT "d:fileadmin/result.pdf|F:fileadmin/_temp_/test.pdf|U:id=test&--orientation=landscape" AS _savePdf + SELECT "d:fileadmin/result.pdf|F:fileadmin/_temp_/test.pdf" AS _savePdf + SELECT "d:fileadmin/result.pdf|F:fileadmin/_temp_/test.pdf|U:id=test&--orientation=landscape" AS _savePdf .. _column-save-zip: @@ -1634,9 +1637,9 @@ Save generated ZIP locally on the server. Example:: - SELECT "d:fileadmin/result.zip|F:fileadmin/_temp_/test.pdf" AS _saveZip - SELECT "d:fileadmin/result.zip|F:fileadmin/_temp_/test.pdf|U:id=test&--orientation=landscape" AS _saveZip - SELECT "d:fileadmin/result.zip|F:fileadmin/_temp_/test.pdf|F:fileadmin/_temp_/test2.xlsx|U:id=test&--orientation=landscape" AS _saveZip + SELECT "d:fileadmin/result.zip|F:fileadmin/_temp_/test.pdf" AS _saveZip + SELECT "d:fileadmin/result.zip|F:fileadmin/_temp_/test.pdf|U:id=test&--orientation=landscape" AS _saveZip + SELECT "d:fileadmin/result.zip|F:fileadmin/_temp_/test.pdf|F:fileadmin/_temp_/test2.xlsx|U:id=test&--orientation=landscape" AS _saveZip .. _column-thumbnail: @@ -1682,17 +1685,17 @@ tag. Something like ``<body style="background-image:url(bgimage.jpg)">`` could b Example:: - # SIP protected, IMG tag, thumbnail width 150px - 10.sql = SELECT 'T:fileadmin/file3.pdf' AS _thumbnail + # SIP protected, IMG tag, thumbnail width 150px + 10.sql = SELECT 'T:fileadmin/file3.pdf' AS _thumbnail - # SIP protected, IMG tag, thumbnail width 50px - 20.sql = SELECT 'T:fileadmin/file3.pdf|W:50' AS _thumbnail + # SIP protected, IMG tag, thumbnail width 50px + 20.sql = SELECT 'T:fileadmin/file3.pdf|W:50' AS _thumbnail - # No SIP protection, IMG tag, thumbnail width 150px - 30.sql = SELECT 'T:fileadmin/file3.pdf|s:0' AS _thumbnail + # No SIP protection, IMG tag, thumbnail width 150px + 30.sql = SELECT 'T:fileadmin/file3.pdf|s:0' AS _thumbnail - # SIP protected, only the URL to the image, thumbnail width 150px - 40.sql = SELECT 'T:fileadmin/file3.pdf|s:1|r:7' AS _thumbnail + # SIP protected, only the URL to the image, thumbnail width 150px + 40.sql = SELECT 'T:fileadmin/file3.pdf|s:1|r:7' AS _thumbnail Dimension @@ -1707,7 +1710,7 @@ Cleaning By default, the thumbnail directories are never cleaned. It's a good idea to install a cronjob which purges all files older than 1 year: :: - find /path/to/files -type f -mtime +365 -delete + find /path/to/files -type f -mtime +365 -delete Render """""" @@ -1718,29 +1721,29 @@ are rendered, instead the `secure` thumbnails are loaded asynchonous via the bro browser, all thumbnails appearing after a time. A way to *pre render* thumbnails, is a periodically called (hidden) T3 page, which iterates over all new uploaded files and -triggers the rendering via column `_thumbnail`. +triggers the rendering via column ``_thumbnail``. Thumbnail: secure """"""""""""""""" -Mode 'secure' is activated via enabling SIP (`s:1`, default). The thumbnail is saved under the path `thumbnailDirSecure` +Mode 'secure' is activated via enabling SIP (``s:1``, default). The thumbnail is saved under the path ``thumbnailDirSecure`` as configured in :ref:`configuration`. The secure path needs to be protected against direct file access by the webmaster / webserver configuration too. QFQ returns a HTML 'img'-tag: :: - <img src="api/download.php?s=badcaffee1234"> + <img src="api/download.php?s=badcaffee1234"> Thumbnail: public """"""""""""""""" -Mode 'public' has to be explicit activated by specifying `s:0`. The thumbnail is saved under the path `thumbnailDirPublic` +Mode 'public' has to be explicit activated by specifying ``s:0``. The thumbnail is saved under the path ``thumbnailDirPublic`` as configured in :ref:`configuration`. QFQ returns a HTML 'img'-tag: :: - <img src="{{thumbnailDirPublic:Y}}/<md5 hash>.png"> + <img src="{{thumbnailDirPublic:Y}}/<md5 hash>.png"> .. _column-monitor: @@ -1775,13 +1778,13 @@ then the default is used. See here for more information about default method: :r **Syntax** :: - 10.sql = SELECT firstName AS _encrypt FROM Person WHERE id = 1 - 20.sql = SELECT "Words to be encrypted" AS _encrypt=AES-128 + 10.sql = SELECT firstName AS _encrypt FROM Person WHERE id = 1 + 20.sql = SELECT "Words to be encrypted" AS _encrypt=AES-128 **A useful situation**:: - 10.sql = SELECT "Words to be encrypted" AS '_encrypt=AES-128|encryptedValue|_hide' - 20.sql = UPDATE Person SET secret = '{{&encryptedValue:RE:all}}' WHERE id = 1 + 10.sql = SELECT "Words to be encrypted" AS '_encrypt=AES-128|encryptedValue|_hide' + 20.sql = UPDATE Person SET secret = '{{&encryptedValue:RE:all}}' WHERE id = 1 Valid encryption methods: @@ -1797,7 +1800,7 @@ Decrypting selected columns or strings which are encrypted with QFQ. **Syntax** :: - 10.sql = SELECT secret AS _decrypt FROM Person WHERE id = 1 + 10.sql = SELECT secret AS _decrypt FROM Person WHERE id = 1 .. _copyToClipboard: @@ -1833,30 +1836,30 @@ API Call QFQ Report (e.g. AJAX) * General use API call to fire a specific QFQ tt-content record. Useful for e.g. AJAX calls. No Typo3 is involved. *No FE-Group access control*. * This defines just a simple API endpoint. For defining a rest API see: :ref:`restApi`. * Custom response headers can be defined by setting the variable `apiResponseHeader` in the record store. - * Multiple headers should be separated by `\n` or `\r\n`. e.g.: `Content-Type: application/json\ncustom-header: fooBar` + * Multiple headers should be separated by ``\n`` or ``\r\n``. e.g.: `Content-Type: application/json\ncustom-header: fooBar` * If the api call succeeds the rendered content of the report is returned as is. (no additional formatting, no JSON encoding) * You can use MYSQL to create Json. See: `MYSQL create Json <https://dev.mysql.com/doc/refman/8.0/en/json-creation-functions.html>`_ and `MariaDB Json functions <https://mariadb.com/kb/en/json-functions/>`_ -* If a QFQ error occurs then a http-status of 400 is returned together with a JSON encoded response of the form: `{"status":"error", "message":"..."}` +* If a QFQ error occurs then a http-status of 400 is returned together with a JSON encoded response of the form: ``{"status":"error", "message":"..."}`` Example QFQ record JS (with tt_content.uid=12345):: - 5.sql = SELECT "See console log for output" + 5.sql = SELECT "See console log for output" - # Register SIP with given arguments. - 10.sql = SELECT 'U:uid=12345&arg1=Hello&arg2=World|s|r:8' AS '_link|col1|_hide' + # Register SIP with given arguments. + 10.sql = SELECT 'U:uid=12345&arg1=Hello&arg2=World|s|r:8' AS '_link|col1|_hide' - # Build JS - 10.tail = <script> - console.log('start api request'); - $.ajax({ - url: 'typo3conf/ext/qfq/Classes/Api/dataReport.php?s={{&col1:RE}}', - data: {arg3:456, arg4:567}, - method: 'POST', - dataType: 'TEXT', - success: function(response, status, jqxhr) {console.log(response); console.log(jqxhr.getAllResponseHeaders());}, - error: function(jqXHR, textStatus, errorThrown) {console.log(jqXHR.responseText, textStatus, errorThrown);} - }); - </script> + # Build JS + 10.tail = <script> + console.log('start api request'); + $.ajax({ + url: 'typo3conf/ext/qfq/Classes/Api/dataReport.php?s={{&col1:RE}}', + data: {arg3:456, arg4:567}, + method: 'POST', + dataType: 'TEXT', + success: function(response, status, jqxhr) {console.log(response); console.log(jqxhr.getAllResponseHeaders());}, + error: function(jqXHR, textStatus, errorThrown) {console.log(jqXHR.responseText, textStatus, errorThrown);} + }); + </script> Example QFQ record called by above AJAX:: @@ -1868,7 +1871,7 @@ Example QFQ record called by above AJAX:: Example text returned by the above AJAX call:: - Hello World 456 5672020-09-22 18:09:47 + Hello World 456 5672020-09-22 18:09:47 .. _rest_client: @@ -1885,12 +1888,12 @@ The received data can be processed in subsequent calls. Example:: - # Retrieve information. Received data is delivered in JSON and decoded / copied on the fly to CLIENT store (CLIENT store is emptied beforehand) - 10.sql = SELECT 'n:https://www.dummy.ord/rest/person/id/123' AS _restClient - 20.sql = SELECT 'Status: {{http-status:C}}<br>Name: {{name:C:alnumx}}<br>Surname: {{surname:C:alnumx}}' + # Retrieve information. Received data is delivered in JSON and decoded / copied on the fly to CLIENT store (CLIENT store is emptied beforehand) + 10.sql = SELECT 'n:https://www.dummy.ord/rest/person/id/123' AS _restClient + 20.sql = SELECT 'Status: {{http-status:C}}<br>Name: {{name:C:alnumx}}<br>Surname: {{surname:C:alnumx}}' - # Simple POST request via https. Result is printed on the page. - 10.sql = SELECT 'n:https://www.dummy.ord/rest/person/id/123|method:POST|content:{"name":"John";"surname":"Doe"}' AS _restClient + # Simple POST request via https. Result is printed on the page. + 10.sql = SELECT 'n:https://www.dummy.ord/rest/person/id/123|method:POST|content:{"name":"John";"surname":"Doe"}' AS _restClient +-------------------+----------------------------------------------------+--------------------------------------------------------+ | Token | Example | Comment | @@ -1911,7 +1914,7 @@ Example:: **Header** -* Each header must be separated by ``\r\n`` or `\n`. +* Each header must be separated by ``\r\n`` or ``\n``. * An explicit given header will overwrite the named default header. * Default header: @@ -1923,7 +1926,7 @@ Example:: Warning: Only use base64 for SSL encrypted connections:: - 10.sql = SELECT CONCAT('n:https://sample.com/id/1234|header:Authorization: Basic ', TO_BASE64('{{username}}:{{password}}') ) + 10.sql = SELECT CONCAT('n:https://sample.com/id/1234|header:Authorization: Basic ', TO_BASE64('{{username}}:{{password}}') ) **Result received** @@ -2037,7 +2040,7 @@ Example:: Output:: - This is a `more..` + This is a `more..` .. _qifempty: @@ -2052,7 +2055,7 @@ Example:: Output:: - hello world- + hello world- .. _qdate_format: @@ -2079,11 +2082,11 @@ Non alphanumerical characters are stripped off. Spaces are replaced by '-'. All Example:: - 10.sql = SELECT QSLUGIFY('abcd ABCD ae.ä.oe.ö.ue.ü z[]{}()<>.,?Z') + 10.sql = SELECT QSLUGIFY('abcd ABCD ae.ä.oe.ö.ue.ü z[]{}()<>.,?Z') Output:: - abcd-abcd-ae-a-oe-o-ue-u-z-z + abcd-abcd-ae-a-oe-o-ue-u-z-z .. _qent_squote: @@ -2094,11 +2097,11 @@ Convert all single ticks in a string to the HTML entity "'" Example:: - 10.sql = SELECT QENT_SQUOTE("John's car") + 10.sql = SELECT QENT_SQUOTE("John's car") Output:: - John's car + John's car .. _qent_dquote: @@ -2109,11 +2112,11 @@ Convert all double ticks in a string to the HTML entity """ Example:: - 10.sql = SELECT QENT_SQUOTE('A "nice" event') + 10.sql = SELECT QENT_SQUOTE('A "nice" event') Output:: - A "nice" event + A "nice" event .. _qesc_squote: @@ -2125,12 +2128,12 @@ escaped single tick. Example:: - Be Music.style = "Rock'n' Roll" - 10.sql = SELECT QESC_SQUOTE(style) FROM Music + Be Music.style = "Rock'n' Roll" + 10.sql = SELECT QESC_SQUOTE(style) FROM Music Output:: - Rock\'n\'n Roll + Rock\'n\'n Roll .. _qesc_dquote: @@ -2142,12 +2145,12 @@ escaped double tick. Example:: - Set Comment.note = 'A "nice" event' - 10.sql = SELECT QESC_DQUOTE(style) FROM Music + Set Comment.note = 'A "nice" event' + 10.sql = SELECT QESC_DQUOTE(style) FROM Music Output:: - Rock\'n\'n Roll + Rock\'n\'n Roll .. _qmanr: @@ -2159,11 +2162,11 @@ The SQL function QMANR(manr) returns '00-000-000', if 'manr' is 00000000. Example:: - 10.sql = SELECT QMANR(12345678) + 10.sql = SELECT QMANR(12345678) Output:: - 12-345-678 + 12-345-678 .. _strip_tags: @@ -2174,11 +2177,11 @@ The SQL function strip_tags(input) returns 'input' without any HTML tags. Example:: - 10.sql = SELECT strip_tags('<a href="https://example.com"><b>my name</b> <i>is john</i></a> - end of sentence') + 10.sql = SELECT strip_tags('<a href="https://example.com"><b>my name</b> <i>is john</i></a> - end of sentence') Output:: - my name is john - end of sentence + my name is john - end of sentence .. _qfq_function: @@ -2205,20 +2208,20 @@ values. Example tt-content record for the function:: - Subheader: getFirstName - Code: - # - # {{pId:R}} - # - render = api - - 100 { - sql = SELECT p.firstName AS _firstName - , NOW() AS now - , CONCAT('p:{{pageSlug:T}}?form=person&r=', p.id ) AS '_pagee|_hide|myLink' - FROM Person AS p - WHERE p.id={{pId:R}} - } + Subheader: getFirstName + Code: + # + # {{pId:R}} + # + render = api + + 100 { + sql = SELECT p.firstName AS _firstName + , NOW() AS now + , CONCAT('p:{{pageSlug:T}}?form=person&r=', p.id ) AS '_pagee|_hide|myLink' + FROM Person AS p + WHERE p.id={{pId:R}} + } Example tt-content record for the calling report:: @@ -2250,9 +2253,9 @@ Example tt-content record for the calling report:: Explanation: * Level 10 iterates over all `person`. -* Level 10.20 calls QFQ function `getFirstName()` by delivering the `pId` via STORE_RECORD. The function expects the return - value `firstName` and `myLink`. -* The function selects in level 100 the person given by ``{{pId:R}}``. The `firstName` is not printed but a hidden column. +* Level 10.20 calls QFQ function ``getFirstName()`` by delivering the ``pId`` via STORE_RECORD. The function expects the return + value ``firstName`` and ``myLink``. +* The function selects in level 100 the person given by ``{{pId:R}}``. The ``firstName`` is not printed but a hidden column. Column ``now`` is printed. Column 'myLink' is a rendered link, but not printed. * Level 10.30 prints the return values ``firstName`` and ``myLink`` (as rendered link and as source definition). The last column is the output of the function - the value of ``NOW()`` @@ -2308,8 +2311,8 @@ By using the ``_link`` column name: * the optional ``M:...`` (Mode) specifies the export type (file, pdf, qfqpdf, zip, export), * the alttext ``a:...`` specifies a message in the download popup. -By using ``_pdf``, ``_Pdf``, ``_file``, ``_File``, ``_zip``, ``_Zip``, ``_excel`` as column name, the options `d`, -`M` (pdf: wkhtml) and `s` will be set. +By using ``_pdf``, ``_Pdf``, ``_file``, ``_File``, ``_zip``, ``_Zip``, ``_excel`` as column name, the options ``d``, +``M`` (pdf: wkhtml) and ``s`` will be set. All files will be read by PHP - therefore the directory might be protected against direct web access. This is the preferred option to offer secure downloads via QFQ. Check `secure-direct-file-access`_. @@ -2323,11 +2326,11 @@ Parameter and (element) sources * *exportFilename* = <filename for save as> - Name, offered in the 'File save as' browser dialog. Default: 'output.<ext>'. - If there is no `exportFilename` defined, then the original filename is taken (if there is one, else: output...). + If there is no ``exportFilename`` defined, then the original filename is taken (if there is one, else: output...). The user typically expects meaningful and distinct file names for different download links. -* mode `persistent link` (s:0) - *download*: `d:[<path/name>]<key1>[/<keyN>]` +* mode ``persistent link`` (s:0) - *download*: ``d:[<path/name>]<key1>[/<keyN>]`` This setup is divided in part a) and b): @@ -2348,12 +2351,12 @@ Parameter and (element) sources SELECT CONCAT('d|F:', n.pathFileName) FROM Note AS n WHERE n.id=? - All `?` in the SQL statement will be replaced by the specified parameter. If there are more `?` than parameter, - the last parameter will be reused for all pending `?`. + All ``?`` in the SQL statement will be replaced by the specified parameter. If there are more ``?`` than parameter, + the last parameter will be reused for all pending ``?``. E.g. ``10.sql = SELECT 'd:1234|t:File.pdf' AS _link`` creates a link ``<a href="typo3conf/ext/qfq/Classes/Api/download.php/1234"><span class="btn btn-default">File.pdf</span></span>``. - If the user clicks on the link, QFQ will extract the `1234` argument and via ``download.php`` the query (defined in + If the user clicks on the link, QFQ will extract the ``1234`` argument and via ``download.php`` the query (defined in the Typo QFQ extension config) will be prepared and fires ``SELECT CONCAT('d|F:', n.pathFileName, '|t:File.pdf') FROM Note AS n WHERE n.id=1234``. The download of the file, specified by ``n.pathFileName``, will start. @@ -2378,14 +2381,14 @@ Parameter and (element) sources * *mode* = <file | pdf | qfqpdf | zip | excel> - * pdf: `wkhtml` will be used to render the pdf. - * qfqpdf: `qfqpdf` will be used to render the pdf. + * pdf: ``wkhtml`` will be used to render the pdf. + * qfqpdf: ``qfqpdf`` will be used to render the pdf. * If ``M:file``, the mime type is derived dynamically from the specified file. In this mode, only one element source is allowed per download link (no concatenation). * In case of multiple element sources, only `pdf`, `zip` and `excel` (template mode) is supported. - * If ``M:zip`` is used together with `p:...`, `U:...` or `u:..`, those HTML pages will be converted to PDF. Those files + * If ``M:zip`` is used together with ``p:...``, ``U:...`` or ``u:..``, those HTML pages will be converted to PDF. Those files get generic filenames inside the archive. * If not specified, the **default** 'Mode' depends on the number of specified element sources (=file or web page): @@ -2440,20 +2443,20 @@ Parameter and (element) sources * 'M:pdf' - *WKHTML Options* for `page`, `urlParam` or `url`: * The 'HTML to PDF' will be done via `wkhtmltopdf`. - * All possible options, suitable for `wkhtmltopdf`, can be submitted in the `p:...`, `u:...` or `U:...` element source. + * All possible options, suitable for `wkhtmltopdf`, can be submitted in the ``p:...``, ``u:...`` or ``U:...`` element source. Check `wkhtmltopdf.txt <https://wkhtmltopdf.org/usage/wkhtmltopdf.txt>`_ for possible options. Be aware that key/value tuple in the documentation is separated by a space, but to respect the QFQ key/value notation of URLs, - the key/value tuple in `p:...`, `u:...` or `U:...` has to be separated by '='. Please see last example below. + the key/value tuple in ``p:...``, ``u:...`` or ``U:...`` has to be separated by '='. Please see last example below. * If an option contains an '&' it must be escaped with double \\ . See example. * 'M:qfqpdf' - *qfqpdf Options* for `page`, `urlParam` or `url`: * The 'HTML to PDF' will be done via `qfqpdf`. * Check https://puppeteer.github.io/puppeteer and https://git.math.uzh.ch/bbaer/qfqpdf/-/tree/master - * All possible options, suitable for `qfqpdf`, can be submitted in the `p:...`, `u:...` or `U:...` element source. + * All possible options, suitable for `qfqpdf`, can be submitted in the ``p:...``, ``u:...`` or ``U:...`` element source. Be aware that key/value tuple in the documentation is separated by a space, but to respect the QFQ key/value notation of URLs, - the key/value tuple in `p:...`, `u:...` or `U:...` has to be separated by '='. Please see last example below. + the key/value tuple in ``p:...``, ``u:...`` or ``U:...`` has to be separated by '='. Please see last example below. * If an option contains an '&' it must be escaped with double \\ . See example. * Page numbering is done via HTML templating / CSS classes: ``--header-template '<div style="font-size:5mm;" class="pageNumber"></div>'`` @@ -2461,58 +2464,58 @@ Parameter and (element) sources Example `_link`: :: - # single `file`. Specifying a popup message window text is not necessary, cause a file directly accessed is fast. - SELECT "d:file.pdf|s|t:Download|F:fileadmin/pdf/test.pdf" AS _link + # single `file`. Specifying a popup message window text is not necessary, cause a file directly accessed is fast. + SELECT "d:file.pdf|s|t:Download|F:fileadmin/pdf/test.pdf" AS _link - # single `file`, with mode - SELECT "d:file.pdf|M:pdf|s|t:Download|F:fileadmin/pdf/test.pdf" AS _link + # single `file`, with mode + SELECT "d:file.pdf|M:pdf|s|t:Download|F:fileadmin/pdf/test.pdf" AS _link - # three sources: two pages and one file - SELECT "d:complete.pdf|s|t:Complete PDF|p:id=detail&r=1|p:id=detail2&r=1|F:fileadmin/pdf/test.pdf" AS _link + # three sources: two pages and one file + SELECT "d:complete.pdf|s|t:Complete PDF|p:id=detail&r=1|p:id=detail2&r=1|F:fileadmin/pdf/test.pdf" AS _link - # qfqpdf - three sources: two pages and one file - SELECT "d:complete.pdf|M:qfqpdf|s|t:Complete PDF|p:id=detail&r=1|p:id=detail2&r=1|F:fileadmin/pdf/test.pdf" AS _link + # qfqpdf - three sources: two pages and one file + SELECT "d:complete.pdf|M:qfqpdf|s|t:Complete PDF|p:id=detail&r=1|p:id=detail2&r=1|F:fileadmin/pdf/test.pdf" AS _link - # three sources: two pages and one file - SELECT "d:complete.pdf|s|t:Complete PDF|p:id=detail&r=1|p:id=detail2&r=1|F:fileadmin/pdf/test.pdf" AS _link + # three sources: two pages and one file + SELECT "d:complete.pdf|s|t:Complete PDF|p:id=detail&r=1|p:id=detail2&r=1|F:fileadmin/pdf/test.pdf" AS _link - # three sources: two pages and one file, parameter to wkhtml will be SIP encoded - SELECT "d:complete.pdf|s|t:Complete PDF|p:id=detail&r=1&_sip=1|p:id=detail2&r=1&_sip=1|F:fileadmin/pdf/test.pdf" AS _link + # three sources: two pages and one file, parameter to wkhtml will be SIP encoded + SELECT "d:complete.pdf|s|t:Complete PDF|p:id=detail&r=1&_sip=1|p:id=detail2&r=1&_sip=1|F:fileadmin/pdf/test.pdf" AS _link - # three sources: two pages and one file, the second page will be in landscape and pagesize A3 - SELECT "d:complete.pdf|s|t:Complete PDF|p:id=detail&r=1|p:id=detail2&r=1&--orientation=Landscape&--page-size=A3|F:fileadmin/pdf/test.pdf" AS _link + # three sources: two pages and one file, the second page will be in landscape and pagesize A3 + SELECT "d:complete.pdf|s|t:Complete PDF|p:id=detail&r=1|p:id=detail2&r=1&--orientation=Landscape&--page-size=A3|F:fileadmin/pdf/test.pdf" AS _link - # One source and a header file. Note: the parameter to the header URL is escaped with double backslash. - SELECT "d:complete.pdf|s|t:Complete PDF|p:id=detail2&r=1&--orientation=Landscape&--header={{URL:R}}?indexp.php?id=head\\&L=1|F:fileadmin/pdf/test.pdf" AS _link + # One source and a header file. Note: the parameter to the header URL is escaped with double backslash. + SELECT "d:complete.pdf|s|t:Complete PDF|p:id=detail2&r=1&--orientation=Landscape&--header={{URL:R}}?indexp.php?id=head\\&L=1|F:fileadmin/pdf/test.pdf" AS _link - # One indirect source reference - SELECT "d:complete.pdf|s|t:Complete PDF|source:centralPdf&pId=1234" AS _link + # One indirect source reference + SELECT "d:complete.pdf|s|t:Complete PDF|source:centralPdf&pId=1234" AS _link - An additional tt-content record is defined with `sub header: centralPdf`. One or multiple attachments might be concatenated. - 10.sql = SELECT '|F:', a.pathFileName FROM Attachments AS a WHERE a.pId={{pId:S}} + An additional tt-content record is defined with `sub header: centralPdf`. One or multiple attachments might be concatenated. + 10.sql = SELECT '|F:', a.pathFileName FROM Attachments AS a WHERE a.pId={{pId:S}} .. Example `_pdf`, `_zip`: :: - # Page 1: p:id=1&--orientation=Landscape&--page-size=A3 - # Page 2: p:id=form - # File 3: F:fileadmin/file.pdf - SELECT 't:PDF|a:Creating a new PDF|p:id=1&--orientation=Landscape&--page-size=A3|p:id=form|F:fileadmin/file.pdf' AS _pdf + # Page 1: p:id=1&--orientation=Landscape&--page-size=A3 + # Page 2: p:id=form + # File 3: F:fileadmin/file.pdf + SELECT 't:PDF|a:Creating a new PDF|p:id=1&--orientation=Landscape&--page-size=A3|p:id=form|F:fileadmin/file.pdf' AS _pdf - # Page 1: p:id=1 - # Page 2: u:http://www.example.com - # File 3: F:fileadmin/file.pdf - SELECT 't:PDF - 3 Files|a:Please be patient|p:id=1|u:http://www.example.com|F:fileadmin/file.pdf' AS _pdf + # Page 1: p:id=1 + # Page 2: u:http://www.example.com + # File 3: F:fileadmin/file.pdf + SELECT 't:PDF - 3 Files|a:Please be patient|p:id=1|u:http://www.example.com|F:fileadmin/file.pdf' AS _pdf - # Page 1: p:id=1 - # Page 2: p:id=form - # File 3: F:fileadmin/file.pdf - SELECT CONCAT('t:ZIP - 3 Pages|a:Please be patient|p:id=1|p:id=form|F:', p.pathFileName) AS _zip + # Page 1: p:id=1 + # Page 2: p:id=form + # File 3: F:fileadmin/file.pdf + SELECT CONCAT('t:ZIP - 3 Pages|a:Please be patient|p:id=1|p:id=form|F:', p.pathFileName) AS _zip .. -Use the `--print-media-type` as wkhtml option to access the page with media type 'printer'. Depending on the website +Use the ``--print-media-type`` as wkhtml option to access the page with media type 'printer'. Depending on the website configuration this switches off navigation and background images. @@ -2521,14 +2524,14 @@ configuration this switches off navigation and background images. Cache ----- -Parameter: `cache[:[timestamp]|[table/id[/column][,...]]` +Parameter: ``cache[:[timestamp]|[table/id[/column][,...]]`` - * Caching will be enabled if the keyword `cache` is given in the download link definition. Example see below. + * Caching will be enabled if the keyword ``cache`` is given in the download link definition. Example see below. * *On the fly* rendered files (like PDF, ZIP, Excel) can be cached on the server (_pdf, _zip, _excel, _file, _savePdf, _saveZip). * Any further access won't trigger a new rendering, instead the already cached file will be delivered. * Cached files will be identified by the md5 sum of their source definition. The md5 name doesn't affect the final *save as filename*. - * Cached files are saved under `fileadmin/protected/cache`. The directory can be configured in the + * Cached files are saved under ``fileadmin/protected/cache``. The directory can be configured in the QFQ extension configuration `File > cacheDirSecure`. See :ref:`qfq.json`. * A cached file becomes outdated (will be rendered and saved again), if: @@ -2544,7 +2547,7 @@ Parameter: `cache[:[timestamp]|[table/id[/column][,...]]` * `timestamp`: format = yyyy-mm-dd [hh[:mm[:ss]]] * `table/id[/column]`: - * Fire query `SELECT <column> FROM <table> WHERE <id>=id`. Compare the result with the cached file modification timestamp. + * Fire query ``SELECT <column> FROM <table> WHERE <id>=id``. Compare the result with the cached file modification timestamp. * Default for `column` is `modified`. * If more complex queries are needed, use `timestamp` instead and precalculate them first. @@ -2557,18 +2560,18 @@ Parameter: `cache[:[timestamp]|[table/id[/column][,...]]` Example:: - # Page 1: p:id=1&--orientation=Landscape&--page-size=A3 - # Page 2: p:id=form&r=234 - # File 3: F:fileadmin/file.pdf + # Page 1: p:id=1&--orientation=Landscape&--page-size=A3 + # Page 2: p:id=form&r=234 + # File 3: F:fileadmin/file.pdf - # This definition flushes the cached file, if file.pdf is younger than the cached file. This is fine if page 1 & 2 never changes. - SELECT 't:PDF|a:Creating a new PDF|p:id=1&--orientation=Landscape&--page-size=A3|p:id=form&r=234|F:fileadmin/file.pdf|cache' AS _pdf + # This definition flushes the cached file, if file.pdf is younger than the cached file. This is fine if page 1 & 2 never changes. + SELECT 't:PDF|a:Creating a new PDF|p:id=1&--orientation=Landscape&--page-size=A3|p:id=form&r=234|F:fileadmin/file.pdf|cache' AS _pdf - # This definition flushes the cached file if file.pdf or content which represent page 1 by table form.id=234 is younger than the cached file. - SELECT 't:PDF|a:Creating a new PDF|p:id=1&--orientation=Landscape&--page-size=A3|p:id=form|F:fileadmin/file.pdf|cache:Form/{{fId:r}}' AS _pdf + # This definition flushes the cached file if file.pdf or content which represent page 1 by table form.id=234 is younger than the cached file. + SELECT 't:PDF|a:Creating a new PDF|p:id=1&--orientation=Landscape&--page-size=A3|p:id=form|F:fileadmin/file.pdf|cache:Form/{{fId:r}}' AS _pdf - # Example with limited use: Cache is skipped until 2022-12-31 23:59:59 or if record in table Form with id=123,column 'modified' younger than cached file. - SELECT 't:PDF - 3 Files|a:Please be patient|p:id=1|u:http://www.example.com|F:fileadmin/file.pdf|cache:2022-12-31 23:59:59,:Form/123' AS _pdf + # Example with limited use: Cache is skipped until 2022-12-31 23:59:59 or if record in table Form with id=123,column 'modified' younger than cached file. + SELECT 't:PDF - 3 Files|a:Please be patient|p:id=1|u:http://www.example.com|F:fileadmin/file.pdf|cache:2022-12-31 23:59:59,:Form/123' AS _pdf Rendering PDF letters ^^^^^^^^^^^^^^^^^^^^^ @@ -2645,7 +2648,7 @@ Best practice: Use in `report`:: - sql = SELECT CONCAT('d:Letter.pdf|t:',p.firstName, ' ', p.name + sql = SELECT CONCAT('d:Letter.pdf|t:',p.firstName, ' ', p.name , '|p:id=letterbody&pId=', p.id, '&_sip=1' , '&--margin-top=50mm' , '&--header-html={{BASE_URL_PRINT:Y}}?id=letterheader' @@ -2660,13 +2663,13 @@ Use in `report`:: Sendmail. Parameter: :: - sendMailAttachment={{SELECT 'd:Letter.pdf|t:', p.firstName, ' ', p.name, '|p:id=letterbody&pId=', p.id, '&_sip=1&--margin-top=50mm&--margin-bottom=20mm&--header-html={{BASE_URL_PRINT:Y}}?id=letterheader&--footer-right="Seite: [page]/[toPage]"&--footer-font-size=8&--footer-spacing=10' FROM Person AS p WHERE p.id={{id:S}} }} + sendMailAttachment={{SELECT 'd:Letter.pdf|t:', p.firstName, ' ', p.name, '|p:id=letterbody&pId=', p.id, '&_sip=1&--margin-top=50mm&--margin-bottom=20mm&--header-html={{BASE_URL_PRINT:Y}}?id=letterheader&--footer-right="Seite: [page]/[toPage]"&--footer-font-size=8&--footer-spacing=10' FROM Person AS p WHERE p.id={{id:S}} }} Replace the static content elements from 2. and 3. by QFQ Content elements as needed:: - 10.sql = SELECT '<div class="letter-receiver"><p>', p.name AS '_+br', p.street AS '_+br', p.city AS '_+br', '</p>' - FROM Person AS p - WHERE p.id={{pId:S}} + 10.sql = SELECT '<div class="letter-receiver"><p>', p.name AS '_+br', p.street AS '_+br', p.city AS '_+br', '</p>' + FROM Person AS p + WHERE p.id={{pId:S}} Export area @@ -2681,13 +2684,13 @@ started as the webserver user). Create a separated export tree in Typo3 Backend, which is IP access restricted. Only localhost or the FE_GROUP 'admin' is allowed to access: :: - tmp.restrictedIPRange = 127.0.0.1,::1 - [IP = {$tmp.restrictedIPRange} ][usergroup = admin] + tmp.restrictedIPRange = 127.0.0.1,::1 + [IP = {$tmp.restrictedIPRange} ][usergroup = admin] page.10 < styles.content.get - [else] + [else] page.10 = TEXT page.10.value = Please access from localhost or log in as 'admin' user. - [global] + [global] .. _excel-export: @@ -2716,7 +2719,7 @@ It's much easier to do all customizations via Excel and creating a template than Setup """"" -* Create a special column name `_excel` (or `_link`) in QFQ/Report. As a source, define a T3 PageContent, which has to +* Create a special column name ``_excel`` (or ``_link``) in QFQ/Report. As a source, define a T3 PageContent, which has to deliver the dynamic content (also :ref:`excel-export-sample<excel-export-sample>`). :: SELECT CONCAT('d:final.xlsx|M:excel|s:1|t:Excel (new)|uid:<tt-content record id>') AS _link @@ -2845,13 +2848,13 @@ Creates a menu with custom links. The same notation and options are used as with Format String:: - <dropdown menu symbol options>||<menu entry 1>||<menu entry 2>||... + <dropdown menu symbol options>||<menu entry 1>||<menu entry 2>||... Each menu entry is separated by two bars! A menu entry itself might contain multiple single bars. Example 1:: - SELECT 'z||p:home|t:Home|o:Jump to home||p:person&form=person&r=123|t:Edit: John Doe|s' AS _link + SELECT 'z||p:home|t:Home|o:Jump to home||p:person&form=person&r=123|t:Edit: John Doe|s' AS _link This defines a menu (three vertical buttons) - a click on it shows two menu entries: 'Home' and 'Edit: John Doe' @@ -2867,7 +2870,7 @@ Format a menu entry: * *qfq link*: All options as with a regular QFQ link. * *header*: If a text starts with '===', it becomes a header in the dropdown menu. Multiple headers are possible. - Headers can't be a link. An additional `r:1` is necessary. + Headers can't be a link. An additional ``r:1`` is necessary. * *separator*: If a text is exactly '---', it becomes a separator line between two menu entries. An additional ``r:1`` is necessary. * *disabled menu entry*: If a text starts with '---' (like separator), the following text becomes a disable menu entry. @@ -2906,7 +2909,7 @@ WebSocket Sending messages via WebSocket and receiving the answer is done via: :: - SELECT 'w:ws://<host>:<port>/<path>|t:<message>' AS _websocket + SELECT 'w:ws://<host>:<port>/<path>|t:<message>' AS _websocket Instead of ``... AS _websocket`` it's also possible to use ``... AS _link`` (same syntax). @@ -2919,7 +2922,7 @@ case 'websocket' or 'link'). Example:: - SELECT 'w:ws://<host>:<port>/<path>|t:<message>' AS '_websocket|_hide' + SELECT 'w:ws://<host>:<port>/<path>|t:<message>' AS '_websocket|_hide' .. tip:: @@ -2927,7 +2930,7 @@ Example:: Example:: - SELECT 'w:ws://<host>:<port>/<path>|t:<message>' AS '_websocket|myName' + SELECT 'w:ws://<host>:<port>/<path>|t:<message>' AS '_websocket|myName' .. tip:: @@ -2935,12 +2938,12 @@ Example:: Example:: - SELECT 'w:ws://<host>:<port>/<path>|t:<message>' AS '_websocket|myName' + SELECT 'w:ws://<host>:<port>/<path>|t:<message>' AS '_websocket|myName' - Results: + Results: - '{{myName:R}}' >> 'w:ws://<host>:<port>/<path>|t:<message>' - '{{&myName:R}}' >> '<received socket answer>' + '{{myName:R}}' >> 'w:ws://<host>:<port>/<path>|t:<message>' + '{{&myName:R}}' >> '<received socket answer>' .. _drag_and_drop: @@ -3038,8 +3041,8 @@ a 'drag and drop', the order number shows the old. To update the dragable elemen predefined html id has to be assigned them. After an update, all changed order number (referenced by the html id) will be updated via AJAX. -The html id per element is defined by `qfq-dnd-ord-id-<id>` where `<id>` is the record id. Same example as above, but -with an updated `n.ord` column:: +The html id per element is defined by ``qfq-dnd-ord-id-<id>`` where ``<id>`` is the record id. Same example as above, but +with an updated ``n.ord`` column:: 10 { sql = SELECT '<tr id="anytag-', n.id,'" data-dnd-id="', n.id,'" data-columns="3">' , n.id AS '_+td', n.note AS '_+td', @@ -3060,7 +3063,7 @@ A dedicated `Form`, without any `FormElements`, is used to define the reorder lo Fields: * Name: <custom form name> - used in Part 1 in the ``_data-dnd-api`` variable. -* Table: <table with the element records> - used to update the records specified by `dragAndDropOrderSql`. +* Table: <table with the element records> - used to update the records specified by ``dragAndDropOrderSql``. * Parameter: @@ -3073,27 +3076,27 @@ Fields: +-------------------------------------------------------+--------------------------------------------------------------+ | dragAndDropOrderSql = | Query to selects the *same* records as the report in the | | {{!SELECT n.id AS id, n.ord AS ord FROM Note AS n | same *order!* Inconsistencies results in order differences. | -| ORDER BY n.ord}} | The columns `id` and `ord` are *mandatory.* | +| ORDER BY n.ord}} | The columns ``id`` and ``ord`` are *mandatory.* | +-------------------------------------------------------+--------------------------------------------------------------+ The form related to the example of part 1 ('div' or 'table'): :: - Form.name: dndSortNote - Form.table: Note - Form.parameter: orderInterval = 1 - Form.parameter: orderColumn = ord - Form.parameter: dragAndDropOrderSql = {{!SELECT n.id AS id, n.ord AS ord FROM Note AS n WHERE n.grId={{grId:S0}} ORDER BY n.ord}} + Form.name: dndSortNote + Form.table: Note + Form.parameter: orderInterval = 1 + Form.parameter: orderColumn = ord + Form.parameter: dragAndDropOrderSql = {{!SELECT n.id AS id, n.ord AS ord FROM Note AS n WHERE n.grId={{grId:S0}} ORDER BY n.ord}} Re-Order: -QFQ iterates over the result set of `dragAndDropOrderSql`. The value of column `id` have to correspond to the dragged HTML - element (given by `data-dnd-id`). Reordering always start with `orderInterval` and is incremented by `orderInterval` with each +QFQ iterates over the result set of ``dragAndDropOrderSql``. The value of column ``id`` have to correspond to the dragged HTML + element (given by ``data-dnd-id``). Reordering always start with ``orderInterval`` and is incremented by ``orderInterval`` with each record of the result set. The client reports a) the id of the dragged HTML element, b) the id of the hovered element and c) the dropped position of above or below the hovered element. This information is compared to the result set and changes are applied where appropriate. Take care that the query of part 1 (display list) does a) select the same records and b) in the same order as the query - defined in part 2 (order records) via `dragAndDropOrderSql`. + defined in part 2 (order records) via ``dragAndDropOrderSql``. If you find that the reorder does not work at expected, those two sql queries are not identical. @@ -3139,6 +3142,12 @@ Located under ``typo3conf/ext/qfq/Resources/Public/icons`` bullet-green.gif +.. figure:: ./Images/bullet-orange.gif + :class: with-border + :width: 20px + + bullet-orange.gif + .. figure:: ./Images/bullet-pink.gif :class: with-border :width: 20px @@ -3382,26 +3391,26 @@ Located under ``typo3conf/ext/qfq/Resources/Public/icons`` QFQ CSS Classes --------------- -* `qfq-table-50`, `qfq-table-80`, `qfq-table-100` - assigned to `<table>`, set min-width and column width to 'auto'. -* Background Color: `qfq-color-grey-1`, `qfq-color-grey-2` - assigned to different tags (table, row, cell). -* `qfq-100` - assigned to different tags, makes an element 'width: 100%'. -* `qfq-left`- assigned to different tags, Text align left. -* `qfq-sticky` - assigned to `<thead>`, makes the header sticky. -* `letter-no-break` - assigned to a `div` will protect a paragraph (CSS: page-break-before: avoid;) not to break around - a page border (converted to PDF via wkhtml). Take care that `qfq-letter.css` is included in TypoScript setup. -* `qfq-badge`, `qfq-badge-error`, `qfq-badge-warning`, `qfq-badge-success`, `qfq-badge-info`, `qfq-badge-invers` - +* ``qfq-table-50``, ``qfq-table-80``, ``qfq-table-100`` - assigned to ``<table>``, set min-width and column width to 'auto'. +* Background Color: ``qfq-color-grey-1``, ``qfq-color-grey-2`` - assigned to different tags (table, row, cell). +* ``qfq-100`` - assigned to different tags, makes an element 'width: 100%'. +* ``qfq-left``- assigned to different tags, Text align left. +* ``qfq-sticky`` - assigned to ``<thead>``, makes the header sticky. +* ``letter-no-break`` - assigned to a ``div`` will protect a paragraph (CSS: page-break-before: avoid;) not to break around + a page border (converted to PDF via wkhtml). Take care that ``qfq-letter.css`` is included in TypoScript setup. +* ``qfq-badge``, ``qfq-badge-error``, ``qfq-badge-warning``, ``qfq-badge-success``, ``qfq-badge-info``, ``qfq-badge-invers`` - colorized BS3 badges:: - <span class="badge">classic</span> - <span class="qfq-badge qfq-badge-success">qfq-badge-success</span> - <span class="qfq-badge qfq-badge-warning">qfq-badge-warning</span> - <span class="qfq-badge qfq-badge-error">qfq-badge-error</span> - <span class="qfq-badge qfq-badge-info">qfq-badge-info</span> - <span class="qfq-badge qfq-badge-inverse">qfq-badge-inverse</span> + <span class="badge">classic</span> + <span class="qfq-badge qfq-badge-success">qfq-badge-success</span> + <span class="qfq-badge qfq-badge-warning">qfq-badge-warning</span> + <span class="qfq-badge qfq-badge-error">qfq-badge-error</span> + <span class="qfq-badge qfq-badge-info">qfq-badge-info</span> + <span class="qfq-badge qfq-badge-inverse">qfq-badge-inverse</span> .. image:: ./Images/QfqCssBadge.png -* `btn-tiny`, `btn-small` - add to `'...|b:btn-info btn-small|t:..' AS link` to render button in small or tiny size. +* ``btn-tiny``, ``btn-small`` - add to ``'...|b:btn-info btn-small|t:..' AS link`` to render button in small or tiny size. .. image:: ./Images/BtnTinySmall.png @@ -3410,34 +3419,44 @@ QFQ CSS Classes Table: vertical text via CSS ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Use class `vertical` and `qfq-vertical-text`. Example:: +Use class ``vertical`` and ``qfq-vertical-text``. Example:: + + <table> + <tr> + <th class="qfq-vertical"><span class="qfq-vertical-text">Column 1</span></th> + <th class="qfq-vertical"><span class="qfq-vertical-text">2</span></th> + <th class="qfq-vertical"><span class="qfq-vertical-text">Very long column title</span></th> + <th class="qfq-vertical"><span class="qfq-vertical-text">4</span></th> + <th class="qfq-vertical"><span class="qfq-vertical-text">5</span></th> + </tr> + <tr><td>1</td><td>2</td><td>3</td><td>very wide text</td><td>5</td></tr> + </table> - <table> - <tr> - <th class="qfq-vertical"><span class="qfq-vertical-text">Column 1</span></th> - <th class="qfq-vertical"><span class="qfq-vertical-text">2</span></th> - <th class="qfq-vertical"><span class="qfq-vertical-text">Very long column title</span></th> - <th class="qfq-vertical"><span class="qfq-vertical-text">4</span></th> - <th class="qfq-vertical"><span class="qfq-vertical-text">5</span></th> - </tr> - <tr><td>1</td><td>2</td><td>3</td><td>very wide text</td><td>5</td></tr> - </table> +Same effect is also possible via special column name ``_vertical`` (ref:`special-column-names`). -Same effect is also possible via special column name `_vertical` (ref:`special-column-names`). +.. _bootstrap: Bootstrap --------- -* Table: `table` -* Table > hover: `table-hover` -* Table > condensed: `table-condensed` +* https://getbootstrap.com/docs/3.4 +* 'Badges <https://getbootstrap.com/docs/3.4/components/#badges>'_ +* 'Panels <https://getbootstrap.com/docs/3.4/components/#panels>'_ +* 'Buttons <https://getbootstrap.com/docs/3.4/css/#buttons>'_ +* 'Glyphicons <https://getbootstrap.com/docs/3.4/components/#glyphicons>'_ +* 'Tables <https://getbootstrap.com/docs/3.4/css/#tables>'_ + + * Table: `table` + * Table > hover: `table-hover` + * Table > condensed: `table-condensed` + Example:: - 10.sql = SELECT id, name, firstName, ... - 10.head = <table class='table table-condensed qfq-table-50'> + 10.sql = SELECT id, name, firstName, ... + 10.head = <table class='table table-condensed qfq-table-50'> -* `qfq-100`, `qfq-left` - makes e.g. a button full width and aligns the text left. +* ``qfq-100``, ``qfq-left`` - makes e.g. a button full width and aligns the text left. Example:: @@ -3457,48 +3476,48 @@ Make a table sortable and/or filterable: Example:: - 10 { - sql = SELECT p.name, p.firstName, p.id, FROM Person AS p - head = <table class="tablesorter tablesorter-filter tablesorter-pager tablesorter-column-selector" id="demoTable"> - <thead><tr> - <th>Name</th><th>First name</th><th class="filter-false sorter-false">ID</th> - </tr></thead> - tail = </table> - } + 10 { + sql = SELECT p.name, p.firstName, p.id, FROM Person AS p + head = <table class="tablesorter tablesorter-filter tablesorter-pager tablesorter-column-selector" id="demoTable"> + <thead><tr> + <th>Name</th><th>First name</th><th class="filter-false sorter-false">ID</th> + </tr></thead> + tail = </table> + } .. important:: Custom settings will be saved per table automatically in the browser local storage. To distinguish different table settings, define an uniq HTML id per table. - Example: `<table class="tablesorter" id="{{pageSlug:T}}-person">` - the `{{pageSlug:T}}` makes it easy to keep the + Example: ``<table class="tablesorter" id="{{pageSlug:T}}-person">`` - the ``{{pageSlug:T}}`` makes it easy to keep the overview over given name on the site. The *tablesorter* options: -* Class `tablesorter-filter` enables row filtering. -* Class `tablesorter-pager` adds table paging functionality. A page navigation +* Class ``tablesorter-filter`` enables row filtering. +* Class ``tablesorter-pager`` adds table paging functionality. A page navigation is shown. -* Class `tablesorter-column-selector` adds a column selector widget. +* Class ``tablesorter-column-selector`` adds a column selector widget. .. _tablesorter-view-saver: Tablesorter View Saver ^^^^^^^^^^^^^^^^^^^^^^ -* Tablesorter view saver: inside of a HTML `table`-tag the command:: +* Tablesorter view saver: inside of a HTML ``table``-tag the command:: {{ '<uniqueName>' AS _tablesorter-view-saver }} This adds a menu to save the current view (column filters, selected columns, sort order). - * `<uniqueName>` should be a name which is unique. Example:: + * ``<uniqueName>`` should be a name which is unique. Example:: <table {{ 'allperson' AS _tablesorter-view-saver }} class="tablesorter tablesorter-filter tablesorter-column-selector" id="{{pageSlug:T}}-example"> ... </table> .. important:: - Always speciy a unique (over your whole T3 installation) HTML ID (`id="{{pageSlug:T}}-example">`). On page load, this + Always speciy a unique (over your whole T3 installation) HTML ID (``id="{{pageSlug:T}}-example">``). On page load, this reference will be used to load the last used settings again. If not specified, and if there are at least two tablesorter without an HTML ID, those will be mixed and might confuse the whole tablesorter. @@ -3506,21 +3525,21 @@ Tablesorter View Saver * 'Views' can be saved as: - * group: every user will see the `view` and can modify it. - * personal: only the user who created the `view` will see/modify it. - * readonly: manually mark a `view` as readonly (no FE User can change it) by setting column `readonly='true'` in table - `Setting` of the corresponding view (identified by `name`). + * group: every user will see the ``view`` and can modify it. + * personal: only the user who created the ``view`` will see/modify it. + * readonly: manually mark a ``view`` as readonly (no FE User can change it) by setting column ``readonly='true'`` in table + ``Setting`` of the corresponding view (identified by ``name``). * Views will be saved in the QFQ system DB table 'Setting'. * Every setting is saved with the T3 FE username. If there is no T3 FE username, the current QFQ cookie is used instead. - * Include 'font-awesome' CSS in your T3 page setup: `typo3conf/ext/qfq/Resources/Public/Css/font-awesome.min.css` to get the icons. + * Include 'font-awesome' CSS in your T3 page setup: ``typo3conf/ext/qfq/Resources/Public/Css/font-awesome.min.css`` to get the icons. * The view 'Clear' is always available and can't be modified. * To preselect a view, append a HTML anker to the current URL. Get the anker by selecting the view and copy it from the browser address bar. Example:: https://localhost/index.php?id=person#allperson=public:email - * 'allperson' is the '<uniqueName>' of the `tablesorter-view-saver` command. + * 'allperson' is the '<uniqueName>' of the ``tablesorter-view-saver`` command. * 'public' means the view is tagged as 'public' visible. * 'email' is the name of the view, as it is shown in the dropdown list. @@ -3537,8 +3556,8 @@ Tablesorter CSV Export $('table.tablesorter').trigger('outputTable'); - * Default export file name: `tableExport.csv` - * Exported with column separator `;` + * Default export file name: ``tableExport.csv`` + * Exported with column separator ``;`` * Only currently filtered rows are exported. * Values are exported as text, without HTML tags * You can change the formatting/value of each cell as follows:: @@ -3573,14 +3592,14 @@ Customization of tablesorter +-----------------------------+----------------------------------------------------------------------------------------+ - * You can pass in a default configuration object for the main `tablesorter()` function by using the attribute - `data-tablesorter-config` on the table. + * You can pass in a default configuration object for the main ``tablesorter()`` function by using the attribute + ``data-tablesorter-config`` on the table. Use JSON syntax when passing in your own configuration, such as: :: data-tablesorter-config='{"theme":"bootstrap","widthFixed":true,"headerTemplate":"{content} {icon}","dateFormat":"ddmmyyyy","widgets":["uitheme","filter","saveSort","columnSelector","output"],"widgetOptions":{"filter_columnFilters":true,"filter_reset":".reset","filter_cssFilter":"form-control","columnSelector_mediaquery":false,"output_delivery":"download","output_saveFileName":"tableExport.csv","output_separator":";"} }' * If the above customization options are not enough, you can output your own HTML for the pager and/or column selector, - as well as your own `$(document).ready()` function with the desired config. In this case, it is recommended not to + as well as your own ``$(document).ready()`` function with the desired config. In this case, it is recommended not to use the above *tablesorter* classes since the QFQ javascript code could interfere with your javascript code. Example:: @@ -3634,83 +3653,88 @@ Include the JS & CSS files via Typoscript * typo3conf/ext/qfq/Resources/Public/JavaScript/moment.min.js * typo3conf/ext/qfq/Resources/Public/JavaScript/fullcalendar.min.js -Integration: Create a `<div>` with +Integration: Create a ``<div>`` with * CSS class "qfq-calendar" -* Tag `data-config`. The content is a Javascript object. +* Tag ``data-config``. The content is a Javascript object. Example:: - 10.sql = SELECT 'Calendar, Standard' - 10.tail = <div class="qfq-calendar" - data-config='{ - "themeSystem": "bootstrap3", - "height": "auto", - "defaultDate": "2020-01-13", - "weekends": false, - "defaultView": "agendaWeek", - "minTime": "05:00:00", - "maxTime": "20:00:00", - "businessHours": { "dow": [ 1, 2, 3, 4 ], "startTime": "10:00", "endTime": "18:00" }, - "events": [ - { "id": "a", "title": "my event", - "start": "2020-01-21"}, - { "id": "b", "title": "my other event", "start": "2020-01-16T09:00:00", "end": "2020-01-16T11:30:00"} - ]}'> - </div> - - # "now" is in the past to switchoff 'highlight of today' - 20.sql = SELECT 'Calendar, 3 day, custom color, agend&list' AS '_+h2' - 20.tail = <div class="qfq-calendar" - data-config='{ - "themeSystem": "bootstrap3", - "height": "auto", - "header": { - "left": "title", - "center": "", - "right": "agenda,listWeek" - }, - "defaultDate": "2020-01-14", - "now": "1999-12-31", - "allDaySlot": false, - "weekends": false, - "defaultView": "agenda", - "dayCount": 3, - "minTime": "08:00:00", - "maxTime": "18:00:00", - "businessHours": { "dow": [ 1, 2, 3, 4 ], "startTime": "10:00", "endTime": "18:00" }, - "events": [ - { "id": "a", "title": "my event", "start": "2020-01-15T10:15:00", "end": "2020-01-15T11:50:00", "color": "#25adf1", "textColor": "#000"}, - { "id": "b", "title": "my other event", "start": "2020-01-16T09:00:00", "end": "2020-01-16T11:30:00", "color": "#5cb85c", "textColor": "#000"}, - { "id": "c", "title": "Eventli", "start": "2020-01-15T13:10:00", "end": "2020-01-15T16:30:00", "color": "#fbb64f", "textColor": "#000"}, - { "id": "d", "title": "Evento", "start": "2020-01-15T13:50:00", "end": "2020-01-15T15:00:00", "color": "#fb4f4f", "textColor": "#000"}, - { "id": "d", "title": "Busy", "start": "2020-01-14T09:00:00", "end": "2020-01-14T12:00:00", "color": "#ccc", "textColor": "#000"}, - { "id": "e", "title": "Banana", "start": "2020-01-16T13:30:00", "end": "2020-01-16T16:00:00", "color": "#fff45b", "textColor": "#000"} - ]}'> - </div> + 10.sql = SELECT 'Calendar, Standard' + 10.tail = <div class="qfq-calendar" + data-config='{ + "themeSystem": "bootstrap3", + "height": "auto", + "defaultDate": "2020-01-13", + "weekends": false, + "defaultView": "agendaWeek", + "minTime": "05:00:00", + "maxTime": "20:00:00", + "businessHours": { "dow": [ 1, 2, 3, 4 ], "startTime": "10:00", "endTime": "18:00" }, + "events": [ + { "id": "a", "title": "my event", + "start": "2020-01-21"}, + { "id": "b", "title": "my other event", "start": "2020-01-16T09:00:00", "end": "2020-01-16T11:30:00"} + ]}'> + </div> + + # "now" is in the past to switchoff 'highlight of today' + 20.sql = SELECT 'Calendar, 3 day, custom color, agend&list' AS '_+h2' + 20.tail = <div class="qfq-calendar" + data-config='{ + "themeSystem": "bootstrap3", + "height": "auto", + "header": { + "left": "title", + "center": "", + "right": "agenda,listWeek" + }, + "defaultDate": "2020-01-14", + "now": "1999-12-31", + "allDaySlot": false, + "weekends": false, + "defaultView": "agenda", + "dayCount": 3, + "minTime": "08:00:00", + "maxTime": "18:00:00", + "businessHours": { "dow": [ 1, 2, 3, 4 ], "startTime": "10:00", "endTime": "18:00" }, + "events": [ + { "id": "a", "title": "my event", "start": "2020-01-15T10:15:00", "end": "2020-01-15T11:50:00", "color": "#25adf1", "textColor": "#000"}, + { "id": "b", "title": "my other event", "start": "2020-01-16T09:00:00", "end": "2020-01-16T11:30:00", "color": "#5cb85c", "textColor": "#000"}, + { "id": "c", "title": "Eventli", "start": "2020-01-15T13:10:00", "end": "2020-01-15T16:30:00", "color": "#fbb64f", "textColor": "#000"}, + { "id": "d", "title": "Evento", "start": "2020-01-15T13:50:00", "end": "2020-01-15T15:00:00", "color": "#fb4f4f", "textColor": "#000"}, + { "id": "d", "title": "Busy", "start": "2020-01-14T09:00:00", "end": "2020-01-14T12:00:00", "color": "#ccc", "textColor": "#000"}, + { "id": "e", "title": "Banana", "start": "2020-01-16T13:30:00", "end": "2020-01-16T16:00:00", "color": "#fff45b", "textColor": "#000"} + ]}'> + </div> .. _reportAsFile: Report As File -------------- -* If the toplevel token `file` is present inside the body of a QFQ tt-content element then the given report file is loaded and rendered. +* If the toplevel token ``file`` is present inside the body of a QFQ tt-content element then the given report file is + loaded and rendered. * The tt-content body is ignored in that case. * The path to the report file must be given relative to the report directory inside the qfq project directory. See :ref:`qfq-project-path-php` - * QFQ provides some special system reports which are located inside the extension directory `typo3conf/ext/qfq/Resources/Private/Report` and can be directly rendered by prepending an underscore and omitting the file extension: + * QFQ provides some special system reports which are located inside the extension directory `typo3conf/ext/qfq/Resources/Private/Report` + and can be directly rendered by prepending an underscore and omitting the file extension: - * `file=_formEditor` will render the standard formEditor report. Check also :ref:`form-editor`. - * `file=_searchRefactor` will render the standard searchRefactor report. + * ``file=_formEditor`` will render the standard formEditor report. Check also :ref:`form-editor`. + * ``file=_searchRefactor`` will render the standard searchRefactor report. -* If the QFQ setting `reportAsFileAutoExport` (see :ref:`extension-manager-qfq-configuration`) is enabled, then every QFQ tt-content element which does not contain the `file` keyword is exported automatically when the report is rendered the first time. +* If the QFQ setting ``reportAsFileAutoExport`` (see :ref:`extension-manager-qfq-configuration`) is enabled, then every + QFQ tt-content element which does not contain the ``file`` keyword is exported automatically when the report is rendered + the first time. * The path of the created file is given by the typo3 page structure - * The tt-content element body is replaced with `file=<path-to-new-file>` + * The tt-content element body is replaced with ``file=<path-to-new-file>`` -* **Backups** : Whenever a report file is edited via the frontend report editor then a backup of the previous version is saved in the `.backup` directory located in the same directory as the report file. +* **Backups** : Whenever a report file is edited via the frontend report editor then a backup of the previous version is + saved in the ``.backup`` directory located in the same directory as the report file. Example tt-content body:: @@ -3934,90 +3958,90 @@ Two queries: nested with hidden variables in a table:: Same as above, but written in the nested notation:: - 10 { - sql = SELECT p.id AS _pId, p.name FROM ExpPerson AS p - rend = <br> - 10 { - # inner query - sql = SELECT a.street FROM ExpAddress AS a WHERE a.pId='{{10.pId}}' + sql = SELECT p.id AS _pId, p.name FROM ExpPerson AS p rend = <br> + + 10 { + # inner query + sql = SELECT a.street FROM ExpAddress AS a WHERE a.pId='{{10.pId}}' + rend = <br> + } } - } Best practice *recommendation* for using parameter - see :ref:`access-column-values`:: - 10 { - sql = SELECT p.id AS _pId, p.name FROM ExpPerson AS p - rend = <br> - 10 { - # inner query - sql = SELECT a.street FROM ExpAddress AS a WHERE a.pId='{{pId:R}}' + sql = SELECT p.id AS _pId, p.name FROM ExpPerson AS p rend = <br> + + 10 { + # inner query + sql = SELECT a.street FROM ExpAddress AS a WHERE a.pId='{{pId:R}}' + rend = <br> + } } - } Create HTML tables. Each column is wrapped in ``<td>``, each row is wrapped in ``<tr>``:: - 10 { - sql = SELECT p.firstName, p.lastName, p.country FROM Person AS p - head = <table class="table"> - tail = </table> - rbeg = <tr> - rend = </tr> - fbeg = <td> - fend = </td> - } + 10 { + sql = SELECT p.firstName, p.lastName, p.country FROM Person AS p + head = <table class="table"> + tail = </table> + rbeg = <tr> + rend = </tr> + fbeg = <td> + fend = </td> + } Maybe a few columns belongs together and should be in one table column. Joining columns, variant A: firstName and lastName in one table column:: - 10 { - sql = SELECT CONCAT(p.firstName, ' ', p.lastName), p.country FROM Person AS p - head = <table class="table"> - tail = </table> - rbeg = <tr> - rend = </tr> - fbeg = <td> - fend = </td> - } + 10 { + sql = SELECT CONCAT(p.firstName, ' ', p.lastName), p.country FROM Person AS p + head = <table class="table"> + tail = </table> + rbeg = <tr> + rend = </tr> + fbeg = <td> + fend = </td> + } Joining columns, variant B: firstName and lastName in one table column:: - 10 { - sql = SELECT '<td>', p.firstName, ' ', p.lastName, '</td><td>', p.country, '</td>' FROM Person AS p - head = <table class="table"> - tail = </table> - rbeg = <tr> - rend = </tr> - } + 10 { + sql = SELECT '<td>', p.firstName, ' ', p.lastName, '</td><td>', p.country, '</td>' FROM Person AS p + head = <table class="table"> + tail = </table> + rbeg = <tr> + rend = </tr> + } Joining columns, variant C: firstName and lastName in one table column. Notice ``fbeg``, ``fend` and ``fskipwrap``:: - 10 { - sql = SELECT '<td>', p.firstName, ' ', p.lastName, '</td>', p.country FROM Person AS p - head = <table class="table"> - tail = </table> - rbeg = <tr> - rend = </tr> - fbeg = <td> - fend = </td> - fskipwrap = 1,2,3,4,5 - } + 10 { + sql = SELECT '<td>', p.firstName, ' ', p.lastName, '</td>', p.country FROM Person AS p + head = <table class="table"> + tail = </table> + rbeg = <tr> + rend = </tr> + fbeg = <td> + fend = </td> + fskipwrap = 1,2,3,4,5 + } Joining columns, variant D: firstName and lastName in one table column. Notice ``fbeg``, ``fend` and ``fskipwrap``:: - 10 { - sql = SELECT CONCAT('<td>', p.firstName, ' ', p.lastName, '</td>') AS '_noWrap', p.country FROM Person AS p - head = <table class="table"> - tail = </table> - rbeg = <tr> - rend = </tr> - fbeg = <td> - fend = </td> - } + 10 { + sql = SELECT CONCAT('<td>', p.firstName, ' ', p.lastName, '</td>') AS '_noWrap', p.country FROM Person AS p + head = <table class="table"> + tail = </table> + rbeg = <tr> + rend = </tr> + fbeg = <td> + fend = </td> + } Recent List ^^^^^^^^^^^ @@ -4025,47 +4049,47 @@ Recent List A nice feature is to show a list with last changed records. The following will show the 10 last modified (Form or FormElement) forms:: - 10 { - sql = SELECT CONCAT('p:{{pageSlug:T}}?form=form&r=', f.id, '|t:', f.name,'|o:', GREATEST(MAX(fe.modified), f.modified)) AS _page - FROM Form AS f - LEFT JOIN FormElement AS fe - ON fe.formId = f.id - GROUP BY f.id - ORDER BY GREATEST(MAX(fe.modified), f.modified) DESC - LIMIT 10 - head = <h3>Recent Forms</h3> - rsep = ,  - } + 10 { + sql = SELECT CONCAT('p:{{pageSlug:T}}?form=form&r=', f.id, '|t:', f.name,'|o:', GREATEST(MAX(fe.modified), f.modified)) AS _page + FROM Form AS f + LEFT JOIN FormElement AS fe + ON fe.formId = f.id + GROUP BY f.id + ORDER BY GREATEST(MAX(fe.modified), f.modified) DESC + LIMIT 10 + head = <h3>Recent Forms</h3> + rsep = ,  + } .. _`vertical-column-title`: Table: vertical column title ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -To orientate a column title vertical, use the QFQ CSS classe `qfq-vertical` in td|th and `qfq-vertical-text` around the text. +To orientate a column title vertical, use the QFQ CSS classe ``qfq-vertical`` in td|th and ``qfq-vertical-text`` around the text. HTML example (second column title is vertical):: - <table><thead> - <tr> - <th>horizontal</th> - <th class="qfq-vertical"><span class="qfq-vertical-text">text vertical</span></th> - </tr> - </thead></table> + <table><thead> + <tr> + <th>horizontal</th> + <th class="qfq-vertical"><span class="qfq-vertical-text">text vertical</span></th> + </tr> + </thead></table> QFQ example:: - 10 { - sql = SELECT title FROM Settings ORDER BY title - fbeg = <th class="qfq-vertical"><span class="qfq-vertical-text"> - fend = </span></th> - head = <table><thead><tr> - rend = </tr></thead> - tail = </table> - - 20.sql = SELECT ... - } + 10 { + sql = SELECT title FROM Settings ORDER BY title + fbeg = <th class="qfq-vertical"><span class="qfq-vertical-text"> + fend = </span></th> + head = <table><thead><tr> + rend = </tr></thead> + tail = </table> + + 20.sql = SELECT ... + } .. _`store_user_examples`: @@ -4115,7 +4139,7 @@ Simulate/switch user: feUser Just set the STORE_USER variable 'feUser'. -All places with `{{feUser:T}}` has to be replaced by `{{feUser:UT}}`:: +All places with ``{{feUser:T}}`` has to be replaced by ``{{feUser:UT}}``:: # Normalize 10.sql = SELECT '{{feUser:UT}}' AS '_=feUser' @@ -4129,7 +4153,7 @@ 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 +``'{{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):: diff --git a/Documentation/Settings.cfg b/Documentation/Settings.cfg index 4ba1acab8bb15be794dea76e072736d29dc4c5ab..d2a1ef89395dacf293401146d8384dab9ec5ac36 100644 --- a/Documentation/Settings.cfg +++ b/Documentation/Settings.cfg @@ -21,8 +21,8 @@ ; you can use in 'conf.py' project = QFQ - Quick Form Query -version = 23.2 -release = 23.2.0 +version = 23.10 +release = 23.10.0 t3author = Carsten Rose copyright = since 2017 by the author diff --git a/Documentation/Store.rst b/Documentation/Store.rst index 80f573e8d147f366c655edc41315dd2c7fc2d602..20ad26fded14b13d8f31fca4e311c7b134faaa1f 100644 --- a/Documentation/Store.rst +++ b/Documentation/Store.rst @@ -286,10 +286,9 @@ QFQ values +-------------------+-------------+--------------------------------------------------------------------------------------------------------------------------------------------+ | Name | Scope | Explanation | +===================+=============+============================================================================================================================================+ -| random | Always | Random string with length of 32 alphanum chars (lower & upper case). This variable is always filled. Each access gives a new value. | -| | | Remember: QFQ variables will be replaced **before** a SQL statement is fired. Uniqueness is given via PHP function *uniqid()*. | +| random | Always | Random string with length of 32 alphanum chars (lower & upper case). See :ref:`RANDOM`. | +-------------------+-------------+--------------------------------------------------------------------------------------------------------------------------------------------+ -| slaveId | FE *any* | see :ref:`slave-id` | +| slaveId | FE *any* | See :ref:`slave-id` | +-------------------+-------------+--------------------------------------------------------------------------------------------------------------------------------------------+ | allRequiredGiven | Form | Form save - Set during check of all required FE. If every *required* FE is given: *1*. Else: *0*. If there is no required FE: *1*. | | | | Even with ``formModeGlobal = requiredOff | requiredOffButMark`` the variable will be set. | @@ -309,6 +308,26 @@ QFQ values | mimeType | FE *upload* | Mime type of the uploaded file. | +-------------------+-------------+--------------------------------------------------------------------------------------------------------------------------------------------+ +.. _RANDOM: + +{{random:V}} +^^^^^^^^^^^^ + +* Random string with length of 32 alphanum chars (lower & upper case). +* The variable *{{random:V}}* is always filled. +* Hint: QFQ variables will be replaced **before** a SQL statement is fired. + + * Each access gives a new value. + * Execution of a SQL statement is **one** access - therefore ``SELECT '{{random:V}}' FROM Person`` will give the same + random value for all *Person*. + * Several ``SELECT ...{{random:V}}...`` will give several different random values. + +* Uniqueness is added via PHP function *uniqid()* + + * This just prepends 13 chars at the beginning of the random string. + * *uniqid()* is only a very precise timestamp - timestamp means it becomes bigger and bigger and the first characters stays the same. + * These first 13 chars should *not* be taken as random! They will make *uniqness* for *{{random:V}}* more likely. + Custom values (via fillStoreVar) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/Documentation/UseCase.rst b/Documentation/UseCase.rst index a8a0fa3da5fb65b46d19fd67099be2db55992d18..2c67317b419a9f6be00b5feb2a1c70300e5fd258 100644 --- a/Documentation/UseCase.rst +++ b/Documentation/UseCase.rst @@ -90,13 +90,16 @@ Table: Person `firstName` varchar(64) NOT NULL DEFAULT '', `email` varchar(128) NOT NULL, `account` varchar(128) NOT NULL, - `auth` varchar(32) NOT NULL, + `auth` varchar(32) NOT NULL DEFAULT '', `authExpire` datetime DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8 PACK_KEYS=1; ALTER TABLE `Person` ADD PRIMARY KEY (`id`); ALTER TABLE `Person` MODIFY `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT; +or:: + + ALTER TABLE `Person` ADD `auth` VARCHAR(32) NOT NULL DEFAULT '' AFTER `account`, ADD `authExpire` DATETIME DEFAULT NULL AFTER `auth`; Registration ^^^^^^^^^^^^ @@ -116,7 +119,7 @@ QFQ content record:: form={{SELECT IF('{{action:SE}}' = '','registration','') }} 20.sql = SELECT "<p>Thank you for your registration.</p><p>An email with further instructions has been sent to you.</p>" - , "<p>You should receive the mail during the next 5 minutes. If not, please check your SPAM folder.</p>" + , "<p>You should receive the mail during the next 5 minutes. If not, please check your JUNK folder.</p>" FROM (SELECT '') AS fake WHERE '{{action:SE}}' = 'thanksRegistration' @@ -125,6 +128,12 @@ QFQ content record:: Form: registration """""""""""""""""" + +.. note:: + + Take care that the QFQ STORE_SYTEM variable `ADMINISTRATIVE_EMAIL` is set. + +JSON Form :: { @@ -168,7 +177,7 @@ Form: registration "modeSql": "", "class": "action", "type": "beforeSave", - "parameter": "sqlValidate={{!SELECT p.id FROM Person AS p WHERE p.email='{{email:F:alnumx}}' OR ('{{firstName:F:allbut}}'=p.firstName AND '{{lastName:F:allbut}}'=p.lastName ) LIMIT 1 }}\r\n expectRecords=0\r\nmessageFail=Sorry, person already registered by name or email. Please just reset the password under <a href='?id=reset'>reset</a>" + "parameter": "sqlValidate={{!SELECT p.id FROM Person AS p WHERE p.email='{{email:F:alnumx}}' OR ('{{firstName:F:allbut}}'=p.firstName AND '{{lastName:F:allbut}}'=p.lastName ) LIMIT 1 }}\r\nexpectRecords=0\r\nmessageFail=Sorry, person already registered by name or email (in case of a namesake please contact <a href="mailto:{{ADMINISTRATIVE_EMAIL:Y}}">{{ADMINISTRATIVE_EMAIL:Y}}</a>). Please just reset the password under <a href='?id=reset'>reset</a>" }, { "enabled": "yes", @@ -213,7 +222,7 @@ QFQ content record:: head = <div class="alert alert-success"> <p>Thank you.</p> <p>If the email address is known in our database, we sent a password reset link to it.</p> - <p>The mail should be received during the next minutes. If not, please check you junk folder.</p> + <p>The mail should be received during the next minutes. If not, please check you JUNK folder.</p> <p>To set a new password, please click on the link provided in the email.</p> </div> } @@ -223,7 +232,9 @@ Form: passwordReset .. note:: - Take care that there is one dummy person record with person.id=1 + * Take care that there is one dummy person record with person.id=1. + * Update 'example.com' references. + * Update sendMailGrId=123, sendMailXId=456 Form 'passwordReset':: @@ -272,8 +283,8 @@ Form 'passwordReset':: "encode": "specialchar", "checkType": "auto", "ord": 50, - "value": "Dear new user\r\nPlease set a new password under {{baseUrl:Y}}?id=set&auth={{auth:V}}\r\nRegards.", - "parameter": "fillStoreVar={{!SELECT CONCAT(p.firstName , ' ', p.lastName) AS name, p.id AS _pId, @expire:=DATE_ADD(NOW(), INTERVAL 4 DAY) AS expireTs, QDATE_FORMAT(@expire) AS expire, p.email, '{{random:V}}' AS auth FROM Person AS p WHERE p.email='{{emailValue:F:alnumx}}' AND p.email!='' LIMIT 1}}\r\n\r\nsendMailTo={{email:VE}}\r\nsendMailSubject=Password Reset\r\nsendMailFrom=webmaster@example.com\r\nsendMailGrId=123\r\nsendMailXId=456\r\n\r\n# Set token & expiration\r\nsqlAfter = {{UPDATE Person SET auth='{{auth:V}}', authExpire='{{expireTs:V}}' WHERE email='{{emailValue:F:alnumx}}' AND email!='' LIMIT 1}}" + "value": "Dear user\r\nPlease set a new password under {{baseUrl:Y}}?id=set&auth={{auth:V}}\r\nRegards.", + "parameter": "fillStoreVar={{!SELECT CONCAT(p.firstName , ' ', p.lastName) AS name, p.id AS _pId, @expire:=DATE_ADD(NOW(), INTERVAL 4 DAY) AS expireTs, QDATE_FORMAT(@expire) AS expire, p.email, '{{random:V}}' AS auth FROM Person AS p WHERE p.email='{{emailValue:F:alnumx}}' AND p.email!='' LIMIT 1}}\r\n\r\nsendMailTo={{email:VE}}\r\nsendMailSubject=Password Reset\r\nsendMailFrom={{ADMINISTRATIVE_EMAIL:Y}}\r\nsendMailGrId=123\r\nsendMailXId=456\r\n\r\n# Set token & expiration\r\nsqlAfter = {{UPDATE Person SET auth='{{auth:V}}', authExpire='{{expireTs:V}}' WHERE email='{{emailValue:F:alnumx}}' AND email!='' LIMIT 1}}" } ] } @@ -313,7 +324,7 @@ QFQ content record:: , 'Token invalid' , IF( NOW()<p.authExpire ,'' - , IF( p.authExpire=0, 'Password already set', 'Token expired') ) + , IF( p.authExpire=0 OR ISNULL(p.authExpire), 'Password already set', 'Token expired') ) ) FROM (SELECT '') AS fake LEFT JOIN Person AS p diff --git a/Documentation/Variable.rst b/Documentation/Variable.rst index 565f841d4dae57a1c83feceba3cfc78968e0a7a9..12b6552e24d8e900a60e6e21bf92f154130c028f 100644 --- a/Documentation/Variable.rst +++ b/Documentation/Variable.rst @@ -44,7 +44,7 @@ provided. Access to: * :ref:`store-variables` * :ref:`sql-variables` * :ref:`row-column-variables` -* :ref:`link-column-variables` +* :ref:`link-function-column-variables` Some examples, including nesting:: @@ -72,6 +72,12 @@ Some examples, including nesting:: # Link Columns {{p:form=Person&r=1|t:Edit Person|E|s AS link}} + # Function Columns: output in {{fullname:R}} + {{getFullname(pId) => fullname AS function}} + + # Function Columns: output direct tt_content + {{getFullname(pId) AS function}} + Leading and trailing spaces inside curly braces are removed. * ``{{ SELECT "Hello World" }}`` becomes ``{{SELECT "Hello World"}}`` @@ -151,19 +157,19 @@ sanitize class. Values from other stores are *not* checked against any sanitize For QFQ variables and FormElements: -+------------------+------+-------+-----------------------------------------------------------------------------------------+ -| Name | Form | Query | Pattern | -+==================+======+=======+=========================================================================================+ -| **alnumx** | Form | Query | [A-Za-z][0-9]@-_.,;: /()ÀÈÌÒÙà èìòùÃÉÃÓÚÃáéÃóúýÂÊÎÔÛâêîôûÃÑÕãñõÄËÃÖÜŸäëïöüÿçß | -+------------------+------+-------+-----------------------------------------------------------------------------------------+ -| **digit** | Form | Query | [0-9] | -+------------------+------+-------+-----------------------------------------------------------------------------------------+ -| **numerical** | Form | Query | [0-9.-+] | -+------------------+------+-------+-----------------------------------------------------------------------------------------+ -| **allbut** | Form | Query | All characters allowed, but not [ ] { } % \ #. The used regexp: ``^[^\[\]{}%\\#]+$',`` | -+------------------+------+-------+-----------------------------------------------------------------------------------------+ -| **all** | Form | Query | no sanitizing | -+------------------+------+-------+-----------------------------------------------------------------------------------------+ ++------------------+------+-------+-------------------------------------------------------------------------------------------+ +| Name | Form | Query | Pattern | ++==================+======+=======+===========================================================================================+ +| **alnumx** | Form | Query | [a-z][A-Z][0-9]@-_.,;: /()ÀÈÌÒÙà èìòùÃĆÉÃÓÚÃáćéÃóúýÂÊÎÔÛâêîôûÃÑÕãñõÄËÃÖÜŸäëïöüÿçČÄÄ𩹮žß | ++------------------+------+-------+-------------------------------------------------------------------------------------------+ +| **digit** | Form | Query | [0-9] | ++------------------+------+-------+-------------------------------------------------------------------------------------------+ +| **numerical** | Form | Query | [0-9.-+] | ++------------------+------+-------+-------------------------------------------------------------------------------------------+ +| **allbut** | Form | Query | All characters allowed, but not [ ] { } % \ #. The used regexp: ``^[^\[\]{}%\\#]+$',`` | ++------------------+------+-------+-------------------------------------------------------------------------------------------+ +| **all** | Form | Query | no sanitizing | ++------------------+------+-------+-------------------------------------------------------------------------------------------+ Only in FormElement: @@ -421,21 +427,42 @@ General note: using this type of variables is only the second choice. First choi :ref:`access-column-values`) - using the STORE_RECORD is more portable cause no renumbering is needed if the level keys change. -.. _`link-column-variables`: +.. _`link-function-column-variables`: -Link column variables ---------------------- +Link/Function column variables +------------------------------ + +Link column +^^^^^^^^^^^ These variables return a link, completely rendered in HTML. The syntax and all features of :ref:`column-link` are available. The following code will render a *new person* button:: {{p:form&form=Person|s|N|t:new person AS link}} -For better reading, the format string might be wrapped in single or double quotes (this is optional): :: +Optional: For better reading, the format string might be wrapped in single or double quotes: :: {{"p:form&form=Person|s|N|t:new person" AS link}} -These variables are especially helpful in: + +Function column +^^^^^^^^^^^^^^^ + +Function column variables are helpful in: * `report`, to create create links or buttons outside of an SQL statement. E.g. in `head`, `rbeg`, ... * `form`, to create links and buttons in labels or notes. + +Definition of a qfqFunction: Report with qfqFunction. Subheader: getFullname:: + + render = api + 10.sql = SELECT CONCAT(lastName, ', ', firstName) FROM Person WHERE id = {{pId:R0}} + +a) Somewhere in a different report or form:: + + {{getFullname(pId) AS function}} + +b) Somewhere in a different report, output is returned in STORE_RECORD variable `fullname`:: + + {{getFullname(pId) => fullname AS function}} + diff --git a/Documentation/conf.py b/Documentation/conf.py index 55c36f4b84b41b3f3f4ebe2aa860bd86f92f56e6..591b4a236a87b3e9014149e8789e0209ce487819 100644 --- a/Documentation/conf.py +++ b/Documentation/conf.py @@ -55,16 +55,16 @@ master_doc = 'index' # General information about the project. project = 'QFQ' copyright = '2023' -author = 'Carsten Rose, Benjamin Baer' +author = 'Carsten Rose, Benjamin Baer, Enis Nuredini, Jan Haller' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = '23.2' +version = '23.10' # The full version, including alpha/beta/rc tags. -release = '23.2.0' +release = '23.10.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/Documentation/docker-sphinx-qfq/requirements.txt b/Documentation/docker-sphinx-qfq/requirements.txt index d29a608226d9d512b1c0439e325079ec9f67b35f..8cc2b116aea948b478ba68182a97d6262e3cf87f 100644 --- a/Documentation/docker-sphinx-qfq/requirements.txt +++ b/Documentation/docker-sphinx-qfq/requirements.txt @@ -1,2 +1,2 @@ -sphinx-rtd-theme==0.4.3 +sphinx-rtd-theme git+https://github.com/readthedocs/readthedocs-sphinx-search@main diff --git a/Gruntfile.js b/Gruntfile.js index 84fb901babd4cc71c89a55877a0a9daaf1e224af..d65c613472645ab489b08678db00c5a4c8234683 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -3,6 +3,7 @@ module.exports = function (grunt) { 'use strict'; var typo3_css = 'extension/Resources/Public/Css/'; + var typo3_css_codemirror = 'extension/Resources/Public/Css/codemirror/'; var typo3_js = 'extension/Resources/Public/JavaScript/'; var typo3_fonts = 'extension/Resources/Public/fonts/'; var typo3_webfonts = 'extension/Resources/Public/webfonts/'; @@ -450,6 +451,15 @@ module.exports = function (grunt) { dest: typo3_css, flatten: true }, + { + cwd: 'node_modules/codemirror/theme', + src: [ + "monokai.css" + ], + expand: true, + dest: typo3_css_codemirror, + flatten: true + }, { cwd: 'node_modules/codemirror/lib', src: [ diff --git a/docker/db_fixture_qfq.sql b/docker/db_fixture_qfq.sql index 5537a1de5a35e167cd1530b3e5bfc55198ae6e97..20a24b48134a082feed6912aa09b155175766d77 100644 --- a/docker/db_fixture_qfq.sql +++ b/docker/db_fixture_qfq.sql @@ -222,53 +222,51 @@ DROP TABLE IF EXISTS `FormElement`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `FormElement` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `formId` int(11) NOT NULL, - `feIdContainer` int(11) NOT NULL DEFAULT 0, - `dynamicUpdate` enum('yes','no') NOT NULL DEFAULT 'no', - `enabled` enum('yes','no') NOT NULL DEFAULT 'yes', - `name` varchar(255) NOT NULL DEFAULT '', - `label` varchar(1023) NOT NULL DEFAULT '', - `mode` enum('show','required','readonly','hidden') NOT NULL DEFAULT 'show', - `modeSql` text NOT NULL, - `class` enum('native','action','container') NOT NULL DEFAULT 'native', - `type` enum('checkbox','date','datetime','dateJQW','datetimeJQW','extra','gridJQW','text','editor','annotate','time','note','password','radio','select','subrecord','upload','annotate','imageCut','fieldset','pill','templateGroup','beforeLoad','beforeSave','beforeInsert','beforeUpdate','beforeDelete','afterLoad','afterSave','afterInsert','afterUpdate','afterDelete','sendMail','paste') NOT NULL DEFAULT 'text', - `subrecordOption` set('edit','delete','new') NOT NULL DEFAULT '', - `encode` enum('none','specialchar') NOT NULL DEFAULT 'specialchar', - `checkType` enum('auto','alnumx','digit','numerical','email','pattern','allbut','all') NOT NULL DEFAULT 'auto', - `checkPattern` varchar(255) NOT NULL DEFAULT '', - `onChange` varchar(255) NOT NULL DEFAULT '', - `ord` int(11) NOT NULL DEFAULT 0, - `tabindex` int(11) NOT NULL DEFAULT 0, - `size` varchar(255) NOT NULL DEFAULT '', - `maxLength` varchar(255) NOT NULL DEFAULT '', - `labelAlign` enum('default','left','center','right') NOT NULL DEFAULT 'default', - `bsLabelColumns` varchar(255) NOT NULL DEFAULT '', - `bsInputColumns` varchar(255) NOT NULL DEFAULT '', - `bsNoteColumns` varchar(255) NOT NULL DEFAULT '', - `rowLabelInputNote` set('row','label','/label','input','/input','note','/note','/row') NOT NULL DEFAULT 'row,label,/label,input,/input,note,/note,/row', - `note` text NOT NULL, - `adminNote` text NOT NULL, - `tooltip` varchar(255) NOT NULL DEFAULT '', - `placeholder` varchar(2048) NOT NULL DEFAULT '', - `value` text NOT NULL, - `sql1` text NOT NULL, - `parameter` text NOT NULL, - `parameterLanguageA` text NOT NULL, - `parameterLanguageB` text NOT NULL, - `parameterLanguageC` text NOT NULL, - `parameterLanguageD` text NOT NULL, - `clientJs` text NOT NULL, - `feGroup` varchar(255) NOT NULL DEFAULT '', - `deleted` enum('yes','no') NOT NULL DEFAULT 'no', - `modified` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(), - `created` datetime NOT NULL DEFAULT current_timestamp(), - PRIMARY KEY (`id`), - KEY `formId` (`formId`), - KEY `formId_class_enabled_deleted` (`formId`,`class`,`enabled`,`deleted`), - KEY `feIdContainer` (`feIdContainer`), - KEY `ord` (`ord`), - KEY `feGroup` (`feGroup`) + `id` int(11) NOT NULL AUTO_INCREMENT, + `formId` int(11) NOT NULL, + `feIdContainer` int(11) NOT NULL DEFAULT 0, + `dynamicUpdate` enum('yes','no') NOT NULL DEFAULT 'no', + `enabled` enum('yes','no') NOT NULL DEFAULT 'yes', + `name` varchar(255) NOT NULL DEFAULT '', + `label` varchar(1023) NOT NULL DEFAULT '', + `mode` enum('show','required','readonly','hidden') NOT NULL DEFAULT 'show', + `modeSql` text NOT NULL, + `class` enum('native','action','container') NOT NULL DEFAULT 'native', + `type` enum('checkbox','date','datetime','dateJQW','datetimeJQW','extra','gridJQW','text','editor','annotate','time','note','password','radio','select','subrecord','upload','annotate','imageCut','fieldset','pill','templateGroup','beforeLoad','beforeSave','beforeInsert','beforeUpdate','beforeDelete','afterLoad','afterSave','afterInsert','afterUpdate','afterDelete','sendMail','paste') NOT NULL DEFAULT 'text', + `subrecordOption` set('edit','delete','new') NOT NULL DEFAULT '', + `encode` enum('none','specialchar') NOT NULL DEFAULT 'specialchar', + `checkType` enum('auto','alnumx','digit','numerical','email','pattern','allbut','all') NOT NULL DEFAULT 'auto', + `checkPattern` varchar(255) NOT NULL DEFAULT '', + `onChange` varchar(255) NOT NULL DEFAULT '', + `ord` int(11) NOT NULL DEFAULT 0, + `tabindex` int(11) NOT NULL DEFAULT 0, + `size` varchar(255) NOT NULL DEFAULT '', + `maxLength` varchar(255) NOT NULL DEFAULT '', + `labelAlign` enum('default','left','center','right') NOT NULL DEFAULT 'default', + `bsLabelColumns` varchar(255) NOT NULL DEFAULT '', + `bsInputColumns` varchar(255) NOT NULL DEFAULT '', + `bsNoteColumns` varchar(255) NOT NULL DEFAULT '', + `rowLabelInputNote` set('row','label','/label','input','/input','note','/note','/row') NOT NULL DEFAULT 'row,label,/label,input,/input,note,/note,/row', + `note` text NOT NULL, + `adminNote` text NOT NULL, + `tooltip` varchar(255) NOT NULL DEFAULT '', + `placeholder` varchar(2048) NOT NULL DEFAULT '', + `value` text NOT NULL, + `sql1` text NOT NULL, + `parameter` text NOT NULL, + `parameterLanguageA` text NOT NULL, + `parameterLanguageB` text NOT NULL, + `parameterLanguageC` text NOT NULL, + `parameterLanguageD` text NOT NULL, + `clientJs` text NOT NULL, + `deleted` enum('yes','no') NOT NULL DEFAULT 'no', + `modified` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(), + `created` datetime NOT NULL DEFAULT current_timestamp(), + PRIMARY KEY (`id`), + KEY `formId` (`formId`), + KEY `formId_class_enabled_deleted` (`formId`,`class`,`enabled`,`deleted`), + KEY `feIdContainer` (`feIdContainer`), + KEY `ord` (`ord`) ) ENGINE=InnoDB AUTO_INCREMENT=765 DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; diff --git a/extension/Classes/Api/save.php b/extension/Classes/Api/save.php index dda9b0ae17e4ae7afdee8d3ffde6dc1fabf57124..765442db462df36a5c06c3bd0e29d27fc253ed05 100644 --- a/extension/Classes/Api/save.php +++ b/extension/Classes/Api/save.php @@ -11,6 +11,7 @@ namespace IMATHUZH\Qfq\Api; require_once(__DIR__ . '/../../vendor/autoload.php'); use IMATHUZH\Qfq\Core\Helper\OnString; +use IMATHUZH\Qfq\Core\Helper\Path; use IMATHUZH\Qfq\Core\Helper\Support; use IMATHUZH\Qfq\Core\QuickFormQuery; use IMATHUZH\Qfq\Core\Store\Store; diff --git a/extension/Classes/Controller/QfqController.php b/extension/Classes/Controller/QfqController.php index 662f9ed0c8de3f0a5903792f513ab59a91640590..6cf5822e70d256f3f1defd9275bbe8f14575e9d2 100644 --- a/extension/Classes/Controller/QfqController.php +++ b/extension/Classes/Controller/QfqController.php @@ -8,6 +8,8 @@ namespace IMATHUZH\Qfq\Controller; require_once(__DIR__ . '/../../vendor/autoload.php'); use IMATHUZH\Qfq\Core\QuickFormQuery; +use IMATHUZH\Qfq\Core\Typo3\T3Handler; +use TYPO3\CMS\Core\Http\HtmlResponse; /** @@ -29,7 +31,6 @@ class QfqController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionController { */ public function showAction() { - $html = ''; $origErrorReporting = ''; $flagOk = false; @@ -87,8 +88,14 @@ class QfqController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionController { error_reporting($origErrorReporting); $this->view->assign('qfqOutput', $html); + $content = $this->view->render(); - return $this->view->render(); + // Compatibility with T3 v11 and higher. + if (T3Handler::typo3VersionGreaterEqual11()) { + $content = new HtmlResponse($content); + } + + return $content; } } \ No newline at end of file diff --git a/extension/Classes/Core/AbstractBuildForm.php b/extension/Classes/Core/AbstractBuildForm.php index b1a8b4f7f1aa61cb5353c0c8e1e99bbfc81a8505..8aedd3f4b49fc18fbd6031ae8b6864b60b63a30f 100644 --- a/extension/Classes/Core/AbstractBuildForm.php +++ b/extension/Classes/Core/AbstractBuildForm.php @@ -198,16 +198,36 @@ abstract class AbstractBuildForm { $rcJson = array(); $parentRecords = $this->evaluate->parse($this->formSpec[F_MULTI_SQL], ROW_REGULAR); + // Check for 'id' or '_id' as column name + $idName = isset($parentRecords[0]['_' . F_MULTI_COL_ID]) ? '_' . F_MULTI_COL_ID : F_MULTI_COL_ID; + + // If form.parameter.processRow is set, checkboxes are added to parentRecords + if (isset($this->formSpec[F_PROCESS_ROW])) { + // Title from query. + $processRowTitle = $this->formSpec[F_PROCESS_ROW]; + + // Will be displayed in <th></th>. + $processRowKey = '<label class="checkbox process-row-all process-row-label-header"><input type="checkbox"><span>' . $processRowTitle . '</span></label>'; + foreach ($parentRecords as &$array) { + // Will be displayed in <td></td>. + $checked = empty($array[F_PROCESS_ROW_COLUMN]) ? '' : 'checked="checked"'; + $processRowName = HelperFormElement::buildFormElementName([FE_NAME => F_PROCESS_ROW_COLUMN], $array[$idName]); + $processRowValue = '<label class="checkbox"><input name="' . $processRowName . + '" type="checkbox" ' . $checked . '></label>'; + $processRow = [$processRowKey => $processRowValue]; + // Inserted at index 0 and thus displayed in first column of table. + $array = $processRow + $array; + } + } + // Importat to destroy the reference (further usage) + unset ($array); // No rows: nothing to do. if (empty($parentRecords)) { return $this->formSpec[F_MULTI_MSG_NO_RECORD]; } - // Check for 'id' or '_id' as column name - $idName = isset($parentRecords[0]['_' . F_MULTI_COL_ID]) ? '_' . F_MULTI_COL_ID : F_MULTI_COL_ID; - - // Check that an column 'id' is given + // Check that a column 'id' is given if (!isset($parentRecords[0][$idName])) { throw new \UserFormException( json_encode([ERROR_MESSAGE_TO_USER => 'Missing column "_' . F_MULTI_COL_ID . '"', ERROR_MESSAGE_TO_DEVELOPER => $this->formSpec[F_MULTI_SQL]]), @@ -254,7 +274,7 @@ abstract class AbstractBuildForm { $tableHead = Support::wrapTag('<tr>', $this->buildMultiFormTableHead($parentRecords[0])); - return '<table class="table"><thead>' . $tableHead . '</thead><tbody>' . $htmlElements . '</tbody></table>'; + return '<table class="table table-multi-form qfq-table-100"><thead>' . $tableHead . '</thead><tbody>' . $htmlElements . '</tbody></table>'; } /** @@ -371,6 +391,17 @@ abstract class AbstractBuildForm { // Build FormElements $htmlElements = $this->elements($recordId, $filter, 0, $json, $modeCollectFe, $htmlElementNameIdZero, $storeUse, $mode); + $formArray[F_TITLE] = $this->formSpec[F_UNEVALUATED_TITLE]; + $evaluatedTitle = $this->evaluate->parseArray($formArray); + // If form title has changed, add new title to JSON + if($this->formSpec[F_TITLE] != $evaluatedTitle[F_TITLE]){ + $element = array( + 'form-element' => 'qfq-form-title', + 'value' => $evaluatedTitle[F_TITLE] + ); + $json[] = $element; + } + if ($mode === FORM_SAVE && $recordId != 0) { // element-update: with 'value' @@ -894,7 +925,11 @@ abstract class AbstractBuildForm { case FE_TYPE_DATETIME: case FE_TYPE_TIME: $elementHtml = DateTime::buildDateTime($formElement, $htmlFormElementName, $value, $jsonElement, $this->formSpec, $this->store, $mode, $this->wrap[WRAP_SETUP_ELEMENT][WRAP_SETUP_CLASS]); - break; + //needed for datepicker to be positioned correctly + if ($flagMulti == true) { + $elementHtml = Support::wrapTag('<div class="col-d-12 col-lg-12">', $elementHtml); + } + break; case FE_TYPE_TEXT: case FE_TYPE_PASSWORD: case 'email': @@ -1176,7 +1211,8 @@ abstract class AbstractBuildForm { $sipValue = $sip->queryStringToSip($queryString, RETURN_SIP); - $json[] = $this->getFormElementForJson(CLIENT_SIP, $sipValue, [FE_MODE => FE_MODE_SHOW]); + $wrapSetupClass = $this->wrap[WRAP_SETUP_ELEMENT][WRAP_SETUP_CLASS] ?? ''; + $json[] = $this->getFormElementForJson(CLIENT_SIP, $sipValue, [FE_MODE => FE_MODE_SHOW], $wrapSetupClass); return HelperFormElement::buildNativeHidden(CLIENT_SIP, $sipValue); } @@ -1190,14 +1226,14 @@ abstract class AbstractBuildForm { * @param string $htmlFormElementName * @param string|array $value * @param array $formElement - * + * @param string $wrap * @param int $optionIdx - * @param string $class + * @param string $optionClass * @return array * @throws \CodeException * @throws \UserFormException */ - public function getFormElementForJson($htmlFormElementName, $value, array $formElement, $optionIdx = 0, $optionClass = '') { + public static function getFormElementForJson(string $htmlFormElementName, $value, array $formElement, $wrap = '', $optionIdx = 0, $optionClass = ''): array { $addClassRequired = array(); $json = HelperFormElement::getJsonFeMode($formElement[FE_MODE]); // disabled, required @@ -1308,7 +1344,7 @@ abstract class AbstractBuildForm { if ($optionClass != '') { $class = $optionClass; } else { - $class = $this->wrap[WRAP_SETUP_ELEMENT][WRAP_SETUP_CLASS]; + $class = $wrap; } if ($formElement[FE_MODE] == FE_MODE_HIDDEN) { @@ -1321,6 +1357,13 @@ abstract class AbstractBuildForm { $key = $formElement[FE_HTML_ID] . HTML_ID_EXTENSION_ROW; $json[API_ELEMENT_UPDATE][$key][API_ELEMENT_ATTRIBUTE]['class'] = $class; + + // Check if FE is upload and parameter downloadButton is set. Has to come after saving the Form. Avoids throwing error + if ($formElement[FE_TYPE] == FE_TYPE_UPLOAD && isset($formElement[FE_FILE_DOWNLOAD_BUTTON]) && isset($_GET["submit_reason"])) { + $json['type-file'] = true; + $json['html-content'] = $formElement[FE_FILE_DOWNLOAD_BUTTON_HTML_INTERNAL] ?? ''; + } + // $json[API_ELEMENT_UPDATE][$key][API_ELEMENT_ATTRIBUTE]['buggy'] = $class; } @@ -1416,7 +1459,7 @@ abstract class AbstractBuildForm { } } - if ($formElement[FE_MAX_LENGTH] > 0 && $value !== '') { + if ($formElement[FE_MAX_LENGTH] > 0 && $value !== '' && !isset($formElement[FE_TYPEAHEAD_SQL])) { // Check if there are \r\n > those should be counted as one char, not two. #13030 $crlf = substr_count($value, "\r\n"); $max = $formElement[FE_MAX_LENGTH] + $crlf; @@ -1483,8 +1526,8 @@ abstract class AbstractBuildForm { if (isset($formElement[FE_CHARACTER_COUNT_WRAP])) { $class .= ' ' . CLASS_CHARACTER_COUNT; $attribute .= Support::doAttribute(DATA_CHARACTER_COUNT_ID, $formElement[FE_HTML_ID] . HTML_ID_EXTENSION_CHARACTER_COUNT); - $attributeCC = Support::doAttribute('id', $formElement[FE_HTML_ID] . HTML_ID_EXTENSION_CHARACTER_COUNT); - $classCC = ($formElement[FE_CHARACTER_COUNT_WRAP] == '') ? Support::doAttribute('class', 'qfq-cc-style') : ''; + $attributeCC = Support::doAttribute(FE_ID, $formElement[FE_HTML_ID] . HTML_ID_EXTENSION_CHARACTER_COUNT); + $classCC = ($formElement[FE_CHARACTER_COUNT_WRAP] == '') ? Support::doAttribute(FE_CLASS, FE_CHARACTER_COUNT_CLASS) : ''; $elementCharacterCount = "<span $attributeCC $classCC></span>"; if ($formElement[FE_CHARACTER_COUNT_WRAP] != '') { @@ -1516,6 +1559,13 @@ abstract class AbstractBuildForm { $formElement[FE_INPUT_TYPE] = 'number'; } + // when size empty but value contains \n then set auto multi line + if(!isset($formElement[FE_TYPEAHEAD_SQL])){ + if (strpos($value, "\n") == true && empty($formElement[FE_SIZE])) { + $formElement[FE_SIZE] = '50,2'; + } + } + // Check for input type 'textarea'. Possible notation: a) '', b) '<width>', c) '<width>,<height>', d) '<width>,<min-height>,<max-height>' $colsRows = explode(',', $formElement[FE_SIZE], 3); @@ -1603,7 +1653,8 @@ abstract class AbstractBuildForm { $attribute .= Support::doAttribute('class', $class); - $json = $this->getFormElementForJson($htmlFormElementName, $value, $formElement); + $wrapSetupClass = $this->wrap[WRAP_SETUP_ELEMENT][WRAP_SETUP_CLASS] ?? ''; + $json = $this->getFormElementForJson($htmlFormElementName, $value, $formElement, $wrapSetupClass); $input = "$htmlTag $attribute>$textarea"; @@ -1957,7 +2008,8 @@ abstract class AbstractBuildForm { $html = Support::wrapTag('<div class="btn-group" data-toggle="buttons">', $html); - $json = $this->getFormElementForJson($htmlFormElementName, $value, $formElement); + $wrapSetupClass = $this->wrap[WRAP_SETUP_ELEMENT][WRAP_SETUP_CLASS] ?? ''; + $json = $this->getFormElementForJson($htmlFormElementName, $value, $formElement, $wrapSetupClass); return $html; } @@ -1980,7 +2032,7 @@ abstract class AbstractBuildForm { * @throws \UserFormException * @throws \UserReportException */ - private function constructRadioPlain(array $formElement, $htmlFormElementName, $value, array &$json, $mode = FORM_LOAD) { + private function constructRadioPlain(array $formElement, string $htmlFormElementName, $value, array &$json, string $mode = FORM_LOAD): string { $attributeBase = ''; $html = ''; @@ -2070,7 +2122,8 @@ abstract class AbstractBuildForm { $jsonTmp[API_ELEMENT_UPDATE][$optionId][API_ELEMENT_ATTRIBUTE]['required'] = ($formElement[FE_MODE] == FE_MODE_REQUIRED) ? 'required' : 'false'; } - $json = array_merge($this->getFormElementForJson($htmlFormElementName, $value, $formElement)); + $wrapSetupClass = $this->wrap[WRAP_SETUP_ELEMENT][WRAP_SETUP_CLASS] ?? ''; + $json = array_merge($this->getFormElementForJson($htmlFormElementName, $value, $formElement, $wrapSetupClass)); $json[API_ELEMENT_UPDATE] = array_merge($json[API_ELEMENT_UPDATE], $jsonTmp[API_ELEMENT_UPDATE]); @@ -2146,7 +2199,8 @@ abstract class AbstractBuildForm { $option .= '>' . $itemValue[$ii] . '</option>'; } - $json = $this->getFormElementForJson($htmlFormElementName, $jsonValues, $formElement); + $wrapSetupClass = $this->wrap[WRAP_SETUP_ELEMENT][WRAP_SETUP_CLASS] ?? ''; + $json = $this->getFormElementForJson($htmlFormElementName, $jsonValues, $formElement, $wrapSetupClass); $formElement = HelperFormElement::prepareExtraButton($formElement, false); $attribute .= HelperFormElement::getAttributeFeMode($formElement[FE_MODE]); @@ -2803,7 +2857,7 @@ abstract class AbstractBuildForm { break; } - $arr = explode('|', $columnValue); + $arr = explode('|', (string)$columnValue); if (count($arr) == 1) { $arr[1] = $arr[0]; } @@ -2812,7 +2866,7 @@ abstract class AbstractBuildForm { $cell = $columnValue; $control[SUBRECORD_COLUMN_MAX_LENGTH][$columnName] = false; } else { - $cell = strip_tags($columnValue); + $cell = strip_tags((string)$columnValue); } if ($control[SUBRECORD_COLUMN_MAX_LENGTH][$columnName] !== false && $control[SUBRECORD_COLUMN_MAX_LENGTH][$columnName] != 0) { @@ -2955,8 +3009,9 @@ abstract class AbstractBuildForm { $attribute .= Support::doAttribute('data-sip', $sipUpload); $attribute .= Support::doAttribute(ATTRIBUTE_DATA_REFERENCE, $formElement[FE_DATA_REFERENCE]); - - $json = $this->getFormElementForJson($htmlFormElementName, $value, $formElement); // Below, $formElement[FE_MODE]=FE_MODE_REQUIRED will be changed. Get the JSON unchanged + // Below, $value and $formElement[FE_MODE]=FE_MODE_REQUIRED will be changed. JSON will be made later, therefore we will need those values unchanged + $jsonValue = $value; + $jsonFormElement = $formElement; if ($value === '' || $value === false) { $textDeleteClass = 'hidden'; @@ -2995,11 +3050,19 @@ abstract class AbstractBuildForm { } if (!empty($value) && Support::isEnabled($formElement, FE_FILE_DOWNLOAD_BUTTON)) { + $testValue = file_exists($value); + + // API calls don't recognize paths like '/fileadmin/protected/...' + if (!$testValue && isset($_GET["submit_reason"])) { + $value = $_SERVER["DOCUMENT_ROOT"] . '/'. $value; + $testValue = file_exists($value); + } + if (is_readable($value)) { $link = new Link($this->sip, $this->dbIndexData); $value = $link->renderLink($this->evaluate->parse($formElement[FE_FILE_DOWNLOAD_BUTTON]), 's|M:file|d|F:' . $value); + $jsonFormElement[FE_FILE_DOWNLOAD_BUTTON_HTML_INTERNAL] = $value; } else { - $msg = "Already uploaded file not found."; // In case debugging is off: showing download button means 'never show the real pathfilename' if ($this->showDebugInfoFlag) { @@ -3009,6 +3072,10 @@ abstract class AbstractBuildForm { } } + // JSON Build + $wrapSetupClass = $this->wrap[WRAP_SETUP_ELEMENT][WRAP_SETUP_CLASS] ?? ''; + $json = $this->getFormElementForJson($htmlFormElementName, $jsonValue, $jsonFormElement, $wrapSetupClass); // Below, $formElement[FE_MODE]=FE_MODE_REQUIRED will be changed. Get the JSON unchanged + $deleteButton = ''; $htmlFilename = Support::wrapTag("<span class='uploaded-file-name'>", $value, false); @@ -3457,6 +3524,9 @@ abstract class AbstractBuildForm { } } + // Get baseUrl for image upload. Path handling in T3 => V10 needs absolute path. + $baseUrl = $this->store::getVar(SYSTEM_BASE_URL, STORE_SYSTEM); + //TODO: static setup for TinyMCE ImagePlugin - needs to be activated / dynamically set by QFQ parameter. $preSettings = [ # "plugins" => "image", @@ -3465,7 +3535,7 @@ abstract class AbstractBuildForm { "file_picker_types" => "file image media", "image_advtab" => true, "automatic_uploads" => true, - "images_upload_url" => Path::appToApi(API_FILE_PHP) . $completeUrl, + "images_upload_url" => $baseUrl . Path::appToApi(API_FILE_PHP) . $completeUrl, "images_reuse_filename" => true, "paste_data_images" => true ]; @@ -3478,7 +3548,8 @@ abstract class AbstractBuildForm { $attribute .= HelperFormElement::getAttributeFeMode($formElement[FE_MODE]); $attribute .= HelperFormElement::getAttributeList($formElement, [F_FE_DATA_PATTERN_ERROR, F_FE_DATA_REQUIRED_ERROR, F_FE_DATA_MATCH_ERROR, F_FE_DATA_ERROR]); - $json = $this->getFormElementForJson($htmlFormElementName, $value, $formElement); + $wrapSetupClass = $this->wrap[WRAP_SETUP_ELEMENT][WRAP_SETUP_CLASS] ?? ''; + $json = $this->getFormElementForJson($htmlFormElementName, $value, $formElement, $wrapSetupClass); $html = Support::wrapTag("<textarea $attribute>", htmlentities($value), false); $formElement = HelperFormElement::prepareExtraButton($formElement, false); @@ -3533,14 +3604,15 @@ abstract class AbstractBuildForm { $attribute .= Support::doAttribute('data-config', $json, true); $minMax = explode(',', $formElement[FE_SIZE], 2); - if(isset($minMax[0]) && $minMax[0] !== '') { + if (isset($minMax[0]) && $minMax[0] !== '') { $attribute .= Support::doAttribute('data-height', $minMax[0]); } $attribute .= HelperFormElement::getAttributeFeMode($formElement[FE_MODE]); $attribute .= HelperFormElement::getAttributeList($formElement, [F_FE_DATA_PATTERN_ERROR, F_FE_DATA_REQUIRED_ERROR, F_FE_DATA_MATCH_ERROR, F_FE_DATA_ERROR]); - $json = $this->getFormElementForJson($htmlFormElementName, $value, $formElement); + $wrapSetupClass = $this->wrap[WRAP_SETUP_ELEMENT][WRAP_SETUP_CLASS] ?? ''; + $json = $this->getFormElementForJson($htmlFormElementName, $value, $formElement, $wrapSetupClass); $html = Support::wrapTag("<textarea $attribute>", htmlentities($value), false); $formElement = HelperFormElement::prepareExtraButton($formElement, false); @@ -3853,7 +3925,8 @@ abstract class AbstractBuildForm { */ public function buildNote(array $formElement, $htmlFormElementName, $value, array &$json, $mode = FORM_LOAD) { - $json = $this->getFormElementForJson($htmlFormElementName, $value, $formElement); + $wrapSetupClass = $this->wrap[WRAP_SETUP_ELEMENT][WRAP_SETUP_CLASS] ?? ''; + $json = $this->getFormElementForJson($htmlFormElementName, $value, $formElement, $wrapSetupClass); $attribute = Support::doAttribute('id', $formElement[FE_HTML_ID] . HTML_ID_EXTENSION_INPUT); $attribute .= Support::doAttribute(ATTRIBUTE_DATA_REFERENCE, $formElement[FE_DATA_REFERENCE]); @@ -3931,7 +4004,8 @@ abstract class AbstractBuildForm { // restore parent processed FE's $this->feSpecNative = $tmpStore; - $json = $this->getFormElementForJson($htmlFormElementName, $value, $formElement); + $wrapSetupClass = $this->wrap[WRAP_SETUP_ELEMENT][WRAP_SETUP_CLASS] ?? ''; + $json = $this->getFormElementForJson($htmlFormElementName, $value, $formElement, $wrapSetupClass); return $html; } diff --git a/extension/Classes/Core/Constants.php b/extension/Classes/Core/Constants.php index f5217be71d719a4a0ca492155840423c310c377b..5abf369038a7fb6cf8904cd92badcda57cea046c 100644 --- a/extension/Classes/Core/Constants.php +++ b/extension/Classes/Core/Constants.php @@ -29,7 +29,7 @@ const SESSION_FE_USER = 'feUser'; const SESSION_FE_USER_GROUP = 'feUserGroup'; const SESSION_BE_USER = 'beUser'; const SESSION_PAGE_LANGUAGE = 'pageLanguage'; - +const SESSION_PAGE_LANGUAGE_PATH = 'pageLanguagePath'; const TABLE_NAME_FORM = 'Form'; const TABLE_NAME_FORM_ELEMENT = 'FormElement'; const TABLE_NAME_SPLIT = 'Split'; @@ -366,7 +366,6 @@ const ERROR_TABLESORTER_NAME_TOO_LONG = 3102; const ERROR_SETTING_RECORD_TOO_MUCH = 3103; const ERROR_SETTING_SYSTEM = 3104; -// Author: Enis Nuredini // Encryption const ERROR_MISSING_ENCRYPTION_KEY = 3105; const ERROR_INVALID_DATABASE_FIELD_TYPE = 3106; @@ -375,7 +374,8 @@ const ERROR_INVALID_ENCRYPTION_METHOD = 3108; const ERROR_VARIABLE_INVALID_ENCRYPTION_METHOD = 3109; const ERROR_NO_STORE_FOUND = 3110; const ERROR_ENCRYPT_CLASS = 3111; -// End author + +const ERROR_MSG_TOO_BIG = "** Data removed: too big **"; // // Store Names: Identifier @@ -447,7 +447,7 @@ const SANITIZE_TYPE_MESSAGE_VIOLATE_EMPTY = 'e'; const SANITIZE_TYPE_MESSAGE_VIOLATE_ZERO = '0'; const SANITIZE_TYPE_MESSAGE_VIOLATE_CLASS = 'c'; -const PATTERN_ALNUMX = '^[@\-_\.,;: \/\(\)a-zA-Z0-9ÀÈÌÒÙà èìòùÃÉÃÓÚÃáéÃóúýÂÊÎÔÛâêîôûÃÑÕãñõÄËÃÖÜŸäëïöüÿçß]*$'; +const PATTERN_ALNUMX = '^[@\-_\.,;: \/\(\)a-zA-Z0-9ÀÈÌÒÙà èìòùÃĆÉÃÓÚÃáćéÃóúýÂÊÎÔÛâêîôûÃÑÕãñõÄËÃÖÜŸäëïöüÿçČÄÄ𩹮žß]*$'; const PATTERN_DIGIT = '^[\d]*$'; const PATTERN_NUMERICAL = '^[\d.+-]*$'; const PATTERN_EMAIL = '^([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})?$'; @@ -482,6 +482,7 @@ const CLIENT_SERVER_NAME = 'SERVER_NAME'; const CLIENT_SERVER_ADDRESS = 'SERVER_ADDR'; const CLIENT_SERVER_PORT = 'SERVER_PORT'; const CLIENT_REMOTE_ADDRESS = 'REMOTE_ADDR'; +const CLIENT_HTTP_X_REAL_IP = 'HTTP_X_REAL_IP'; const CLIENT_REQUEST_SCHEME = 'REQUEST_SCHEME'; const CLIENT_REQUEST_METHOD = 'REQUEST_METHOD'; const CLIENT_SCRIPT_FILENAME = 'SCRIPT_FILENAME'; @@ -523,7 +524,8 @@ const TYPO3_PAGE_KEYWORDS = 'pageKeywords'; const TYPO3_PAGE_NAV_TITLE = 'pageNavTitle'; const TYPO3_VERSION = 't3Version'; -const TYPO3_PAGE_LANGUAGE = 'pageLanguage'; +const TYPO3_PAGE_LANGUAGE = SESSION_PAGE_LANGUAGE; +const TYPO3_PAGE_LANGUAGE_PATH = SESSION_PAGE_LANGUAGE_PATH; const TYPO3_DEBUG_SHOW_BODY_TEXT = 'debugShowBodyText'; const TYPO3_SQL_LOG_ABSOLUTE = 'sqlLog'; const TYPO3_SQL_LOG_MODE = 'sqlLogMode'; @@ -568,6 +570,7 @@ const FORCE_RUN_PAGE_SLUG_MIGRATION_CHECK = 'FORCE_RUN_PAGE_SLUG_MIGRATION_CHECK const SYSTEM_FORM_SUBMIT_LOG_MODE = 'formSubmitLogMode'; const FORM_SUBMIT_LOG_MODE_ALL = 'all'; const FORM_SUBMIT_LOG_MODE_NONE = 'none'; +const FORM_SUBMIT_LOG_MODE_MODIFY = 'modify'; const SYSTEM_LOCK_SAME_OWNER = 'lockSameOwner'; @@ -774,6 +777,7 @@ const DOWNLOAD_POPUP_REPLACE_TITLE = '#downloadPopupReplaceTitle#'; const SYSTEM_DRAG_AND_DROP_JS = 'hasDragAndDropJS'; const SYSTEM_SQL_DIRECT_DOWNLOAD = 'sqlDirect'; // becomes sqlDirectdownload.php, sqlDirectdl.php, sqlDirectdl2.php, sqlDirectdl3.php const SYSTEM_EDIT_INLINE_REPORTS = 'editInlineReports'; +const SYSTEM_EDIT_INLINE_REPORT_DARK_THEME = 'editInlineReportDarkTheme'; const SYSTEM_UNIT_TEST_FORM_CONTENT = 'unitTestFormContent'; const SYSTEM_PARAMETER_LANGUAGE_FIELD_NAME = 'parameterLanguageFieldName'; @@ -791,6 +795,7 @@ const SYSTEM_PROTECTED_FOLDER_CHECK = 'protectedFolderCheck'; const EXTRA_ENABLE_SWITCH = 'enableSwitch'; const EXTRA_COLUMN_NAME_AlIAS_SLUG = 'columnNameAliasSlug'; +const EXTRA_FORM_SUBMIT_LOG_ID = 'formSubmitLogId'; const MODE_HTML = 'html'; const MODE_JSON = 'json'; @@ -820,7 +825,6 @@ const SIP_EXCLUDE_XDEBUG_SESSION_START = 'XDEBUG_SESSION_START'; // FURTHER: all extracted params from 'urlparam const ACTION_KEYWORD_SLAVE_ID = 'slaveId'; - const VAR_RANDOM = 'random'; const VAR_FILE_DESTINATION = 'fileDestination'; const VAR_SLAVE_ID = ACTION_KEYWORD_SLAVE_ID; @@ -1162,6 +1166,9 @@ const F_MODE_REQUIRED_OFF_BUT_MARK = 'requiredOffButMark'; const F_MODE_SKIP_REQUIRED_CHECK = 'skipRequiredCheck'; // deprecated since third revision of #9617 const F_MODE_GLOBAL = 'formModeGlobal'; +const F_PROCESS_ROW = 'processRow'; +const F_PROCESS_ROW_COLUMN = '_processRow'; + const F_SAVE_BUTTON_ACTIVE = 'saveButtonActive'; const F_SAVE_BUTTON_TEXT = SYSTEM_SAVE_BUTTON_TEXT; @@ -1218,6 +1225,8 @@ const CLIENT_REST_FORM = '_form'; const F_REMEMBER_LAST_PILL = SYSTEM_REMEMBER_LAST_PILL; const F_DO_NOT_LOG_COLUMN = SYSTEM_DO_NOT_LOG_COLUMN; +const F_UNEVALUATED_TITLE = 'unevaluatedTitle'; + // Form Columns: Only in form file const F_FILE_FORM_ELEMENT = 'FormElement_ff'; // Key for FormElements array saved in Form File @@ -1297,6 +1306,8 @@ const FE_FILE_SPLIT_OPTIONS = 'fileSplitOptions'; const FE_FILE_SPLIT_OPTIONS_JPEG = '-density 150 -quality 90'; const FE_FILE_SPLIT_TABLE_NAME = 'tableNameSplit'; const FE_FILE_DOWNLOAD_BUTTON = 'downloadButton'; + +const FE_FILE_DOWNLOAD_BUTTON_HTML_INTERNAL = 'htmlDownloadButton'; // Internal use const FE_FILE_AUTO_ORIENT = 'autoOrient'; const FE_FILE_AUTO_ORIENT_CMD = 'autoOrientCmd'; const FE_FILE_AUTO_ORIENT_CMD_DEFAULT = 'convert -auto-orient {{fileDestination:V}} {{fileDestination:V}}.new; mv {{fileDestination:V}}.new {{fileDestination:V}}'; @@ -1379,6 +1390,7 @@ const FE_EDITOR_TYPE = 'editorType'; // tinymce | codemirror const FE_EDITOR_TYPE_TINYMCE = 'tinymce'; const FE_EDITOR_TYPE_CODEMIRROR = 'codemirror'; const FE_EDITOR_FILE_UPLOAD_PATH = 'fileUploadPath'; +const FE_DEFAULT_VALUE = 'defaultValue'; const FE_SENDMAIL_TO = 'sendMailTo'; // Receiver email adresses. Separate multiple by comma. @@ -1445,6 +1457,7 @@ const FE_TYPEAHEAD_LDAP_SEARCH_PER_TOKEN = F_TYPEAHEAD_LDAP_SEARCH_PER_TOKEN; const FE_FILL_STORE_LDAP = 'fillStoreLdap'; const FE_FILL_STORE_VAR = 'fillStoreVar'; const FE_CHARACTER_COUNT_WRAP = 'characterCountWrap'; +const FE_CHARACTER_COUNT_CLASS = 'qfq-cc-style'; const FE_INPUT_EXTRA_BUTTON_LOCK = 'extraButtonLock'; const FE_INPUT_EXTRA_BUTTON_PASSWORD = 'extraButtonPassword'; const FE_INPUT_EXTRA_BUTTON_INFO = 'extraButtonInfo'; @@ -1773,7 +1786,7 @@ const LINE_ALT_INSERT_ID = 'altInsertId'; const COLUMN_LINK = 'link'; const COLUMN_EXEC = 'exec'; const COLUMN_THUMBNAIL = 'thumbnail'; - +const COLUMN_FUNCTION = 'function'; const COLUMN_PPAGE = 'Page'; const COLUMN_PPAGEC = 'Pagec'; const COLUMN_PPAGED = 'Paged'; @@ -1944,7 +1957,7 @@ const NAME_SIP = 'sip'; const NAME_URL_PARAM = 'param'; const NAME_RIGHT = 'picturePositionRight'; const NAME_ACTION_DELETE = 'actionDelete'; -const NAME_ORDER_TEXT ='orderText'; +const NAME_ORDER_TEXT = 'orderText'; const NAME_ORDER_TEXT_WRAP = 'orderTextWrap'; const NAME_EXTRA_CONTENT_WRAP = 'extraContentWrap'; const NAME_FILE = 'file'; @@ -2313,4 +2326,7 @@ const ERROR_MESSAGE = 'error-message'; // Misc const BASE_URL_FAKE = 'http://i_am_set_in_constants_php/qfq/'; -const HTTP_EXAMPLE_COM = 'http://example.com/'; \ No newline at end of file +const HTTP_EXAMPLE_COM = 'http://example.com/'; + +// Max columnsize FormSubmitLog.formData +const LOG_MAX_FORMDATA = 65535; diff --git a/extension/Classes/Core/Database/Database.php b/extension/Classes/Core/Database/Database.php index 23a5b3add80e8289b5f6832162a62677a45a8af1..d22f9dcb31aa446a3b6dd5e5d0cce091a15a8c5e 100644 --- a/extension/Classes/Core/Database/Database.php +++ b/extension/Classes/Core/Database/Database.php @@ -74,7 +74,7 @@ class Database { * @throws \UserFormException * @throws \UserReportException */ - public function __construct($dbIndex = DB_INDEX_DEFAULT) { + public function __construct($dbIndex = DB_INDEX_DEFAULT, $config = array()) { if (empty($dbIndex)) { $dbIndex = DB_INDEX_DEFAULT; @@ -88,6 +88,11 @@ class Database { $this->sqlLogAbsolute = Path::absoluteSqlLogFile(); $dbInit = $storeSystem[SYSTEM_DB_INIT]; + // In case if typo3 database is asked, use given config for credentials instead of system store data. + if (count($config) > 1) { + $storeSystem = $config; + } + $config = $this->getConnectionDetails($dbIndex, $storeSystem); $this->dbName = $config[SYSTEM_DB_NAME]; @@ -609,6 +614,7 @@ class Database { ['tt', TYPO3_TT_CONTENT_UID, STORE_TYPO3], ['level', SYSTEM_REPORT_FULL_LEVEL, STORE_SYSTEM], ['form', SIP_FORM, STORE_SIP], + ['fslId', EXTRA_FORM_SUBMIT_LOG_ID, STORE_EXTRA], ]; $t3msg = ''; diff --git a/extension/Classes/Core/Database/DatabaseUpdate.php b/extension/Classes/Core/Database/DatabaseUpdate.php index 6abca4696f8edeb7b4a3b654f0c459ad1bbbed9d..dba7a43fe5c57a1ea60a31f89f2e999f5bbf598a 100644 --- a/extension/Classes/Core/Database/DatabaseUpdate.php +++ b/extension/Classes/Core/Database/DatabaseUpdate.php @@ -498,11 +498,15 @@ class DatabaseUpdate { $KEY_SQL_UPDATE = 'sql_update'; // sql update statement for that blob // get reports from tt_content.bodytext - $reports = $this->db->sql("SELECT tt.`uid`, tt.`header`, tt.`bodytext`, tt.`hidden`, p.`hidden` AS pageHidden FROM `" . $dbT3 . "`.`tt_content` AS tt, `" . $dbT3 . "`.`pages` AS p WHERE tt.`CType`='qfq_qfq' AND tt.`deleted`=0 AND p.`deleted`=0 AND p.uid=tt.pid"); + $reports = $this->db->sql("SELECT tt.`uid`, tt.`header`, tt.`bodytext`, tt.`hidden`, p.`hidden` AS pageHidden," + . " p.`title` AS pageTitle, p.`uid` AS pageId FROM `" + . $dbT3 . "`.`tt_content` AS tt, `" . $dbT3 + . "`.`pages` AS p WHERE tt.`CType`='qfq_qfq' AND tt.`deleted`=0 AND p.`deleted`=0 AND p.uid=tt.pid ORDER BY p.uid, tt.sorting"); $qfqCodeBlobs = array_map(function ($r) use ($dbT3, $KEY_SQL_UPDATE, $KEY_CONTENT, $KEY_TITLE) { $maybeHidden = (intval($r['hidden']) !== 0) || (intval($r['pageHidden']) !== 0); return [ - $KEY_TITLE => 'QFQ Report with uid=' . $r['uid'] . ' and header: ' . $r['header'] + $KEY_TITLE => 'QFQ Report with uid <u>' . $r['uid'] . '</u> and header <u>' . $r['header'] + . '</u> on page <u>' . $r['pageId'] . '/' . $r['pageTitle'] . '</u>' . ($maybeHidden ? '<br><small>Note: Content element is probably hidden / not in use.</small>' : ''), $KEY_CONTENT => $r['bodytext'], $KEY_SQL_UPDATE => "UPDATE `$dbT3`.`tt_content` SET `bodytext` = ? WHERE uid=" . $r['uid'] . ";" @@ -551,8 +555,7 @@ class DatabaseUpdate { 'parameterLanguageB', 'parameterLanguageC', 'parameterLanguageD', - 'clientJs', - 'feGroup' + 'clientJs' ]; $formElements = $this->db->sql("SELECT `fe`.`id`, `fe`.`" . join("`, `fe`.`", $formElementColumnsToCheck) . "` FROM `FormElement` AS fe"); foreach ($formElements as $i => $formElement) { @@ -699,7 +702,7 @@ class DatabaseUpdate { . '<li>Use {{pageSlug:T}} instead of {{pageAlias:T}} or {{pageId:T}} in the _link columns. E.g. "p:{{pageSlug}}?foo=bar" AS link .</li>' . '<li>Use {{baseUrlLang:Y}}/{{pageSlug:T}} instead of {{pageAlias:T}} or {{pageId:T}} in hardcoded ' . htmlentities("<a>") . ' tags. E.g. href="{{baseUrlLang:Y}}/{{pageSlug:T}}?foo=bar".</li>' . '<li>Replace hardcoded aliases in QFQ code with the slugs of the pages. E.g. "p:/some/page?foo=bar" AS _link.</li>' - . '<li>Replace "U: ... &id=[alias]& ... " with "p:[slug]? ... " for all sepecial columns except _paged. I.e. "U: ... &id=[alias]& ... " becomes "p:[slug]? ... ". </li>' + . '<li>Replace "U: ... &id=[alias]& ... " with "p:[slug]? ... " for all special columns except _paged. I.e. "U: ... &id=[alias]& ... " becomes "p:[slug]? ... ". </li>' . '<li>Hint: Typo3 replaces "_" with "-" when converting an alias to a slug during the Typo3 version 9 upgrade.</li>' . '<li>Hint: A page slug always starts with a slash "/" and QFQ expects the slash to be there.</li>' . '<li>Note: After the page slug comes a "?" not a "&". E.g. "p:/some/page?foo=bar" AS _link. </li>' @@ -707,7 +710,7 @@ class DatabaseUpdate { . '<h2>AUTO SUGGESTIONS</h2>' - . 'In the report below the suggested changes are prominently marked with colour.' + . 'In the report below the suggested changes are prominently marked with color.' . "<br> If there is a $noSuggestionSymbol then there is no suggestion and you will have to fix it manually." . "<br> Tip: use ctrl+f and copy $noSuggestionSymbol into the search bar to quickly jump between matches." diff --git a/extension/Classes/Core/Database/DatabaseUpdateData.php b/extension/Classes/Core/Database/DatabaseUpdateData.php index 8704cd78808e978000d1b87aa92c82e0209885e2..51e765a4c8adb13864dd96d2948c9dbd2d142e3e 100644 --- a/extension/Classes/Core/Database/DatabaseUpdateData.php +++ b/extension/Classes/Core/Database/DatabaseUpdateData.php @@ -30,7 +30,6 @@ $UPDATE_ARRAY = array( '0.16.0' => [ "ALTER TABLE `FormElement` ADD INDEX `feIdContainer` (`feIdContainer`)", "ALTER TABLE `FormElement` ADD INDEX `ord` (`ord`)", - "ALTER TABLE `FormElement` ADD INDEX `feGroup` (`feGroup`)", "ALTER TABLE `FormElement` ADD `adminNote` TEXT NOT NULL AFTER `note`", ], @@ -224,6 +223,10 @@ $UPDATE_ARRAY = array( "ALTER TABLE `Setting` CHANGE `public` `public` TINYINT(1) NOT NULL DEFAULT '0';", ], + '23.6.0' => [ + "ALTER TABLE `FormSubmitLog` ADD INDEX IF NOT EXISTS `createdFeUserFormId` (`created`, `feUser`, `formId`);", + ], + ); diff --git a/extension/Classes/Core/Evaluate.php b/extension/Classes/Core/Evaluate.php index 86349f265260281b635a2099d024a121c94a30b4..c82ba2f867dec33185f9746467b5a649851b4237 100644 --- a/extension/Classes/Core/Evaluate.php +++ b/extension/Classes/Core/Evaluate.php @@ -15,11 +15,11 @@ use IMATHUZH\Qfq\Core\Helper\OnString; use IMATHUZH\Qfq\Core\Helper\Path; use IMATHUZH\Qfq\Core\Helper\Support; use IMATHUZH\Qfq\Core\Report\Link; +use IMATHUZH\Qfq\Core\Report\Report; use IMATHUZH\Qfq\Core\Report\Tablesorter; use IMATHUZH\Qfq\Core\Store\Sip; use IMATHUZH\Qfq\Core\Store\Store; - const EVALUATE_DB_INDEX_DEFAULT = 0; /** * Class Evaluate @@ -52,9 +52,8 @@ class Evaluate { private $endDelimiter = ''; private $endDelimiterLength = 0; private $sqlKeywords = array('SELECT ', 'INSERT ', 'DELETE ', 'UPDATE ', 'SHOW ', 'REPLACE ', 'TRUNCATE ', 'DESCRIBE ', 'EXPLAIN ', 'SET '); - private $escapeTypeDefault = ''; - + private $report = null; // private $debugStack = array(); @@ -146,7 +145,7 @@ class Evaluate { * @throws \UserFormException * @throws \UserReportException */ - public function parse($line, $sqlMode = ROW_IMPLODE_ALL, $recursion = 0, &$debugStack = array(), &$foundInStore = '') { + public function parse($line, $sqlMode = ROW_IMPLODE_ALL, $recursion = 0, &$debugStack = array(), &$foundInStore = '', $frCmd = '') { if ($line === '') { return ''; @@ -162,6 +161,7 @@ class Evaluate { $result = $line; + $recursion = ($recursion === null) ? 0 : $recursion; $debugIndent = str_repeat(' ', $recursion); $debugLocal[] = $debugIndent . "Parse: $result"; @@ -185,7 +185,7 @@ class Evaluate { $match = substr($result, $posMatchOpen + $this->startDelimiterLength, $posFirstClose - $posMatchOpen - $this->startDelimiterLength); $tmpSqlMode = ($posFirstClose == $posLastClose) ? $sqlMode : ROW_IMPLODE_ALL; - $evaluated = $this->substitute($match, $foundInStore, $tmpSqlMode); + $evaluated = $this->substitute($match, $foundInStore, $tmpSqlMode, $frCmd); // newline $debugLocal[] = ''; @@ -215,7 +215,7 @@ class Evaluate { $evaluated = Support::encryptDoubleCurlyBraces($evaluated); } else { if (strpos($evaluated, $this->endDelimiter) !== false) { - $evaluated = $this->parse($evaluated, ROW_IMPLODE_ALL, $recursion + 1, $debugLocal, $foundInStore); + $evaluated = $this->parse($evaluated, ROW_IMPLODE_ALL, $recursion + 1, $debugLocal, $foundInStore, $frCmd); } } } @@ -296,6 +296,34 @@ class Evaluate { return DND_DATA_DND_API . '="' . Path::urlApi(API_DRAG_AND_DROP_PHP) . '?s=' . $s . '"'; } + /** Execute qfqFunction and output value. Content is accessible in record store + * + * @param $arrToken + * @param $foundInStore + * @return string + * @throws \CodeException + * @throws \UserFormException + * @throws \UserReportException + * @throws \DbException + */ + private function inlineFunction($arrToken, &$foundInStore): string { + $output = ''; + $token = OnString::trimQuote(trim(implode(' ', $arrToken))); + if ($this->report === null) { + $this->report = new Report(array(), $this); + } + + $this->report->doQfqFunction($token); + $foundInStore = TOKEN_FOUND_AS_COLUMN; + + // Check for => in qfqFunction. If not given then output content. + if (!strpos($token, '=>')) { + $output = $this->store::getVar(COLUMN_FUNCTION_OUTPUT, STORE_RECORD); + } + + return $output; + } + /** * Tries to substitute $token. * Token might be: @@ -315,7 +343,7 @@ class Evaluate { * @throws \UserFormException * @throws \UserReportException */ - public function substitute($token, &$foundInStore = '', $sqlMode = ROW_IMPLODE_ALL) { + public function substitute($token, &$foundInStore = '', $sqlMode = ROW_IMPLODE_ALL, $frCmd = '') { $token = trim($token); $dbIndex = $this->dbIndex; @@ -392,7 +420,11 @@ class Evaluate { } // create baseUrl attribute for tablesorter api $baseUrlAttribute = DATA_TABLESORTER_BASE_URL . "='" . $this->store->getVar(SYSTEM_BASE_URL, STORE_SYSTEM) . "'"; - return ($this->tablesorter->inlineTablesorterView($arrToken[VAR_INDEX_VALUE], $foundInStore)) . $baseUrlAttribute; + return ($this->tablesorter->inlineTablesorterView($arrToken[VAR_INDEX_VALUE], $foundInStore, $frCmd)) . $baseUrlAttribute; + break; + + case COLUMN_FUNCTION: + return ($this->inlineFunction($arrToken, $foundInStore)); break; default: break; diff --git a/extension/Classes/Core/Exception/AbstractException.php b/extension/Classes/Core/Exception/AbstractException.php index 170396df39e7444c181f9da9f33bb90db5392443..84e905b7000f19d9b6c8fdc3ced586d618b8621c 100644 --- a/extension/Classes/Core/Exception/AbstractException.php +++ b/extension/Classes/Core/Exception/AbstractException.php @@ -44,8 +44,8 @@ class AbstractException extends \Exception { public $store = null; - protected $file = ''; - protected $line = ''; +# protected string $file = ''; +# protected int $line = 0; protected $httpStatusCode = '400 Bad Request'; @@ -184,7 +184,7 @@ class AbstractException extends \Exception { 'Debug', EXCEPTION_TABLE_CLASS); $arrDebugHiddenClean = OnArray::htmlentitiesOnArray($arrDebugHidden); - $arrDebugHiddenClean[EXCEPTION_STACKTRACE] = implode($arrTrace, '<br>'); + $arrDebugHiddenClean[EXCEPTION_STACKTRACE] = implode('<br>', $arrTrace); // $arrDebugHiddenClean[EXCEPTION_EDIT_FORM] = implode($arrTrace, '<br>'); $hidden = OnArray::arrayToHtmlTable($arrDebugHiddenClean, 'Details', EXCEPTION_TABLE_CLASS); @@ -195,7 +195,7 @@ class AbstractException extends \Exception { } $qfqLog = Path::absoluteQfqLogFile(); - $arrDebugHidden[EXCEPTION_STACKTRACE] = PHP_EOL . implode($arrTrace, PHP_EOL); + $arrDebugHidden[EXCEPTION_STACKTRACE] = PHP_EOL . implode(PHP_EOL, $arrTrace); $arrLogAll = array_merge($arrMsg, $arrShow, $arrDebugShow, $arrDebugHidden); $logAll = OnArray::arrayToLog($arrLogAll); Logger::logMessage($logAll, $qfqLog); diff --git a/extension/Classes/Core/File.php b/extension/Classes/Core/File.php index 29cb303a716eaf4d183b0816a615c05485657a01..947aad7de715acf91172c45d4ed49e314476e2b4 100644 --- a/extension/Classes/Core/File.php +++ b/extension/Classes/Core/File.php @@ -267,7 +267,11 @@ class File { HelperFile::mkDirParent($fileToWrite); move_uploaded_file($temp[FILES_TMP_NAME], $fileToWrite); + // baseUrl needed for absolute path. Necessary for image upload in T3 V10 or higher + $baseUrl = Store::getVar(SYSTEM_BASE_URL, STORE_SYSTEM, SANITIZE_ALLOW_ALL); + + // Respond to the successful upload with JSON. - return $imageUploadDir . $neededSlash . $changedFileName; + return $baseUrl . $imageUploadDir . $neededSlash . $changedFileName; } } \ No newline at end of file diff --git a/extension/Classes/Core/Form/TypeAhead.php b/extension/Classes/Core/Form/TypeAhead.php index afec74b071e3ae3f0c2be5f128693f79fd70642b..a4020472294a82367f898112a6145e4702f75c99 100644 --- a/extension/Classes/Core/Form/TypeAhead.php +++ b/extension/Classes/Core/Form/TypeAhead.php @@ -70,13 +70,14 @@ class TypeAhead { $sipVars = $sipClass->getVarsFromSip($this->vars[TYPEAHEAD_API_SIP]); // Check for an optional given dbIndex: '[<int>]SELECT ...' - $sql = $sipVars[FE_TYPEAHEAD_SQL]; - if ($sql[0] === '[') { + $sql = $sipVars[FE_TYPEAHEAD_SQL] ?? ''; + if (($sql[0] ?? '') === '[') { $pos = strpos($sql, ']'); $dbIndex = substr($sql, 1, $pos - 1); $sipVars[FE_TYPEAHEAD_SQL] = substr($sql, $pos + 1); } + $this->db = new Database($dbIndex); if (isset($sipVars[FE_TYPEAHEAD_SQL])) { diff --git a/extension/Classes/Core/Helper/DateTime.php b/extension/Classes/Core/Helper/DateTime.php index 97c241d011102894b2a7cbc8bb9a5daaec8d3d20..cdf815b2267b144000210291e5652f0f88788614 100644 --- a/extension/Classes/Core/Helper/DateTime.php +++ b/extension/Classes/Core/Helper/DateTime.php @@ -29,7 +29,7 @@ class DateTime { * @throws \UserFormException * @throws \UserReportException */ - public function buildDateTime(array $formElement, $htmlFormElementName, $value, array &$json, array $formSpec, Store $store, $mode = FORM_LOAD, $wrappedClass = ''): string { + public static function buildDateTime(array $formElement, $htmlFormElementName, $value, array &$json, array $formSpec, Store $store, $mode = FORM_LOAD, $wrappedClass = ''): string { $attribute = ''; $placeholder = ''; $datetimeKeys = array( @@ -102,7 +102,12 @@ class DateTime { // truncate if necessary if ($value != '' && $formElement[FE_MAX_LENGTH] > 0) { if ($formElement[FE_TYPE] === FE_TYPE_TIME && $tableColumnTypes[$formElement[FE_NAME]] === DB_COLUMN_TYPE_DATETIME) { - $value = explode(' ', $value)[1]; + $value = explode(' ', $value); + if (count($value) > 1) { + $value = $value[1]; + } else { + $value = $value[0]; + } } else { $value = substr($value, 0, $formElement[FE_MAX_LENGTH]); } @@ -186,7 +191,7 @@ class DateTime { $attribute .= HelperFormElement::getAttributeList($formElement, [FE_MIN, FE_MAX]); - $json = AbstractBuildForm::getFormElementForJson($htmlFormElementName, $value, $formElement, null, $wrappedClass); + $json = AbstractBuildForm::getFormElementForJson($htmlFormElementName, $value, $formElement, null, null, $wrappedClass); $formElement = HelperFormElement::prepareExtraButton($formElement, true); @@ -207,7 +212,7 @@ class DateTime { * @param $formSpec * @return string */ - private function getDatePickerClassName($formElement, $formSpec): string { + private static function getDatePickerClassName($formElement, $formSpec): string { $datePickerClassName = ''; if ($formElement[FE_DATE_TIME_PICKER_TYPE] === DATE_TIME_PICKER_QFQ) { $datePickerClassName = 'qfq-datepicker'; @@ -225,7 +230,7 @@ class DateTime { * @param $formElement * @return array */ - private function getDefaultDateFormat(&$formElement): array { + private static function getDefaultDateFormat(&$formElement): array { $defaultDateFormat = explode(' ', $formElement[FE_DATE_FORMAT], 2); $defaultFormat = array(); if (isset($defaultDateFormat[1])) { @@ -248,7 +253,7 @@ class DateTime { * @param $value * @return false|mixed|string */ - private function formatValueForBrowser($feType, $value) { + private static function formatValueForBrowser($feType, $value) { $dateOldFormat = date_create($value); if ($feType === FE_TYPE_DATE) { $value = date_format($dateOldFormat, "Y-m-d"); @@ -262,7 +267,7 @@ class DateTime { * @param $feType * @return string */ - private function getDateTimeBrowserType($feType): string { + private static function getDateTimeBrowserType($feType): string { if ($feType == FE_TYPE_DATE) { $type = 'date'; } elseif ($feType == FE_TYPE_TIME) { @@ -276,9 +281,9 @@ class DateTime { /** * @param $defaultFormat * @param $feType - * @return mixed + * @return array|string */ - private function getDateTimeFormatQfq($defaultFormat, $feType) { + private static function getDateTimeFormatQfq($defaultFormat, $feType) { switch ($defaultFormat['date']) { case FORMAT_DATE_INTERNATIONAL: case FORMAT_DATE_INTERNATIONAL_QFQ: @@ -304,7 +309,7 @@ class DateTime { * @param $feShowSeconds * @return mixed */ - private function getTimeFormat($defaultFormat, $feDateTimePickerType, $feShowSeconds) { + private static function getTimeFormat($defaultFormat, $feDateTimePickerType, $feShowSeconds) { if ($defaultFormat['timeParts'][0] === 'HH' || $defaultFormat['timeParts'][0] === 'hh') { $defaultFormat['date'] = $defaultFormat['timeParts'][0] . ':' . $defaultFormat['timeParts'][1]; } else if ($feDateTimePickerType === DATE_TIME_PICKER_QFQ) { @@ -323,7 +328,7 @@ class DateTime { * @param $enabledDays * @return false|string */ - private function getDateTimePickerDisabledDays($enabledDays) { + private static function getDateTimePickerDisabledDays($enabledDays) { // convert enabled days from datetimepicker user input daysOfWeekEnabled to disabled days $enabledDays = explode(',', $enabledDays); $disabledDays = ''; @@ -335,7 +340,7 @@ class DateTime { $flagDayPoint = true; } } - if ($flagDayPoint == false) { + if (!$flagDayPoint) { $disabledDays .= $i . ','; } } @@ -357,7 +362,7 @@ class DateTime { * @return void * @throws \CodeException */ - private function setDateTimePickerAttributes($formElement, $defaultDateFormat, $dateTimeKeys, $dateTimeAttributes, &$attribute) { + private static function setDateTimePickerAttributes($formElement, $defaultDateFormat, $dateTimeKeys, $dateTimeAttributes, &$attribute) { $keyCount = 0; foreach ($dateTimeKeys as $key) { if (isset($formElement[$key]) && $formElement[$key] != "" && $key != FE_DATE_FORMAT) { @@ -373,7 +378,7 @@ class DateTime { * @param $formElement * @return void */ - private function setNoQfqMinMax(&$formElement) { + private static function setNoQfqMinMax(&$formElement) { if ($formElement[FE_TYPE] == FE_TYPE_DATE) { $dateMinMaxFormat = "Y-m-d"; } else { @@ -400,7 +405,7 @@ class DateTime { * @return string - checked datetime string * @throws \UserFormException */ - public static function doDateTime(array &$formElement, $value) { + public static function doDateTime(array &$formElement, $value): string { // check for browser dateTimePickerType and adjust value $typeBrowser = false; diff --git a/extension/Classes/Core/Helper/HelperFormElement.php b/extension/Classes/Core/Helper/HelperFormElement.php index fc7c05b9069465c31fb24e95c8008f1c4bae497e..e2a485b63559a18842c492cb1fc40f092dd5044a 100644 --- a/extension/Classes/Core/Helper/HelperFormElement.php +++ b/extension/Classes/Core/Helper/HelperFormElement.php @@ -465,14 +465,14 @@ EOF; } } // PASSWORD -// if (!$skip && isset($formElement[FE_INPUT_EXTRA_BUTTON_PASSWORD])) { +// if (!$skip && isset($formElement[FE_INPUT_EXTRA_BUTTON_PASSWORD])) { if (!$skip && HelperFormElement::booleParameter($formElement[FE_INPUT_EXTRA_BUTTON_PASSWORD] ?? '-')) { $formElement[FE_TYPE] = 'password'; $extraButton .= <<<EOF <button class="btn btn-info" - onclick="$('#$id').attr('type',$('#$id').attr('type')==='password' ? 'text': 'password')"> + onclick="$('#$id').toggleClass('qfq-password')"> <span class="glyphicon glyphicon-eye-open" aria-hidden="true"></span> </button> EOF; diff --git a/extension/Classes/Core/Helper/OnString.php b/extension/Classes/Core/Helper/OnString.php index a56742fde221b12e14be8f39b9fa83d7b0717a63..4a22778881710a77760cad737a20bc87086a4bdb 100644 --- a/extension/Classes/Core/Helper/OnString.php +++ b/extension/Classes/Core/Helper/OnString.php @@ -53,9 +53,8 @@ class OnString { * @param $content * @return array|string|string[]|null */ - public static function strReplaceFirst($from, $to, $content) - { - $from = '/'.preg_quote($from, '/').'/'; + public static function strReplaceFirst($from, $to, $content) { + $from = '/' . preg_quote($from, '/') . '/'; return preg_replace($from, $to, $content, 1); } @@ -504,8 +503,8 @@ class OnString { break; // Author: Enis Nuredini case TOKEN_ESCAPE_ENCRYPT: - if(isset($encryptionToken) && $encryptionToken !== '') { - if(EncryptDecrypt::checkForValidEncryptMethod($encryptionToken)) { + if (isset($encryptionToken) && $encryptionToken !== '') { + if (EncryptDecrypt::checkForValidEncryptMethod($encryptionToken)) { $encryptionMethod = $encryptionToken; } else { throw new \UserReportException("Invalid encryption method used in variable", ERROR_VARIABLE_INVALID_ENCRYPTION_METHOD); @@ -791,5 +790,80 @@ class OnString { return $params; } + /** + * Very specific function: converts $arr to JSON and checks if it exceeds $max. + * If it exceeds, search for the biggest element, replace the content by ERROR_MSG_TOO_BIG. + * Repeat that, until it's below $max + * + * @param array $arr + * @param $max + * @return array|mixed + */ + + public static function limitSizeJsonEncode(array $arr, $currentLength, $max) { + $maxValue = 0; + $maxKey = ''; + $origArrSize = 0; + $replaceLength = strlen(ERROR_MSG_TOO_BIG); + + // Stop searching + if ($max == 0 || empty($arr)) { + return array(); + } + + // Search biggest element. + foreach ($arr as $key => $value) { + + if (is_array($value)) { + // Radios might be delivered as Array: pseudo flatten by misusing json_encode() + $value = json_encode($value); + } + + $len = strlen($value); +// $origArrSize+=$len; + if ($len > $maxValue) { + $maxValue = strlen($value); + $maxKey = $key; + } + } + + // Found a biggest element + if ($maxKey == '') { + throw new \UserFormException(json_encode([ERROR_MESSAGE_TO_USER => "No biggest element found", + ERROR_MESSAGE_TO_DEVELOPER => "Strange: element is to big ($origArrSize) but found no 'biggest' element."]), ERROR_MISSING_OPEN_DELIMITER); + } else { + // Copy all elements, replace the biggest + foreach ($arr as $key => $value) { + if ($key == $maxKey) { + + // Check that the payload is bigger than ERROR_MSG_TOO_BIG + if (!is_array($arr[$key]) && (strlen($arr[$key]) < $replaceLength)) { + throw new \UserFormException(json_encode([ERROR_MESSAGE_TO_USER => "Can't shrink array", + ERROR_MESSAGE_TO_DEVELOPER => "Makes no sense: the replacement[$replaceLength] is bigger than the payload[" . strlen($arr[$key]) . ']']), ERROR_MISSING_OPEN_DELIMITER); + } + $arrNew[$key] = ERROR_MSG_TOO_BIG; + } else { + $arrNew[$key] = $value; + } + } + + $newLen = strlen(json_encode($arrNew, JSON_UNESCAPED_UNICODE)); + + // Final Size still too big? + if ($newLen > $maxValue) { + + // Detect infinite loop. + if ($newLen >= $currentLength) { + throw new \UserFormException(json_encode([ERROR_MESSAGE_TO_USER => "Can't shrink array", + ERROR_MESSAGE_TO_DEVELOPER => "max: $max, current: " . $currentLength . ", new reduced: " . $newLen]), ERROR_MISSING_OPEN_DELIMITER); + } else { + // Dive deeper to replace the next biggest element. + $arrNew = $self::limitSizeJsonEncode($arrNew, $newLen, $maxValue); + } + } + } + + return $arrNew; + } } diff --git a/extension/Classes/Core/Helper/Path.php b/extension/Classes/Core/Helper/Path.php index a474373efae83e87293508986d8a2027c407ddc4..4c1acc4ec29da3da4e01da3c2c3763d35320a6a7 100644 --- a/extension/Classes/Core/Helper/Path.php +++ b/extension/Classes/Core/Helper/Path.php @@ -493,10 +493,16 @@ class Path { public static function findAbsoluteApp() { // look for typo3conf directory $absoluteApp = self::realpath(self::join(__DIR__, '../../../../../../')); - if (!file_exists(self::join($absoluteApp, self::APP_TO_TYPO3_CONF))) { - Thrower::userFormException('App path seems to be wrong: Directory "typo3conf" not found in app path.', - " Current app path: $absoluteApp .", - " In unit tests this can be manually set using Path::setAbsoluteApp() before the path is accessed."); + + if (!defined('PHPUNIT_QFQ')) { + if (!file_exists(self::join($absoluteApp, self::APP_TO_TYPO3_CONF))) { + Thrower::userFormException('App path seems to be wrong: Directory "typo3conf" not found in app path.' . + " Current app path: $absoluteApp .", + " In unit tests this can be manually set using Path::setAbsoluteApp() before the path is accessed."); + } + } else { + //TODO: CR: Keine Idee ob das hier Sinn macht + Path::setAbsoluteApp('typo3conf'); } self::setAbsoluteApp($absoluteApp); } diff --git a/extension/Classes/Core/Helper/Sanitize.php b/extension/Classes/Core/Helper/Sanitize.php index 92ca819e2d70f96a19f35ef9765e33e7be0231f1..85d4c30a8e49be8496a0f9105d225fe05d3d3c5d 100644 --- a/extension/Classes/Core/Helper/Sanitize.php +++ b/extension/Classes/Core/Helper/Sanitize.php @@ -294,7 +294,7 @@ class Sanitize { return; } - if (ctype_digit($_GET[$key])) { + if (ctype_digit((string)$_GET[$key])) { return; } diff --git a/extension/Classes/Core/Helper/Support.php b/extension/Classes/Core/Helper/Support.php index 4d0ab356adc07e1d1a36c97eec2e190c81dff543..5ee87e5861990b982ca96231c254e3786d26d3df 100644 --- a/extension/Classes/Core/Helper/Support.php +++ b/extension/Classes/Core/Helper/Support.php @@ -84,13 +84,26 @@ class Support { $arr = KeyValueStringParser::parse($uri, '=', '&'); $type = self::$store->getVar(TYPO3_PAGE_TYPE, STORE_TYPO3); $language = self::$store->getVar(TYPO3_PAGE_LANGUAGE, STORE_TYPO3); + $questionMarkNeeded = true; + if (strpos($uri, '?') !== false && T3Handler::typo3VersionGreaterEqual10()) { + $questionMarkNeeded = false; + } if ($type != 0 && $type !== false && !isset($arr[CLIENT_PAGE_TYPE])) { - $uri .= '&type=' . $type; + if ($questionMarkNeeded) { + $uri .= '?type=' . $type; + $questionMarkNeeded = false; + } else { + $uri .= '&type=' . $type; + } } if ($language != 0 && $language !== false && !isset($arr[CLIENT_PAGE_LANGUAGE])) { - $uri .= '&L=' . $language; + if ($questionMarkNeeded) { + $uri .= '?L=' . $language; + } else { + $uri .= '&L=' . $language; + } } return $uri; @@ -160,8 +173,8 @@ class Support { /** * Extract Tag(s) from $tag (eg: <div><input class="form-control">, might contain further attributes) and wrap it - * around - * $value. If $flagOmitEmpty==true && $value=='': return ''. + * around $value. + * If $flagOmitEmpty==true && $value=='': return ''. * * @param string $tag * @param string $value @@ -807,7 +820,7 @@ class Support { */ public static function mergeUrlComponents($host, $hostOrPath, $query) { $url = ''; - + $pageSlug = ''; if ($host != '' && substr($host, -1, 1) != '/') { $host .= '/'; @@ -823,17 +836,33 @@ class Support { if (substr($query, 0, 9) == 'index.php') { $query = substr($query, 9); + } else if (!defined('PHPUNIT_QFQ')) { //ToDo adjust unit test for t3 v10 + $countHost = strlen($url); + $queryTmp = substr($query, $countHost); + $pageSlug = explode('?', $queryTmp, 2); + $pageSlug = $pageSlug[0] ?? ''; + if (!empty($pageSlug)) { + $pageSlugCount = strlen($pageSlug); + $finalCount = strlen($url) + $pageSlugCount; + $query = substr($query, $finalCount); + } } + //T3 v10 link is constructed differently if (false !== strpos(substr($query, 1), '?')) { throw new \CodeException('Found a "?" after the beginning of the query - this is forbidden', ERROR_BROKEN_PARAMETER); } if ($query != '') { - $url = $url . '?' . $query; - $url = str_replace('??', '?', $url); + if(T3Handler::typo3VersionGreaterEqual10()){ + $url = $url . $pageSlug . '?' . $query; + }else{ + $url = $url . '?' . $query; + } + $url = str_replace('??', '?', $url ); } - + //Variante 1(T3 < v10): index.php?s=wt5reit45itt + //Variante 2(T3 >= v10): ?s=grksan40nag04n return $url; } diff --git a/extension/Classes/Core/QuickFormQuery.php b/extension/Classes/Core/QuickFormQuery.php index 2555cfedd68e2f53a769d1b18a32adb0b822703a..643e13fc8db07b3336cc5597b2aa29a1b2de934e 100644 --- a/extension/Classes/Core/QuickFormQuery.php +++ b/extension/Classes/Core/QuickFormQuery.php @@ -21,6 +21,7 @@ use IMATHUZH\Qfq\Core\Helper\HelperFormElement; use IMATHUZH\Qfq\Core\Helper\KeyValueStringParser; use IMATHUZH\Qfq\Core\Helper\Logger; use IMATHUZH\Qfq\Core\Helper\OnArray; +use IMATHUZH\Qfq\Core\Helper\OnString; use IMATHUZH\Qfq\Core\Helper\Path; use IMATHUZH\Qfq\Core\Helper\Support; use IMATHUZH\Qfq\Core\Report\Monitor; @@ -31,6 +32,7 @@ use IMATHUZH\Qfq\Core\Store\Session; use IMATHUZH\Qfq\Core\Store\Sip; use IMATHUZH\Qfq\Core\Store\Store; use IMATHUZH\Qfq\Core\Store\T3Info; +use IMATHUZH\Qfq\Core\Typo3\T3Handler; /** * Class Qfq @@ -173,6 +175,18 @@ class QuickFormQuery { $this->dbIndexData = $this->store->getVar(SYSTEM_DB_INDEX_DATA, STORE_SYSTEM); $this->dbIndexQfq = $this->store->getVar(SYSTEM_DB_INDEX_QFQ, STORE_SYSTEM); + $this->dbIndexT3 = $this->dbIndexQfq; + + if (!defined('PHPUNIT_QFQ')) { + $t3DbConfig = T3Handler::getTypo3DbConfig($this->dbIndexData, $this->dbIndexQfq, $this->dbIndexT3); + } + // Create Typo3 db object if config information exist. In case of api, it doesn't exist. + if (count($t3DbConfig) > 1 && $this->dbIndexT3 !== 0) { + $this->dbArray[$this->dbIndexT3] = new Database($this->dbIndexT3, $t3DbConfig); + } else { + // Fallback to qfq user credentials. These are used usually for typo3 db. + $this->dbArray[$this->dbIndexT3] = new Database($this->dbIndexQfq); + } $this->dbArray[$this->dbIndexData] = new Database($this->dbIndexData); @@ -186,7 +200,7 @@ class QuickFormQuery { \UserReportException::$report_bodytext = $t3data[T3DATA_BODYTEXT]; \UserReportException::$report_header = $t3data[T3DATA_HEADER]; \UserReportException::$report_pathFileName = $reportPathFileNameFull; - \UserReportException::$report_db = $this->dbArray[$this->dbIndexData]; + \UserReportException::$report_db = $this->dbArray[$this->dbIndexT3]; $this->evaluate = new Evaluate($this->store, $this->dbArray[$this->dbIndexData]); @@ -208,7 +222,7 @@ class QuickFormQuery { // Create report file if file keyword not found (and auto export is enabled in qfq settings) if ($reportPathFileNameFull === null && $t3data[T3DATA_UID] !== 0 && strtolower($this->store->getVar(SYSTEM_REPORT_AS_FILE_AUTO_EXPORT, STORE_SYSTEM)) === 'yes') { - $reportPathFileNameFull = ReportAsFile::create_file_from_ttContent($t3data[T3DATA_UID], $this->dbArray[$this->dbIndexData]); + $reportPathFileNameFull = ReportAsFile::create_file_from_ttContent($t3data[T3DATA_UID], $this->dbArray[$this->dbIndexT3]); } // Save pathFileName for use in inline editor @@ -301,6 +315,15 @@ class QuickFormQuery { $forwardPage = $this->formSpec[F_FORWARD_PAGE]; + //Language handling for T3 V10 and above + if (OnString::strStartsWith($forwardPage, '/') && T3Handler::typo3VersionGreaterEqual10()) { + $languagePath = Store::getVar(TYPO3_PAGE_LANGUAGE_PATH, STORE_TYPO3); + } + + if (isset($languagePath) && $languagePath !== '') { + $forwardPage = '/' . $languagePath . $forwardPage; + } + switch ($this->formSpec[F_FORWARD_MODE]) { case F_FORWARD_MODE_URL_SIP: $forwardPage = store::getSipInstance()->queryStringToSip($forwardPage, RETURN_URL); @@ -549,7 +572,7 @@ class QuickFormQuery { } //Change recordId from Multiform to 0 - No row exception possible - if ($this->formSpec[F_MULTI_MODE] !== 'none') { + if ($this->formSpec[F_MULTI_MODE] !== 'none' && isset($this->formSpec[F_MULTI_MODE])) { $recordId = 0; $this->store->setVar(SIP_RECORD_ID, $recordId, STORE_SIP); } @@ -606,6 +629,9 @@ class QuickFormQuery { $tableDefinition = $this->dbArray[$this->dbIndexData]->getTableDefinition($this->formSpec[F_TABLE_NAME]); $this->store->fillStoreTableDefaultColumnType($tableDefinition); + // Check if empty columns exists and set default from fe or database + $this->setFeDefaultValues(); + // Check if the defined column primary key exist. if ($this->store::getVar($this->formSpec[F_PRIMARY_KEY], STORE_TABLE_COLUMN_TYPES) === false) { throw new \UserFormException("Primary Key '" . $this->formSpec[F_PRIMARY_KEY] . "' not found in table " . $this->formSpec[F_TABLE_NAME], ERROR_INVALID_OR_MISSING_PARAMETER); @@ -662,15 +688,17 @@ class QuickFormQuery { break; case FORM_SAVE: - $this->logFormSubmitRequest(); - + $formSubmitLogId = $this->logFormSubmitRequest(); $recordId = $this->store->getVar(SIP_RECORD_ID, STORE_SIP . STORE_TYPO3); // SAVE $save = new Save($this->formSpec, $this->feSpecAction, $this->feSpecNative, $this->feSpecNativeRaw); - $rc = $save->process($recordId); + if ($recordId == 0 && $formSubmitLogId != 0) { + // Update recordId from formSubmitLog + $this->updateLogFormSubmitRequest($formSubmitLogId, $rc); + } if ($formMode == FORM_REST) { $data = $this->doRestPostPut($rc); $flagApiStructureReGroup = false; @@ -883,17 +911,28 @@ class QuickFormQuery { } /** + * Logs the form submit to table FormSubmitLog. + * * @throws \CodeException * @throws \DbException * @throws \UserFormException */ private function logFormSubmitRequest() { + $tableName = $this->formSpec[F_TABLE_NAME]; $formSubmitLogMode = $this->formSpec[F_FORM_SUBMIT_LOG_MODE] ?? $this->store->getVar(SYSTEM_FORM_SUBMIT_LOG_MODE, STORE_SYSTEM, SANITIZE_ALLOW_ALNUMX); if ($formSubmitLogMode === FORM_SUBMIT_LOG_MODE_NONE) { - return; + return 0; + } + + // Log is ignored in some special cases while mode LOG_MODIFY + if ($formSubmitLogMode === FORM_SUBMIT_LOG_MODE_MODIFY) { + // If table FormSubmitLog and given statement INSERT/UPDATE and table Dirty and given statement INSERT/UPDATE/DELETE + if ($tableName === 'FormSubmitLog' || $tableName === 'Dirty') { + return 0; + } } $formData = $_POST; @@ -923,13 +962,20 @@ class QuickFormQuery { } } - $formData = json_encode($formData, JSON_UNESCAPED_UNICODE); + $formDataJson = json_encode($formData, JSON_UNESCAPED_UNICODE); + $currentLength = strlen($formDataJson); + // FormSubmitLog.formData (TEXT) = 65535 + if ($currentLength > LOG_MAX_FORMDATA) { + // Oops, FormSubmitLog can only store LOG_MAX_FORMDATA. + // JSON will be shrinked: remove elements as long as it is too big, return rest. + $formDataJson = json_encode(OnString::limitSizeJsonEncode($formData, $currentLength, LOG_MAX_FORMDATA), JSON_UNESCAPED_UNICODE); + } $sql = "INSERT INTO `FormSubmitLog` (`formData`, `sipData`, `clientIp`, `feUser`, `userAgent`, `formId`, `formName`, `recordId`, `pageId`, `sessionId`, `created`)" . "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())"; - $clientIp = $_SERVER[CLIENT_REMOTE_ADDRESS] ?? ''; - $userAgent = $_SERVER[CLIENT_HTTP_USER_AGENT] ?? ''; + $clientIp = $this->store->getVar(CLIENT_REMOTE_ADDRESS, STORE_CLIENT . STORE_EMPTY); + $userAgent = $this->store->getVar(CLIENT_HTTP_USER_AGENT, STORE_CLIENT . STORE_EMPTY); $sipData = json_encode($this->store->getStore(STORE_SIP), JSON_UNESCAPED_UNICODE); $formId = $this->formSpec[F_ID]; $formName = $this->formSpec[F_NAME]; @@ -937,7 +983,25 @@ class QuickFormQuery { $pageId = $this->store->getVar(TYPO3_PAGE_ID, STORE_TYPO3, SANITIZE_ALLOW_ALNUMX); $sessionId = session_id(); - $params = [$formData, $sipData, $clientIp, $feUser, $userAgent, $formId, $formName, $recordId, $pageId, $sessionId]; + $params = [$formDataJson, $sipData, $clientIp, $feUser, $userAgent, $formId, $formName, $recordId, $pageId, $sessionId]; + + $this->dbArray[$this->dbIndexQfq]->sql($sql, ROW_REGULAR, $params); + $formSubmitLogId = $this->dbArray[$this->dbIndexQfq]->getLastInsertId(); + $this->store::setVar(EXTRA_FORM_SUBMIT_LOG_ID, $formSubmitLogId, STORE_EXTRA); + + return $formSubmitLogId; + } + + /** Update last FormSubmitLog record with new inserted recordId + * @param $formSubmitLogId + * @param $recordId + * @throws \CodeException + * @throws \DbException + * @throws \UserFormException + */ + private function updateLogFormSubmitRequest($formSubmitLogId, $recordId) { + $sql = "UPDATE `FormSubmitLog` SET `recordId` = ? WHERE `id` = ?"; + $params = [$recordId, $formSubmitLogId]; $this->dbArray[$this->dbIndexQfq]->sql($sql, ROW_REGULAR, $params); } @@ -1172,9 +1236,14 @@ class QuickFormQuery { // In case $form[F_REST_TOKEN] is a query which results to an empty answer; every token will fail. $flagRestToken = !empty($form[F_REST_TOKEN]); + // Needed in case of FORM_SAVE, when title should be updated + $unevaluatedTitle = $form[F_TITLE]; + // Evaluate all fields $formSpec = $this->evaluate->parseArray($form); + $formSpec[F_UNEVALUATED_TITLE] = $unevaluatedTitle; + // If it is empty, set it to true to force the TOKEN check (which will always fail) if ($flagRestToken && $form[F_REST_TOKEN] == '') { $form[F_REST_TOKEN] = true; @@ -1809,7 +1878,16 @@ class QuickFormQuery { if ($this->t3data[T3DATA_REPORT_PATH_FILENAME] !== '') { $bodytext = $this->t3data[T3DATA_BODYTEXT_RAW]; } - $html .= $this->buildInlineReport($this->t3data[T3DATA_UID] ?? null, $this->t3data[T3DATA_REPORT_PATH_FILENAME] ?? null, $this->dbArray[$this->dbIndexData], $bodytext ?? null); + + $tooltip = 'tt-content: uid=' . $this->t3data['uid'] . ', header=' . $this->t3data['header']; + + # Define inline editor theme + $systemInlineReportDarkTheme = $this->store->getVar(SYSTEM_EDIT_INLINE_REPORT_DARK_THEME, STORE_SYSTEM, SANITIZE_ALLOW_ALLBUT); + $editorTheme = $systemInlineReportDarkTheme ? 'monokai' : 'default'; + + $html .= $this->buildInlineReport($this->t3data[T3DATA_UID] ?? null, + $this->t3data[T3DATA_REPORT_PATH_FILENAME] ?? null, $this->dbArray[$this->dbIndexT3], + $bodytext ?? null, null, null, $tooltip, $editorTheme); } $html .= $report->process($this->t3data[T3DATA_BODYTEXT]); @@ -1827,14 +1905,17 @@ class QuickFormQuery { * @throws \CodeException * @throws \UserFormException */ - public static function buildInlineReport(?int $uid, ?string $reportPathFileNameFull, Database $db, ?string $bodytext = '', ?string $btnClass = 'btn-xs btn-default', ?string $buttonText = ''): string { + public static function buildInlineReport(?int $uid, ?string $reportPathFileNameFull, Database $db, ?string $bodytext = '', ?string $btnClass = 'btn-xs btn-default', ?string $buttonText = '', ?string $btnTooltip = '', string $editorTheme = 'default'): string { if ($uid === null) { return ''; } + $btnClass = $btnClass ?? 'btn-xs btn-default'; $t3vars = T3Info::getVars(); + $baseUrl = Store::getVar(SYSTEM_BASE_URL, STORE_SYSTEM); $icon = Support::renderGlyphIcon('glyphicon-edit'); $showFormJs = '$("#tt-content-edit-' . $uid . '").toggleClass("hidden")'; - $toggleBtn = Support::wrapTag("<a class='targetEditReport $btnClass' onclick='$showFormJs' style='float:right;'>", $icon . ' ' . $buttonText); + $toggleBtn = Support::wrapTag("<a class='targetEditReport $btnClass' data-base-url='$baseUrl' onclick='$showFormJs' style='float:right;' title='" + . htmlentities($btnTooltip, ENT_QUOTES) . "'>", $icon . ' ' . $buttonText); $ttContentParam = $db->getBodyText($uid, false); $pageParam = $db->getPageParam($ttContentParam[COLUMN_PID], false); @@ -1853,7 +1934,7 @@ class QuickFormQuery { Support::doAttribute('title', 'Save & Reload'); $saveBtnIcon = Support::renderGlyphIcon(GLYPH_ICON_CHECK); $saveBtn = Support::wrapTag("<button $saveBtnAttributes>", $saveBtnIcon); - $saveBtn .= '<div style="clear:both;"></div>'; + $saveBtn .= '<div style="clear:both;"></div>'; $header = "Page: $pageTitle Content ID: $uid<br>$reportPathFileNameFullHtml"; $headerInput = '<div style="display: flex;"> <div style="display: flex; flex-direction: column; margin-right: 10px; width: 50%;"> @@ -1873,7 +1954,7 @@ class QuickFormQuery { } $ttContentCode = Support::htmlEntityEncodeDecode(MODE_ENCODE, $bodytext); - $json = json_encode(array('mode' => 'text/x-sql', 'lineNumbers' => true, 'lineWrapping' => true), JSON_UNESCAPED_SLASHES); + $json = json_encode(array('mode' => 'text/x-sql', 'lineNumbers' => true, 'lineWrapping' => true, 'theme' => $editorTheme), JSON_UNESCAPED_SLASHES); $codeBoxAttributes = Support::doAttribute('style', "width:100%;") . Support::doAttribute('id', "tt-content-code-$uid") . Support::doAttribute('name', REPORT_INLINE_BODYTEXT) . @@ -1920,9 +2001,9 @@ class QuickFormQuery { // $bodytextNew = Support::htmlEntityEncodeDecode(MODE_DECODE, $_POST[REPORT_INLINE_BODYTEXT]); if (intval($isFile) === 1) { - ReportAsFile::write_file_uid($uid, $bodytextNew, $this->dbArray[$this->dbIndexData], $headerNew, $subheaderNew); + ReportAsFile::write_file_uid($uid, $bodytextNew, $this->dbArray[$this->dbIndexT3], $headerNew, $subheaderNew); } else { - ReportAsFile::write_tt_content($uid, $this->dbArray[$this->dbIndexData], $bodytextNew, $headerNew, $subheaderNew); + ReportAsFile::write_tt_content($uid, $this->dbArray[$this->dbIndexT3], $bodytextNew, $headerNew, $subheaderNew); } $this->formSpec[F_FORWARD_MODE] = 'auto'; } @@ -2449,7 +2530,7 @@ EOF; $uid = $this->store::getVar(T3DATA_UID, STORE_SIP); $beUser = $this->store::getVar(TYPO3_BE_USER, STORE_SIP); $beUserUid = $this->store::getVar(TYPO3_BE_USER_UID, STORE_SIP); - $ttContentOld = $this->dbArray[$this->dbIndexData]->getBodyText($uid, false); + $ttContentOld = $this->dbArray[$this->dbIndexT3]->getBodyText($uid, false); $isFile = $this->store->getVar(REPORT_SAVE_FILE, STORE_SIP . STORE_ZERO, SANITIZE_ALLOW_DIGIT); // If some of the following values are not given. @@ -2490,8 +2571,41 @@ EOF; // Only save history if changes are given if (!empty($payload)) { - $this->dbArray[$this->dbIndexData]->setHistoryRecord($dataHistory); + $this->dbArray[$this->dbIndexT3]->setHistoryRecord($dataHistory); } } + /** + * Get and set default values for FEs from FE.parameter.defaultValue or from db default value. + * + * @throws \CodeException + * @throws \UserFormException + * @throws \DbException + * @throws \UserReportException + */ + + private function setFeDefaultValues(): void { + + $feValues = $this->store->getStore(STORE_FORM); + $dbDefaultValues = $this->store->getStore(STORE_TABLE_DEFAULT); + + // Check each FE + foreach ($this->feSpecNative as $fe) { + $feName = $fe[FE_NAME]; + // If $feValues[$feName] if given and empty + if (isset($feValues[$feName]) && ($feValues[$feName] === '' || $feValues[$feName] === 0)) { + if (isset($fe[FE_DEFAULT_VALUE])) { + // Is there a custom default value configured (prio over DB scheme default value) + $defaultValue = $fe[FE_DEFAULT_VALUE]; + } elseif (isset($dbDefaultValues[$feName]) && $dbDefaultValues[$feName] !== null) { + // Take DB scheme default value + $defaultValue = $dbDefaultValues[$feName]; + } else { + // no default: skip + continue; + } + $this->store->setVar($feName, $defaultValue, STORE_FORM); + } + } + } } \ No newline at end of file diff --git a/extension/Classes/Core/Report/Link.php b/extension/Classes/Core/Report/Link.php index 869c0851866f2d1afddf67cac569a8fd5b11def4..ae43e505a8165552dfeaf19aa0d30261f4fecab8 100644 --- a/extension/Classes/Core/Report/Link.php +++ b/extension/Classes/Core/Report/Link.php @@ -231,6 +231,8 @@ class Link { $this->dbArray[$this->dbIndexData] = new Database($this->dbIndexData); if ($this->dbIndexData != $this->dbIndexQfq) { $this->dbArray[$this->dbIndexQfq] = new Database($this->dbIndexQfq); + } else { + $this->dbArray[$this->dbIndexQfq] = $this->dbArray[$this->dbIndexData]; } // $this->cssLinkClassInternal = $this->store->getVar(SYSTEM_CSS_LINK_CLASS_INTERNAL, STORE_SYSTEM); @@ -395,8 +397,12 @@ class Link { throw new \UserReportException("Missing or invalid uid value: $uid ", ERROR_MISSING_VALUE); } + # Define inline editor theme + $systemInlineReportDarkTheme = $this->store->getVar(SYSTEM_EDIT_INLINE_REPORT_DARK_THEME, STORE_SYSTEM, SANITIZE_ALLOW_ALLBUT); + $editorTheme = $systemInlineReportDarkTheme ? 'monokai' : 'default'; + // Build inline report editing - return QuickFormQuery::buildInlineReport($uid, null, $this->dbArray[$this->dbIndexData], null, $param[TOKEN_BOOTSTRAP_BUTTON] ?? '', $param[TOKEN_TEXT] ?? ''); + return QuickFormQuery::buildInlineReport($uid, null, $this->dbArray[$this->dbIndexQfq], null, $param[TOKEN_BOOTSTRAP_BUTTON] ?? '', $param[TOKEN_TEXT] ?? '', $param[TOKEN_TOOL_TIP] ?? '', $editorTheme); } /** @@ -587,6 +593,10 @@ class Link { $this->store->setVar(SYSTEM_DOWNLOAD_POPUP, DOWNLOAD_POPUP_REQUEST, STORE_SYSTEM); } + if (isset($tokenGiven[TOKEN_DELETE]) && $tokenGiven[TOKEN_DELETE] === true ) { + $vars[NAME_BOOTSTRAP_BUTTON] = "btn btn-default"; + } + if (($vars[NAME_DROPDOWN] ?? '') == '1') { $ul = ''; // Render menu items only if the menu is active. @@ -1442,9 +1452,11 @@ class Link { $timeout = ($arr[QUESTION_INDEX_TIMEOUT] === '') ? '0' : $arr[QUESTION_INDEX_TIMEOUT] * 1000; $flagModalStatus = ($arr[QUESTION_INDEX_FLAG_MODAL] === '') ? '1' : $arr[QUESTION_INDEX_FLAG_MODAL]; $flagModal = ($flagModalStatus === "1") ? 'true' : 'false'; + // Check if affected button has class 'record-delete' and prevent it being triggered with enter press if its not focused + $preventScript = ($vars[NAME_LINK_CLASS] === 'record-delete') ? 'if (event.detail === 0 && event.target !== document.activeElement) { event.preventDefault(); return false; }' : ''; $js = <<<EOF -var alert = new QfqNS.Alert({ message: '$text', type: '$level', modal: $flagModal, timeout: $timeout, buttons: [ +$preventScript var alert = new QfqNS.Alert({ message: '$text', type: '$level', modal: $flagModal, timeout: $timeout, buttons: [ { label: '$ok', eventName: 'ok' } $cancel ] } ); diff --git a/extension/Classes/Core/Report/Report.php b/extension/Classes/Core/Report/Report.php index 371be74b879afaa1d19747e44b962776dcd7da85..c5dad53c847bcc7ace1a5be6d0b1507dcf12cfe1 100644 --- a/extension/Classes/Core/Report/Report.php +++ b/extension/Classes/Core/Report/Report.php @@ -36,6 +36,7 @@ use IMATHUZH\Qfq\Core\Helper\Sanitize; use IMATHUZH\Qfq\Core\Helper\Support; use IMATHUZH\Qfq\Core\Store\Sip; use IMATHUZH\Qfq\Core\Store\Store; +use IMATHUZH\Qfq\Core\Typo3\T3Handler; const DEFAULT_QUESTION = 'question'; const DEFAULT_ICON = 'icon'; @@ -44,6 +45,7 @@ const DEFAULT_BOOTSTRAP_BUTTON = 'bootstrapButton'; /** * Class Report * @package qfq + * */ class Report { @@ -170,11 +172,13 @@ class Report { // Default should already set in QuickFormQuery() Constructor $this->dbIndexData = $this->store->getVar(TOKEN_DB_INDEX, STORE_TYPO3); + $this->dbIndexQfq = $this->store->getVar(SYSTEM_DB_INDEX_QFQ, STORE_SYSTEM); if ($this->dbIndexData === false) { $this->dbIndexData = DB_INDEX_DEFAULT; } $this->dbArr[$this->dbIndexData] = new Database($this->dbIndexData); + $this->dbArr[$this->dbIndexQfq] = new Database($this->dbIndexQfq); $this->variables = new Variables($evaluate, $t3data["uid"]); $this->link = new Link($this->sip, $this->dbIndexData, $phpUnit); @@ -228,7 +232,7 @@ class Report { * @throws \UserFormException * @throws \UserReportException */ - public function process($bodyText) { + public function process($bodyText): string { //phpUnit Test: clean environment $this->frArray = array(); @@ -349,7 +353,7 @@ class Report { * @param string $clause : the sort argument 0 ASC, 1 ASC... according to the number of columns * @param bool|true $ascending */ - private function sortIndexArray(array &$ary, $clause, $ascending = true) { + private function sortIndexArray(array &$ary, $clause, $ascending = true): void { $clause = str_ireplace('order by', '', $clause); $clause = preg_replace('/\s+/', ' ', $clause); @@ -369,41 +373,32 @@ class Report { $dirAry[] = $def; } } - $fnBody = ''; - for ($i = count($keyAry) - 1; $i >= 0; $i--) { - $k = $keyAry[$i]; - $t = $dirAry[$i]; - $f = -1 * $t; - $aStr = '$a[\'' . $k . '\']'; - $bStr = '$b[\'' . $k . '\']'; - - if (strpos($k, '(') !== false) { - $aStr = '$a->' . $k; - $bStr = '$b->' . $k; - } - if ($fnBody == '') { - $fnBody .= "if({$aStr} == {$bStr}) { return 0; }\n"; - $fnBody .= "return ({$aStr} < {$bStr}) ? {$t} : {$f};\n"; - } else { - $fnBody = "if({$aStr} == {$bStr}) {\n" . $fnBody; - $fnBody .= "}\n"; - $fnBody .= "return ({$aStr} < {$bStr}) ? {$t} : {$f};\n"; - } - } + $sortFn = function ($a, $b) use ($keyAry, $dirAry) { + for ($i = 0; $i < count($keyAry); $i++) { + $k = $keyAry[$i]; + $t = $dirAry[$i]; + $f = -1 * $t; + + if (strpos($k, '(') !== false) { + $aStr = $a->$k ?? null; + $bStr = $b->$k ?? null; + } else { + $aStr = $a[$k] ?? null; + $bStr = $b[$k] ?? null; + } - if ($fnBody) { - $sortFn = create_function('$a,$b', $fnBody); + if ($aStr == $bStr) { + continue; // Equal, so check next key + } - // TODO: at the moment, $sortFn() triggers some E_NOTICE warnings. We stop these here for a short time. - $errorSet = error_reporting(); - error_reporting($errorSet & ~E_NOTICE); + return ($aStr < $bStr) ? $t : $f; // Different, so we can determine order + } - usort($ary, $sortFn); + return 0; // All keys are equal + }; - error_reporting($errorSet); - error_clear_last(); - } + usort($ary, $sortFn); } /** @@ -430,7 +425,7 @@ class Report { * Return 'return values' in STORE_RECORD and QFQ function output in {{_output:R}}. * BTW: the QFQ function is cached and read only once. The evaluation is not cached. * - * @param $cmd # 'getFirstName(pId) => firstName, myLink' + * @param string $cmd # 'getFirstName(pId) => firstName, myLink' * @throws \CodeException * @throws \DbException * @throws \DownloadException @@ -443,7 +438,7 @@ class Report { * @throws \UserFormException * @throws \UserReportException */ - private function doQfqFunction($cmd) { + public function doQfqFunction(string $cmd): void { // QFQ function cache static $functionCache = array(); @@ -467,11 +462,18 @@ class Report { $bodytextArr = $functionCache[$rcFunctionName]; } else { // Multi DB setup: check for the correct DB - if (DB_INDEX_T3 != $this->dbArr[$this->dbIndexData]->getDbIndex()) { - // Current DB is wrong: get DB with DB_INDEX_T3 - $db = new Database(DB_INDEX_T3); + $this->dbIndexT3 = $this->dbIndexQfq; + + if (!defined('PHPUNIT_QFQ')) { + $t3DbConfig = T3Handler::getTypo3DbConfig($this->dbIndexData, $this->dbIndexQfq,$this->dbIndexT3); + } + + // Create Typo3 db object if config information exist. In case of api, it doesn't exist. + if (count($t3DbConfig) > 1 && $this->dbIndexT3 !== 0) { + $db = new Database($this->dbIndexT3, $t3DbConfig); } else { - $db = $this->dbArr[$this->dbIndexData]; + // Fallback to qfq user credentials. These are used usually for typo3 db. + $db = $this->dbArr[$this->dbIndexQfq]; } $bodytextArr = $db->getBodyText($rcFunctionName); @@ -595,7 +597,7 @@ class Report { $this->doQfqFunction($this->frArray[$fullLevel . "." . TOKEN_FUNCTION]); } - $sql = $this->variables->doVariables($this->frArray[$fullLevel . "." . TOKEN_SQL]); + $sql = $this->variables->doVariables($this->frArray[$fullLevel . "." . TOKEN_SQL], TOKEN_SQL); $this->store->setVar(SYSTEM_SQL_FINAL, $sql, STORE_SYSTEM); @@ -675,6 +677,7 @@ class Report { // replace {{<level>.line.count}} and {{<level>.line.total}} in __result__, if the variables specify their own full_level. This can't be replaced before firing the query. for ($ii = 0; $ii < count($row); $ii++) { + $row[$ii] = ($row[$ii] === null) ? '' : $row[$ii]; $row[$ii] = str_replace("{{" . $fullLevel . ".line.count}}", $rowIndex, $row[$ii]); $row[$ii] = str_replace("{{" . $fullLevel . ".line.total}}", $rowTotal, $row[$ii]); } diff --git a/extension/Classes/Core/Report/RestClient.php b/extension/Classes/Core/Report/RestClient.php index 675519bb7e5d72a4e40e2b0f97621970e9e77214..83641d650987548e88d092dd7565ff12683f975a 100644 --- a/extension/Classes/Core/Report/RestClient.php +++ b/extension/Classes/Core/Report/RestClient.php @@ -104,13 +104,13 @@ class RestClient { throw new \UserReportException("Missing RestClient target", ERROR_MISSING_VALUE); } - $param[TOKEN_L_CONTENT] = trim($param[TOKEN_L_CONTENT]) ?? ''; + $param[TOKEN_L_CONTENT] = trim($param[TOKEN_L_CONTENT] ?? ''); - if(!empty($param[TOKEN_L_CONTENT_FILE])){ - $param[TOKEN_L_CONTENT_FILE] = trim($param[TOKEN_L_CONTENT_FILE]) ?? ''; + if (!empty($param[TOKEN_L_CONTENT_FILE])) { + $param[TOKEN_L_CONTENT_FILE] = trim($param[TOKEN_L_CONTENT_FILE]); } - if(!empty($param[TOKEN_L_HEADER])){ + if (!empty($param[TOKEN_L_HEADER])) { $param[TOKEN_L_HEADER] = trim($param[TOKEN_L_HEADER]); } @@ -192,13 +192,14 @@ class RestClient { $context = stream_context_create($opts); $stream = @fopen($url, 'r', false, $context); - $output = stream_get_contents($stream); if ($stream === false) { $err = error_get_last(); throw new Exception("Api call {$method} {$url} failed: " . $err['message'], ERROR_REST_API_CALL); } + $output = stream_get_contents($stream); + // get HTTP status code $status_line = $http_response_header[0]; preg_match('{HTTP\/\S*\s(\d{3})}', $status_line, $match); diff --git a/extension/Classes/Core/Report/SendMail.php b/extension/Classes/Core/Report/SendMail.php index 78991acb0fc9176027e0cebbecf51ba906d73a83..f079fe368844905257f95d7bbb17ee5f0c9f378d 100644 --- a/extension/Classes/Core/Report/SendMail.php +++ b/extension/Classes/Core/Report/SendMail.php @@ -200,6 +200,9 @@ class SendMail { $mailConfig[SENDMAIL_TOKEN_BODY] = Support::wrapTag(SENDMAIL_HTML_TOKEN, $mailConfig[SENDMAIL_TOKEN_BODY]); // At least HTML code cleaned by QNL2BR() is missing any '\r\n'. A typicall MTA will inject a line break every 1000 Characters - // this might be in the middle of a word or URL. To prevent this, reinsert '\r\n' after each <br>. + // TinyMce or other potential applications delivers '<br />' instead of '<br>' + $mailConfig[SENDMAIL_TOKEN_BODY] = str_ireplace("<br />", "<br>", $mailConfig[SENDMAIL_TOKEN_BODY]); + // First normalize existing "<br>\r\n" to "<br>", than replace "<br>" by "<br>\r\n". $mailConfig[SENDMAIL_TOKEN_BODY] = str_ireplace("<br>", "<br>\r\n", str_ireplace("<br>\r\n", "<br>", $mailConfig[SENDMAIL_TOKEN_BODY])); diff --git a/extension/Classes/Core/Report/Tablesorter.php b/extension/Classes/Core/Report/Tablesorter.php index 5bcb051746f75ab37c64f38346da3464f049ef41..52a499cf675794c55832393f432bf58a2037962d 100644 --- a/extension/Classes/Core/Report/Tablesorter.php +++ b/extension/Classes/Core/Report/Tablesorter.php @@ -56,7 +56,7 @@ class Tablesorter { * @throws \UserFormException * @throws \UserReportException */ - public function inlineTablesorterView($token, &$foundInStore) { + public function inlineTablesorterView($token, &$foundInStore, $frCmd = '') { $token = OnString::trimQuote($token); if (empty($token)) { @@ -74,6 +74,11 @@ class Tablesorter { $s = $this->link->renderLink('U:' . SETTING_TABLESORTER_TABLE_ID . '=' . $token . '&' . SETTING_TABLESORTER_FE_USER . '=' . $feUser . '|s|r:8'); $view = $this->getTableViewAsJson($token, $feUser); + // Improve twig compatibility with escaped double quotes + if ($frCmd === TOKEN_SQL) { + $view = addslashes($view); + } + // data-tablesorter-sip='badcaffee1234' data-tablesorter-view='....' return "data-tablesorter-id='" . $token . "' " . DATA_TABLESORTER_SIP . "='" . $s . "' " . DATA_TABLESORTER_VIEW . "='" . $view . "' "; } diff --git a/extension/Classes/Core/Report/Variables.php b/extension/Classes/Core/Report/Variables.php index 79caaadba731280e45c825feb392f60a341d869d..834614f3fc08d8e3a8ed1fd575ac74a18bbb2b36 100644 --- a/extension/Classes/Core/Report/Variables.php +++ b/extension/Classes/Core/Report/Variables.php @@ -68,7 +68,7 @@ class Variables { * @throws \UserFormException * @throws \UserReportException */ - public function doVariables($text) { + public function doVariables($text, $frCmd = '') { if ($text == '') { return ''; @@ -81,7 +81,7 @@ class Variables { $str = preg_replace_callback('/{{\s*(([0-9]+.)+[a-zA-Z0-9_.]+)(:[a-zA-Z-]*)*\s*}}/', 'self::replaceVariables', $text); // Try the Stores - return $this->eval->parse($str); + return $this->eval->parse($str, null, null, $dummyStack, $dummyStore, $frCmd); } /** diff --git a/extension/Classes/Core/Save.php b/extension/Classes/Core/Save.php index 60f4f47f0834fc59784f59144ffeb4eedec4e306..4bc1a8ebc9e4986620589e95c52ea868480c92ab 100644 --- a/extension/Classes/Core/Save.php +++ b/extension/Classes/Core/Save.php @@ -279,9 +279,18 @@ class Save { $fillStoreForm = new FillStoreForm(); $storeVarBase = $this->store->getStore(STORE_VAR); + $flagCheckProcessRow = isset($this->formSpec[F_PROCESS_ROW]) && $this->formSpec[F_PROCESS_ROW] != '0'; foreach ($parentRecords as $row) { + // Checks if row has to be processed. + if ($flagCheckProcessRow) { + $processRowName = HelperFormElement::buildFormElementName([FE_NAME => F_PROCESS_ROW_COLUMN], $row[$idName]); + if ('on' !== $this->store->getVar($processRowName, STORE_CLIENT . STORE_ZERO, SANITIZE_ALLOW_ALNUMX)) { + continue; + } + } + // Always start with a clean STORE_VAR $this->store->setStore($storeVarBase, STORE_VAR, true); @@ -298,7 +307,8 @@ class Save { $rc = $this->processSingle($row[$idName], $formAction); } - return $rc; + // If no rows are selected, saving is still possible, thus requiring a null coalescing operator. + return $rc ?? ''; } /** @@ -404,9 +414,9 @@ class Save { $formValues = $this->store->getStore(STORE_FORM); $formValues = $this->createEmptyTemplateGroupElements($formValues); - $feColumnnTypes = array(); + $feColumnTypes = array(); foreach ($this->feSpecNative as $fe) { - $feColumnnTypes[$fe['name']] = $fe['type']; + $feColumnTypes[$fe['name']] = $fe['type']; } // Get htmlAllow parameters of all formValues and store in $feSpecsTags @@ -440,25 +450,41 @@ class Save { } $this->store->setVar(SYSTEM_FORM_ELEMENT, "Column: $column", STORE_SYSTEM); + if (!isset($feColumnTypes[$column])) { + $feColumnTypes[$column] = ''; + } // Convert time to datetime if mysql column is datetime, keep date if given - if ($tableColumnTypes[$column] === DB_COLUMN_TYPE_DATETIME && $feColumnnTypes[$column] === FE_TYPE_TIME) { + if ($tableColumnTypes[$column] === DB_COLUMN_TYPE_DATETIME && $feColumnTypes[$column] === FE_TYPE_TIME) { + // Keep old date value if exists $actualDate = explode(' ', $this->store->getVar($column, STORE_RECORD), 2)[0]; if ($actualDate === '0000-00-00' || !isset($actualDate)) { - $formValues[$column] = '2000-01-01 ' . $formValues[$column]; + $actualDate = '2000-01-01'; + } + + // Check if date already is given to prevent double date issues. + $actualDateFragments = explode(' ', $formValues[$column], 2); + if (count($actualDateFragments) > 1) { + $formValues[$column] = $actualDate . ' ' . $actualDateFragments[1]; } else { $formValues[$column] = $actualDate . ' ' . $formValues[$column]; } } // Convert date to datetime if mysql column is datetime, keep time if given - if ($tableColumnTypes[$column] === DB_COLUMN_TYPE_DATETIME && $feColumnnTypes[$column] === FE_TYPE_DATE) { + if ($tableColumnTypes[$column] === DB_COLUMN_TYPE_DATETIME && $feColumnTypes[$column] === FE_TYPE_DATE) { $actualTime = '00:00:00'; + // Keep old time value if exists $timeFragment = explode(' ', $this->store->getVar($column, STORE_RECORD), 2); if (isset($timeFragment[1])) { $actualTime = $timeFragment[1]; } - $formValues[$column] = $formValues[$column] . ' ' . $actualTime; + + // Check if time from datetime already is given and prevent issues with double time string + $actualTimeFragments = explode(' ', $formValues[$column], 2); + if (!isset($actualTimeFragments[1])) { + $formValues[$column] = $formValues[$column] . ' ' . $actualTime; + } } // Check if an empty string has to be converted to null. @@ -474,6 +500,11 @@ class Save { $formValues[$column] = $this->purifierHtml($formValues[$column]); } + if ($feColumnTypes[$column] === FE_TYPE_SELECT) { + // Typecast: some maria-db have a problem if an integer is assigned to an enum string. + $formValues[$column] = (string)$formValues[$column]; + } + $newValues[$column] = $formValues[$column]; $realColumnFound = true; } @@ -548,7 +579,7 @@ class Save { case 'ul': case 'ol': $listFlag = true; - break; + break; case 'textDecoration': case 'u': case 'ins': @@ -800,7 +831,7 @@ class Save { $statusUpload = $this->store->getVar($formValues[$column] ?? '', STORE_EXTRA); // Get file stats $vars = array(); - $vars[VAR_FILE_SIZE] = $statusUpload[FILES_SIZE] ?? ''; + $vars[VAR_FILE_SIZE] = $statusUpload[FILES_SIZE] ?? 0; $vars[VAR_FILE_MIME_TYPE] = $statusUpload[FILES_TYPE] ?? ''; // Check for 'unzip'. diff --git a/extension/Classes/Core/Store/Client.php b/extension/Classes/Core/Store/Client.php index 0a5199d333c4218025a44e5eed40a03d929115e6..a0e56341cff1f2bf5352dc67d040fab00ee5aabf 100644 --- a/extension/Classes/Core/Store/Client.php +++ b/extension/Classes/Core/Store/Client.php @@ -52,6 +52,11 @@ class Client { $cookie[CLIENT_COOKIE_QFQ] = $_COOKIE[SESSION_NAME]; } + // In case we're bedind a proxy, use the real ip as remote_address + if (isset($_SERVER[CLIENT_HTTP_X_REAL_IP])) { + $_SERVER[CLIENT_REMOTE_ADDRESS] = $_SERVER[CLIENT_HTTP_X_REAL_IP]; + } + if (isset($_SERVER)) { $server = Sanitize::htmlentitiesArr($_SERVER); // $_SERVER values might be compromised. } diff --git a/extension/Classes/Core/Store/Config.php b/extension/Classes/Core/Store/Config.php index 2740cf2f677cf8021a14da828836541d3c4afd40..12be88952982b26343bb4b94514a42328efaaf20 100644 --- a/extension/Classes/Core/Store/Config.php +++ b/extension/Classes/Core/Store/Config.php @@ -72,7 +72,6 @@ class Config { */ private static function readConfig($PhpUnitOverloadAbsoluteConfigFilePath = '') { $updateT3QfqConfig = false; - $configT3qfq = null; $baseUrl = ''; if (self::$config !== null && $PhpUnitOverloadAbsoluteConfigFilePath === '') { @@ -135,6 +134,10 @@ class Config { $updateT3QfqConfig = true; // Legacy behaviour. } + if (defined('PHPUNIT_QFQ')) { + $config[SYSTEM_BASE_URL] = $baseUrl = HTTP_EXAMPLE_COM; + } + $config = self::renameConfigElements($config); $config = self::setDefaults($config); self::checkDeprecated($config); @@ -149,7 +152,7 @@ class Config { self::checkMandatoryParameter($config); if ($updateT3QfqConfig) { - T3Handler::updateT3QfqConfig($configT3qfq, false, $baseUrl); + T3Handler::updateT3QfqConfig($config, false, $baseUrl); } // Add custom variables after T3QfqConfig is updated and written @@ -662,11 +665,17 @@ class Config { $config[SYSTEM_BASE_URL] = ($_SERVER[CLIENT_REQUEST_SCHEME] ?? 'http') . '://' . $config[SYSTEM_BASE_URL]; } - if (empty($config[SYSTEM_HTTP_ORIGIN])) { + if (isset($_SERVER[CLIENT_REQUEST_SCHEME])) { $url = $_SERVER[CLIENT_REQUEST_SCHEME] ?? ''; $url .= '://'; $url .= $_SERVER[CLIENT_HTTP_HOST] ?? ''; - $config[SYSTEM_HTTP_ORIGIN] = OnString::urlStripFile($url); + $finalUrl = OnString::urlStripFile($url); + + // Changes should be saved in config file for different reasons. If it's executed over autocron, SERVER vars don't exist. Is used for tinyMce image uploads + if ($finalUrl !== ($config[SYSTEM_HTTP_ORIGIN] ?? '')) { + $config[SYSTEM_HTTP_ORIGIN] = $finalUrl; + T3Handler::updateT3QfqConfig($finalUrl, SYSTEM_HTTP_ORIGIN); + } } return $config; diff --git a/extension/Classes/Core/Store/FillStoreForm.php b/extension/Classes/Core/Store/FillStoreForm.php index 3dc13e177d71b4f370f30d5cfb52a15e13638a6b..9527a94a4d0a23e99543278d64e8749e07201ce6 100644 --- a/extension/Classes/Core/Store/FillStoreForm.php +++ b/extension/Classes/Core/Store/FillStoreForm.php @@ -330,7 +330,7 @@ class FillStoreForm { $newValues[$formElement[FE_NAME]] = $val; // empty select, radio and checkbox elements used to be 0 for correct handling - if(($formElement[FE_TYPE] === FE_TYPE_SELECT || $formElement[FE_TYPE] === FE_TYPE_RADIO || $formElement[FE_TYPE] === FE_TYPE_CHECKBOX) && $val == '') { + if (($formElement[FE_TYPE] === FE_TYPE_SELECT || $formElement[FE_TYPE] === FE_TYPE_RADIO || $formElement[FE_TYPE] === FE_TYPE_CHECKBOX) && $val == '') { $newValues[$formElement[FE_NAME]] = 0; } } diff --git a/extension/Classes/Core/Store/Session.php b/extension/Classes/Core/Store/Session.php index cfc0194b8a99875818ad38f8285f1d9b01680f52..b6f046b744d7979801bcb3360ba72be93435bbc4 100644 --- a/extension/Classes/Core/Store/Session.php +++ b/extension/Classes/Core/Store/Session.php @@ -216,6 +216,7 @@ class Session { $feUserGroup = $GLOBALS["TSFE"]->fe_user->user["usergroup"] ?? false; $beUser = $GLOBALS["BE_USER"]->user["username"] ?? false; $languageId = T3Info::getLanguageId() ?? false; + $languagePath = T3Info::getLanguagePath($languageId) ?? false; // Cookie identifier $cookieFe = ($_COOKIE['fe_typo_user']) ?? false; @@ -239,6 +240,7 @@ class Session { // page language should be saved in session even fe user is not logged in. Session::set(SESSION_PAGE_LANGUAGE, $languageId); + Session::set(SESSION_PAGE_LANGUAGE_PATH, $languagePath); } else { // If we are called through API there is no T3 environment. Assume nothing has changed, and fake the following check to always 'no change'. $feUidLoggedIn = $feUserUidSession; diff --git a/extension/Classes/Core/Store/Sip.php b/extension/Classes/Core/Store/Sip.php index b4c73c12d03fa3c1be20668d9ff7c1b064f58c3d..4778dcbc495972f5e80741d2acfe55d0dc8ebdff 100644 --- a/extension/Classes/Core/Store/Sip.php +++ b/extension/Classes/Core/Store/Sip.php @@ -8,7 +8,7 @@ namespace IMATHUZH\Qfq\Core\Store; - + use IMATHUZH\Qfq\Core\Exception\Thrower; use IMATHUZH\Qfq\Core\Helper\KeyValueStringParser; use IMATHUZH\Qfq\Core\Helper\OnArray; diff --git a/extension/Classes/Core/Store/Store.php b/extension/Classes/Core/Store/Store.php index 4bed37683805cca10009b1542dee436e07af163c..bcde3238da1791ff7d55c1dfb9c26372dfca6634 100644 --- a/extension/Classes/Core/Store/Store.php +++ b/extension/Classes/Core/Store/Store.php @@ -124,6 +124,7 @@ class Store { CLIENT_SERVER_ADDRESS => SANITIZE_ALLOW_ALNUMX, CLIENT_SERVER_PORT => SANITIZE_ALLOW_DIGIT, CLIENT_REMOTE_ADDRESS => SANITIZE_ALLOW_ALNUMX, + CLIENT_HTTP_X_REAL_IP => SANITIZE_ALLOW_ALNUMX, CLIENT_REQUEST_SCHEME => SANITIZE_ALLOW_ALNUMX, CLIENT_REQUEST_METHOD => SANITIZE_ALLOW_ALNUMX, CLIENT_SCRIPT_FILENAME => SANITIZE_ALLOW_ALNUMX, @@ -382,7 +383,7 @@ class Store { } else { // No T3 environment (called by API): restore from SESSION - foreach ([SESSION_FE_USER, SESSION_FE_USER_UID, SESSION_FE_USER_GROUP, SESSION_BE_USER, SESSION_PAGE_LANGUAGE] as $key) { + foreach ([SESSION_FE_USER, SESSION_FE_USER_UID, SESSION_FE_USER_GROUP, SESSION_BE_USER, SESSION_PAGE_LANGUAGE, SESSION_PAGE_LANGUAGE_PATH] as $key) { if (isset($_SESSION[SESSION_NAME][$key])) { $arr[$key] = $_SESSION[SESSION_NAME][$key]; } @@ -467,11 +468,16 @@ class Store { */ public static function fillStoreTableDefaultColumnType(array $tableDefinition) { + // Defaults: empty strings are given as "''" > fix them to "". + foreach ($tableDefinition as $key => $value) { + if (($tableDefinition[$key]['Default'] ?? '') == "''") { + $tableDefinition[$key]['Default'] = ''; + } + } self::setStore(array_column($tableDefinition, 'Default', 'Field'), STORE_TABLE_DEFAULT, true); self::setStore(array_column($tableDefinition, 'Type', 'Field'), STORE_TABLE_COLUMN_TYPES, true); } - /** * Set's a single $key/$value pair $store. * @@ -603,18 +609,20 @@ class Store { // no sanitizeClass specified: take predefined (if exist) or default. if ($sanitizeClass === '' || $sanitizeClass === null) { - $sanitizeDefault = SANITIZE_DEFAULT_OF_STORE[$store]; - $sanitizeClass = isset(self::$sanitizeClass[$key]) ? self::$sanitizeClass[$key] : $sanitizeDefault; + $sanitizeClassFinal = self::$sanitizeClass[$key] ?? SANITIZE_DEFAULT_OF_STORE[$store]; + } else { + $sanitizeClassFinal = $sanitizeClass; } - $rawVal = isset(self::$raw[$store][$finalKey]) ? self::$raw[$store][$finalKey] : null; - if (self::$sanitizeStore[$store] && $sanitizeClass != '') { - if ($sanitizeClass == SANITIZE_ALLOW_PATTERN) { + + $rawVal = self::$raw[$store][$finalKey] ?? null; + if (self::$sanitizeStore[$store] && $sanitizeClassFinal != '') { + if ($sanitizeClassFinal == SANITIZE_ALLOW_PATTERN) { // We do not have any pattern at this point. For those who be affected, they already checked earlier. So set 'no check' - $sanitizeClass = SANITIZE_ALLOW_ALL; + $sanitizeClassFinal = SANITIZE_ALLOW_ALL; } - return Sanitize::sanitize($rawVal, $sanitizeClass, '', '', SANITIZE_EMPTY_STRING, '', $typeMessageViolate); + return Sanitize::sanitize($rawVal, $sanitizeClassFinal, '', '', SANITIZE_EMPTY_STRING, '', $typeMessageViolate); } else { if ($store == STORE_SIP && (substr($key, 0, $len) == SIP_PREFIX_BASE64)) { $rawVal = base64_decode($rawVal); @@ -746,7 +754,7 @@ class Store { foreach ([TYPO3_FE_USER, TYPO3_FE_USER_UID, TYPO3_FE_USER_GROUP, TYPO3_FE_USER_EMAIL, TYPO3_BE_USER, TYPO3_BE_USER_UID, TYPO3_BE_USER_EMAIL, TYPO3_TT_CONTENT_UID, T3DATA_UID, - TYPO3_PAGE_ID, TYPO3_PAGE_ALIAS, TYPO3_PAGE_SLUG, TYPO3_PAGE_TYPE, TYPO3_PAGE_LANGUAGE] as $key) { + TYPO3_PAGE_ID, TYPO3_PAGE_ALIAS, TYPO3_PAGE_SLUG, TYPO3_PAGE_TYPE, TYPO3_PAGE_LANGUAGE, TYPO3_PAGE_LANGUAGE_PATH] as $key) { if (isset($tempArray[$key])) { $t3varsArray[$key] = $tempArray[$key]; } diff --git a/extension/Classes/Core/Store/T3Info.php b/extension/Classes/Core/Store/T3Info.php index 4c47479a645dc984374bb204366970a2e7f04466..e436594c9ab764d0e8c7fe780336c9e3cfc7cf88 100644 --- a/extension/Classes/Core/Store/T3Info.php +++ b/extension/Classes/Core/Store/T3Info.php @@ -52,6 +52,8 @@ class T3Info { $t3vars[TYPO3_PAGE_LANGUAGE] = self::getLanguageId(); + $t3vars[TYPO3_PAGE_LANGUAGE_PATH] = self::getLanguagePath($t3vars[TYPO3_PAGE_LANGUAGE]); + $t3vars[TYPO3_BE_USER_LOGGED_IN] = self::beUserLoggedIn() ? 'yes' : 'no'; $t3vars[TYPO3_BE_USER] = isset($GLOBALS["BE_USER"]->user["username"]) ? $GLOBALS["BE_USER"]->user["username"] : ''; @@ -111,12 +113,10 @@ class T3Info { return null; } $allSites = $sf->getAllSites(); - if (count($allSites) > 1) { - Thrower::userReportException('Site language Error.', 'QFQ is does not run on Typo3 setup with multiple sites yet. Please contact QFQ maintainers.'); - } elseif (count($allSites) < 1) { + if (count($allSites) < 1) { return null; } else { - $site = reset($allSites); + $site = self::getSiteConfiguration($sf); $lang = $site->getLanguageById($lid); $base = $lang->getBase(); return (string)$base; @@ -142,4 +142,40 @@ class T3Info { } return false; } + + /** + * Returns the language path part. Helpful for forwardPage handling of forms in T3 >=V10 + * + * @return string + */ + public static function getLanguagePath($languageId): string { + $languagePath = ''; + if (T3Handler::typo3VersionGreaterEqual10() && isset($GLOBALS['TYPO3_REQUEST'])) { + $siteIdentifier = $GLOBALS['TYPO3_REQUEST']->getAttribute('site')->getIdentifier(); + $siteConfiguration = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Core\Site\SiteFinder::class)->getSiteByIdentifier($siteIdentifier)->getConfiguration(); + $languages = $siteConfiguration['languages']; + $actualLanguage = $languages[$languageId]['base']; + $languagePath = trim($actualLanguage, '/'); + } + + return $languagePath; + } + + /** + * Returns the site configuration from current page. Used for T3 >=V10. + * + * object $sf + * @return object + */ + public static function getSiteConfiguration($sf): object { + if (T3Handler::typo3VersionGreaterEqual10() && isset($GLOBALS['TYPO3_REQUEST'])) { + $siteIdentifier = $GLOBALS['TYPO3_REQUEST']->getAttribute('site')->getIdentifier(); + $actualSite = $sf->getSiteByIdentifier($siteIdentifier); + } else { + $allSites = $sf->getAllSites(); + $actualSite = reset($allSites); + } + + return $actualSite; + } } \ No newline at end of file diff --git a/extension/Classes/Core/Typo3/T3Handler.php b/extension/Classes/Core/Typo3/T3Handler.php index 7565bfa1363b2010cc70c0f32f62aa3039346c4e..e52578fa4f6cbd2125cbcc513bae7299b3aa74f9 100644 --- a/extension/Classes/Core/Typo3/T3Handler.php +++ b/extension/Classes/Core/Typo3/T3Handler.php @@ -302,4 +302,60 @@ class T3Handler { public static function isTypo3Loaded() { return class_exists('\TYPO3\CMS\Core\Utility\GeneralUtility'); } + + /** + * Note: Loads Typo3 if not running. + * Return value is only computed once, then cached. + * Used for Controller handling. + * + * @return bool + */ + public static function typo3VersionGreaterEqual11(): ?bool { + if (defined('PHPUNIT_QFQ')) { + // assume Typo3 version grater than 8 in unittests + return true; + } + + // Warning: caching!!! Do not try to do version compare with different version number during one run and than cache such result. + static $cache = null; + if (is_null($cache)) { + $cache = version_compare(self::getTypo3Version(), '11.0.0') >= 0; + } + return $cache; + } + + /** + * Get typo3 database configuration. + * + * @param int $dbIndexData + * @param int $dbIndexQfq + * @param int $dbIndexT3 + * @return array + * @throws \UserFormException + */ + public static function getTypo3DbConfig(int $dbIndexData, int $dbIndexQfq, int &$dbIndexT3): array { + $config = array(); + for ($i = 1; $i <= 3; $i++) { + if ($dbIndexData !== $i && $dbIndexQfq !== $i) { + $dbIndexT3 = $i; + continue; + } + } + + self::t3AutoloadIfNotRunning(); + + $configurationManager = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Configuration\\ConfigurationManager'); + + // Same as $GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['qfq'] + if (isset($GLOBALS['TYPO3_CONF_VARS']) && !defined('PHPUNIT_QFQ')) { + $configT3 = $configurationManager->getLocalConfiguration(); + $typo3DbCredentials = $configT3['DB']['Connections']['Default']; + $config['DB_' . $dbIndexT3 . '_USER'] = $typo3DbCredentials['user']; + $config['DB_' . $dbIndexT3 . '_PASSWORD'] = $typo3DbCredentials['password']; + $config['DB_' . $dbIndexT3 . '_SERVER'] = $typo3DbCredentials['host']; + $config['DB_' . $dbIndexT3 . '_NAME'] = $typo3DbCredentials['dbname']; + } + + return $config; + } } \ No newline at end of file diff --git a/extension/Classes/Sql/qfqDefaultTables.sql b/extension/Classes/Sql/qfqDefaultTables.sql index b90f3b1921a321a9ccef3837ae8832bced15c64e..1f3d4cf9f8d0e4207ff64af9c55be1b92996e161 100644 --- a/extension/Classes/Sql/qfqDefaultTables.sql +++ b/extension/Classes/Sql/qfqDefaultTables.sql @@ -109,7 +109,6 @@ CREATE TABLE IF NOT EXISTS `FormElement` `parameterLanguageD` TEXT NOT NULL, `clientJs` TEXT NOT NULL, - `feGroup` VARCHAR(255) NOT NULL DEFAULT '', `deleted` ENUM ('yes', 'no') NOT NULL DEFAULT 'no', `modified` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, @@ -118,9 +117,7 @@ CREATE TABLE IF NOT EXISTS `FormElement` KEY `formId` (`formId`), KEY `formId_class_enabled_deleted` (`formId`, `class`, `enabled`, `deleted`), KEY `feIdContainer` (`feIdContainer`), - KEY `ord` (`ord`), - KEY `feGroup` (`feGroup`) - + KEY `ord` (`ord`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8 @@ -202,7 +199,8 @@ CREATE TABLE IF NOT EXISTS `FormSubmitLog` INDEX (`formId`), INDEX (`formName`), INDEX (`recordId`), - INDEX (`pageId`) + INDEX (`pageId`), + INDEX `createdFeUserFormId` (`created`, `feUser`, `formId`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8 diff --git a/extension/Configuration/Services.yaml b/extension/Configuration/Services.yaml new file mode 100644 index 0000000000000000000000000000000000000000..ee8d37f1f66331929e3cce5b8d532d90a5fd5445 --- /dev/null +++ b/extension/Configuration/Services.yaml @@ -0,0 +1,5 @@ +services: + IMATHUZH\Qfq\Controller\QfqController: + autowire: true + autoconfigure: true + public: false \ No newline at end of file diff --git a/extension/Configuration/TCA/tt_content.php b/extension/Configuration/TCA/tt_content.php index 739285e6d53d12c0ac04b037de0e8a3b8370bc1f..269203ff7f1247dcf7afc9971b19b4f2b0c73bd2 100644 --- a/extension/Configuration/TCA/tt_content.php +++ b/extension/Configuration/TCA/tt_content.php @@ -35,7 +35,9 @@ if (is_array($GLOBALS['TCA']['tt_content']['types'])) { --palette--;;layoutpalette1, --palette--;;layoutpalette2, --palette--;;spacearound, - linkToTop, section_frame, sectionIndex', + linkToTop, section_frame, sectionIndex, + --div--;Language, + sys_language_uid, l18n_parent, l18n_diffsource', 'columnsOverrides' => [ 'bodytext' => [ 'config' => [ diff --git a/extension/RELEASE.txt b/extension/RELEASE.txt index a4814fc4e20b1f49d045f947de5c74d908af16b0..3ff16b0271c3f96e36e497186c8ca7e7e7d35055 100644 --- a/extension/RELEASE.txt +++ b/extension/RELEASE.txt @@ -52,6 +52,213 @@ Features Bug Fixes ^^^^^^^^^ +Version 23.10.1 +--------------- + +Date: 22.10.2023 + +Notes +^^^^^ + +Features +^^^^^^^^ + +* #15098 / Implemented qfqFunction in QFQ variable for usage in forms. +* Doc: Replace many places single back tick by double back tick. Add hint for 'Row size too large'. Added Enis & Jan + as Developer. Add hint use mysqldump with one row per record. + +Bug Fixes +^^^^^^^^^ + +* #17003 / inline edit - dark mode has wrong css path. +* #17075 / Fix broken '... AS _restClient'. +* #17091 / upload_Incorrect_integer_value_fileSize. +* RTD: Fix broken readthedocs rendering. + +Version 23.10.0 +--------------- + +Date: 05.10.2023 + +Features +^^^^^^^^ + +* #16350 / QFQ Table 'FormSubmiLog': update recordid after insert. +* #16350 / sql.log: reference to FormSubmitLog entry. All SQL statements generated by one HTTP Post (Form Submit) can + be identified. +* #16350 / Do not log Dirty. +* #16350 / If the T3 instance is behind a proxy, log HTTP_X_REAL_IP instead of REMOTE_ADDR in logfiles. +* #16584 / FormEditor Report: Default without statistics. +* #16589 / Implemented language configuration in backend for tt-content type qfq. +* #16798 / Report inline edit v2. Improved search inside inline edit report. Whole content will be searched. Added + ability to switch the editor to dark mode. +* Doc: Refactor description of {{random:V}}. +* Doc: Add config option 'protectedFolderCheck'. + +Bug Fixes +^^^^^^^^^ + +* #16573 / Fixed wrong built date and datetime string if default value was given. +* #16574 / Added multiple siteConfigurations compatibility for typo3 v10 and 11. +* #16616 / Fixed typeahead api query response problem if typeahead sql is not used. +* #16664 / Fix multi db user error. +* #16975 / Fix problem if a 'Form Submit' contains more than 64kB data. This can happen easily for 'fabric' elements. + +Version 23.6.4 +-------------- + +Date: 26.06.2023 + +Bug Fixes +^^^^^^^^^ + +* #16485 / TypeAhead: strpos() string, array given error. +* #16488 / Missing default values break saving records. New custom FE.parameter.defaultValue. +* #16491 / FE Typ Upload - JS failure: document.querySelector() is null. + +Version 23.6.3 +-------------- + +Date: 22.06.2023 + +Bug Fixes +^^^^^^^^^ + +* #16478 / Rest API: Fixed stream_get_contents failure in PHP8.1 Generic Error. + +Version 23.6.2 +-------------- + +Date: 21.06.2023 + +Bug Fixes +^^^^^^^^^ + +* #16475 / Spontaneous spaces in HTML emails. +* #16476 / SQL columns Text/Blog with default empty string becomes "''" + + +Version 23.6.1 +-------------- + +Date: 16.06.2023 + +Notes +^^^^^ + +* QFQ is Typo3 V11 compatible + +Bug Fixes +^^^^^^^^^ + +* #16372 / Upload Element 'Undefined index htmlDownloadButton' und 'unknown mode ID' +* #16381 / Form title dynamic update broken. +* #16392 / Reevaluate sanitize class for each store. +* Fix db column enum dropdown 'data truncated' +* DB Update 'alter index' pre check if exists +* FE: Upload - fix undefined index. + +Version 23.6.0 +-------------- + +Date: 09.06.2023 + +Features +^^^^^^^^ + +* Typo3 V11 compatible +* #9579 / Multiform with Process Row. +* #15770 / httpOrigin: Parameter missing in QFQ Config. +* #16285 / PHP V8 Migration. +* #16364 / T3 >=V10 pageSlug and ForwardPage. +* Add .phpunit.result.cache to .gitignore. +* Add bullet-orange.gif. +* Add index createdFeUserFormId to table FormSubmitLog. +* Doc: Add link to icons. Update use-case self-registration and add info in case of namesake. Unify word 'spam' to 'junk'. + Add bootstrap links. +* Remove Form/FormElement parameter 'feGroup'. +* QFQ Typo3 Updatewizard: Sort output by page and tt-content ordering. Add page uid an title to be reported. + Inline Edit: add tt_content uid and header as tooltip. +* Update package.json. +* Update to PHPUnit version 9. + +Bug Fixes +^^^^^^^^^ + +* #12468 / Form: Dynamic Update Form.title after save. +* #14636 / 'Clear Me'-cross lighter and 2 px up. +* #15445 / Doc: JS Output Widged added. +* #15497 / Delete link: a) broken for 'table', b) broken with 'r:3'. +* #15527 / Form/subrecord: class 'qfq-table-50' no impact. +* #15654 / Form FE required 'Undefined index Error'. +* #15659 / Multi Form: Datetime Picker not aligned. +* #15691 / T3 V10: Export PDF (PDF Generator) invalid link causes error. +* #15726 / Formelement upload stays required when changed from required to hidden. +* #15729 / Form: text readonly input should show newline as '<br>'. +* #15747 / Typeahead does not work with special character. Added croatian special chars (Ć,ć,ÄŒ,Ä,Ä,Ä‘,Å ,Å¡,Ž,ž) to alnumx + whitelist. +* #15763 / Search/Refactor: a) search for ID, b) message if nothing is found - New highlighting for disabled content. + Added output for not found in search. +* #15773 / TypeAhead missing check type warning. +* #15813 / Pressing 'Enter' in Form asks: Do you really want to delete the record? +* #15875 / Unecessary qfq-config save. +* #15905 / FE type time undefined index. +* #15913 / Refactor: Footer displayed at the wrong place. +* #15921 / Frontend edit Report Codemirror resizeable. +* #16004 / Template Group Fields broken. +* #16046 / Datepicker not initialized in Template Group. +* #16051 / Dropdown menu: Download file broken with ZIP File. +* #16055 / Dropdown option with render mode 3 format problem. +* #16064 / Dynamic Update Checkbox and Form Store in parameter. +* #16073 / saveButtonActive is disabled after first save. +* #16201 / Character Count Class Quotation Mark. +* #16204 / TypeAhead Prefetch Expects String Array Given. +* #16228 / extraButtonPassword unhide broken. +* #16264 / Multi DB Broken since QFQ V23.2.0. +* #16273 / TinyMCE image upload path incorrect for T3 v10 and upwards. + +Version 23.3.1 +-------------- + +Date: 31.03.2023 + +Bug Fixes +^^^^^^^^^ + +* #15920 / QFQ variable evaluation broken: wrong variable name + +Version 23.3.0 +-------------- + +Date: 30.03.2023 + +Features +^^^^^^^^ + +* #15491 / Search refactor redesign and T3 V10 compatiblity: underscore character, counter fixed, page alias and slug handling. +* #15529 / Form/subrecord: Design Titel / Box. +* #15570 / Changed DB handling in class FormAction. Fixed multi-db problem with this. Included change for check of + existing form editor report. +* #15579 / Ability for searching all possible characters, includes not replaced QFQ variables. +* #15627 / Added character count and maxLength feature for TinyMCE. +* Add various icons to documentation. +* Doc Form.rst: reference 'orderColumn'. +* Doc Report.rst: fix typo, add icons, improved example for tablesorter. +* Add indexes for table FormSubmitLog. +* FormEditor: Show Tablename in pill 'table definition'. +* FormEditor: FE subrecord > show container id below name. + +Bug Fixes +^^^^^^^^^ + +* #14754 / Using double quotes in tableview config caused sql error. +* #15483 / Protected folder check fixed. Changed default of wget, preventing errors. Changed handling from protected + folder check, new once a day. +* #15521 / FormEditor assigns always container, even none is selected. Change handling of form variables from type select, + radio and checkbox. Expected 0 from client request instead of empty string. +* #15523 / Search/Refactor broken for Multi-DB. +* #15626 / Multi-DB: FormEditor save error. + Version 23.2.0 -------------- @@ -67,18 +274,19 @@ Notes Features ^^^^^^^^ - * 7650 / For FormElement which are required: `indicateRequired=0|1` show or hide the 'required asterix'. - * 10013 / FormEditor: Several textarea inputs changed to 'editor' incl. SQL syntax highlighting. - * 14995 / Support for refactoring: search in QFQ Form/FormElement and Typo3 pages/tt-content tables. - * QMANR: SQL prepared function to format Swiss Matrikelnumbers +* #7650 / For FormElement which are required: `indicateRequired=0|1` show or hide the 'required asterix'. +* #10013 / FormEditor: Several textarea inputs changed to 'editor' incl. SQL syntax highlighting. +* #14995 / Support for refactoring: search in QFQ Form/FormElement and Typo3 pages/tt-content tables. +* QMANR: SQL prepared function to format Swiss Matrikelnumbers Bug Fixes ^^^^^^^^^ - * 8668 / Fix that if a pill becomes disabled, child FE should not respected during save. - * 15168 / Fixed language form save problem for t3 v >= 9. - * 15420 / TypeAhead Prefetch triggers 'form changed' after save. - * 15482 / Added missing password font file. +* #7899 / Fe.type=password / retype / required: always complain about missing value - bug has been fixed earlier. +* #8668 / Fix that if a pill becomes disabled, child FE should not respected during save. +* #15168 / Fixed language form save problem for t3 v >= 9. +* #15420 / TypeAhead Prefetch triggers 'form changed' after save. +* #15482 / Added missing password font file. Version 23.1.1 @@ -94,19 +302,19 @@ Notes Features ^^^^^^^^ - * #11980 / check protected folder for access from outside after new qfq installation or update. - * #13566 / Cleanup: Delete config-example.qfq.php - * #15053 / New dynamic QFQ config option: fillStoreSystemBySqlRow. - * Update SANITIZE_ALLOW_EMAIL_MESSAGE to be more generic: 'Invalid email format' - * Update Documentation/Form.rst: Added QFQ Function QMANR(manr), broken ref to restAuthorization, fixed typos. - * Update copyright year +* #11980 / check protected folder for access from outside after new qfq installation or update. +* #13566 / Cleanup: Delete config-example.qfq.php +* #15053 / New dynamic QFQ config option: fillStoreSystemBySqlRow. +* Update SANITIZE_ALLOW_EMAIL_MESSAGE to be more generic: 'Invalid email format' +* Update Documentation/Form.rst: Added QFQ Function QMANR(manr), broken ref to restAuthorization, fixed typos. +* Update copyright year Bug Fixes ^^^^^^^^^ - * #2665 / Fixed not working dynamic update with tinymce and codemirror. Improved frontend edit visualization, elements in window will be resized automatically. - * #11517 / Fixed Bug not showing extraInfoButton on certain FormElements like: TextArea, Select, Checkbox, Radio, Upload, Editor. - * #12066 / Form: Enter as submit forward not working. +* #2665 / Fixed not working dynamic update with tinymce and codemirror. Improved frontend edit visualization, elements in window will be resized automatically. +* #11517 / Fixed Bug not showing extraInfoButton on certain FormElements like: TextArea, Select, Checkbox, Radio, Upload, Editor. +* #12066 / Form: Enter as submit forward not working. Version 23.1.0 -------------- @@ -116,25 +324,25 @@ Date: 05.01.2023 Features ^^^^^^^^ - * #6250 / Enhance layout subrecord. - * #10003 / Fieldset: stronger visualize group. - * #15036 / FormElement subrecord summary additional row(s). - * #15154 / FormElement input check via regexp: trim whitespace before regexp. +* #6250 / Enhance layout subrecord. +* #10003 / Fieldset: stronger visualize group. +* #15036 / FormElement subrecord summary additional row(s). +* #15154 / FormElement input check via regexp: trim whitespace before regexp. Bug Fixes ^^^^^^^^^ - * #14305 / Bug "No form found with this id". - * #14997 / Tablesorter: Filter value shown but no effect after 'Browser Back' - * #15191 / Broken datepicker in template group. - * #15214 / Inline report save history. - * #15229 / Typeahead and qfq-clear-me will be initialized after adding new fields. - * #15230 / Set own button class for dropdown. - * #15314 / FE & BE Email now filled in 'sipForTypo3Vars' and therefore available in API calls. - * #15316 / config.baseUrl might be overwritten if no scheme or multiple urls are given. - * Doc: Fix missing ! in doc: sqlValidate. Fix index undefined htmlAllow. Fix filemtime() Warning. - * Fixed date to datetime convert bug. - * Fixed error coming from typeahead when array is empty. +* #14305 / Bug "No form found with this id". +* #14997 / Tablesorter: Filter value shown but no effect after 'Browser Back' +* #15191 / Broken datepicker in template group. +* #15214 / Inline report save history. +* #15229 / Typeahead and qfq-clear-me will be initialized after adding new fields. +* #15230 / Set own button class for dropdown. +* #15314 / FE & BE Email now filled in 'sipForTypo3Vars' and therefore available in API calls. +* #15316 / config.baseUrl might be overwritten if no scheme or multiple urls are given. +* Doc: Fix missing ! in doc: sqlValidate. Fix index undefined htmlAllow. Fix filemtime() Warning. +* Fixed date to datetime convert bug. +* Fixed error coming from typeahead when array is empty. Version 22.12.1 @@ -152,21 +360,21 @@ Notes Features ^^^^^^^^ - * #5715 / Caching for on the fly rendred files (PDF, Excel, ZIP). - * #6250 / Enhance form layout: a) Subrecord, b) Subrecord-Title - * #9927 / Do QFQ Update (config, table defintion) only if there is a BE user logged in the current session. - * #12503 / Detect likely unwanted UPDATE statement with missing WHERE. - * New Button Sizes: btn-tiny, btn-small +* #5715 / Caching for on the fly rendred files (PDF, Excel, ZIP). +* #6250 / Enhance form layout: a) Subrecord, b) Subrecord-Title +* #9927 / Do QFQ Update (config, table defintion) only if there is a BE user logged in the current session. +* #12503 / Detect likely unwanted UPDATE statement with missing WHERE. +* New Button Sizes: btn-tiny, btn-small Bug Fixes ^^^^^^^^^ - * #14305 / Inline Report edit saves history to Typo 3. - * #14506 / Tablesorter: after form close, already defined filter criteria in tablesorter will be applied. - * #15188 / Clean up config to T3 parameter. - * #15193 / No form found with this id error in MultiDB Setup. - * #15206 / Form Save: Internal Error in MultiDB Setup. - * Fix Doc: latest CCS/JS includes. +* #14305 / Inline Report edit saves history to Typo 3. +* #14506 / Tablesorter: after form close, already defined filter criteria in tablesorter will be applied. +* #15188 / Clean up config to T3 parameter. +* #15193 / No form found with this id error in MultiDB Setup. +* #15206 / Form Save: Internal Error in MultiDB Setup. +* Fix Doc: latest CCS/JS includes. Version 22.12.0 --------------- @@ -237,11 +445,11 @@ Features Bug Fixes ^^^^^^^^^ - * #11325 / SQL CALL() - insert() and select() should be supported now. - * #14622 / FormElement `unexpected error` on save. - * #15005 / TypeAhead Dynamic Update Mode Hidden to Required - * #15014 / Excel import broken if multiple regions defined. - * #15048 / STORE_USER broken. +* #11325 / SQL CALL() - insert() and select() should be supported now. +* #14622 / FormElement `unexpected error` on save. +* #15005 / TypeAhead Dynamic Update Mode Hidden to Required +* #15014 / Excel import broken if multiple regions defined. +* #15048 / STORE_USER broken. Version 22.10.1 --------------- @@ -251,29 +459,29 @@ Date: 24.10.2022 Notes ^^^^^ - * Migration of existing Typo3 V9 to V10: Check #12584, #12440 +* Migration of existing Typo3 V9 to V10: Check #12584, #12440 Features ^^^^^^^^ - * #12584, #12440 / Typo3 V10 Migration Script Replace Alias Patterns - * #14738 / Datetimepicker: verify various setups - * #14802 / DateTime own class and unit tests - * Add doc to table vertical column via CSS, - * Add qfq-badge-* to doc +* #12584, #12440 / Typo3 V10 Migration Script Replace Alias Patterns +* #14738 / Datetimepicker: verify various setups +* #14802 / DateTime own class and unit tests +* Add doc to table vertical column via CSS, +* Add qfq-badge-* to doc Bug Fixes ^^^^^^^^^ - * #14618 / Changed logic behind getting first value from array (check for typeahead array was already given but was - placed after the checkForEncryptedValue function). No exception possible. Function name changed to generic. Added new - function is_multi_array to check if array is multidimensional and return true or false. - * #14736 / Datetimepicker: FormElement.parameter.dateFormat - required. - * #14755 / Fix for typeahead problem in t3 v9 and higher. Typeahead url will be setup same as save.php. Results to an absolute path. - * #14813 / Report: dbIndex - missing per level definition - * #14844 / Fixed bug of no working dynamic update and empty output in Form Store with Datetimepicker. - * #14857 / String expected, array given. - * #14885 / Fabric zoom problem. +* #14618 / Changed logic behind getting first value from array (check for typeahead array was already given but was + placed after the checkForEncryptedValue function). No exception possible. Function name changed to generic. Added new + function is_multi_array to check if array is multidimensional and return true or false. +* #14736 / Datetimepicker: FormElement.parameter.dateFormat - required. +* #14755 / Fix for typeahead problem in t3 v9 and higher. Typeahead url will be setup same as save.php. Results to an absolute path. +* #14813 / Report: dbIndex - missing per level definition +* #14844 / Fixed bug of no working dynamic update and empty output in Form Store with Datetimepicker. +* #14857 / String expected, array given. +* #14885 / Fabric zoom problem. @@ -285,26 +493,26 @@ Date: 04.10.2022 Notes ^^^^^ - * TinyMCE Feature "upload images via drag'n'drop" now supported. +* TinyMCE Feature "upload images via drag'n'drop" now supported. Features ^^^^^^^^ - * #14813 / Report: dbIndex - missing per level definition - * #12474 / Check BaseConfigURL if it is given and the the last char is '/' - * #12452 / baseUrl: add automatically '/' at end - * #10782 / Tiny MCE: Image Upload & Image drag'n'drop +* #14813 / Report: dbIndex - missing per level definition +* #12474 / Check BaseConfigURL if it is given and the the last char is '/' +* #12452 / baseUrl: add automatically '/' at end +* #10782 / Tiny MCE: Image Upload & Image drag'n'drop Bug Fixes ^^^^^^^^^ - * #14803 / MultiDB (different DB hosts): broken 'show table definition' in FormEditor - - * #14791 / datetimepicker: undefined index: timeParts[2] - * #14619 / Added note to missed bug fix. - * #12630 / Added code commentary. Reformatted code. getUniqueFileName function changed. New logFileMessages implemented. - * #14463 / llow statusbar (resize button included) to show as default. - * #14455 / Drag and drop triggers record lock. - * #13818 / htmlspecialchars_decode() - expects parameter 1 to be string, array given +* #14803 / MultiDB (different DB hosts): broken 'show table definition' in FormEditor - +* #14791 / datetimepicker: undefined index: timeParts[2] +* #14619 / Added note to missed bug fix. +* #12630 / Added code commentary. Reformatted code. getUniqueFileName function changed. New logFileMessages implemented. +* #14463 / llow statusbar (resize button included) to show as default. +* #14455 / Drag and drop triggers record lock. +* #13818 / htmlspecialchars_decode() - expects parameter 1 to be string, array given Version 22.9.2 -------------- @@ -314,13 +522,13 @@ Date: 22.09.2022 Features ^^^^^^^^ - * #12630 / dateTimePicker refactored. Mode: 'qfq', 'browser', 'no'. Default: 'qfq' - * Add release notes to update UZH CD to latest version and to remove QFQ JS/CSS includes from custom typoscript template. +* #12630 / dateTimePicker refactored. Mode: 'qfq', 'browser', 'no'. Default: 'qfq' +* Add release notes to update UZH CD to latest version and to remove QFQ JS/CSS includes from custom typoscript template. Bug Fixes ^^^^^^^^^ - * #14619 / dateTimePicher: missing popup on first click, record not dirty +* #14619 / dateTimePicher: missing popup on first click, record not dirty Version 22.9.1 -------------- @@ -528,7 +736,7 @@ Bug Fixes * #3446 / Unknown permission mode: 'logged_in' * #9268 / SELECT with outer brackets not recognized as SELECT * #13030 / Max length cuts - line endings \r\n has been counted as two chars. During input they are counted as 1 and - therefore on data load the string has been cutted. + therefore on data load the string has been cutted. * #13139 / Tablesorter: some elements are in front of a sticky title row * #13507 / QFQ function should work without 'sql=' * #13525 / makefile adjusted for multiple users @@ -810,8 +1018,8 @@ Date: 25.06.2020 Bug Fixes ^^^^^^^^^ - * #10641 / TypeAheadTag: Fehler beim gleichzeitigen anlegen mehrerer neuer Tags - * #10794 / Documentation: Crontab entry more clearly +* #10641 / TypeAheadTag: Fehler beim gleichzeitigen anlegen mehrerer neuer Tags +* #10794 / Documentation: Crontab entry more clearly Version 20.6.1 @@ -833,27 +1041,27 @@ Date: 14.06.2020 Notes ^^^^^ - * Add note in Installation.rst to start Apache with Locale en_US.UTF-8. This helps to support Umlaut and other characters - in filenames and wkhtml commandline options (like header/footer). - * Migrate documentation from T3 to ReadTheDocs.io - looks older but 'search' is much more better. New: chapters separated - in individual files. - * For the image to PDF feature, installation of `img2pdf` is required (please check :ref:`preparation`). +* Add note in Installation.rst to start Apache with Locale en_US.UTF-8. This helps to support Umlaut and other characters + in filenames and wkhtml commandline options (like header/footer). +* Migrate documentation from T3 to ReadTheDocs.io - looks older but 'search' is much more better. New: chapters separated + in individual files. +* For the image to PDF feature, installation of `img2pdf` is required (please check :ref:`preparation`). Features ^^^^^^^^ - * #10751 / Allow images to be concatenated for PDF download. - * Fontawesome updated 5.13. - * Extend FE.label size to 1023. - * Local documentation rendering directly via Sphinx. - * Manual: Search is working, table width not truncated anymore, PDF & epub export, redirect qfq.io/doc to docs.qfq.io. - * Update copyright notice. +* #10751 / Allow images to be concatenated for PDF download. +* Fontawesome updated 5.13. +* Extend FE.label size to 1023. +* Local documentation rendering directly via Sphinx. +* Manual: Search is working, table width not truncated anymore, PDF & epub export, redirect qfq.io/doc to docs.qfq.io. +* Update copyright notice. Bug Fixes ^^^^^^^^^ - * #10507 / FormElement.type: 'annotate' is defined two times in Enum - * #10705 / New function 'HelperFile::joinPathFilename($pre, $post)'. Joins only if $post is without leading slash. +* #10507 / FormElement.type: 'annotate' is defined two times in Enum +* #10705 / New function 'HelperFile::joinPathFilename($pre, $post)'. Joins only if $post is without leading slash. Version 20.4.1 diff --git a/extension/Resources/Private/Form/copyForm.json b/extension/Resources/Private/Form/copyForm.json index 99cfd193a84babdfcde1cfc99f98a93d37f72e04..963cc8d51d7f16aa16b9add325e233ac70d28640 100644 --- a/extension/Resources/Private/Form/copyForm.json +++ b/extension/Resources/Private/Form/copyForm.json @@ -67,7 +67,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41" @@ -107,7 +106,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41" @@ -147,7 +145,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41" @@ -187,7 +184,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41" @@ -227,7 +223,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41" @@ -267,7 +262,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41" diff --git a/extension/Resources/Private/Form/cron.json b/extension/Resources/Private/Form/cron.json index 73c032238c316819722f717f46951f68a1f9644b..cad94e44502adf928c18e8836939dc8d4b332764 100644 --- a/extension/Resources/Private/Form/cron.json +++ b/extension/Resources/Private/Form/cron.json @@ -67,7 +67,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41" @@ -107,7 +106,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41" @@ -147,7 +145,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41" @@ -187,7 +184,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41" @@ -227,7 +223,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41" @@ -267,7 +262,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41" @@ -307,7 +301,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41" @@ -347,7 +340,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41" @@ -387,7 +379,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41" @@ -427,7 +418,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41" @@ -467,7 +457,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41" @@ -507,7 +496,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41" @@ -547,7 +535,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41" diff --git a/extension/Resources/Private/Form/form.json b/extension/Resources/Private/Form/form.json index 76dce8636b95e357813492e822957432b93ba757..0ebb76222dc46e5160b76b23be432cffb0acd8e6 100644 --- a/extension/Resources/Private/Form/form.json +++ b/extension/Resources/Private/Form/form.json @@ -67,7 +67,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41" @@ -107,7 +106,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41" @@ -147,7 +145,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41" @@ -187,7 +184,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -228,7 +224,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -269,7 +264,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -310,7 +304,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -351,7 +344,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -392,7 +384,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -433,7 +424,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -474,7 +464,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -515,7 +504,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41" @@ -555,7 +543,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -596,7 +583,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -637,7 +623,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -678,7 +663,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -719,7 +703,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -760,7 +743,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -801,7 +783,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -842,7 +823,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41" @@ -882,7 +862,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -923,7 +902,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -964,7 +942,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -1005,7 +982,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -1046,7 +1022,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -1087,7 +1062,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -1128,7 +1102,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -1169,7 +1142,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -1210,7 +1182,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -1251,7 +1222,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41" @@ -1291,7 +1261,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -1332,7 +1301,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -1373,7 +1341,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41" @@ -1413,7 +1380,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -1454,7 +1420,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -1495,7 +1460,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -1536,7 +1500,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -1577,7 +1540,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -1618,7 +1580,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2022-06-03 15:18:07", "created": "2022-06-03 15:17:24" @@ -1658,7 +1619,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2022-06-03 15:19:42", "created": "2022-06-03 15:06:55", diff --git a/extension/Resources/Private/Form/formElement.json b/extension/Resources/Private/Form/formElement.json index f0b0ce561e6eae5217a3245a4d201b95d00052c9..d68c9c01ce18b8ffc9f6b4cd39e8a4cad015ebde 100644 --- a/extension/Resources/Private/Form/formElement.json +++ b/extension/Resources/Private/Form/formElement.json @@ -67,7 +67,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41" @@ -107,7 +106,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -148,7 +146,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -189,7 +186,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -230,7 +226,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2022-18-05 15:30:41", "created": "2022-18-05 15:30:41", @@ -270,7 +265,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2022-18-05 15:30:41", "created": "2022-18-05 15:30:41", @@ -311,7 +305,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -352,7 +345,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -393,7 +385,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -434,7 +425,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -475,7 +465,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -516,7 +505,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -557,7 +545,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -598,7 +585,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -639,7 +625,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -680,7 +665,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -721,7 +705,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -762,7 +745,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41" @@ -802,7 +784,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -843,7 +824,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -884,7 +864,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -925,7 +904,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -966,7 +944,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -1007,7 +984,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41" @@ -1047,7 +1023,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -1088,7 +1063,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -1129,7 +1103,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -1170,7 +1143,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -1211,7 +1183,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -1252,7 +1223,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -1293,7 +1263,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -1334,7 +1303,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -1375,7 +1343,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -1416,7 +1383,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -1457,7 +1423,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41" @@ -1497,7 +1462,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -1538,7 +1502,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -1579,7 +1542,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41", @@ -1620,7 +1582,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41" @@ -1660,7 +1621,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-06 12:05:41", "created": "2021-04-06 12:05:41" @@ -1702,7 +1662,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2022-08-28 09:41:12", "created": "2022-08-28 09:37:36" @@ -1744,7 +1703,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2022-08-28 09:40:13", "created": "2022-08-28 09:40:13", diff --git a/extension/Resources/Private/Form/formJson.json b/extension/Resources/Private/Form/formJson.json index f865068dbe469470339d5f983345e47d9c63fb81..c8dc30d700f537375cbd6710ff181507c505cc8f 100644 --- a/extension/Resources/Private/Form/formJson.json +++ b/extension/Resources/Private/Form/formJson.json @@ -67,7 +67,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-09 11:38:15", "created": "2021-03-22 11:57:51" @@ -107,7 +106,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-22 15:27:31", "created": "2021-03-22 11:44:22" @@ -147,7 +145,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-09 11:38:15", "created": "2021-04-08 10:40:09" @@ -187,7 +184,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-22 15:30:18", "created": "2021-04-22 15:11:12" @@ -227,7 +223,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-09 11:38:15", "created": "2021-03-24 14:44:04" @@ -267,7 +262,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-09 11:38:15", "created": "2021-03-30 09:13:40" @@ -307,7 +301,6 @@ "parameterLanguageC": "", "parameterLanguageD": "", "clientJs": "", - "feGroup": "", "deleted": "no", "modified": "2021-04-09 11:38:15", "created": "2021-03-30 09:14:48" diff --git a/extension/Resources/Private/Report/formEditor.qfqr b/extension/Resources/Private/Report/formEditor.qfqr index 23dc0be4d7010babd85d4352f31290bd7540b163..c233c99dee55c7b3d4fdd95f8ec721700926fe38 100644 --- a/extension/Resources/Private/Report/formEditor.qfqr +++ b/extension/Resources/Private/Report/formEditor.qfqr @@ -27,10 +27,10 @@ dbIndex={{indexQfq:Y}} 30 { # 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">' + sql = SELECT '<th data-sorter="false" class="filter-false" width="100px;"><div class="btn-group">' , CONCAT('p:{{pageSlug:T}}&form=form&') as _pagen - , CONCAT('p:{{pageSlug:T}}&form=formJson&|t:json') as _pagen - , '</th><th>Name' + , CONCAT('p:{{pageSlug:T}}&form=formJson&|t:json|b|s') as _page + , '</div></th><th>Name' , '</th><th>Title' , '</th><th>Table' , '</th><th>#' @@ -39,31 +39,33 @@ dbIndex={{indexQfq:Y}} , '</th><th><em>PageId</em></th>' FROM (SELECT '') AS fake WHERE {{formIdHistory:S0}}=0 - head = <div style="clear:both;"></div><div class="panel panel-default" style="width: min-content;min-width: fit-content;"><div class="panel-heading"><h2 style="margin:0;">FormEditor<span class="pull-right" style="margin-top:-4px;">{{&switchButton:RE::-}}</span></h2></div><div style="clear:both;"></div><div class="panel-body"> - <table class="table table-hover qfq-table-50 tablesorter tablesorter-filter" id="{{pageSlug:T}}-form"> + head = <div style="clear:both;"></div> + <div class="panel panel-default" style="width: min-content;min-width: fit-content;"> + <div class="panel-heading"> + <h2 style="margin:0;">FormEditor + <span class="pull-right" style="margin-top:-4px;"> + {{'p:{{pageSlug:T}}&stats=1|b|s|t:Statistic' AS _link}} {{&switchButton:RE::-}} + </span> + </h2> + </div> + <div style="clear:both;"></div> + <div class="panel-body"> + <table class="table table-hover qfq-table-50 tablesorter tablesorter-filter" id="{{pageSlug:T}}-form"> rbeg = <thead class="qfq-sticky"><tr> rend = </tr></thead><tbody> tail = </tbody></table></div> 40 { # All forms - sql = SELECT '<td>' + sql = SELECT '<td><div class="btn-group">' , CONCAT('p:{{pageSlug:T}}&form=form&r=', f.id) as _pagee - , CONCAT('p:{{pageSlug:T}}&form=formJson&r=', f.id, '|t:json') as _pagee - , '</td>' + , CONCAT('p:{{pageSlug:T}}&form=formJson&r=', f.id, '|t:json|s|b') as _page + , '</div></td>' , CONCAT(f.name, ' <span class="text-muted">(', f.id, ')</span>') , QMORE(strip_tags(f.title),50) , f.tableName - , CONCAT('p:{{pageSlug: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>') FROM Form AS f - LEFT JOIN FormSubmitLog AS fsl - ON fsl.formId=f.id - WHERE {{formIdHistory:S0}}=0 - GROUP BY f.id + WHERE '{{stats:S0}}'=0 ORDER BY f.name rbeg = <tr> rend = </tr> @@ -71,9 +73,41 @@ dbIndex={{indexQfq:Y}} fend = </td> fskipwrap = 1,2,3,4 } + + 50 { + sql = SELECT '' FROM (SELECT '') AS fake WHERE '{{stats:S0}}'=1 + + 60 { + # All forms + sql = SELECT '<td><div class="btn-group">' + , CONCAT('p:{{pageSlug:T}}&form=form&r=', f.id) as _pagee + , CONCAT('p:{{pageSlug:T}}&form=formJson&r=', f.id, '|t:json|s|b') as _page + , '</div></td>' + , CONCAT(f.name, ' <span class="text-muted">(', f.id, ')</span>') + , QMORE(strip_tags(f.title),50) + , f.tableName + , CONCAT('p:{{pageSlug: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>') + FROM Form AS f + LEFT JOIN FormSubmitLog AS fsl + ON fsl.formId=f.id + WHERE '{{stats:S0}}'=1 + GROUP BY f.id + ORDER BY f.name + rbeg = <tr> + rend = </tr> + fbeg = <td> + fend = </td> + fskipwrap = 1,2,3,4 + } + } + } -50 { +70 { # History of a Form {{formIdHistory:S0}} sql = SELECT f.name , fsl.feUser diff --git a/extension/Resources/Private/Report/searchRefactor.qfqr b/extension/Resources/Private/Report/searchRefactor.qfqr index b7d0741e7b0c1c063683165f6ace106b35a51510..8f40e798a37f8cd8437cf4124917b73b9ca4488a 100644 --- a/extension/Resources/Private/Report/searchRefactor.qfqr +++ b/extension/Resources/Private/Report/searchRefactor.qfqr @@ -8,6 +8,7 @@ # Search value in FormElement, Form, tt_content and pages # + dbIndex={{indexQfq:Y}} form={{form:SE}} @@ -23,7 +24,7 @@ form={{form:SE}} 20 { sql = SELECT IF('{{firstPageLoad:CE:alnumx}}' = '', 'yes', 'no') AS _firstPageLoad , IF('{{file:SE}}' != '', '{{file:S::w}}', '{{file:UE}}') AS '_=file' - , 'Search in following columns:\n\nFormElement:\tlabel, note, value, sql1, parameter, modeSql\nForm:\t\ttitle, forwardPage, parameter, tableName\nT3 tt_content:\tbodytext, header, subheader\nT3 pages:\ttitle, subtitle, alias' AS _tooltipTable + , 'Search in following columns:\n\nFormElement:\tid, label, note, value, sql1, parameter, modeSql\nForm:\t\tid, title, forwardPage, parameter, tableName\nT3 tt_content:\tuid, bodytext, header, subheader\nT3 pages:\tuid, title, subtitle, alias' AS _tooltipTable } 30 { @@ -104,7 +105,9 @@ form={{form:SE}} FROM FormElement AS fe WHERE '{{finalSearch:RE:all:Sm}}' <> '' - AND (fe.label LIKE '{{finalSearch:RE:all:Sm}}' + AND (fe.id LIKE '{{finalSearch:RE:all:Sm}}' + AND IF('{{excludedContent:RE:all:Sm}}' <> '', fe.id NOT LIKE CONCAT('%', '{{excludedContent:RE:all:Sm}}', '%'), 1=1) + OR fe.label LIKE '{{finalSearch:RE:all:Sm}}' AND IF('{{excludedContent:RE:all:Sm}}' <> '', fe.label NOT LIKE CONCAT('%', '{{excludedContent:RE:all:Sm}}', '%'), 1=1) OR fe.note LIKE '{{finalSearch:RE:all:Sm}}' AND IF('{{excludedContent:RE:all:Sm}}' <> '', fe.note NOT LIKE CONCAT('%', '{{excludedContent:RE:all:Sm}}', '%'), 1=1) @@ -136,7 +139,10 @@ form={{form:SE}} , fe.enabled , fe.class , CONCAT( - IF(fe.label LIKE '{{finalSearch:RE:all:Sm}}' + IF(fe.id LIKE '{{finalSearch:RE:all:Sm}}' + AND IF('{{excludedContent:RE:all:Sm}}' <> '', fe.id NOT LIKE CONCAT('%', '{{excludedContent:RE:all:Sm}}', '%'), 1=1) + , CONCAT('<strong>ID:</strong><br>', QMARK(fe.id, '{{search:RE:all:Sm}}')), '') + , IF(fe.label LIKE '{{finalSearch:RE:all:Sm}}' AND IF('{{excludedContent:RE:all:Sm}}' <> '', fe.label NOT LIKE CONCAT('%', '{{excludedContent:RE:all:Sm}}', '%'), 1=1) , CONCAT('<strong>Label:</strong><br>', QMARK(fe.label, '{{search:RE:all:Sm}}')), '') , IF(fe.note LIKE '{{finalSearch:RE:all:Sm}}' @@ -157,7 +163,9 @@ form={{form:SE}} FROM FormElement AS fe WHERE '{{finalSearch:RE:all:Sm}}' <> '' - AND (fe.label LIKE '{{finalSearch:RE:all:Sm}}' + AND (fe.id LIKE '{{finalSearch:RE:all:Sm}}' + AND IF('{{excludedContent:RE:all:Sm}}' <> '', fe.id NOT LIKE CONCAT('%', '{{excludedContent:RE:all:Sm}}', '%'), 1=1) + OR fe.label LIKE '{{finalSearch:RE:all:Sm}}' AND IF('{{excludedContent:RE:all:Sm}}' <> '', fe.label NOT LIKE CONCAT('%', '{{excludedContent:RE:all:Sm}}', '%'), 1=1) OR fe.note LIKE '{{finalSearch:RE:all:Sm}}' AND IF('{{excludedContent:RE:all:Sm}}' <> '', fe.note NOT LIKE CONCAT('%', '{{excludedContent:RE:all:Sm}}', '%'), 1=1) @@ -193,7 +201,9 @@ form={{form:SE}} FROM Form AS f WHERE '{{finalSearch:RE:all:Sm}}' <> '' - AND (f.title LIKE '{{finalSearch:RE:all:Sm}}' + AND (f.id LIKE '{{finalSearch:RE:all:Sm}}' + AND IF('{{excludedContent:RE:all:Sm}}' <> '', f.id NOT LIKE CONCAT('%', '{{excludedContent:RE:all:Sm}}', '%'), 1=1) + OR f.title LIKE '{{finalSearch:RE:all:Sm}}' AND IF('{{excludedContent:RE:all:Sm}}' <> '', f.title NOT LIKE CONCAT('%', '{{excludedContent:RE:all:Sm}}', '%'), 1=1) OR f.forwardPage LIKE '{{finalSearch:RE:all:Sm}}' AND IF('{{excludedContent:RE:all:Sm}}' <> '', f.forwardPage NOT LIKE CONCAT('%', '{{excludedContent:RE:all:Sm}}', '%'), 1=1) @@ -216,7 +226,10 @@ form={{form:SE}} , f.id , f.name , f.tableName - , CONCAT(IF(f.title LIKE '{{finalSearch:RE:all:Sm}}' + , CONCAT(IF(f.id LIKE '{{finalSearch:RE:all:Sm}}' + AND IF('{{excludedContent:RE:all:Sm}}' <> '', f.id NOT LIKE CONCAT('%', '{{excludedContent:RE:all:Sm}}', '%'), 1=1) + , CONCAT('<strong>ID:</strong><br>', QMARK(f.id, '{{search:RE:all:Sm}}')),'') + , IF(f.title LIKE '{{finalSearch:RE:all:Sm}}' AND IF('{{excludedContent:RE:all:Sm}}' <> '', f.title NOT LIKE CONCAT('%', '{{excludedContent:RE:all:Sm}}', '%'), 1=1) , CONCAT('<strong>Title:</strong><br>', QMARK(f.title, '{{search:RE:all:Sm}}')),'') , IF(f.forwardPage LIKE '{{finalSearch:RE:all:Sm}}' @@ -231,7 +244,9 @@ form={{form:SE}} FROM Form AS f WHERE '{{finalSearch:RE:all:Sm}}' <> '' - AND (f.title LIKE '{{finalSearch:RE:all:Sm}}' + AND (f.id LIKE '{{finalSearch:RE:all:Sm}}' + AND IF('{{excludedContent:RE:all:Sm}}' <> '', f.id NOT LIKE CONCAT('%', '{{excludedContent:RE:all:Sm}}', '%'), 1=1) + OR f.title LIKE '{{finalSearch:RE:all:Sm}}' AND IF('{{excludedContent:RE:all:Sm}}' <> '', f.title NOT LIKE CONCAT('%', '{{excludedContent:RE:all:Sm}}', '%'), 1=1) OR f.forwardPage LIKE '{{finalSearch:RE:all:Sm}}' AND IF('{{excludedContent:RE:all:Sm}}' <> '', f.forwardPage NOT LIKE CONCAT('%', '{{excludedContent:RE:all:Sm}}', '%'), 1=1) @@ -264,12 +279,15 @@ form={{form:SE}} FROM {{dbNameT3:Y}}.tt_content AS r WHERE '{{finalSearch:RE:all:Sm}}' != '' - AND (r.bodytext LIKE '{{finalSearch:RE:all:Sm}}' + AND (r.uid LIKE '{{finalSearch:RE:all:Sm}}' + AND IF('{{excludedContent:RE:all:Sm}}' != '', r.uid NOT LIKE CONCAT('%', '{{excludedContent:RE:all:Sm}}', '%'), 1=1) + OR r.bodytext LIKE '{{finalSearch:RE:all:Sm}}' AND IF('{{excludedContent:RE:all:Sm}}' != '', r.bodytext NOT LIKE CONCAT('%', '{{excludedContent:RE:all:Sm}}', '%'), 1=1) OR r.header LIKE '{{finalSearch:RE:all:Sm}}' AND IF('{{excludedContent:RE:all:Sm}}' != '', r.header NOT LIKE CONCAT('%', '{{excludedContent:RE:all:Sm}}', '%'), 1=1) OR r.subheader LIKE '{{finalSearch:RE:all:Sm}}' AND IF('{{excludedContent:RE:all:Sm}}' != '', r.subheader NOT LIKE CONCAT('%', '{{excludedContent:RE:all:Sm}}', '%'), 1=1)) + AND r.deleted = 0 LIMIT 1 @@ -279,15 +297,19 @@ form={{form:SE}} fend = </th> } - # SQL statement will find and list all the relevant persons (only executed if a search term is entered) + # SQL statement will find and list all the relevant tt_content (only executed if a search term is entered) 150 { - sql = SELECT CONCAT('i:uid=', r.uid, '|b:btn btn-default') AS _link + sql = SELECT IF(r.hidden = 0, '<tr>', '<tr class="text-muted">') AS _noWrap + , CONCAT('i:uid=', r.uid, '|b:btn btn-default') AS _link , r.uid , r.pid , r.header , r.subheader , IF(r.hidden = 0, 'No', 'Yes') - , CONCAT(IF(r.bodytext LIKE '{{finalSearch:RE:all:Sm}}' + , CONCAT(IF(r.uid LIKE '{{finalSearch:RE:all:Sm}}' + AND IF('{{excludedContent:RE:all:Sm}}' <> '', r.uid NOT LIKE CONCAT('%', '{{excludedContent:RE:all:Sm}}', '%'), 1=1) + , CONCAT('<strong>Uid:</strong><br>', QMARK(r.uid, '{{search:RE:all:Sm}}')),'') + , IF(r.bodytext LIKE '{{finalSearch:RE:all:Sm}}' AND IF('{{excludedContent:RE:all:Sm}}' <> '', r.bodytext NOT LIKE CONCAT('%', '{{excludedContent:RE:all:Sm}}', '%'), 1=1) , CONCAT('<strong>Bodytext:</strong><br>', QMARK(r.bodytext, '{{search:RE:all:Sm}}')),'') , IF(r.header LIKE '{{finalSearch:RE:all:Sm}}' @@ -300,7 +322,9 @@ form={{form:SE}} FROM {{dbNameT3:Y}}.tt_content AS r WHERE '{{finalSearch:RE:all:Sm}}' != '' - AND (r.bodytext LIKE '{{finalSearch:RE:all:Sm}}' + AND (r.uid LIKE '{{finalSearch:RE:all:Sm}}' + AND IF('{{excludedContent:RE:all:Sm}}' != '', r.uid NOT LIKE CONCAT('%', '{{excludedContent:RE:all:Sm}}', '%'), 1=1) + OR r.bodytext LIKE '{{finalSearch:RE:all:Sm}}' AND IF('{{excludedContent:RE:all:Sm}}' != '', r.bodytext NOT LIKE CONCAT('%', '{{excludedContent:RE:all:Sm}}', '%'), 1=1) OR r.header LIKE '{{finalSearch:RE:all:Sm}}' AND IF('{{excludedContent:RE:all:Sm}}' != '', r.header NOT LIKE CONCAT('%', '{{excludedContent:RE:all:Sm}}', '%'), 1=1) @@ -313,7 +337,6 @@ form={{form:SE}} head = <tbody> tail = </tbody></table></div></div><input id='r-box-counter' value='{{30.60.130.150.line.count}}' type='hidden'> - rbeg = <tr> rend = </tr> fbeg = <td> fend = </td> @@ -331,7 +354,9 @@ form={{form:SE}} FROM {{dbNameT3:Y}}.pages AS p WHERE '{{finalSearch:RE:all:Sm}}' != '' - AND (p.title LIKE '{{finalSearch:RE:all:Sm}}' + AND (p.uid LIKE '{{finalSearch:RE:all:Sm}}' + AND IF('{{excludedContent:RE:all:Sm}}' != '', p.uid NOT LIKE CONCAT('%', '{{excludedContent:RE:all:Sm}}', '%'), 1=1) + OR p.title LIKE '{{finalSearch:RE:all:Sm}}' AND IF('{{excludedContent:RE:all:Sm}}' != '', p.title NOT LIKE CONCAT('%', '{{excludedContent:RE:all:Sm}}', '%'), 1=1) OR p.subtitle LIKE '{{finalSearch:RE:all:Sm}}' AND IF('{{excludedContent:RE:all:Sm}}' != '', p.subtitle NOT LIKE CONCAT('%', '{{excludedContent:RE:all:Sm}}', '%'), 1=1) @@ -349,13 +374,17 @@ form={{form:SE}} # SQL statement will find and list all the relevant pages (only executed if a search term is entered) 180 { - sql = SELECT p.uid + sql = SELECT IF(p.hidden = 0, '<tr>', '<tr class="text-muted">') AS _noWrap + , p.uid , p.pid , p.title , p.subtitle , p.{{columnNameAliasSlug:XE}} , IF(p.hidden = 0, 'No', 'Yes') - , CONCAT(IF(p.title LIKE '{{finalSearch:RE:all:Sm}}' + , CONCAT(IF(p.uid LIKE '{{finalSearch:RE:all:Sm}}' + AND IF('{{excludedContent:RE:all:Sm}}' <> '', p.uid NOT LIKE CONCAT('%', '{{excludedContent:RE:all:Sm}}', '%'), 1=1) + , CONCAT('<strong>Uid:</strong><br>', QMARK(p.uid, '{{search:RE:all:Sm}}')),'') + , IF(p.title LIKE '{{finalSearch:RE:all:Sm}}' AND IF('{{excludedContent:RE:all:Sm}}' <> '', p.title NOT LIKE CONCAT('%', '{{excludedContent:RE:all:Sm}}', '%'), 1=1) , CONCAT('<strong>Title:</strong><br>', QMARK(p.title, '{{search:RE:all:Sm}}')),'') , IF(p.subtitle LIKE '{{finalSearch:RE:all:Sm}}' @@ -369,7 +398,9 @@ form={{form:SE}} FROM {{dbNameT3:Y}}.pages AS p WHERE '{{finalSearch:RE:all:Sm}}' != '' - AND (p.title LIKE '{{finalSearch:RE:all:Sm}}' + AND (p.uid LIKE '{{finalSearch:RE:all:Sm}}' + AND IF('{{excludedContent:RE:all:Sm}}' != '', p.uid NOT LIKE CONCAT('%', '{{excludedContent:RE:all:Sm}}', '%'), 1=1) + OR p.title LIKE '{{finalSearch:RE:all:Sm}}' AND IF('{{excludedContent:RE:all:Sm}}' != '', p.title NOT LIKE CONCAT('%', '{{excludedContent:RE:all:Sm}}', '%'), 1=1) OR p.subtitle LIKE '{{finalSearch:RE:all:Sm}}' AND IF('{{excludedContent:RE:all:Sm}}' != '', p.subtitle NOT LIKE CONCAT('%', '{{excludedContent:RE:all:Sm}}', '%'), 1=1) @@ -382,14 +413,13 @@ form={{form:SE}} head = <tbody> tail = </tbody></table><input id='p-box-counter' value='{{30.60.160.180.line.count}}' type='hidden'> - rbeg = <tr> renr = </tr> fbeg = <td> fend = </td> # Iterate over all tt_contents from each page and build edit button 190 { - sql = SELECT CONCAT('i:uid=', r.uid, '|t:', r.uid, '|b:btn btn-default') AS _link + sql = SELECT CONCAT('i:uid=', r.uid, '|t:', r.uid, '|b:btn btn-', IF(r.hidden = 0, 'default', 'warning|o:tt_content is disabled')) AS _link , ' ' FROM {{dbNameT3:Y}}.pages AS p, {{dbNameT3:Y}}.tt_content AS r WHERE p.uid = {{pageId:R0}} @@ -402,7 +432,15 @@ form={{form:SE}} } } + # If no results are found, show a message 200 { + sql = SELECT '<h2 class="alert alert-info">Nothing found in tables.</h2>' + FROM (SELECT '') AS fake + WHERE ('{{30.60.70.80.line.total}}' + '{{30.60.100.110.line.total}}' + '{{30.60.130.140.line.total}}' + '{{30.60.160.170.line.total}}') = 0 + AND '{{search:CE:all:Sm}}' != '' + } + + 210 { sql = SELECT '<script> $(document).ready(function() { var resultBoxes = document.getElementsByClassName("result-box-counter"); Array.from(resultBoxes).forEach(setCounter); diff --git a/extension/Resources/Public/icons/bullet-orange.gif b/extension/Resources/Public/icons/bullet-orange.gif new file mode 100644 index 0000000000000000000000000000000000000000..dde24273078c6ad8222744aa5893e1bfdf2274e2 Binary files /dev/null and b/extension/Resources/Public/icons/bullet-orange.gif differ diff --git a/extension/Tests/Readme.md b/extension/Tests/Readme.md index 3b973c2e0414d7b15bfdd7610d9d8a7229185a77..08dc3be6033fffb07bd9a861404c8ef9473cf6c3 100644 --- a/extension/Tests/Readme.md +++ b/extension/Tests/Readme.md @@ -1,24 +1 @@ - -# QFQ Tests - -## PhpUnit - -### Run unit tests from CLI: - -Make sure dev dependencies are installed: -```shell script -# in extension directory -composer update --dev -``` - -Run all tests: -```shell script -# in extension directory -vendor/bin/phpunit --configuration phpunit.xml -``` - -Run single test: -```shell script -# in extension directory -vendor/bin/phpunit --configuration phpunit.xml --filter <test_name> -``` +Please check: Documentation-develop/PHPUNIT.md \ No newline at end of file diff --git a/extension/Tests/Unit/Core/BodytextParserTest.php b/extension/Tests/Unit/Core/BodytextParserTest.php index 40da513367c2298e1c32f118cc446f175906c315..b069ba46657ff45d209644b50d5d7ef48c448d62 100644 --- a/extension/Tests/Unit/Core/BodytextParserTest.php +++ b/extension/Tests/Unit/Core/BodytextParserTest.php @@ -16,7 +16,7 @@ use PHPUnit\Framework\TestCase; * Class BodytextParserTest * @package qfq */ -class BodytextParserTest extends TestCase { +final class BodytextParserTest extends TestCase { public function testProcessPlain() { $btp = new BodytextParser(); @@ -350,23 +350,24 @@ EOF; } /** - * @expectedException \UserFormException * */ public function testProcessExceptionClose() { $btp = new BodytextParser(); + $this->expectException(\UserFormException::class); + // Nested: unmatched close bracket $btp->process("10.sql = SELECT 'Hello World'\n } \n30.sql = SELECT 'Hello World'\n"); } /** - * @expectedException \UserFormException * */ public function testProcessExceptionOpen() { $btp = new BodytextParser(); + $this->expectException(\UserFormException::class); // Nested: unmatched open bracket $btp->process("10.sql = SELECT 'Hello World'\n20 { \n30.sql = SELECT 'Hello World'\n"); diff --git a/extension/Tests/Unit/Core/BuildFormPlainTest.php b/extension/Tests/Unit/Core/BuildFormPlainTest.php index 3aea5e070b5184eb1f170c629ee3b3d143dc3b34..0aab8fdfb1cab35134867d1e4cf55e7ec8479e19 100644 --- a/extension/Tests/Unit/Core/BuildFormPlainTest.php +++ b/extension/Tests/Unit/Core/BuildFormPlainTest.php @@ -6,8 +6,6 @@ namespace IMATHUZH\Qfq\Tests\Unit\Core; use IMATHUZH\Qfq\Core\BuildFormPlain; - -use IMATHUZH\Qfq\Core\Helper\Path; use IMATHUZH\Qfq\Core\Helper\Support; use IMATHUZH\Qfq\Core\QuickFormQuery; use IMATHUZH\Qfq\Tests\Unit\Core\Database\AbstractDatabaseTest; @@ -42,7 +40,7 @@ class BuildFormPlainTest extends AbstractDatabaseTest { $build = new BuildFormPlain([F_DB_INDEX => DB_INDEX_DEFAULT], array(), array(), $this->dbArray); $formId1 = $build->getFormId(); - $this->assertRegExp('/qfq-form-[0-9a-f]{13}/', $formId1); + $this->assertMatchesRegularExpression('/qfq-form-[0-9a-f]{13}/', $formId1); $formId2 = $build->getFormId(); $this->assertEquals($formId1, $formId2); @@ -478,14 +476,20 @@ class BuildFormPlainTest extends AbstractDatabaseTest { $result = $build->buildSubrecord($formElement, 'name:1', '', $json); $this->assertEquals('<table class="' . SUBRECORD_TABLE_CLASS_DEFAULT . '" id="123-123" ><caption class="qfq-subrecord-title">Name</caption><thead><tr><th>Name</th></tr></thead><tbody ><tr class="record" ><td><b>Doe</b></td></tr><tr class="record" ><td><b>Smith</b></td></tr></tbody></table>', $result); + // _id: 1, icon: bullet-green.gif + $formElement['sql1'] = $this->dbArray[DB_INDEX_DEFAULT]->sql('SELECT id AS "_id", "bullet-green.gif" AS "Status|icon" FROM Person ORDER BY id LIMIT 2'); + $result = $build->buildSubrecord($formElement, 'name:1', '', $json); + // For unknown reason: $baseUrl is empty here when run on gitlab runner if (($this->baseUrl ?? '') == '') { $this->baseUrl = HTTP_EXAMPLE_COM; } - // _id: 1, icon: bullet-green.gif - $formElement['sql1'] = $this->dbArray[DB_INDEX_DEFAULT]->sql('SELECT id AS "_id", "bullet-green.gif" AS "Status|icon" FROM Person ORDER BY id LIMIT 2'); - $result = $build->buildSubrecord($formElement, 'name:1', '', $json); - $expected = '<table class="' . SUBRECORD_TABLE_CLASS_DEFAULT . '" id="123-123" ><caption class="qfq-subrecord-title">Name</caption><thead><tr><th>Status</th></tr></thead><tbody ><tr class="record" ><td><image src=\'' . $this->baseUrl . 'typo3conf/ext/qfq/Resources/Public/icons/bullet-green.gif\'></td></tr><tr class="record" ><td><image src=\'' . $this->baseUrl . 'typo3conf/ext/qfq/Resources/Public/icons/bullet-green.gif\'></td></tr></tbody></table>'; + + $baseUrl = $this->baseUrl; +// $baseUrl = 'http://failed.to.set.baseUrl/'; +// $baseUrl = 'http://example.com/'; + + $expected = '<table class="' . SUBRECORD_TABLE_CLASS_DEFAULT . '" id="123-123" ><caption class="qfq-subrecord-title">Name</caption><thead><tr><th>Status</th></tr></thead><tbody ><tr class="record" ><td><image src=\'' . $baseUrl . 'typo3conf/ext/qfq/Resources/Public/icons/bullet-green.gif\'></td></tr><tr class="record" ><td><image src=\'' . $baseUrl . 'typo3conf/ext/qfq/Resources/Public/icons/bullet-green.gif\'></td></tr></tbody></table>'; $this->assertEquals($expected, $result); // _id: 1, mailto: john@doe.com @@ -521,7 +525,7 @@ class BuildFormPlainTest extends AbstractDatabaseTest { // _id: 1, name: Doe, link $formElement['sql1'] = $this->dbArray[DB_INDEX_DEFAULT]->sql('SELECT id AS "_id", name, CONCAT("s:1|p:form&form=person&r=" , id , "|t:", name) AS "link" FROM Person ORDER BY id LIMIT 2'); $result = $build->buildSubrecord($formElement, 'name:1', '', $json); - $this->assertEquals('<table class="' . SUBRECORD_TABLE_CLASS_DEFAULT . '" id="123-123" ><caption class="qfq-subrecord-title">Name</caption><thead><tr><th>name</th><th></th></tr></thead><tbody ><tr class="record" ><td>Doe</td><td><a href="' . $this->baseUrl . 'form?s=badcaffee1234" >Doe</a></td></tr><tr class="record" ><td>Smith</td><td><a href="' . $this->baseUrl . 'form?s=badcaffee1234" >Smith</a></td></tr></tbody></table>', $result); + $this->assertEquals('<table class="' . SUBRECORD_TABLE_CLASS_DEFAULT . '" id="123-123" ><caption class="qfq-subrecord-title">Name</caption><thead><tr><th>name</th><th></th></tr></thead><tbody ><tr class="record" ><td>Doe</td><td><a href="' . $baseUrl . 'form?s=badcaffee1234" >Doe</a></td></tr><tr class="record" ><td>Smith</td><td><a href="' . $baseUrl . 'form?s=badcaffee1234" >Smith</a></td></tr></tbody></table>', $result); } /** @@ -601,7 +605,7 @@ class BuildFormPlainTest extends AbstractDatabaseTest { /** * */ - protected function setUp() { + protected function setUp(): void { $_GET['form'] = 'phpunit_person'; diff --git a/extension/Tests/Unit/Core/Database/AbstractDatabaseTest.php b/extension/Tests/Unit/Core/Database/AbstractDatabaseTest.php index f010d3f79c7cace2da79596537b46bc9ad665dc3..302823c0ef349c34dbfa1e68864d5b0062c81d05 100644 --- a/extension/Tests/Unit/Core/Database/AbstractDatabaseTest.php +++ b/extension/Tests/Unit/Core/Database/AbstractDatabaseTest.php @@ -6,7 +6,6 @@ namespace IMATHUZH\Qfq\Tests\Unit\Core\Database; use IMATHUZH\Qfq\Core\Database\Database; - use IMATHUZH\Qfq\Core\Store\Sip; use IMATHUZH\Qfq\Core\Store\Store; use PHPUnit\Framework\TestCase; @@ -65,7 +64,7 @@ abstract class AbstractDatabaseTest extends TestCase { * @throws \UserReportException * @throws \Exception */ - protected function setUp() { + protected function setUp(): void { // Init the store also reads db credential configuration $this->store = Store::getInstance('', true); @@ -74,6 +73,7 @@ abstract class AbstractDatabaseTest extends TestCase { $this->sip->sipUniqId('badcaffee1234'); $dbName = $this->store->getVar('DB_1_NAME', STORE_SYSTEM); + $dbserver = $this->store->getVar('DB_1_SERVER', STORE_SYSTEM); if ($dbName == '') { throw new \CodeException('Missing DB_1_NAME in ' . CONFIG_QFQ_PHP, ERROR_MISSING_REQUIRED_PARAMETER); @@ -81,6 +81,8 @@ abstract class AbstractDatabaseTest extends TestCase { if (strpos($dbName, '_phpunit') === false) { $dbName .= '_phpunit'; $this->store->setVar('DB_1_NAME', $dbName, STORE_SYSTEM); +// $dbserver .= '_phpunit'; +// $this->store->setVar('DB_1_SERVER', $dbserver, STORE_SYSTEM); } } diff --git a/extension/Tests/Unit/Core/Database/DatabaseFunction.php b/extension/Tests/Unit/Core/Database/DatabaseFunctionTest.php similarity index 98% rename from extension/Tests/Unit/Core/Database/DatabaseFunction.php rename to extension/Tests/Unit/Core/Database/DatabaseFunctionTest.php index 5e6f96a3320edab65cd71fa7fe89503a316b792b..2fd1662ef8144f5a784de48eaf051f1870bd281b 100644 --- a/extension/Tests/Unit/Core/Database/DatabaseFunction.php +++ b/extension/Tests/Unit/Core/Database/DatabaseFunctionTest.php @@ -10,7 +10,7 @@ require_once(__DIR__ . '/AbstractDatabaseTest.php'); /** * Class DatabaseTest */ -class DatabaseFunction extends AbstractDatabaseTest { +class DatabaseFunctionTest extends AbstractDatabaseTest { /** * @var \IMATHUZH\Qfq\Core\Database\Database @@ -117,7 +117,7 @@ class DatabaseFunction extends AbstractDatabaseTest { * @throws \UserReportException * @throws \Exception */ - protected function setUp() { + protected function setUp(): void { parent::setUp(); $this->db = $this->dbArray[DB_INDEX_DEFAULT]; diff --git a/extension/Tests/Unit/Core/Database/DatabaseTest.php b/extension/Tests/Unit/Core/Database/DatabaseTest.php index 8922e3992f161fab731e20080951759f7f5237d1..ca84a68a962ab5e2847ad057350f9c8a316a54f4 100644 --- a/extension/Tests/Unit/Core/Database/DatabaseTest.php +++ b/extension/Tests/Unit/Core/Database/DatabaseTest.php @@ -177,79 +177,76 @@ class DatabaseTest extends AbstractDatabaseTest { } /** - * @expectedException DbException - * * @throws \CodeException * @throws \DbException * @throws \UserFormException */ public function testSqlExceptionExpect0() { + + $this->expectException(\DbException::class); + $data = $this->dbArray[DB_INDEX_DEFAULT]->sql('SELECT * FROM Person ORDER BY id LIMIT 1', ROW_EXPECT_0); } /** - * @expectedException DbException - * * @throws \CodeException * @throws \DbException * @throws \UserFormException */ public function testSqlExceptionExpect1_0() { + + $this->expectException(\DbException::class); + $data = $this->dbArray[DB_INDEX_DEFAULT]->sql('SELECT * FROM Person ORDER BY id LIMIT 0', ROW_EXPECT_1); } /** - * @expectedException DbException - * * @throws \CodeException * @throws \DbException * @throws \UserFormException */ public function testSqlExceptionExpect1_2() { + $this->expectException(\DbException::class); $data = $this->dbArray[DB_INDEX_DEFAULT]->sql('SELECT * FROM Person ORDER BY id ', ROW_EXPECT_1); } /** - * @expectedException DbException - * * @throws \CodeException * @throws \DbException * @throws \UserFormException */ public function testSqlExceptionExpect01() { + $this->expectException(\DbException::class); $data = $this->dbArray[DB_INDEX_DEFAULT]->sql('SELECT * FROM Person ORDER BY id ', ROW_EXPECT_0_1); } /** - * @expectedException DbException - * * @throws \CodeException * @throws \DbException * @throws \UserFormException */ public function testSqlExceptionExpectGE1() { + $this->expectException(\DbException::class); $data = $this->dbArray[DB_INDEX_DEFAULT]->sql('SELECT * FROM Person ORDER BY id LIMIT 0', ROW_EXPECT_GE_1); } /** - * @expectedException DbException - * * @throws \CodeException * @throws \DbException * @throws \UserFormException */ public function testSanitizeException() { + $this->expectException(\DbException::class); $this->dbArray[DB_INDEX_DEFAULT]->sql('some garbage'); } /** - * @expectedException DbException - * * @throws \CodeException * @throws \DbException * @throws \UserFormException */ public function testReportException() { + $this->expectException(\DbException::class); $this->dbArray[DB_INDEX_DEFAULT]->sql("INSERT INTO Person (`id`,`name`) VALUES (1,'test')"); } @@ -439,14 +436,14 @@ class DatabaseTest extends AbstractDatabaseTest { ['Field' => 'adrId', 'Type' => 'int(11)', 'Null' => 'NO', 'Key' => '', 'Default' => '0', 'Extra' => ''], ['Field' => 'gender', 'Type' => "enum('','male','female')", 'Null' => 'NO', 'Key' => '', 'Default' => 'male', 'Extra' => ''], ['Field' => 'groups', 'Type' => "set('','a','b','c')", 'Null' => 'NO', 'Key' => '', 'Default' => '', 'Extra' => ''], - ['Field' => 'modified', 'Type' => "timestamp", 'Null' => 'NO', 'Key' => '', 'Default' => 'current_timestamp()', 'Extra' => 'on update current_timestamp()'], - ['Field' => 'created', 'Type' => "datetime", 'Null' => 'NO', 'Key' => '', 'Default' => '', 'Extra' => ''], + ['Field' => 'modified', 'Type' => "datetime", 'Null' => 'YES', 'Key' => '', 'Default' => 'current_timestamp()', 'Extra' => 'on update current_timestamp()'], + ['Field' => 'created', 'Type' => "datetime", 'Null' => 'YES', 'Key' => '', 'Default' => null, 'Extra' => ''], ]; $version = $this->dbArray[DB_INDEX_DEFAULT]->sql("SELECT @@version;", ROW_IMPLODE_ALL); if ($version < "10.3") { # In 10.2,10.2: CURRENT_TIMESTAMP - $expected[6] = ['Field' => 'modified', 'Type' => "timestamp", 'Null' => 'NO', 'Key' => '', 'Default' => 'CURRENT_TIMESTAMP', 'Extra' => 'on update CURRENT_TIMESTAMP']; + $expected[6] = ['Field' => 'modified', 'Type' => "datetime", 'Null' => 'YES', 'Key' => '', 'Default' => 'CURRENT_TIMESTAMP', 'Extra' => 'on update CURRENT_TIMESTAMP']; } $this->assertEquals($expected, $this->dbArray[DB_INDEX_DEFAULT]->getTableDefinition('Person')); @@ -498,6 +495,9 @@ class DatabaseTest extends AbstractDatabaseTest { $this->assertEquals(true, $this->dbArray[DB_INDEX_DEFAULT]->hasLimit('SELECT ... LIMIT 1 , 1 ')); } + /** + * @return void + */ public function testMakeArrayDict() { $this->assertEquals(array(), $this->dbArray[DB_INDEX_DEFAULT]->makeArrayDict(array(), 'key', 'value')); @@ -540,6 +540,12 @@ class DatabaseTest extends AbstractDatabaseTest { } + /** + * @return void + * @throws \CodeException + * @throws \DbException + * @throws \UserFormException + */ public function testLeadingBrace() { $allRows = $this->dbArray[DB_INDEX_DEFAULT]->sql('(SELECT * FROM Person WHERE id=0)'); $this->assertEquals(array(), $allRows); @@ -559,12 +565,12 @@ class DatabaseTest extends AbstractDatabaseTest { * @throws \UserReportException * @throws \Exception */ - protected function setUp() { + protected function setUp(): void { parent::setUp(); // remove existing tables $allTables = $this->dbArray[DB_INDEX_DEFAULT]->sql("SHOW TABLES", ROW_REGULAR); - foreach ($allTables AS $val) { + foreach ($allTables as $val) { $table = current($val); $this->dbArray[DB_INDEX_DEFAULT]->sql("DROP TABLE $table"); } diff --git a/extension/Tests/Unit/Core/Database/DatabaseUpdateTest.php b/extension/Tests/Unit/Core/Database/DatabaseUpdateTest.php index 1b2a94bb3caf17fe8e64c29a88fc90ce962f537a..f57018cf9dbd9bf44f6779b81f17740a42a207ac 100644 --- a/extension/Tests/Unit/Core/Database/DatabaseUpdateTest.php +++ b/extension/Tests/Unit/Core/Database/DatabaseUpdateTest.php @@ -68,12 +68,12 @@ class DatabaseUpdateTest extends AbstractDatabaseTest { * @throws \UserFormException * @throws \UserReportException */ - protected function setUp() { + protected function setUp(): void { parent::setUp(); // remove existing tables $allTables = $this->dbArray[DB_INDEX_DEFAULT]->sql("SHOW TABLES", ROW_REGULAR); - foreach ($allTables AS $val) { + foreach ($allTables as $val) { $table = current($val); $this->dbArray[DB_INDEX_DEFAULT]->sql("DROP TABLE $table"); } diff --git a/extension/Tests/Unit/Core/Database/fixtures/Generic.sql b/extension/Tests/Unit/Core/Database/fixtures/Generic.sql index 4093300e8e992e00662adf30da39066cd42f75fe..bcf4f651a8e6eb133d3e8651e86231ba50c204fa 100644 --- a/extension/Tests/Unit/Core/Database/fixtures/Generic.sql +++ b/extension/Tests/Unit/Core/Database/fixtures/Generic.sql @@ -1,20 +1,22 @@ +/* Errors in SQL statement (after the first one) are not reported!!! */ + DROP TABLE IF EXISTS Person; -CREATE TABLE Person ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, - name VARCHAR(128), - firstName VARCHAR(128), - adrId INT(11) NOT NULL DEFAULT 0, - gender ENUM('', 'male', 'female') NOT NULL DEFAULT 'male', - groups SET('', 'a', 'b', 'c') NOT NULL DEFAULT '', - modified TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - created DATETIME NOT NULL +CREATE TABLE Person +( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(128), + firstName VARCHAR(128), + adrId INT(11) NOT NULL DEFAULT 0, + gender ENUM ('', 'male', 'female') NOT NULL DEFAULT 'male', + groups SET ('', 'a', 'b', 'c') NOT NULL DEFAULT '', + modified DATETIME NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + created DATETIME NULL DEFAULT NULL ); INSERT INTO Person (name, firstName, gender, groups) VALUES ('Doe', 'John', 'male', 'c'), ('Smith', 'Jane', 'female', 'a,c'); - DROP TABLE IF EXISTS Note; CREATE TABLE Note ( id BIGINT AUTO_INCREMENT PRIMARY KEY, diff --git a/extension/Tests/Unit/Core/Database/fixtures/TestFormEditor.sql b/extension/Tests/Unit/Core/Database/fixtures/TestFormEditor.sql index 4e6729a50ff31096d6848b58b6ae15bb2e91f2a6..f6c1aaeae7dbf42e0f1f84ca5235324e5e3e6ade 100644 --- a/extension/Tests/Unit/Core/Database/fixtures/TestFormEditor.sql +++ b/extension/Tests/Unit/Core/Database/fixtures/TestFormEditor.sql @@ -104,7 +104,6 @@ CREATE TABLE IF NOT EXISTS `FormElement` `parameterLanguageD` TEXT NOT NULL, `clientJs` TEXT NOT NULL, - `feGroup` VARCHAR(255) NOT NULL DEFAULT '', `deleted` ENUM ('yes', 'no') NOT NULL DEFAULT 'no', `modified` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, @@ -114,9 +113,7 @@ CREATE TABLE IF NOT EXISTS `FormElement` KEY `formId` (`formId`), KEY `formId_class_enabled_deleted` (`formId`, `class`, `enabled`, `deleted`), KEY `feIdContainer` (`feIdContainer`), - KEY `ord` (`ord`), - KEY `feGroup` (`feGroup`) - + KEY `ord` (`ord`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8 @@ -237,7 +234,6 @@ VALUES (130, 2, 'sql1', 'sql1', 'show', 'text', 'native', 130, '40,4', 255, '', '', '', '', '', '', 103, 'no', ''), (131, 2, 'parameter', 'Parameter', 'show', 'text', 'native', 130, '40,4', 255, '', '', '', '', '', '', 103, 'no', ''), (132, 2, 'clientJs', 'ClientJS', 'show', 'text', 'native', 130, 40, 255, '', '', '', '', '', '', 103, 'no', ''), - (133, 2, 'feGroup', 'feGroup', 'show', 'text', 'native', 130, 40, 255, '', '', '', '', '', '', 104, 'no', ''), (134, 2, 'debug', 'Debug', 'show', 'checkbox', 'native', 130, 0, 0, '', '', '', '', '', '', 104, 'no', ''), (135, 2, 'deleted', 'Deleted', 'show', 'checkbox', 'native', 400, 0, 0, '', '', '', '', '', '', 104, 'no', ''), (136, 2, 'modified', 'Modified', 'readonly', 'text', 'native', 410, 40, 20, '', '', '', '', '', '', 104, 'no', ''), diff --git a/extension/Tests/Unit/Core/DeleteTest.php b/extension/Tests/Unit/Core/DeleteTest.php index 67ec6f6d0973d764049c16fdc19a890f1fdf2f8d..dc6e3be95264125729d8088ba3b6ec0183778e93 100644 --- a/extension/Tests/Unit/Core/DeleteTest.php +++ b/extension/Tests/Unit/Core/DeleteTest.php @@ -22,10 +22,10 @@ require_once(__DIR__ . '/Database/AbstractDatabaseTest.php'); class DeleteTest extends AbstractDatabaseTest { /** - * @expectedException CodeException */ public function testProcessException() { + $this->expectException(\CodeException::class); $delete = new Delete(true); // empty 'form' not allowed @@ -33,10 +33,10 @@ class DeleteTest extends AbstractDatabaseTest { } /** - * @expectedException CodeException */ public function testProcessException2() { + $this->expectException(\CodeException::class); $delete = new Delete(true); // empty record id not allowed @@ -45,10 +45,10 @@ class DeleteTest extends AbstractDatabaseTest { } /** - * @expectedException CodeException */ public function testProcessException3() { + $this->expectException(\CodeException::class); $delete = new Delete(true); $formName = 'Person'; @@ -57,10 +57,10 @@ class DeleteTest extends AbstractDatabaseTest { } /** - * @expectedException CodeException */ public function testProcessException4() { + $this->expectException(\CodeException::class); $delete = new Delete(true); // unknown table not allowed @@ -68,7 +68,14 @@ class DeleteTest extends AbstractDatabaseTest { $delete->process($form, 0); } - protected function setUp() { + /** + * @return void + * @throws \CodeException + * @throws \DbException + * @throws \UserFormException + * @throws \UserReportException + */ + protected function setUp(): void { $this->store = Store::getInstance('form=TestFormName', true); parent::setUp(); @@ -89,8 +96,7 @@ class DeleteTest extends AbstractDatabaseTest { /** * @throws \UserFormException */ - protected function tearDown() - { + protected function tearDown(): void { parent::tearDown(); Path::findAbsoluteApp(); } diff --git a/extension/Tests/Unit/Core/EvaluateTest.php b/extension/Tests/Unit/Core/EvaluateTest.php index 700559dfa4a36d14c558e8974e7af16fc6279311..4c81ea2d9c22234d398985f8f962233c29ec5eed 100644 --- a/extension/Tests/Unit/Core/EvaluateTest.php +++ b/extension/Tests/Unit/Core/EvaluateTest.php @@ -104,8 +104,7 @@ class EvaluateTest extends AbstractDatabaseTest { $this->assertEquals('1', $eval->parse('{{SELECT count(id) FROM Person WHERE name="Zappa" AND firstname="Frank"}}')); // SHOW tables - $expected = "idbigint(20)NOPRIauto_incrementnamevarchar(128)YESfirstNamevarchar(128)YESadrIdint(11)NO0genderenum('','male','female')NOmalegroupsset('','a','b','c')NOmodifiedtimestampNOcurrent_timestamp()on update current_timestamp()createddatetimeNO"; - + $expected = "idbigint(20)NOPRIauto_incrementnamevarchar(128)YESfirstNamevarchar(128)YESadrIdint(11)NO0genderenum('','male','female')NOmalegroupsset('','a','b','c')NOmodifieddatetimeYEScurrent_timestamp()on update current_timestamp()createddatetimeYES"; $version = $this->dbArray[DB_INDEX_DEFAULT]->sql("SELECT @@version;", ROW_IMPLODE_ALL); if ($version < "10.3") { # In 10.2,10.2: CURRENT_TIMESTAMP @@ -592,8 +591,6 @@ class EvaluateTest extends AbstractDatabaseTest { } /** - * @expectedException \UserFormException - * * @throws \CodeException * @throws \DbException * @throws \UserFormException @@ -601,6 +598,7 @@ class EvaluateTest extends AbstractDatabaseTest { */ public function testVariableNotFoundException() { + $this->expectException(\UserFormException::class); $eval = new Evaluate($this->store, $this->dbArray[DB_INDEX_DEFAULT]); $eval->parse('go {{unknownVar:S::X}} stop'); @@ -608,7 +606,7 @@ class EvaluateTest extends AbstractDatabaseTest { /** */ - protected function setUp() { + protected function setUp(): void { try { $this->store = Store::getInstance('form=TestFormName', true); diff --git a/extension/Tests/Unit/Core/Form/DirtyTest.php b/extension/Tests/Unit/Core/Form/DirtyTest.php index b34db704a2824ca32e4fa3d26df49d542475610e..3fe8a8c1098c372b81aedfe20d9012d6e3f9df3f 100644 --- a/extension/Tests/Unit/Core/Form/DirtyTest.php +++ b/extension/Tests/Unit/Core/Form/DirtyTest.php @@ -625,10 +625,10 @@ class DirtyTest extends AbstractDatabaseTest { } /** - * @expectedException \UserFormException */ // public function testExclusiveAliceReleaseException() { // +// $this->expectException(\UserFormException::class); // $_GET[CLIENT_SIP] = $this->sip->queryStringToSip("input?r=1&form=lockExclusive", RETURN_SIP); // // // Alice release @@ -1150,8 +1150,6 @@ class DirtyTest extends AbstractDatabaseTest { /** * Lock non existing record * - * @expectedException dBException - * * @throws \CodeException * @throws \DbException * @throws \UserFormException @@ -1159,6 +1157,8 @@ class DirtyTest extends AbstractDatabaseTest { */ public function testLockNonExistingRecord() { + $this->expectException(\DbException::class); + // Create clean environment $this->clean('SessionCookieAlice'); @@ -1195,7 +1195,7 @@ class DirtyTest extends AbstractDatabaseTest { * @throws \UserFormException * @throws \UserReportException */ - protected function setUp() { + protected function setUp(): void { Session::getInstance(true); @@ -1218,7 +1218,7 @@ class DirtyTest extends AbstractDatabaseTest { /** * */ - protected function tearDown() { + protected function tearDown(): void { parent::tearDown(); self::$mysqli->real_query("DELETE FROM Form WHERE name='form' AND id!=1000"); diff --git a/extension/Tests/Unit/Core/Form/FormActionTest.php b/extension/Tests/Unit/Core/Form/FormActionTest.php index 931d8d0be19b5e7cb5db254b351c572f881e90c3..6d9f611ab6f8259355882ce60fc29b071a4f4a9a 100644 --- a/extension/Tests/Unit/Core/Form/FormActionTest.php +++ b/extension/Tests/Unit/Core/Form/FormActionTest.php @@ -70,7 +70,6 @@ class FormActionTest extends AbstractDatabaseTest { /** * Expect 0 recrod, but get 1 - * @expectedException \UserFormException * * @throws \CodeException * @throws \DbException @@ -82,6 +81,9 @@ class FormActionTest extends AbstractDatabaseTest { * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception */ public function testBeforeLoadException1() { + + $this->expectException(\UserFormException::class); + $formSpec[F_TABLE_NAME] = 'Person'; $formAction = new FormAction($formSpec, $this->dbArray, true); @@ -95,7 +97,6 @@ class FormActionTest extends AbstractDatabaseTest { /** * Expect 1 recrod, but get 0 - * @expectedException \UserFormException * * @throws \CodeException * @throws \DbException @@ -107,6 +108,9 @@ class FormActionTest extends AbstractDatabaseTest { * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception */ public function testBeforeLoadException0() { + + $this->expectException(\UserFormException::class); + $formSpec[F_TABLE_NAME] = 'Person'; $formAction = new FormAction($formSpec, $this->dbArray, true); @@ -120,7 +124,6 @@ class FormActionTest extends AbstractDatabaseTest { /** * Expect '0,1', but get 2 records - * @expectedException \UserFormException * * @throws \CodeException * @throws \DbException @@ -132,6 +135,8 @@ class FormActionTest extends AbstractDatabaseTest { * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception */ public function testBeforeLoadException2() { + $this->expectException(\UserFormException::class); + $formSpec[F_TABLE_NAME] = 'Person'; $formAction = new FormAction($formSpec, $this->dbArray, true); @@ -145,7 +150,6 @@ class FormActionTest extends AbstractDatabaseTest { /** * Expect '0,1', but get 2 records - * @expectedException \UserFormException * * @throws \CodeException * @throws \DbException @@ -157,6 +161,8 @@ class FormActionTest extends AbstractDatabaseTest { * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception */ public function testBeforeLoadException3() { + $this->expectException(\UserFormException::class); + $formSpec[F_TABLE_NAME] = 'Person'; $formAction = new FormAction($formSpec, $this->dbArray, true); @@ -208,7 +214,6 @@ class FormActionTest extends AbstractDatabaseTest { /** * Expect '0,1', but get 2 records - * @expectedException \UserFormException * * @throws \CodeException * @throws \DbException @@ -220,6 +225,8 @@ class FormActionTest extends AbstractDatabaseTest { * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception */ public function testBeforeLoadException5() { + $this->expectException(\UserFormException::class); + $formSpec[F_TABLE_NAME] = 'Person'; $formAction = new FormAction($formSpec, $this->dbArray, true); @@ -261,7 +268,6 @@ class FormActionTest extends AbstractDatabaseTest { /** * Do check for 2 action records, fail on second. - * @expectedException \UserFormException * * @throws \CodeException * @throws \DbException @@ -273,6 +279,9 @@ class FormActionTest extends AbstractDatabaseTest { * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception */ public function testBeforeLoadException6() { + + $this->expectException(\UserFormException::class); + $formSpec[F_TABLE_NAME] = 'Person'; $formAction = new FormAction($formSpec, $this->dbArray, true); @@ -427,7 +436,7 @@ class FormActionTest extends AbstractDatabaseTest { * @throws \UserFormException * @throws \UserReportException */ - protected function setUp() { + protected function setUp(): void { $this->store = Store::getInstance('form=TestFormName', true); parent::setUp(); diff --git a/extension/Tests/Unit/Core/Helper/HelperFileTest.php b/extension/Tests/Unit/Core/Helper/HelperFileTest.php index 13889927e72bd0ac01911ba80bb8cdb7922e0a3f..b11f6b03d6f43a651ee49232a7074175af197ca6 100644 --- a/extension/Tests/Unit/Core/Helper/HelperFileTest.php +++ b/extension/Tests/Unit/Core/Helper/HelperFileTest.php @@ -70,7 +70,7 @@ class HelperFileTest extends TestCase { /** * */ - protected function setUp() { + protected function setUp(): void { parent::setUp(); } diff --git a/extension/Tests/Unit/Core/Helper/HelperFormElementTest.php b/extension/Tests/Unit/Core/Helper/HelperFormElementTest.php index 5884a8d907f8ea4f5e8a91f5cd75b6a6766b56e8..f8c552f052321554cc8480e2dacee83894fa939a 100644 --- a/extension/Tests/Unit/Core/Helper/HelperFormElementTest.php +++ b/extension/Tests/Unit/Core/Helper/HelperFormElementTest.php @@ -58,14 +58,14 @@ class HelperFormElementTest extends TestCase { /** - * @expectedException \UserFormException - * * @throws \CodeException * @throws \UserFormException * @throws \UserReportException */ public function testExplodeFieldParameterException() { + $this->expectException(\UserFormException::class); + $a = [0 => ['id' => 111, 'name' => 'hello', 'collision' => 'dummy', 'parameter' => "work=12\ncollision=1:new,2:edit,3:delete"]]; HelperFormElement::explodeParameterInArrayElements($a, FE_PARAMETER); @@ -327,13 +327,13 @@ class HelperFormElementTest extends TestCase { // } // // /** -// * @expectedException \UserFormException // * // * @throws \CodeException // * @throws \UserFormException // * @throws \UserReportException // */ // public function testGetKeyValueListFromSqlEnumSpecException() { +// $this->expectException(\UserFormException::class); // $form = array(); // $formElement = array(); // @@ -349,21 +349,22 @@ class HelperFormElementTest extends TestCase { /** - * @expectedException \UserFormException */ public function testtestPenColorToHexException1() { + $this->expectException(\UserFormException::class); + // too short HelperFormElement::penColorToHex([FE_DEFAULT_PEN_COLOR => '12345']); } /** - * @expectedException \UserFormException - * */ public function testtestPenColorToHexException2() { + $this->expectException(\UserFormException::class); + // too long HelperFormElement::penColorToHex([FE_DEFAULT_PEN_COLOR => '1234567']); diff --git a/extension/Tests/Unit/Core/Helper/KeyValueStringParserTest.php b/extension/Tests/Unit/Core/Helper/KeyValueStringParserTest.php index 5175e8852c07c57c4c1a0716a0cedaa3d7b8badd..1a6d5a6cc4dec803652a742af2c6b77e33d18ed6 100644 --- a/extension/Tests/Unit/Core/Helper/KeyValueStringParserTest.php +++ b/extension/Tests/Unit/Core/Helper/KeyValueStringParserTest.php @@ -38,9 +38,9 @@ class KeyValueStringParserTest extends TestCase { } /** - * @expectedException \UserFormException */ public function testNoKey() { + $this->expectException(\UserFormException::class); KeyValueStringParser::parse(":value,key:value"); } diff --git a/extension/Tests/Unit/Core/Helper/OnStringTest.php b/extension/Tests/Unit/Core/Helper/OnStringTest.php index 4db31203321d05b9cd6a632611cbc6dbb8aa226d..8411ec6bd125544398caf13cd107b21f08ba90c1 100644 --- a/extension/Tests/Unit/Core/Helper/OnStringTest.php +++ b/extension/Tests/Unit/Core/Helper/OnStringTest.php @@ -168,10 +168,11 @@ class OnStringTest extends TestCase { } /** - * @expectedException \UserFormException - * + * */ public function testExtractFormRecordId_1() { + $this->expectException(\UserFormException::class); + $arrId = array(); $arrForm = array(); @@ -180,10 +181,11 @@ class OnStringTest extends TestCase { } /** - * @expectedException \UserFormException - * */ public function testExtractFormRecordId_2() { + + $this->expectException(\UserFormException::class); + $arrId = array(); $arrForm = array(); @@ -192,50 +194,47 @@ class OnStringTest extends TestCase { } /** - * @expectedException \UserFormException - * */ public function testRemoveNewlinesInNestedExpression_missingClosing_1() { + $this->expectException(\UserFormException::class); + $str = OnString::removeNewlinesInNestedExpression("Hi! {{Test "); } /** - * @expectedException \UserFormException - * */ public function testRemoveNewlinesInNestedExpression_missingClosing_2() { + $this->expectException(\UserFormException::class); + $str = OnString::removeNewlinesInNestedExpression("Hi! {{Test}"); } /** - * @expectedException \UserFormException - * */ public function testRemoveNewlinesInNestedExpression_missingClosing_3() { + $this->expectException(\UserFormException::class); + $str = OnString::removeNewlinesInNestedExpression("Hi! {{Test {{var }}"); } /** - * @expectedException \UserFormException - * */ public function testRemoveNewlinesInNestedExpression_missingOpening_1() { + $this->expectException(\UserFormException::class); $str = OnString::removeNewlinesInNestedExpression("}}"); } /** - * @expectedException \UserFormException - * */ public function testRemoveNewlinesInNestedExpression_missingOpening_2() { + $this->expectException(\UserFormException::class); $str = OnString::removeNewlinesInNestedExpression("{}}"); } /** - * @expectedException \UserFormException - * */ public function testRemoveNewlinesInNestedExpression_missingOpening_3() { + $this->expectException(\UserFormException::class); $str = OnString::removeNewlinesInNestedExpression("{{Test}}}}"); } diff --git a/extension/Tests/Unit/Core/Helper/SanitizeTest.php b/extension/Tests/Unit/Core/Helper/SanitizeTest.php index 0cb37bbd47f8d1be110c320dac1ea3aa53a4eaa2..c4fe128cb3b8c3219ea59bd2f9eb0b5d76de8961 100644 --- a/extension/Tests/Unit/Core/Helper/SanitizeTest.php +++ b/extension/Tests/Unit/Core/Helper/SanitizeTest.php @@ -226,16 +226,16 @@ class SanitizeTest extends TestCase { } /** - * @expectedException CodeException */ public function testSanitizeException() { + $this->expectException(\CodeException::class); Sanitize::sanitize('Hello World', 'invalid sanitize class'); } /** - * @expectedException \UserFormException */ public function testSanitizeExceptionCheckFailed() { + $this->expectException(\UserFormException::class); Sanitize::sanitize('string', SANITIZE_ALLOW_DIGIT, '', '', SANITIZE_EXCEPTION); } diff --git a/extension/Tests/Unit/Core/Helper/SupportTest.php b/extension/Tests/Unit/Core/Helper/SupportTest.php index b268fff49250266593034a4d43bfe715217862ca..3aa0f09d277f3642c9571be00a431e18e911a4ed 100644 --- a/extension/Tests/Unit/Core/Helper/SupportTest.php +++ b/extension/Tests/Unit/Core/Helper/SupportTest.php @@ -355,177 +355,181 @@ class SupportTest extends TestCase { } /** - * @expectedException \UserFormException */ public function testDateTimeGermanToInternationalException01() { + $this->expectException(\UserFormException::class); Support::dateTimeGermanToInternational('1'); } /** - * @expectedException \UserFormException */ public function testDateTimeGermanToInternationalException02() { + $this->expectException(\UserFormException::class); Support::dateTimeGermanToInternational('1.'); } /** - * @expectedException \UserFormException */ public function testDateTimeGermanToInternationalException03() { + $this->expectException(\UserFormException::class); Support::dateTimeGermanToInternational('1.1'); } /** - * @expectedException \UserFormException */ public function testDateTimeGermanToInternationalException04() { + $this->expectException(\UserFormException::class); Support::dateTimeGermanToInternational('1.1.'); } /** - * @expectedException \UserFormException */ public function testDateTimeGermanToInternationalException05() { + $this->expectException(\UserFormException::class); Support::dateTimeGermanToInternational('1.1.1'); } /** - * @expectedException \UserFormException */ public function testDateTimeGermanToInternationalException06() { + $this->expectException(\UserFormException::class); Support::dateTimeGermanToInternational('1.1.1.'); } /** - * @expectedException \UserFormException */ public function testDateTimeGermanToInternationalException07() { + $this->expectException(\UserFormException::class); Support::dateTimeGermanToInternational('1.1.1.1'); } /** - * @expectedException \UserFormException */ public function testDateTimeGermanToInternationalException08() { + $this->expectException(\UserFormException::class); Support::dateTimeGermanToInternational('123.1.11'); } /** - * @expectedException \UserFormException */ public function testDateTimeGermanToInternationalException09() { + $this->expectException(\UserFormException::class); Support::dateTimeGermanToInternational('1.123.11'); } /** - * @expectedException \UserFormException */ public function testDateTimeGermanToInternationalException10() { + $this->expectException(\UserFormException::class); Support::dateTimeGermanToInternational('1.1.123'); } /** - * @expectedException \UserFormException */ public function testDateTimeGermanToInternationalException11() { + $this->expectException(\UserFormException::class); Support::dateTimeGermanToInternational('1.1.12345'); } /** - * @expectedException \UserFormException */ public function testDateTimeGermanToInternationalException12() { + $this->expectException(\UserFormException::class); Support::dateTimeGermanToInternational('1-01-01'); } /** - * @expectedException \UserFormException */ public function testDateTimeGermanToInternationalException13() { + $this->expectException(\UserFormException::class); Support::dateTimeGermanToInternational('12-01-01'); } /** - * @expectedException \UserFormException */ public function testDateTimeGermanToInternationalException14() { + $this->expectException(\UserFormException::class); Support::dateTimeGermanToInternational('123-01-01'); } /** - * @expectedException \UserFormException */ public function testDateTimeGermanToInternationalException15() { + $this->expectException(\UserFormException::class); Support::dateTimeGermanToInternational('12345-01-01'); } /** - * @expectedException \UserFormException */ public function testDateTimeGermanToInternationalException16() { + $this->expectException(\UserFormException::class); Support::dateTimeGermanToInternational('1234-1-01'); } /** - * @expectedException \UserFormException */ public function testDateTimeGermanToInternationalException17() { + $this->expectException(\UserFormException::class); + $this->expectException(\UserFormException::class); Support::dateTimeGermanToInternational('1234-01-1'); } /** - * @expectedException \UserFormException */ public function testDateTimeGermanToInternationalException18() { + $this->expectException(\UserFormException::class); Support::dateTimeGermanToInternational('1:'); } /** - * @expectedException \UserFormException */ public function testDateTimeGermanToInternationalException19() { + $this->expectException(\UserFormException::class); Support::dateTimeGermanToInternational('1:1:'); } /** - * @expectedException \UserFormException */ public function testDateTimeGermanToInternationalException20() { + $this->expectException(\UserFormException::class); Support::dateTimeGermanToInternational('1:1:1:1'); } /** - * @expectedException \UserFormException */ public function testDateTimeGermanToInternationalException21() { + $this->expectException(\UserFormException::class); Support::dateTimeGermanToInternational('123:1:1'); } /** - * @expectedException \UserFormException */ public function testDateTimeGermanToInternationalException22() { + $this->expectException(\UserFormException::class); Support::dateTimeGermanToInternational('1:123:1'); } /** - * @expectedException \UserFormException */ public function testDateTimeGermanToInternationalException23() { + $this->expectException(\UserFormException::class); Support::dateTimeGermanToInternational('1:1:123'); } + /** + * @return void + */ public function testDateTimeRegexp() { // TBD $this->assertEquals(1, 1); } /** - * @throws \UserFormException */ public function testConvertDateTime() { +// $this->expectException(\UserFormException::class); $dateFormat = FORMAT_DATE_INTERNATIONAL; $value = ''; @@ -748,21 +752,33 @@ class SupportTest extends TestCase { $this->assertEquals('01.02.2034 01:03', Support::convertDateTime($value, $dateFormat, 0, 0, 0)); } + /** + * @return void + */ public function testDateTimeZero() { // TBD $this->assertEquals(1, 1); } + /** + * @return void + */ public function testSplitDateToArray() { // TBD $this->assertEquals(1, 1); } + /** + * @return void + */ public function testGetDateTimePlaceholder() { // TBD $this->assertEquals(1, 1); } + /** + * @return void + */ public function testEncryptDoubleCurlyBraces() { #/+open+/# #/+close+/# @@ -786,6 +802,9 @@ class SupportTest extends TestCase { } } + /** + * @return void + */ public function testRandomAlphaNum() { // TBD $this->assertEquals(1, 1); @@ -914,21 +933,24 @@ class SupportTest extends TestCase { } /** - * @expectedException CodeException */ public function testMergeUrlComponentsException() { + $this->expectException(\CodeException::class); Support::mergeUrlComponents('', '', 'sub?id=1'); } + /** + * @return void + */ public function testSetFeDefaults() { // TBD $this->assertEquals(1, 1); } /** - * @throws \UserFormException */ public function testAdjustFeToColumnDefinition() { + // Test CheckType Auto $formElementTemplate = [ FE_NAME => 'feTest', @@ -1046,6 +1068,9 @@ class SupportTest extends TestCase { } + /** + * @return void + */ public function testExtendFilename() { // TBD $this->assertEquals(1, 1); @@ -1125,6 +1150,9 @@ class SupportTest extends TestCase { } + /** + * @return void + */ public function testMoveFile() { // TBD $this->assertEquals(1, 1); @@ -1181,9 +1209,9 @@ class SupportTest extends TestCase { } /** - * @expectedException CodeException */ public function testInsertAttributeException1() { + $this->expectException(\CodeException::class); Support::insertAttribute('<>', 'class', 'qfq'); } @@ -1265,6 +1293,9 @@ class SupportTest extends TestCase { $this->assertEquals(1073741824000, Support::returnBytes('1000g')); } + /** + * @return void + */ public function testQfqExec() { // TBD $this->assertEquals(1, 1); @@ -1275,7 +1306,7 @@ class SupportTest extends TestCase { * @throws \UserFormException * @throws \UserReportException */ - protected function setUp() { + protected function setUp(): void { parent::setUp(); // Path::setUrlApp(BASE_URL_FAKE); diff --git a/extension/Tests/Unit/Core/Report/LinkTest.php b/extension/Tests/Unit/Core/Report/LinkTest.php index 996d3ec39ba5bba32402200810ed4f9ab21f4ad7..2a1b2ebb65cd4912a196349f6f6e53105177d89a 100644 --- a/extension/Tests/Unit/Core/Report/LinkTest.php +++ b/extension/Tests/Unit/Core/Report/LinkTest.php @@ -32,26 +32,24 @@ class LinkTest extends TestCase { private $baseUrl = ''; /** - * @expectedException UserReportException - * * @throws \CodeException * @throws \UserFormException * @throws \UserReportException */ public function testUnknownTokenException1() { + $this->expectException(\UserReportException::class); $link = new Link($this->sip, DB_INDEX_DEFAULT, true); $link->renderLink('x:hello world'); } /** - * @expectedException UserReportException - * * @throws \CodeException * @throws \UserFormException * @throws \UserReportException */ public function testUnknownTokenException2() { + $this->expectException(\UserReportException::class); $link = new Link($this->sip, DB_INDEX_DEFAULT, true); $link->renderLink('abc:hello world'); @@ -200,14 +198,13 @@ class LinkTest extends TestCase { } /** - * @expectedException UserReportException - * * @throws \CodeException * @throws \UserFormException * @throws \UserReportException * @throws \DbException */ public function testLinkUrlBasicExceptionDouble() { + $this->expectException(\UserReportException::class); $link = new Link($this->sip, DB_INDEX_DEFAULT, true); $link->renderLink('u:http://example.com|u:http://new.org'); @@ -914,13 +911,12 @@ class LinkTest extends TestCase { } /** - * @expectedException UserReportException - * * @throws \CodeException * @throws \UserFormException * @throws \UserReportException */ public function testGlyphException() { + $this->expectException(\UserReportException::class); $link = new Link($this->sip, DB_INDEX_DEFAULT, true); // Missing 'glyph'-name @@ -928,13 +924,12 @@ class LinkTest extends TestCase { } /** - * @expectedException UserReportException - * * @throws \CodeException * @throws \UserFormException * @throws \UserReportException */ public function testPictureException1() { + $this->expectException(\UserReportException::class); $link = new Link($this->sip, DB_INDEX_DEFAULT, true); // r: default (0) @@ -942,13 +937,12 @@ class LinkTest extends TestCase { } /** - * @expectedException UserReportException - * * @throws \CodeException * @throws \UserFormException * @throws \UserReportException */ public function testPictureException2() { + $this->expectException(\UserReportException::class); $link = new Link($this->sip, DB_INDEX_DEFAULT, true); // r: default (0) @@ -956,13 +950,12 @@ class LinkTest extends TestCase { } /** - * @expectedException UserReportException - * * @throws \CodeException * @throws \UserFormException * @throws \UserReportException */ public function testPictureException3() { + $this->expectException(\UserReportException::class); $link = new Link($this->sip, DB_INDEX_DEFAULT, true); // r: default (0) @@ -970,13 +963,12 @@ class LinkTest extends TestCase { } /** - * @expectedException UserReportException - * * @throws \CodeException * @throws \UserFormException * @throws \UserReportException */ public function testPictureException4() { + $this->expectException(\UserReportException::class); $link = new Link($this->sip, DB_INDEX_DEFAULT, true); // r: default (0) @@ -984,13 +976,12 @@ class LinkTest extends TestCase { } /** - * @expectedException UserReportException - * * @throws \CodeException * @throws \UserFormException * @throws \UserReportException */ public function testGlyphExceptionDouble1() { + $this->expectException(\UserReportException::class); $link = new Link($this->sip, DB_INDEX_DEFAULT, true); // r: default (0) @@ -998,13 +989,12 @@ class LinkTest extends TestCase { } /** - * @expectedException UserReportException - * * @throws \CodeException * @throws \UserFormException * @throws \UserReportException */ public function testGlyphExceptionDouble2() { + $this->expectException(\UserReportException::class); $link = new Link($this->sip, DB_INDEX_DEFAULT, true); // r: default (0) @@ -1012,13 +1002,12 @@ class LinkTest extends TestCase { } /** - * @expectedException UserReportException - * * @throws \CodeException * @throws \UserFormException * @throws \UserReportException */ public function testGlyphExceptionDouble3() { + $this->expectException(\UserReportException::class); $link = new Link($this->sip, DB_INDEX_DEFAULT, true); // r: default (0) @@ -1026,13 +1015,12 @@ class LinkTest extends TestCase { } /** - * @expectedException UserReportException - * * @throws \CodeException * @throws \UserFormException * @throws \UserReportException */ public function testGlyphExceptionDouble4() { + $this->expectException(\UserReportException::class); $link = new Link($this->sip, DB_INDEX_DEFAULT, true); // r: default (0) @@ -1040,13 +1028,12 @@ class LinkTest extends TestCase { } /** - * @expectedException UserReportException - * * @throws \CodeException * @throws \UserFormException * @throws \UserReportException */ public function testGlyphExceptionDouble5() { + $this->expectException(\UserReportException::class); $link = new Link($this->sip, DB_INDEX_DEFAULT, true); // r: default (0) @@ -1054,13 +1041,13 @@ class LinkTest extends TestCase { } /** - * @expectedException UserReportException * * @throws \CodeException * @throws \UserFormException * @throws \UserReportException */ public function testGlyphExceptionDouble6() { + $this->expectException(\UserReportException::class); $link = new Link($this->sip, DB_INDEX_DEFAULT, true); // r: default (0) @@ -1326,13 +1313,13 @@ class LinkTest extends TestCase { } /** - * @expectedException UserReportException * * @throws \CodeException * @throws \UserFormException * @throws \UserReportException */ public function testSipException1() { + $this->expectException(\UserReportException::class); $link = new Link($this->sip, DB_INDEX_DEFAULT, true); // r: default (0) @@ -1340,13 +1327,12 @@ class LinkTest extends TestCase { } /** - * @expectedException UserReportException - * * @throws \CodeException * @throws \UserFormException * @throws \UserReportException */ public function testSipException2() { + $this->expectException(\UserReportException::class); $link = new Link($this->sip, DB_INDEX_DEFAULT, true); // r: default (0) @@ -1556,13 +1542,12 @@ EOF; } /** - * @expectedException UserReportException - * * @throws \CodeException * @throws \UserFormException * @throws \UserReportException */ public function testDownloadException() { + $this->expectException(\UserReportException::class); $link = new Link($this->sip, DB_INDEX_DEFAULT, true); // Empty @@ -1570,13 +1555,12 @@ EOF; } /** - * @expectedException UserReportException - * * @throws \CodeException * @throws \UserFormException * @throws \UserReportException */ public function testDownloadException1() { + $this->expectException(\UserReportException::class); $link = new Link($this->sip, DB_INDEX_DEFAULT, true); // Empty @@ -1792,13 +1776,12 @@ EOF; } /** - * @expectedException UserReportException - * * @throws \CodeException * @throws \UserFormException * @throws \UserReportException */ // public function testDeleteException1() { +// $this->expectException(\UserReportException::class); // $link = new Link($this->sip, DB_INDEX_DEFAULT, true); // // // Missing recordId @@ -1811,7 +1794,7 @@ EOF; * @throws \UserFormException * @throws \UserReportException */ - protected function setUp() { + protected function setUp(): void { parent::setUp(); $this->store = Store::getInstance('', true); diff --git a/extension/Tests/Unit/Core/Report/ReportTest.php b/extension/Tests/Unit/Core/Report/ReportTest.php index e05cb03d0a4c477dd69cb3c5b29272813b1a3f13..789b04a23d134f5480adf434c6ab768b05d85644 100644 --- a/extension/Tests/Unit/Core/Report/ReportTest.php +++ b/extension/Tests/Unit/Core/Report/ReportTest.php @@ -113,8 +113,6 @@ class ReportTest extends AbstractDatabaseTest { } /** - * @expectedException UserReportException - * * @throws \CodeException * @throws \DbException * @throws \DownloadException @@ -129,6 +127,7 @@ class ReportTest extends AbstractDatabaseTest { */ public function testUnknownTokenException() { + $this->expectException(\UserReportException::class); // empty (missing '=') $result = $this->report->process('10.sql SELECT "Hello = World"'); } @@ -1019,8 +1018,6 @@ EOF; /** * Missing missing form or table * - * @expectedException UserReportException - * * @throws \CodeException * @throws \DbException * @throws \DownloadException @@ -1034,6 +1031,7 @@ EOF; * @throws \UserReportException */ public function testMissingPagedParameterException1() { + $this->expectException(\UserReportException::class); $this->report->process("10.sql = SELECT 'something&r=1' AS _Paged FROM Person ORDER BY id LIMIT 1"); } @@ -1041,8 +1039,6 @@ EOF; /** * missing form, missing table * - * @expectedException UserReportException - * * @throws \CodeException * @throws \DbException * @throws \DownloadException @@ -1056,12 +1052,11 @@ EOF; * @throws \UserReportException */ public function testMissingPagedParameterException4() { + $this->expectException(\UserReportException::class); $this->report->process("10.sql = SELECT 'r=123' AS _Paged FROM Person ORDER BY id LIMIT 1"); } /** - * @expectedException UserReportException - * * @throws \CodeException * @throws \DbException * @throws \DownloadException @@ -1075,12 +1070,11 @@ EOF; * @throws \UserReportException */ public function testMissingPagedParameterException6() { + $this->expectException(\UserReportException::class); $this->report->process("10.sql = SELECT 'table&r=123' AS _Paged FROM Person ORDER BY id LIMIT 1"); } /** - * @expectedException UserReportException - * * @throws \CodeException * @throws \DbException * @throws \DownloadException @@ -1094,6 +1088,7 @@ EOF; * @throws \UserReportException */ public function testMissingPagedParameterException7() { + $this->expectException(\UserReportException::class); $this->report->process("10.sql = SELECT 'form&r=123' AS _Paged FROM Person ORDER BY id LIMIT 1"); } @@ -1676,7 +1671,7 @@ EOF; * @throws \UserFormException * @throws \UserReportException */ - protected function setUp() { + protected function setUp(): void { parent::setUp(); diff --git a/extension/Tests/Unit/Core/SaveTest.php b/extension/Tests/Unit/Core/SaveTest.php index db7a82436541eca7c882a6915094748a401b5094..78cbf436440b17a12146bb2746b9845db86d330c 100644 --- a/extension/Tests/Unit/Core/SaveTest.php +++ b/extension/Tests/Unit/Core/SaveTest.php @@ -257,7 +257,7 @@ class SaveTest extends AbstractDatabaseTest { * @throws \UserFormException * @throws \UserReportException */ - protected function setUp() { + protected function setUp(): void { parent::setUp(); $this->executeSQLFile(__DIR__ . '/Database/fixtures/Generic.sql', true); diff --git a/extension/Tests/Unit/Core/Store/ConfigTest.php b/extension/Tests/Unit/Core/Store/ConfigTest.php index ec9c9909cf8788ada02016ecca03a6acea342f48..516ab33bb268c97a56892f25db8c8a03d392f276 100644 --- a/extension/Tests/Unit/Core/Store/ConfigTest.php +++ b/extension/Tests/Unit/Core/Store/ConfigTest.php @@ -20,14 +20,13 @@ class ConfigTest extends TestCase { /** - * @expectedException \UserFormException - * * @throws \CodeException * @throws \UserFormException * @throws \UserReportException */ public function testCheckForAttack() { + $this->expectException(\UserFormException::class); $config = Config::setDefaults(array()); $_GET['fake'] = '012345678901234567890123456789012345678901234567890123456789'; @@ -53,14 +52,13 @@ class ConfigTest extends TestCase { } /** - * @expectedException \UserFormException - * * @throws \CodeException * @throws \UserFormException * @throws \UserReportException */ public function testCheckForAttack3() { + $this->expectException(\UserFormException::class); $config = Config::setDefaults(array()); // Check for customized length (above) @@ -71,26 +69,25 @@ class ConfigTest extends TestCase { } /** - * @expectedException \UserFormException - * * @throws \CodeException * @throws \UserFormException * @throws \UserReportException */ public function testCheckForAttack4() { + $this->expectException(\UserFormException::class); $config = Config::setDefaults(array()); $_POST['email'] = 'no@email.com'; Config::checkForAttack($config); } - protected function setUp() { + protected function setUp(): void { $_GET = array(); $_POST = array(); } - protected function tearDown() { + protected function tearDown(): void { $_GET = array(); $_POST = array(); } diff --git a/extension/Tests/Unit/Core/Store/FillStoreFormTest.php b/extension/Tests/Unit/Core/Store/FillStoreFormTest.php index c9fc7ffa344b12d3eea41cdd461eea41049a9f7b..22d5c9061394752842c9d8f91c6cf731a75591e9 100644 --- a/extension/Tests/Unit/Core/Store/FillStoreFormTest.php +++ b/extension/Tests/Unit/Core/Store/FillStoreFormTest.php @@ -48,14 +48,13 @@ class FillStoreFormTest extends TestCase { // } /** - * @expectedException \UserFormException - * * @throws \CodeException * @throws \DbException * @throws \UserFormException * @throws \UserReportException */ // public function testDoDateTimeInvalidDate() { +//$this->expectException(\UserReportException::class); // $formElement = [ FE_TYPE => FE_TYPE_DATE, // FE_DATE_FORMAT => FORMAT_DATE_INTERNATIONAL, // FE_SHOW_SECONDS => 0 ]; @@ -67,14 +66,13 @@ class FillStoreFormTest extends TestCase { // } /** - * @expectedException \UserFormException - * * @throws \CodeException * @throws \DbException * @throws \UserFormException * @throws \UserReportException */ // public function testDoDateTimeInvalidDateWithTime() { +// $this->expectException(\UserReportException::class); // $formElement = [ FE_TYPE => FE_TYPE_DATE, // FE_DATE_FORMAT => FORMAT_DATE_INTERNATIONAL, // FE_SHOW_SECONDS => 0 ]; diff --git a/extension/Tests/Unit/Core/Store/SessionTest.php b/extension/Tests/Unit/Core/Store/SessionTest.php index 55f853579bd07700b38ca0a8dea401aa4986ef94..1418085e649ef8d51cdde48fe338665bf2dd3885 100644 --- a/extension/Tests/Unit/Core/Store/SessionTest.php +++ b/extension/Tests/Unit/Core/Store/SessionTest.php @@ -66,7 +66,7 @@ class SessionTest extends TestCase { $this->assertEquals(false, $val); } - public function setup() { + public function setUp(): void { Session::getInstance(true); } } \ No newline at end of file diff --git a/extension/Tests/Unit/Core/Store/SipTest.php b/extension/Tests/Unit/Core/Store/SipTest.php index fc6e162f6570aeb52cc02cab5dc11f74ccf4d2a1..6d13a7b4d1d0743ed5fe4d159d2d5bc35642d65b 100644 --- a/extension/Tests/Unit/Core/Store/SipTest.php +++ b/extension/Tests/Unit/Core/Store/SipTest.php @@ -166,7 +166,7 @@ class SipTest extends TestCase { * @throws \UserFormException * @throws \UserReportException */ - protected function setUp() { + protected function setUp(): void { parent::setUp(); $this->store = Store::getInstance('', true); diff --git a/extension/Tests/Unit/Core/Store/StoreTest.php b/extension/Tests/Unit/Core/Store/StoreTest.php index 9d65bf50aa654295a729211a11b41a3cb25ecc8a..5b21983fd5638ad20f78fd778d6b73ca93b326aa 100644 --- a/extension/Tests/Unit/Core/Store/StoreTest.php +++ b/extension/Tests/Unit/Core/Store/StoreTest.php @@ -33,7 +33,7 @@ class StoreTest extends TestCase { * @throws \UserFormException * @throws \UserReportException */ - public function setUp() { + public function setUp(): void { // Client Variables has to setup before the first instantiation of 'Store' $_GET[] = array(); $_POST[] = array(); @@ -264,14 +264,13 @@ class StoreTest extends TestCase { } // /** -// * @expectedException \UserFormException -// * // * @throws \CodeException // * @throws \UserFormException // * @throws \UserReportException // */ // public function testConfigMandatoryValues() { // +// $this->expectException(\UserReportException::class); // $fileName = $this->createFile(''); // // the following produces Undefined index exception since you give an empty config.ini which does not contain the index. // // What do you want to test here? @@ -488,12 +487,11 @@ class StoreTest extends TestCase { } /** - * @expectedException \UserFormException - * * @throws \CodeException * @throws \UserFormException */ public function testGetStore() { + $this->expectException(\UserFormException::class); $this->assertEquals(array(), $this->store->getStore('unknownstore')); } diff --git a/extension/Tests/phpunit.md b/extension/Tests/phpunit.md index 7cab89c2c351958c018d63ede14b814e6239a85a..08dc3be6033fffb07bd9a861404c8ef9473cf6c3 100644 --- a/extension/Tests/phpunit.md +++ b/extension/Tests/phpunit.md @@ -1,40 +1 @@ -# Phpunit - -## Setup - -Requirements for running the php unittests: -- `make bootstrap` was executed -- The following files exist at the same location (either `extension/` or `typo3conf/`): - * `qfq.json` - * `LocalConfiguration.php` -- The database credentials in `qfq.json` are correct -- The database with the name `DB_1_NAME` followed by `_phpunit` exists. E.g. `app_qfq_phpunit` where `DB_1_NAME=app_qfq` - - -In Tests/Unit/ you may find a mockup of `LocalConfiguration.php` -and a template for `qfq.json`. - -## Run unit tests from commandline - -REMARK: Running the unit tests without specifying the configuration file `phpunit.xml` will not work. See section "Autoloader" for explanation. - -From the extension folder run: - -`vendor/bin/phpunit --configuration phpunit.xml` - -## Phpunit configurations - -Phpunit configurations are stored in extension/phpunit.xml -Running the tests without specifying these configurations will not work. - -## Autoloader - -The test classes use the composer autoloader to reference to the source classes. -The autoloader is loaded by phpunit before each test -as specified in phpunit.xml by the line `<phpunit bootstrap="vendor/autoload.php">` - -## Run tests without typo3 installation (e.g. gitlab runner) - -As defined in the phpunit command of projectRoot/Makefile. - -The files phpunit_qfq.json and phpunit_LocalConfiguration.php are copied outside the extension folder by the Makefile. +Please check: Documentation-develop/PHPUNIT.md \ No newline at end of file diff --git a/extension/composer.json b/extension/composer.json index 0b570dba8cb4901a8e0b09065c93bfc8bde9cd49..848142983e29bb2e9ddcc9e0b9ab647c57ce8e58 100644 --- a/extension/composer.json +++ b/extension/composer.json @@ -6,7 +6,7 @@ "ezyang/htmlpurifier": "^4.15" }, "require-dev": { - "phpunit/phpunit": "^6.5" + "phpunit/phpunit": "^9" }, "autoload": { "psr-4": { diff --git a/extension/ext_conf_template.txt b/extension/ext_conf_template.txt index a47fb6d64bf3c154bac0b18c83c5023291fce7fb..ad1aa082aacf1ff073e1d8de1d204cd25fc336f6 100644 --- a/extension/ext_conf_template.txt +++ b/extension/ext_conf_template.txt @@ -16,6 +16,9 @@ lockSameOwner = true # cat=config/config; type=boolean; label=Show edit inline reports. In the frontend, for every QFQ Report record an edit symbol is shown. Click on it will open a window to edit the QFQ report. editInlineReports = 1 +# cat=config/config; type=boolean; label=Switch edit inline report editor to dark mode. Default is light mode. +editInlineReportDarkTheme = 0 + # cat=config/config; type=string; label=Report as File Auto Export:Default is 'no'. If set to 'yes': When a QFQ tt-content record is rendered which does not contain the "file=" keyword, then its body is exported to a file in the qfq-project directory and the tt-content body is replaced by "file=<path_to_file>". reportAsFileAutoExport = diff --git a/extension/ext_emconf.php b/extension/ext_emconf.php index 1da4c8d35a76e5ceca259e8ea253b01a8aa6d657..f0fdd0d5f5de098026d190cd7861e5c1bb7ac78a 100644 --- a/extension/ext_emconf.php +++ b/extension/ext_emconf.php @@ -7,19 +7,18 @@ $EM_CONF['qfq'] = array( 'title' => 'Quick Form Query', 'description' => 'Framework to build web applications: Form (dynamic), report, typeahead, multi language, link protection, PDF, send mail (dynamic attachments, PDFs), multiple databases, record locking, secure up/download.', 'category' => 'fe', - 'author' => 'Carsten Rose, Benjamin Baer', + 'author' => 'Carsten Rose, Benjamin Baer, Enis Nuredini, Jan Haller', 'author_email' => 'carsten.rose@math.uzh.ch', 'dependencies' => 'fluid,extbase', 'clearcacheonload' => true, 'state' => 'stable', - 'version' => '23.2.0', + 'version' => '23.10.0', 'constraints' => [ 'depends' => [ - 'typo3' => '7.0.0-10.9.99', + 'typo3' => '8.0.0-11.9.99', ], 'conflicts' => [], 'suggests' => [], ], - ); diff --git a/extension/ext_localconf.php b/extension/ext_localconf.php index 4818b74b5f997ff92029940d690039c2081f03f9..726084a9766662b399afa72ce5d3daed57a56ccf 100644 --- a/extension/ext_localconf.php +++ b/extension/ext_localconf.php @@ -5,11 +5,32 @@ if (!defined('TYPO3_MODE')) { die ('Access denied.'); } + +if (method_exists(\TYPO3\CMS\Core\Utility\VersionNumberUtility::class, 'getNumericTypo3Version')) { + // For TYPO3 v10 and later + $typo3Version = \TYPO3\CMS\Core\Utility\VersionNumberUtility::getNumericTypo3Version(); +} else { + // For TYPO3 v9 and earlier + $typo3Version = TYPO3_version; +} + +$typo3VersionInteger = \TYPO3\CMS\Core\Utility\VersionNumberUtility::convertVersionNumberToInteger($typo3Version); + +if ($typo3VersionInteger >= 10000000) { + // TYPO3 version is 10.0.0 or higher + $controllerAction = [\IMATHUZH\Qfq\Controller\QfqController::class => 'show']; + $nonCacheableControllerAction = [\IMATHUZH\Qfq\Controller\QfqController::class => 'show']; +} else { + // TYPO3 version is lower than 10.0.0 + $controllerAction = array('Qfq' => 'show'); + $nonCacheableControllerAction = array('Qfq' => 'show'); +} + \TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin( 'IMATHUZH.' . 'qfq', 'Qfq', - array('Qfq' => 'show'), - array('Qfq' => 'show'), // put here as well, if controller output must not be cached + $controllerAction, + $nonCacheableControllerAction, \TYPO3\CMS\Extbase\Utility\ExtensionUtility::PLUGIN_TYPE_CONTENT_ELEMENT ); diff --git a/extension/phpunit.xml b/extension/phpunit.xml index 13d101389f445f94b5ee6ccd2c61a20248dd4dbd..c011d68c307a7f3cf195f5d45ca7219630c1587d 100644 --- a/extension/phpunit.xml +++ b/extension/phpunit.xml @@ -1,16 +1,18 @@ -<phpunit bootstrap="Tests/phpunit_bootstrap.php"> - <testsuites> - <testsuite name="qfq"> - <directory>Tests/Unit</directory> - </testsuite> - </testsuites> - <filter> - <whitelist processUncoveredFilesFromWhitelist="true"> - <directory suffix=".php">Classes/</directory> - </whitelist> - </filter> - <php> - <ini name='session.cookie_lifetime' value='259200' /> - <const name="PHPUNIT_QFQ" value="true"/> - </php> -</phpunit> \ No newline at end of file +<?xml version="1.0"?> +<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" bootstrap="Tests/phpunit_bootstrap.php" + xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"> + <coverage processUncoveredFiles="true"> + <include> + <directory suffix=".php">Classes/</directory> + </include> + </coverage> + <testsuites> + <testsuite name="qfq"> + <directory>Tests/Unit</directory> + </testsuite> + </testsuites> + <php> + <ini name="session.cookie_lifetime" value="259200"/> + <const name="PHPUNIT_QFQ" value="true"/> + </php> +</phpunit> diff --git a/javascript/src/FieldTemplate.js b/javascript/src/FieldTemplate.js index df8f1047045a66971a34ee3911eb976738cf9517..b80b4ad9a6e996b81c8b4f28901c4124eab26713 100644 --- a/javascript/src/FieldTemplate.js +++ b/javascript/src/FieldTemplate.js @@ -24,6 +24,7 @@ var QfqNS = QfqNS || {}; n.initializeQfqClearMe(); n.initializeDatetimepicker(true); QfqNS.TypeAhead.install(null); + n.initializeCheckmarks(n.lastAppendElement); }; /** @@ -74,6 +75,9 @@ var QfqNS = QfqNS || {}; $target.append(deserializedTemplate); n.informFormOfChange($target); + // Store last append element + n.lastAppendElement = deserializedTemplate; + lines = n.countLines(escapedTargetSelector); if (lines >= maxChildren && responsibleButton) { n.disableButton(responsibleButton); @@ -83,14 +87,15 @@ var QfqNS = QfqNS || {}; n.adjustIds = function (item, startId) { if (item.id !== undefined && item.id !== '' && !item.classList.contains("hidden") && (item.tagName === "INPUT" || item.tagName === "DIV")) { var id = item.id; - var last2 = id.slice(-2); - var endingChars = startId + last2; + var parts = id.split('-'); + if (item.tagName === "INPUT") { - item.id = id.slice(0, -1) + startId; + parts[parts.length -1] = startId; } else { - item.id = id.slice(0, -3) + endingChars; + parts[parts.length -2] = startId; } + item.id = parts.join('-'); } // Get inner children and adjust id @@ -203,8 +208,8 @@ var QfqNS = QfqNS || {}; var $container = $line.parent(); var buttonToEnable = $line.data('field.template.addButton'); - var last3Chars = $line[0].childNodes[0].id.slice(-3); - var startId = last3Chars.slice(0, 1); + var parts = $line[0].childNodes[0].id.split('-'); + var startId = parts[parts.length - 2]; var arrayCount = startId - 1; function adjustChildIds (actualChild, startId) { @@ -374,4 +379,38 @@ var QfqNS = QfqNS || {}; n.expandRetainedPlaceholders($element, index + 1); }); }; + + /** + * Initialize checkmark elements for checkboxes and radio buttons. + * @private + */ + n.initializeCheckmarks = function (element) { + + var $target = $(element); + + $target.find(".radio-inline").each(function() { + if (!$(this).find(".checkmark").length) { + $(this).append($("<span>", { class: "checkmark", aria: "hidden"})); + } + }); + + $target.find(".checkbox-inline").each(function() { + if (!$(this).find(".checkmark").length) { + $(this).append($("<span>", { class: "checkmark", aria: "hidden"})); + } + }); + + $target.find(".radio").each(function() { + if (!$(this).find(".checkmark").length) { + $(this).append($("<span>", { class: "checkmark", aria: "hidden"})); + } + }); + + $target.find(".checkbox").each(function() { + if (!$(this).find(".checkmark").length) { + $(this).append($("<span>", { class: "checkmark", aria: "hidden"})); + } + }); + + }; })(QfqNS); \ No newline at end of file diff --git a/javascript/src/Form.js b/javascript/src/Form.js index 81bbb5048535e2ff0af84e737354cceaaeb163f2..19958e805b3efa4104053b407e55f19824f41d07 100644 --- a/javascript/src/Form.js +++ b/javascript/src/Form.js @@ -112,7 +112,7 @@ var QfqNS = QfqNS || {}; */ n.Form.prototype.changeHandler = function (event) { // Trim whitespace after change. Before validation happens. - if (event.target.value !== undefined) { + if (event.target.value !== undefined && event.target.type !== 'file') { var value = event.target.value; event.target.value = value.trim(); } diff --git a/javascript/src/Helper/codemirror.js b/javascript/src/Helper/codemirror.js index 57c7996e231726067513438a45529deabd0812c0..aad30e4e008a4984dc75a86f3f12a50ebfff3620 100644 --- a/javascript/src/Helper/codemirror.js +++ b/javascript/src/Helper/codemirror.js @@ -118,18 +118,21 @@ $(document).ready(function () { // We prepare the content for extern window and show it. Only if onclick doesn't exist. Compatibility for old way is given this way. $(targetEditReportButton).click(function () { + var baseUrl = $(this).data('base-url'); if (!$(this).is("[onclick]")) { var formContent = $($(this).next()[0].outerHTML); - showHtmlEditor(formContent); + showHtmlEditor(formContent, baseUrl); } }); //function to show editor window - function showHtmlEditor(formContent) { + function showHtmlEditor(formContent, baseUrl) { $(formContent[0]).removeAttr("class"); $(formContent[0]).addClass("externWindow"); + var cssPath = baseUrl + "typo3conf/ext/qfq/Resources/Public/Css/codemirror/monokai.css"; + var darkTheme = '<link rel="stylesheet" href="' + cssPath + '">'; var idNameForWindow = $(formContent[0]).attr('id'); - htmlContent = '<!DOCTYPE html>' + $("head").html() + $(formContent)[0].outerHTML; + htmlContent = '<!DOCTYPE html>' + $("head").html() + darkTheme + $(formContent)[0].outerHTML; newWindow(idNameForWindow); } @@ -185,6 +188,10 @@ $(document).ready(function () { QfqNS.Log.warning("'data-config' is invalid: " + configData); } } + + // Add viewportMargin to the configuration, makes whole content searchable + configData.viewportMargin = Infinity; + var cm = CodeMirror.fromTextArea(this, configData); cm.on('change', (function ($form, $textArea) { return function (instance, changeObj) { diff --git a/javascript/src/Main.js b/javascript/src/Main.js index 85daeb4dfd79f17aade4d9ae9ce2174efd328cb9..b4660c404f6f6d4d28c34e28fbb58032a5870c49 100644 --- a/javascript/src/Main.js +++ b/javascript/src/Main.js @@ -194,7 +194,6 @@ $(document).ready( function () { }; n.initializeQfqClearMe(); - n.initializeDatetimepicker(false); n.Helper.calendar(); })(QfqNS); diff --git a/javascript/src/QfqForm.js b/javascript/src/QfqForm.js index 94b73d8f5f971a616746fbd0b466ff4aab65e921..147144d1c92e9e22c5f4361a3a3b977b634a7464 100644 --- a/javascript/src/QfqForm.js +++ b/javascript/src/QfqForm.js @@ -153,7 +153,7 @@ var QfqNS = QfqNS || {}; //n.Helper.jqxEditor(); n.Helper.tinyMce(); n.Helper.codemirror(); - + this.form.on('form.submit.before', n.Helper.tinyMce.prepareSave); this.form.on('form.validation.before', n.Helper.tinyMce.prepareSave); this.form.on('form.validation.failed', this.validationError); @@ -164,6 +164,13 @@ var QfqNS = QfqNS || {}; $(".radio").append($("<span>", { class: "checkmark", aria: "hidden"})); $(".checkbox").append($("<span>", { class: "checkmark", aria: "hidden"})); + // Feature process all rows + $(".process-row-all input[type=checkbox]").on("click", function() { + var checkboxes = document.querySelectorAll('input[name^="_processRow-"]'); + for (var i = 0; i < checkboxes.length; i++) { + checkboxes[i].checked = $(this).is(':checked'); + } + }); }; n.QfqForm.prototype.on = n.EventEmitter.onMixin; @@ -316,7 +323,7 @@ var QfqNS = QfqNS || {}; var $messageContainer = $formControl.siblings('.hidden.with-errors'); if ($messageContainer.length === 0) { - if ($formControl.parent().hasClass('input-group')) { + if ($formControl.parent().hasClass('input-group') || $formControl.parent('.twitter-typeahead')) { $messageContainer = $formControl.parent().siblings('.hidden.with-errors'); } } @@ -330,8 +337,8 @@ var QfqNS = QfqNS || {}; var $messageContainer = $formControl.siblings('.with-errors'); if ($messageContainer.length === 0) { - if ($formControl.parent().hasClass('input-group')) { - $messageContainer = $formControl.parent().siblings('.hidden.with-errors'); + if ($formControl.parent().hasClass('input-group') || $formControl.parent('.twitter-typeahead')) { + $messageContainer = $formControl.parent().siblings('.with-errors'); } } @@ -546,7 +553,7 @@ var QfqNS = QfqNS || {}; if (data.status === "error") { this._createError("Error while updating form:<br>" + - (data.message ? data.message : "No reason given")); + (data.message ? data.message : "No reason given")); return; } @@ -579,6 +586,7 @@ var QfqNS = QfqNS || {}; n.QfqForm.prototype.handleSaveClick = function () { this.lastButtonPress = "save"; n.Log.debug("save click"); + this.checkHiddenRequired(); this.getSaveButton().removeClass('btn-info'); this.getSaveButton().addClass('btn-warning active disabled'); if (!this.form.saveInProgress) { @@ -646,7 +654,7 @@ var QfqNS = QfqNS || {}; var form = document.getElementById(this.form.formId); var inputs = form.elements; - + for (var i = 0; i < inputs.length; i++) { var e = inputs[i]; if(!e.willValidate) { @@ -835,6 +843,18 @@ var QfqNS = QfqNS || {}; this.setButtonEnabled(this.getDeleteButton(), false); }; + /** + * Called when form is saved. + * Once a file is deleted that was previously saved, the input will be required. This checks if said input is hidden and removes required attribute. + */ + n.QfqForm.prototype.checkHiddenRequired = function() { + var $form = $(this.form.$form[0]); + var $required = $form.find(':input:file:hidden[required]'); + if(!!$required[0]) { + $required.prop("required", false); + } + }; + /** * Called when form is changed. * @@ -873,8 +893,12 @@ var QfqNS = QfqNS || {}; */ n.QfqForm.prototype.resetHandler = function (obj) { this.getSaveButton().removeClass(this.getSaveButtonAttentionClass()); - this.getSaveButton().addClass("disabled"); - this.getSaveButton().attr("disabled", "disabled"); + + // Only disable button if class enable-save-button not given + if (!$('#' + QfqNS.escapeJqueryIdSelector(this.formId)).data('enable-save-button')) { + this.getSaveButton().addClass("disabled"); + this.getSaveButton().attr("disabled", "disabled"); + } this.resetLockState(); }; @@ -1250,6 +1274,16 @@ var QfqNS = QfqNS || {}; n.Log.error("configuration lacks 'form-element' attribute. Skipping."); continue; } + if(formElementName === 'qfq-form-title') { + $("." + formElementName).html(configurationItem.value); + } + // Insert download button for uploads after Form is saved. + if (configurationItem["type-file"] && document.querySelector("button[name='delete-" + formElementName + "']") !== null) { + var downloadButton = configurationItem["html-content"]; + var fileNameSpan = document.querySelector("button[name='delete-" + formElementName + "']").previousElementSibling; + fileNameSpan.innerHTML = ''; + fileNameSpan.insertAdjacentHTML('afterbegin', downloadButton); + } try { var element = n.Element.getElement(formElementName); // Cleaner way to set states for tinymce diff --git a/javascript/src/QfqPage.js b/javascript/src/QfqPage.js index a833290477034669ac609c03aca0fdbd2d7670b8..02791409a413d0762d60b746eafd5e74133181e4 100644 --- a/javascript/src/QfqPage.js +++ b/javascript/src/QfqPage.js @@ -144,6 +144,7 @@ var QfqNS = QfqNS || {}; QfqNS.TypeAhead.install(this.settings.typeAheadUrl); QfqNS.CharacterCount.initialize(); + n.initializeDatetimepicker(false); }; /** diff --git a/javascript/src/QfqRecordList.js b/javascript/src/QfqRecordList.js index b69d0601dacb473870d7e99e2725410c3dc5c451..364b19537a9ebeb8b0ee50bdaee23e13507e7f93 100644 --- a/javascript/src/QfqRecordList.js +++ b/javascript/src/QfqRecordList.js @@ -34,7 +34,26 @@ var QfqNS = QfqNS || {}; * @private */ n.QfqRecordList.prototype.connectClickHandler = function () { - $("." + this.deleteButtonClass).click(this.handleDeleteButtonClick.bind(this)); + var that = this; + var specificClass = 'record-delete'; // Replace this with the class you want to check + + $("." + this.deleteButtonClass).on('click', function (event) { + // Check if the event is triggered by the "Enter" key + var enterKeyTriggered = event.originalEvent.detail === 0; + + // Check if the delete button is focused + var isButtonFocused = $(event.target).is(":focus"); + + // If the event is triggered by the "Enter" key and the delete button has the specific class, stop the event + if (enterKeyTriggered && !isButtonFocused && $(event.target).hasClass(specificClass)) { + event.preventDefault(); + event.stopPropagation(); + return; + } + + // If the event is not stopped, call the handleDeleteButtonClick method + that.handleDeleteButtonClick(event); + }); }; n.QfqRecordList.prototype.handleDeleteButtonClick = function (event) { diff --git a/less/qfq-bs.css.less b/less/qfq-bs.css.less index bed131f411283ea737a408ce52e9e84d4367bd3b..80263dec069ca9da89277f9366d73ba6382a4c1d 100644 --- a/less/qfq-bs.css.less +++ b/less/qfq-bs.css.less @@ -447,17 +447,17 @@ select.qfq-locked:invalid { .qfq-table-50 { min-width: 50%; - width: auto; + width: auto !important; } .qfq-table-80 { min-width: 80%; - width: auto; + width: auto !important; } .qfq-table-100 { min-width: 100%; - width: auto; + width: auto !important; } @@ -1168,13 +1168,13 @@ span.qfq-more-text { .qfq-clear-me-button { display: block; - right:12px; + right: 12px; top: 2px; font-size: 18px; line-height: 18px; - color: #888; - padding: 6px 14px; - position:absolute; + color: #d8d8d8; + padding: 4px 14px; + position: absolute; cursor: pointer; } @@ -1464,6 +1464,29 @@ input.qfq-password { font-size: 10pt; } -.CodeMirror { +.CodeMirror:not(.externWindow > .CodeMirror) { resize: both; +} + +// lower min-height for checkbox in header +.process-row-label-header { + min-height: 20px !important; + font-weight: bold; +} + +// +.table-multi-form > tbody > tr > td { + vertical-align: middle; +} + +// gray text and corrected padding for inactive dropdown options (render mode 3) +.dropdown-menu>li { + padding: 3px 20px; + color: gray; +} + +// compensate previous rule for active options (render mode 0) +.dropdown-menu>li>a { + margin: -3px -20px; + color: #333; } \ No newline at end of file diff --git a/package.json b/package.json index f506cac291bc594e9307be40b535ca507fef77a6..27a62141a9c00a87574c1e8500294efb2ba53b9c 100644 --- a/package.json +++ b/package.json @@ -8,11 +8,11 @@ "bootstrap-datetimepicker": "0.0.7", "bootstrap-validator": "^0.11.5", "chart.js": "^2.9.4", - "codemirror": "^5.65.9", + "codemirror": "^5.65.12", "corejs-typeahead": "^1.3.1", "eonasdan-bootstrap-datetimepicker": "^4.17.49", "fullcalendar": "^3.10.2", - "grunt": "^1.5.3", + "grunt": "^1.6.1", "grunt-concat-in-order": "^0.2.6", "grunt-contrib-concat": "^1.0.1", "grunt-contrib-copy": "^1.0.0", diff --git a/setVersion.sh b/setVersion.sh index 8f97d027ce12be4da85ba6e1998d13f5167053c0..b350609dc22e7a4760d4d45274a303e93cba7b06 100755 --- a/setVersion.sh +++ b/setVersion.sh @@ -42,9 +42,17 @@ function newVersionMD() { FILE="Documentation-develop/NewVersion.md" - sed -i -r 's/^\s*New version.+$/ New version '$VERSION'/g' ${FILE} - sed -i -r 's/^\s*git tag v.+$/ git tag v'$VERSION'/g' ${FILE} +# New version v23.3.0 + sed -i -r 's/New version v[0-9]+\.[0-9]+\.[0-9]/New version v'$VERSION'/g' ${FILE} + +# Tag: v23.3.0 + sed -i -r 's/Tag: v[0-9]+\.[0-9]+\.[0-9]/Tag: v'$VERSION'/g' ${FILE} + +# git tag -a v23.3.0 -m 'New version v23.3.0' git push + sed -i -r 's/^\s*git tag -a v[0-9]+\.[0-9]+\.[0-9] / git tag -a v'$VERSION' /g' ${FILE} + sed -i -r 's/^\s*git push -u origin v.+$/ git push -u origin v'$VERSION'/g' ${FILE} + } function myExit() { @@ -74,3 +82,4 @@ extEmConf versionFile confPy newVersionMD + diff --git a/version b/version index 11ebcd0e4f9cc9a4685388f1d18926da03118325..7d7a0702435d3d1f633fc95f064cdd3df32f6caa 100644 --- a/version +++ b/version @@ -1 +1 @@ -23.2.0 +23.10.0