In shallow mode, only convert markup to array that the GUI editor understands.
authorColeman Watts <coleman@civicrm.org>
Sat, 30 Nov 2019 15:52:33 +0000 (10:52 -0500)
committerCiviCRM <info@civicrm.org>
Wed, 16 Sep 2020 02:13:20 +0000 (19:13 -0700)
Any markup not using a tag/class/attribute understood by the GUI editor
will have its contents preserved "as-is" in a "#markup" string.

ext/afform/core/CRM/Afform/ArrayHtml.php
ext/afform/mock/tests/phpunit/api/v4/AfformTest.php
ext/afform/mock/tests/phpunit/api/v4/formatExamples/apple.php
ext/afform/mock/tests/phpunit/api/v4/formatExamples/banana.php
ext/afform/mock/tests/phpunit/api/v4/formatExamples/cherry.php
ext/afform/mock/tests/phpunit/api/v4/formatExamples/comments.php
ext/afform/mock/tests/phpunit/api/v4/formatExamples/self-closing.php
ext/afform/mock/tests/phpunit/api/v4/formatExamples/stylized.php

index b0dae5fe7ea318612f91e630033cb47790480d27..59c8cae0a5c69997d33d0321ccbdf54419245dc6 100644 (file)
@@ -125,7 +125,7 @@ class CRM_Afform_ArrayHtml {
       }
     }
 
-    if (isset($array['#markup']) && !$this->formatWhitespace) {
+    if (isset($array['#markup']) && (!$this->formatWhitespace || strpos($array['#markup'], '<') === FALSE)) {
       $buf .= '>' . $array['#markup'] . '</' . $tag . '>';
     }
     elseif (isset($array['#markup'])) {
@@ -179,6 +179,7 @@ class CRM_Afform_ArrayHtml {
     }
 
     $doc = new DOMDocument();
+    $doc->preserveWhiteSpace = !$this->formatWhitespace;
     @$doc->loadHTML("<html><body>$html</body></html>");
 
     // FIXME: Validate expected number of child nodes
@@ -205,8 +206,8 @@ class CRM_Afform_ArrayHtml {
         $arr[$attribute->name] = $this->decodeAttrValue($type, $txt);
       }
       if ($node->childNodes->length > 0) {
-        // In shallow mode, return "af-markup" containers as-is
-        if (!$this->deepCoding && !empty($arr['class']) && strpos($arr['class'], 'af-markup') !== FALSE) {
+        // In shallow mode, return markup as-is if node isn't supported by the gui editor
+        if (!$this->deepCoding && !$this->isNodeEditable($arr)) {
           $arr['#markup'] = '';
           foreach ($node->childNodes as $child) {
             $arr['#markup'] .= $child->ownerDocument->saveXML($child);
@@ -335,4 +336,19 @@ class CRM_Afform_ArrayHtml {
     }
   }
 
+  /**
+   * Determine if a node is recognized by the gui editor.
+   *
+   * @param array $item
+   * @return bool
+   */
+  public function isNodeEditable(array $item) {
+    if ($item['#tag'] === 'af-field' || $item['#tag'] === 'af-form' || isset($item['af-fieldset'])) {
+      return TRUE;
+    }
+    $editableClasses = ['af-block', 'af-text', 'af-button'];
+    $classes = explode(' ', $item['class'] ?? '');
+    return (bool) array_intersect($editableClasses, $classes);
+  }
+
 }
index 6a7fdd87e198e4fe745a8514ea7a72fe896a1e19..45bc30c2dc6b8f8b3482343347668bb5cc10b25c 100644 (file)
@@ -9,6 +9,25 @@ class api_v4_AfformTest extends api_v4_AfformTestCase {
   use \Civi\Test\Api3TestTrait;
   use \Civi\Test\ContactTestTrait;
 
+  /**
+   * DOMDocument outputs some tags a little different than they were input.
+   * It's not really a problem but can trip up tests.
+   *
+   * @param array|string $markup
+   * @return array|string
+   */
+  private function fudgeMarkup($markup) {
+    if (is_array($markup)) {
+      foreach ($markup as $idx => $item) {
+        $markup[$idx] = $this->fudgeMarkup($item);
+      }
+      return $markup;
+    }
+    else {
+      return str_replace([' />', '/>'], ['/>', ' />'], $markup);
+    }
+  }
+
   public function getBasicDirectives() {
     return [
       ['mockPage', ['title' => '', 'description' => '', 'server_route' => 'civicrm/mock-page']],
@@ -117,7 +136,7 @@ class api_v4_AfformTest extends api_v4_AfformTestCase {
       ->setLayoutFormat($readFormat)
       ->execute();
 
-    $this->assertEquals($readLayout, $result[0]['layout'], "Based on \"$exampleName\", writing content as \"$updateFormat\" and reading back as \"$readFormat\".");
+    $this->assertEquals($readLayout, $this->fudgeMarkup($result[0]['layout']), "Based on \"$exampleName\", writing content as \"$updateFormat\" and reading back as \"$readFormat\".");
 
     Civi\Api4\Afform::revert()->addWhere('name', '=', $formName)->execute();
   }
@@ -152,7 +171,7 @@ class api_v4_AfformTest extends api_v4_AfformTestCase {
       ->execute()
       ->first();
 
-    $this->assertEquals($example['stripped'] ?? $example['shallow'], $result['layout']);
+    $this->assertEquals($example['stripped'] ?? $example['shallow'], $this->fudgeMarkup($result['layout']));
 
     Civi\Api4\Afform::save()
       ->addRecord(['name' => $directiveName, 'layout' => $result['layout']])
@@ -166,7 +185,7 @@ class api_v4_AfformTest extends api_v4_AfformTestCase {
       ->execute()
       ->first();
 
-    $this->assertEquals($example['pretty'], $result['layout']);
+    $this->assertEquals($example['pretty'], $this->fudgeMarkup($result['layout']));
   }
 
   public function testAutoRequires() {
index d0674fc335a37b8084a824c70969a358817f9e59..18a4bb0eabe800f63566ca0133753b560b2d2b6c 100644 (file)
@@ -4,7 +4,7 @@ return [
   'html' => '<strong>New text!</strong>',
   'pretty' => "<strong>New text!</strong>\n",
   'shallow' => [
-    ['#tag' => 'strong', '#children' => [['#text' => 'New text!']]],
+    ['#tag' => 'strong', '#markup' => 'New text!'],
   ],
   'deep' => [
     ['#tag' => 'strong', '#children' => [['#text' => 'New text!']]],
index 4e2e63db3cf78dffe1b876fb98c9aa44f5871c3d..da70ac501c14dcead1967ec6117366b707c8e133 100644 (file)
@@ -1,17 +1,20 @@
 <?php
 
 return [
-  'html' => '<div><strong>  New text!</strong><af-field name="do_not_sms" defn="{label: \'Do not do any of the emailing\'}" /></div>',
-  'pretty' => '<div>
-  <strong>New text!</strong>
+  'html' => '<div class="af-block"><strong>  New text!</strong><strong class="af-text"> No whitespace! </strong><af-field name="do_not_sms" defn="{label: \'Do not do any of the emailing\'}" /></div>',
+  'pretty' => '<div class="af-block">
+  <strong>  New text!</strong>
+  <strong class="af-text">No whitespace!</strong>
   <af-field name="do_not_sms" defn="{label: \'Do not do any of the emailing\'}" />
 </div>
 ',
   'stripped' => [
     [
       '#tag' => 'div',
+      'class' => 'af-block',
       '#children' => [
-        ['#tag' => 'strong', '#children' => [['#text' => 'New text!']]],
+        ['#tag' => 'strong', '#markup' => '  New text!'],
+        ['#tag' => 'strong', 'class' => 'af-text', '#children' => [['#text' => 'No whitespace!']]],
         ['#tag' => 'af-field', 'name' => 'do_not_sms', 'defn' => "{label: 'Do not do any of the emailing'}"],
       ],
     ],
@@ -19,8 +22,10 @@ return [
   'shallow' => [
     [
       '#tag' => 'div',
+      'class' => 'af-block',
       '#children' => [
-        ['#tag' => 'strong', '#children' => [['#text' => '  New text!']]],
+        ['#tag' => 'strong', '#markup' => '  New text!'],
+        ['#tag' => 'strong', 'class' => 'af-text', '#children' => [['#text' => ' No whitespace! ']]],
         ['#tag' => 'af-field', 'name' => 'do_not_sms', 'defn' => "{label: 'Do not do any of the emailing'}"],
       ],
     ],
@@ -28,8 +33,10 @@ return [
   'deep' => [
     [
       '#tag' => 'div',
+      'class' => 'af-block',
       '#children' => [
         ['#tag' => 'strong', '#children' => [['#text' => '  New text!']]],
+        ['#tag' => 'strong', 'class' => 'af-text', '#children' => [['#text' => ' No whitespace! ']]],
         ['#tag' => 'af-field', 'name' => 'do_not_sms', 'defn' => ['label' => 'Do not do any of the emailing']],
       ],
     ],
index 44376d4cd9e6d8548f79c2f1c2c58cc5c720f06d..4060cfb6c87a3d7f0670d2226da0ba8a49b8307e 100644 (file)
@@ -1,20 +1,21 @@
 <?php
 
 return [
-  'html' => '<span>First</span> <span>Second</span>',
-  'pretty' => "<span>First</span>\n<span>Second</span>\n",
-  'shallow' => [
-    ['#tag' => 'span', '#children' => [['#text' => 'First']]],
-    ['#text' => ' '],
-    ['#tag' => 'span', '#children' => [['#text' => 'Second']]],
-  ],
+  'html' => '<span>First</span>   <span>Second</span>',
+  'pretty' => "<span>First</span>
+<span>Second</span>\n",
   'stripped' => [
-    ['#tag' => 'span', '#children' => [['#text' => 'First']]],
-    ['#tag' => 'span', '#children' => [['#text' => 'Second']]],
+    ['#tag' => 'span', '#markup' => 'First'],
+    ['#tag' => 'span', '#markup' => 'Second'],
+  ],
+  'shallow' => [
+    ['#tag' => 'span', '#markup' => 'First'],
+    ['#text' => '   '],
+    ['#tag' => 'span', '#markup' => 'Second'],
   ],
   'deep' => [
     ['#tag' => 'span', '#children' => [['#text' => 'First']]],
-    ['#text' => ' '],
+    ['#text' => '   '],
     ['#tag' => 'span', '#children' => [['#text' => 'Second']]],
   ],
 ];
index c311e930e67ffd258b6be64b33136e7bb4737329..e060fb6b53c09dfd50e6bde1409b39c9af8f8a5e 100644 (file)
@@ -5,13 +5,7 @@ return [
   'shallow' => [
     [
       '#tag' => 'div',
-      '#children' => [
-        ['#text' => 'One'],
-        ['#comment' => ' uno '],
-        ['#text' => ' Two '],
-        ['#comment' => 'dos & so on '],
-        ['#text' => ' Three'],
-      ],
+      '#markup' => 'One<!-- uno --> Two <!--dos & so on --> Three',
     ],
     ['#comment' => 'tres-a--b---c'],
   ],
index 62063a80877d1b82a8d5e9cb8743793705083d70..3132e0b4c25a6ea057590081d276ad6d1eb8faed 100644 (file)
@@ -5,20 +5,13 @@ return [
   'pretty' => '<span class="one"></span>
 <img class="two" />
 <div>
-  <br class="three" />
-  <br />
+  <br class="three" /><br />
 </div>
 ',
   'shallow' => [
     ['#tag' => 'span', 'class' => 'one'],
     ['#tag' => 'img', 'class' => 'two'],
-    [
-      '#tag' => 'div',
-      '#children' => [
-        ['#tag' => 'br', 'class' => 'three'],
-        ['#tag' => 'br'],
-      ],
-    ],
+    ['#tag' => 'div', '#markup' => '<br class="three" /><br />'],
   ],
   'deep' => [
     ['#tag' => 'span', 'class' => 'one'],
index f99f6ba756517db780006a4bc3d8cb677e22747e..65aaede619ffc5b5da8a9202aac9a6dc708be8a6 100644 (file)
@@ -2,21 +2,21 @@
 
 return [
   'html' => '<p><strong>Stylized</strong> text is <em>wonky</em> text!</p>',
-  'pretty' => "<p><strong>Stylized</strong> text is <em>wonky</em> text!</p>\n",
+  'pretty' => "<p>
+  <strong>Stylized</strong> text is <em>wonky</em> text!
+</p>\n",
   'shallow' => [
-    ['#tag' => 'p', '#children' => [
-      ['#tag' => 'strong', '#children' => [['#text' => 'Stylized']]],
-      ['#text' => ' text is '],
-      ['#tag' => 'em', '#children' => [['#text' => 'wonky']]],
-      ['#text' => ' text!'],
-    ]],
+    ['#tag' => 'p', '#markup' => '<strong>Stylized</strong> text is <em>wonky</em> text!'],
   ],
   'deep' => [
-    ['#tag' => 'p', '#children' => [
-      ['#tag' => 'strong', '#children' => [['#text' => 'Stylized']]],
-      ['#text' => ' text is '],
-      ['#tag' => 'em', '#children' => [['#text' => 'wonky']]],
-      ['#text' => ' text!'],
-    ]],
+    [
+      '#tag' => 'p',
+      '#children' => [
+        ['#tag' => 'strong', '#children' => [['#text' => 'Stylized']]],
+        ['#text' => ' text is '],
+        ['#tag' => 'em', '#children' => [['#text' => 'wonky']]],
+        ['#text' => ' text!'],
+      ],
+    ],
   ],
 ];