From 1d995d9640d4911ee41ed40833420bca8faad70f Mon Sep 17 00:00:00 2001
From: Carsten  Rose <carsten.rose@math.uzh.ch>
Date: Wed, 1 Mar 2017 22:13:31 +0100
Subject: [PATCH] Implemented generating of 'id' per FormElement.

Support.php: new function insertAttribute().
AbstractBuildForm.php: added 'id' to all FormElements.
BuildFormBootstrap: extended customWrap to insert 'id' in every wrap element.
QuickFormQuery.php: Add 'id' to Form ToolTip.
---
 extension/qfq/qfq/AbstractBuildForm.php     | 66 ++++++++++++---------
 extension/qfq/qfq/BuildFormBootstrap.php    | 16 +++--
 extension/qfq/qfq/Constants.php             |  8 +++
 extension/qfq/qfq/QuickFormQuery.php        |  5 +-
 extension/qfq/qfq/helper/Support.php        | 39 +++++++++++-
 extension/qfq/tests/phpunit/SupportTest.php | 25 ++++++++
 6 files changed, 121 insertions(+), 38 deletions(-)

diff --git a/extension/qfq/qfq/AbstractBuildForm.php b/extension/qfq/qfq/AbstractBuildForm.php
index 303972969..7876f7981 100644
--- a/extension/qfq/qfq/AbstractBuildForm.php
+++ b/extension/qfq/qfq/AbstractBuildForm.php
@@ -398,15 +398,10 @@ abstract class AbstractBuildForm {
             //In case the current element is a 'RETYPE' element: take the element name of the source FormElement. Needed in the next row to retrieve the default value.
             $name = (isset($formElement[FE_RETYPE_SOURCE_NAME])) ? $formElement[FE_RETYPE_SOURCE_NAME] : $formElement[FE_NAME];
 
-            // Get default value
-//            $value = ($formElement[FE_VALUE] === '') ? $this->store->getVar($name, $storeUse,
-//                $formElement['checkType']) : $formElement[FE_VALUE];
-
             // If there is a value explicit defined: take it
             $value = $formElement[FE_VALUE];
-            //
             if ($value === '') {
-                // Only take the default, if the FE is a real tablecolumn.
+                // Only take the default, if the FE is a real tablecolumn. See #2064
                 if ($this->store->getVar($formElement[FE_NAME], STORE_TABLE_COLUMN_TYPES) !== false) {
                     $value = $this->store->getVar($name, $storeUse, $formElement['checkType']);
                 }
@@ -445,7 +440,7 @@ abstract class AbstractBuildForm {
             if ($flagOutput) {
                 // debugStack as Tooltip
                 if ($this->showDebugInfo && count($debugStack) > 0) {
-                    $elementHtml .= Support::doTooltip($formElement[FE_HTML_ID], implode("\n", $debugStack));
+                    $elementHtml .= Support::doTooltip($formElement[FE_HTML_ID] . HTML_ID_EXTENSION_TOOLTIP, implode("\n", $debugStack));
                 }
 
                 // Construct Marshaller Name: buildRow
@@ -690,17 +685,19 @@ abstract class AbstractBuildForm {
      *
      *
      * @param array $formElement
-     * @param $htmlFormElementName
-     * @param $value
+     * @param string $htmlFormElementName
+     * @param string $value
      * @param array $json
      * @param string $mode FORM_LOAD | FORM_UPDATE | FORM_SAVE
-     * @return string
+     * @return string complete rendered HTML input element.
      * @throws \qfq\UserFormException
      */
     public function buildInput(array $formElement, $htmlFormElementName, $value, array &$json, $mode = FORM_LOAD) {
         $textarea = '';
+        $attribute = '';
 
-        $attribute = Support::doAttribute('name', $htmlFormElementName);
+        $attribute .= Support::doAttribute('id', $formElement[FE_HTML_ID]);
+        $attribute .= Support::doAttribute('name', $htmlFormElementName);
         $attribute .= Support::doAttribute('class', 'form-control');
 
         if (isset($formElement[FE_RETYPE_SOURCE_NAME])) {
@@ -708,7 +705,7 @@ abstract class AbstractBuildForm {
             $attribute .= Support::doAttribute('data-match', '[name=' . str_replace(':', '\\:', $htmlFormElementNamePrimary) . ']');
         }
 
-        // Check for input type 'textarea'
+        // Check for input type 'textarea'.
         $colsRows = explode(',', $formElement['size'], 2);
         if (count($colsRows) === 2) {
             // <textarea>
@@ -723,9 +720,9 @@ abstract class AbstractBuildForm {
 
             $this->adjustMaxLength($formElement);
 
-            if ($formElement['maxLength'] > 0 && $value !== '') {
+            if ($formElement[FE_MAX_LENGTH] > 0 && $value !== '') {
                 // crop string only if it's not empty (substr returns false on empty strings)
-                $value = substr($value, 0, $formElement['maxLength']);
+                $value = substr($value, 0, $formElement[FE_MAX_LENGTH]);
             }
 
             // 'maxLength' needs an upper 'L': naming convention for DB tables!
@@ -1217,6 +1214,7 @@ abstract class AbstractBuildForm {
         $html = '';
         $valueJson = false;
 
+        $attribute .= Support::doAttribute('id', $formElement[FE_HTML_ID]);
         $attribute .= Support::doAttribute('name', $htmlFormElementName);
         $attribute .= Support::doAttribute('value', $formElement['checked'], false);
         $attribute .= Support::doAttribute('title', $formElement['tooltip']);
@@ -1271,6 +1269,7 @@ abstract class AbstractBuildForm {
         $html = '';
         $valueJson = false;
 
+        $attribute .= Support::doAttribute('id', $formElement[FE_HTML_ID]);
         $attribute .= Support::doAttribute('name', $htmlFormElementName);
         $attribute .= Support::doAttribute('value', $formElement['checked'], false);
         $attribute .= Support::doAttribute('title', $formElement['tooltip']);
@@ -1360,6 +1359,7 @@ abstract class AbstractBuildForm {
             $jsonValue = false;
             $classActive = '';
             $htmlFormElementNameUniq = HelperFormElement::prependFormElementNameCheckBoxMulti($htmlFormElementName, $ii);
+            $attribute .= Support::doAttribute('id', $formElement[FE_HTML_ID] . '-' . $ii);
             $attribute .= Support::doAttribute('name', $htmlFormElementNameUniq);
 
             $attribute .= Support::doAttribute('value', $itemKey[$ii]);
@@ -1416,7 +1416,7 @@ abstract class AbstractBuildForm {
 
         $html = $this->buildNativeHidden(HelperFormElement::prependFormElementNameCheckBoxMulti($htmlFormElementName, 'h'), '');
 
-        $orientation = ($formElement['maxLength'] > 1) ? ALIGN_HORIZONTAL : ALIGN_VERTICAL;
+        $orientation = ($formElement[FE_MAX_LENGTH] > 1) ? ALIGN_HORIZONTAL : ALIGN_VERTICAL;
         $checkboxClass = ($orientation === ALIGN_HORIZONTAL) ? 'checkbox-inline' : 'checkbox';
         $br = '';
 
@@ -1425,6 +1425,7 @@ abstract class AbstractBuildForm {
             $jsonValue = false;
             $attribute = $attributeBase;
             $htmlFormElementNameUniq = HelperFormElement::prependFormElementNameCheckBoxMulti($htmlFormElementName, $ii);
+            $attribute .= Support::doAttribute('id', $formElement[FE_HTML_ID] . '-' . $ii);
             $attribute .= Support::doAttribute('name', $htmlFormElementNameUniq);
 
             // Do this only the first round.
@@ -1455,9 +1456,9 @@ abstract class AbstractBuildForm {
             $htmlElement = Support::wrapTag("<div class='$checkboxClass'>", $htmlElement, true);
 
             // control orientation
-            if ($formElement['maxLength'] > 1) {
+            if ($formElement[FE_MAX_LENGTH] > 1) {
 
-                if ($jj == $formElement['maxLength']) {
+                if ($jj == $formElement[FE_MAX_LENGTH]) {
                     $jj = 0;
                     $br = '<br>';
                 } else {
@@ -1567,6 +1568,7 @@ abstract class AbstractBuildForm {
 
         $attributeBase = $this->getAttributeFeMode($formElement[FE_MODE]);
         $attributeBase .= $this->getAttributeList($formElement, [F_FE_DATA_PATTERN_ERROR, F_FE_DATA_REQUIRED_ERROR, F_FE_DATA_MATCH_ERROR, F_FE_DATA_ERROR]);
+        $attributeBase .= Support::doAttribute('id', $formElement[FE_HTML_ID]);
         $attributeBase .= Support::doAttribute('name', $htmlFormElementName);
         $attributeBase .= Support::doAttribute('type', $formElement[FE_TYPE]);
         $attributeBase .= Support::doAttribute('data-load', ($formElement[FE_DYNAMIC_UPDATE] === 'yes') ? 'data-load' : '');
@@ -1624,6 +1626,7 @@ abstract class AbstractBuildForm {
      * @throws \qfq\UserFormException
      */
     private function constructRadioPlain(array $formElement, $htmlFormElementName, $value, array &$json, $mode = FORM_LOAD) {
+        $attributeBase = '';
 
         if (isset($formElement[FE_BUTTON_CLASS])) {
             return $this->constructRadioButton($formElement, $htmlFormElementName, $value, $json, $mode);
@@ -1636,15 +1639,16 @@ abstract class AbstractBuildForm {
         // Fill $itemKey & $itemValue
         $this->getKeyValueListFromSqlEnumSpec($formElement, $itemKey, $itemValue);
 
-        $attributeBase = $this->getAttributeFeMode($formElement[FE_MODE]);
+        $attributeBase .= $this->getAttributeFeMode($formElement[FE_MODE]);
         $attributeBase .= $this->getAttributeList($formElement, [F_FE_DATA_PATTERN_ERROR, F_FE_DATA_REQUIRED_ERROR, F_FE_DATA_MATCH_ERROR, F_FE_DATA_ERROR]);
+        $attributeBase .= Support::doAttribute('id', $formElement[FE_HTML_ID]);
         $attributeBase .= Support::doAttribute('name', $htmlFormElementName);
         $attributeBase .= Support::doAttribute('type', $formElement[FE_TYPE]);
         $attributeBase .= Support::doAttribute('data-load', ($formElement[FE_DYNAMIC_UPDATE] === 'yes') ? 'data-load' : '');
 
         $jj = 0;
 
-        $orientation = ($formElement['maxLength'] > 1) ? ALIGN_HORIZONTAL : ALIGN_VERTICAL;
+        $orientation = ($formElement[FE_MAX_LENGTH] > 1) ? ALIGN_HORIZONTAL : ALIGN_VERTICAL;
         $radioClass = ($orientation === ALIGN_HORIZONTAL) ? 'radio-inline' : 'radio';
         $br = '';
 
@@ -1675,9 +1679,9 @@ abstract class AbstractBuildForm {
                 $htmlElement = Support::wrapTag('<label>', $htmlElement);
             }
 
-            if ($formElement['maxLength'] > 1) {
+            if ($formElement[FE_MAX_LENGTH] > 1) {
 
-                if ($jj == $formElement['maxLength']) {
+                if ($jj == $formElement[FE_MAX_LENGTH]) {
                     $jj = 0;
                     $br = '<br>';
                 } else {
@@ -1712,11 +1716,13 @@ abstract class AbstractBuildForm {
     public function buildSelect(array $formElement, $htmlFormElementName, $value, array &$json, $mode = FORM_LOAD) {
         $itemKey = array();
         $itemValue = array();
+        $attribute = '';
 
         // Fill $itemKey & $itemValue
         $this->getKeyValueListFromSqlEnumSpec($formElement, $itemKey, $itemValue);
 
-        $attribute = $this->getAttributeFeMode($formElement[FE_MODE]);
+        $attribute .= $this->getAttributeFeMode($formElement[FE_MODE]);
+        $attribute .= Support::doAttribute('id', $formElement[FE_HTML_ID]);
         $attribute .= Support::doAttribute('name', $htmlFormElementName);
         $attribute .= Support::doAttribute('class', 'form-control');
         $attribute .= Support::doAttribute('title', $formElement['tooltip']);
@@ -2188,6 +2194,7 @@ abstract class AbstractBuildForm {
 
         $hiddenSipUpload = $this->buildNativeHidden($htmlFormElementName, $sipUpload);
 
+        $attribute .= Support::doAttribute('id', $formElement[FE_HTML_ID]);
         $attribute .= Support::doAttribute('name', $htmlFormElementName);
 //        $attribute .= Support::doAttribute('class', 'form-control');
         $attribute .= Support::doAttribute('type', 'file');
@@ -2237,8 +2244,10 @@ abstract class AbstractBuildForm {
      * @throws UserFormException
      */
     public function buildDateTime(array $formElement, $htmlFormElementName, $value, array &$json, $mode = FORM_LOAD) {
+        $attribute = '';
 
-        $attribute = Support::doAttribute('name', $htmlFormElementName);
+        $attribute .= Support::doAttribute('id', $formElement[FE_HTML_ID]);
+        $attribute .= Support::doAttribute('name', $htmlFormElementName);
         $attribute .= Support::doAttribute('class', 'form-control');
 
         $arrMinMax = null;
@@ -2271,8 +2280,8 @@ abstract class AbstractBuildForm {
         }
 
         // truncate if necessary
-        if ($value != '' && $formElement['maxLength'] > 0) {
-            $value = substr($value, 0, $formElement['maxLength']);
+        if ($value != '' && $formElement[FE_MAX_LENGTH] > 0) {
+            $value = substr($value, 0, $formElement[FE_MAX_LENGTH]);
         }
 
         $type = $formElement[FE_TYPE];
@@ -2422,13 +2431,15 @@ abstract class AbstractBuildForm {
      * @throws \qfq\UserFormException
      */
     public function buildEditor(array $formElement, $htmlFormElementName, $value, array &$json, $mode = FORM_LOAD) {
+        $attribute = '';
 
         //TODO plugin autoresize nutzen um Editorgroesse anzugeben
 
         $this->adjustMaxLength($formElement);
 
-        $attribute = Support::doAttribute('name', $htmlFormElementName);
-        $attribute .= Support::doAttribute('id', $htmlFormElementName);
+        $attribute .= Support::doAttribute('id', $formElement[FE_HTML_ID]);
+        $attribute .= Support::doAttribute('name', $htmlFormElementName);
+//        $attribute .= Support::doAttribute('id', $htmlFormElementName);
 
         $attribute .= Support::doAttribute('class', 'qfq-tinymce');
         $attribute .= Support::doAttribute('data-control-name', "$htmlFormElementName");
@@ -2601,6 +2612,7 @@ abstract class AbstractBuildForm {
         // save parent processed FE's
         $tmpStore = $this->feSpecNative;
 
+        $attribute .= Support::doAttribute('id', $formElement[FE_HTML_ID]);
         $attribute .= Support::doAttribute('name', $htmlFormElementName);
         $attribute .= Support::doAttribute('data-load', ($formElement[FE_DYNAMIC_UPDATE] === 'yes') ? 'data-load' : '');
 
diff --git a/extension/qfq/qfq/BuildFormBootstrap.php b/extension/qfq/qfq/BuildFormBootstrap.php
index b618dc094..fcd0467d3 100644
--- a/extension/qfq/qfq/BuildFormBootstrap.php
+++ b/extension/qfq/qfq/BuildFormBootstrap.php
@@ -421,19 +421,19 @@ EOF;
         }
 
         $html .= $this->customWrap($formElement, $htmlLabel, FE_WRAP_LABEL, $formElement[FE_BS_LABEL_COLUMNS],
-            [$this->wrap[WRAP_SETUP_LABEL][WRAP_SETUP_START], $this->wrap[WRAP_SETUP_LABEL][WRAP_SETUP_END]]);
+            [$this->wrap[WRAP_SETUP_LABEL][WRAP_SETUP_START], $this->wrap[WRAP_SETUP_LABEL][WRAP_SETUP_END]], $formElement[FE_HTML_ID] . HTML_ID_EXTENSION_LABEL);
 
         $html .= $this->customWrap($formElement, $htmlElement, FE_WRAP_INPUT, $formElement[FE_BS_INPUT_COLUMNS],
-            [$this->wrap[WRAP_SETUP_INPUT][WRAP_SETUP_START], $this->wrap[WRAP_SETUP_INPUT][WRAP_SETUP_END]]);
+            [$this->wrap[WRAP_SETUP_INPUT][WRAP_SETUP_START], $this->wrap[WRAP_SETUP_INPUT][WRAP_SETUP_END]], $formElement[FE_HTML_ID] . HTML_ID_EXTENSION_INPUT);
 
         $note = Support::wrapTag("<div class='qfq-note'>", $formElement[FE_NOTE], true);
         $html .= $this->customWrap($formElement, $note, FE_WRAP_NOTE, $formElement[FE_BS_NOTE_COLUMNS],
-            [$this->wrap[WRAP_SETUP_NOTE][WRAP_SETUP_START], $this->wrap[WRAP_SETUP_NOTE][WRAP_SETUP_END]]);
+            [$this->wrap[WRAP_SETUP_NOTE][WRAP_SETUP_START], $this->wrap[WRAP_SETUP_NOTE][WRAP_SETUP_END]], $formElement[FE_HTML_ID] . HTML_ID_EXTENSION_NOTE);
 
         // ROW
         $openTag = (Support::findInSet('row', $formElement[FE_WRAP_ROW_LABEL_INPUT_NOW])) ? $this->wrap[WRAP_SETUP_ELEMENT][WRAP_SETUP_START] : '';
         $closeTag = (Support::findInSet('/row', $formElement[FE_WRAP_ROW_LABEL_INPUT_NOW])) ? $this->wrap[WRAP_SETUP_ELEMENT][WRAP_SETUP_END] : '';
-        $html = $this->customWrap($formElement, $html, FE_WRAP_ROW, 99, [$openTag, $closeTag]);
+        $html = $this->customWrap($formElement, $html, FE_WRAP_ROW, 99, [$openTag, $closeTag], $formElement[FE_HTML_ID] . HTML_ID_EXTENSION_ROW);
 
         return $html;
     }
@@ -448,13 +448,15 @@ EOF;
      * @return string Wrapped $htmlElement
      * @throws \qfq\UserFormException
      */
-    private function customWrap(array $formElement, $htmlElement, $wrapName, $bsColumns, array $wrapArray) {
+    private function customWrap(array $formElement, $htmlElement, $wrapName, $bsColumns, array $wrapArray, $htmlId = '') {
 
+        // If $bsColumns==0: do not wrap with default.
         if ($bsColumns == 0) {
             $wrapArray[0] = '';
             $wrapArray[1] = '';
         }
 
+        // If there is a 'per FormElement'-wrap, take it.
         if (isset($formElement[$wrapName])) {
             $wrapArray = explode('|', $formElement[$wrapName], 2);
         }
@@ -463,6 +465,10 @@ EOF;
             throw new UserFormException("Need open & close wrap token for FormElement.parameter" . $wrapName . " - E.g.: <div ...>|</div>", ERROR_MISSING_VALUE);
         }
 
+        if ($wrapArray[0] != '') {
+            $wrapArray[0] = Support::insertAttribute($wrapArray[0], 'id', $htmlId);
+        }
+
         return $wrapArray[0] . $htmlElement . $wrapArray[1];
     }
 
diff --git a/extension/qfq/qfq/Constants.php b/extension/qfq/qfq/Constants.php
index 75ca169ac..302817e24 100644
--- a/extension/qfq/qfq/Constants.php
+++ b/extension/qfq/qfq/Constants.php
@@ -161,6 +161,7 @@ const ERROR_SENDMAIL_MISSING_VALUE = 1072;
 const ERROR_OVERWRITE_RECORD_ID = 1073;
 const ERROR_MISSING_SLAVE_ID_DEFINITION = 1074;
 const ERROR_MISSING_INTL = 1075;
+const ERROR_HTML_TOKEN_TOO_SHORT = 1076;
 
 // Subrecord
 const ERROR_SUBRECORD_MISSING_COLUMN_ID = 1100;
@@ -574,6 +575,7 @@ const RETYPE_FE_NAME_EXTENSION = 'RETYPE';
 
 const FE_HTML_ID = 'htmlId'; // Will be dynamically computed during runtime.
 
+
 // FormElement Types
 const FE_TYPE_UPLOAD = 'upload';
 const FE_TYPE_EXTRA = 'extra';
@@ -599,6 +601,12 @@ const ESCAPE_WITH_HTML_QUOTE = 'htmlquote';
 const FLAG_ALL = 'flagAll';
 const FLAG_DYNAMIC_UPDATE = 'flagDynamicUpdate';
 
+const HTML_ID_EXTENSION_LABEL = '-l';
+const HTML_ID_EXTENSION_INPUT = '-i';
+const HTML_ID_EXTENSION_NOTE = '-n';
+const HTML_ID_EXTENSION_TOOLTIP = '-t';
+const HTML_ID_EXTENSION_ROW = '-r';
+
 const QUERY_TYPE_SELECT = 'type: select,show,describe,explain';
 const QUERY_TYPE_INSERT = 'type: insert';
 const QUERY_TYPE_UPDATE = 'type: update,replace,delete';
diff --git a/extension/qfq/qfq/QuickFormQuery.php b/extension/qfq/qfq/QuickFormQuery.php
index d1970ad96..f2860fbc9 100644
--- a/extension/qfq/qfq/QuickFormQuery.php
+++ b/extension/qfq/qfq/QuickFormQuery.php
@@ -186,9 +186,8 @@ class QuickFormQuery {
         $html = '';
 
         if ($this->store->getVar(TYPO3_DEBUG_SHOW_BODY_TEXT, STORE_TYPO3) === 'yes') {
-//            TODO: hier den Tootltip mit eienr ID versehen
-//            $htmlId = HelperFormElement::buildFormElementId($this->)
-            $html .= Support::doTooltip('', $this->t3data['bodytext']);
+            $htmlId = HelperFormElement::buildFormElementId($this->formSpec[F_ID], 0, 0, 0);
+            $html .= Support::doTooltip($htmlId . HTML_ID_EXTENSION_TOOLTIP, $this->t3data['bodytext']);
         }
 
         $html .= $this->doForm(FORM_LOAD);
diff --git a/extension/qfq/qfq/helper/Support.php b/extension/qfq/qfq/helper/Support.php
index 1b3f38a32..7526a468b 100644
--- a/extension/qfq/qfq/helper/Support.php
+++ b/extension/qfq/qfq/helper/Support.php
@@ -100,11 +100,14 @@ class Support {
 
     /**
      * Format's an attribute: $type=$value. If $flagOmitEmpty==true && $value=='': return ''.
+     * Add's a space at the end.
      *
      * @param string $type
      * @param string|array $value
      * @param bool $flagOmitEmpty
-     * @return string
+     * @param string $modeEscape
+     * @return string correctly fomratted attribute. Space at the end.
+     * @throws CodeException
      */
     public static function doAttribute($type, $value, $flagOmitEmpty = true, $modeEscape = ESCAPE_WITH_BACKSLASH) {
 
@@ -120,7 +123,7 @@ class Support {
         switch (strtolower($type)) {
             case 'size':
             case 'maxlength':
-                // empty or '0' for attributes of type 'size' or 'maxlenght' result in unsuable input elements: skip this.
+            // empty or '0' for attributes of type 'size' or 'maxlength' result in unsuable input elements: skip this.
                 if ($value === '' || $value == 0) {
                     return '';
                 }
@@ -137,7 +140,6 @@ class Support {
         $value = self::escapeDoubleTick(trim($value), $modeEscape);
 
         return $type . '="' . $value . '" ';
-
     }
 
     /**
@@ -175,6 +177,37 @@ class Support {
         return $newStr;
     }
 
+    /**
+     * Format's an attribute and inserts them at the beginning of the $htmlTag:
+     * If $flagOmitEmpty==true && $value=='': insert nothing
+     *
+     * @param string $htmlTag with open and closing angle.
+     * @param string $type
+     * @param string|array $value
+     * @param bool $flagOmitEmpty
+     * @param string $modeEscape
+     * @return string correctly fomratted attribute. Space at the end.
+     * @throws CodeException
+     */
+    public static function insertAttribute($htmlTag, $type, $value, $flagOmitEmpty = true, $modeEscape = ESCAPE_WITH_BACKSLASH) {
+        $htmlTag = trim($htmlTag);
+
+        // e.g. '<div class=...' will be exploded to '<div' and 'class=...'
+        $parts = explode(' ', $htmlTag, 2);
+        if (count($parts) < 2) {
+            if (strlen($htmlTag) < 3) {
+                throw new CodeException('HTML Token too short (<3 chars):' . $htmlTag, ERROR_HTML_TOKEN_TOO_SHORT);
+            } else {
+                $parts[0] = substr($htmlTag, 0, -1);
+                $parts[1] = '>';
+            }
+        }
+
+        $attr = self::doAttribute($type, $value, $flagOmitEmpty, $modeEscape);
+
+        return $parts[0] . ' ' . $attr . $parts[1];
+    }
+
     /**
      * Search for the parameter $needle in $haystack. The arguments has to be seperated by ','.
      *
diff --git a/extension/qfq/tests/phpunit/SupportTest.php b/extension/qfq/tests/phpunit/SupportTest.php
index 33a5adef3..7c9a6b164 100644
--- a/extension/qfq/tests/phpunit/SupportTest.php
+++ b/extension/qfq/tests/phpunit/SupportTest.php
@@ -404,6 +404,31 @@ class SupportTest extends \PHPUnit_Framework_TestCase {
         $this->assertEquals('\\"\\"\\"', $new);
     }
 
+    public function testInsertAttribute() {
+
+        $new = Support::insertAttribute('<i>', 'class', 'qfq');
+        $this->assertEquals('<i class="qfq" >', $new);
+
+        $new = Support::insertAttribute('<div>', 'class', 'qfq');
+        $this->assertEquals('<div class="qfq" >', $new);
+
+        $new = Support::insertAttribute(' <div> ', 'class', 'qfq');
+        $this->assertEquals('<div class="qfq" >', $new);
+
+        $new = Support::insertAttribute('<div >', 'class', 'qfq');
+        $this->assertEquals('<div class="qfq" >', $new);
+
+        $new = Support::insertAttribute('<div class="123">', 'class', 'qfq');
+        $this->assertEquals('<div class="qfq" class="123">', $new);
+    }
+
+    /**
+     * @expectedException \qfq\CodeException
+     */
+    public function testInsertAttributeException1() {
+        Support::insertAttribute('<>', 'class', 'qfq');
+    }
+
     protected function setUp() {
         parent::setUp();
 
-- 
GitLab