Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

Differences Between: [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403]

   1  <?php
   2  
   3  /**

   4   * Definition for tables.  The general idea is to extract out all of the

   5   * essential bits, and then reconstruct it later.

   6   *

   7   * This is a bit confusing, because the DTDs and the W3C

   8   * validators seem to disagree on the appropriate definition. The

   9   * DTD claims:

  10   *

  11   *      (CAPTION?, (COL*|COLGROUP*), THEAD?, TFOOT?, TBODY+)

  12   *

  13   * But actually, the HTML4 spec then has this to say:

  14   *

  15   *      The TBODY start tag is always required except when the table

  16   *      contains only one table body and no table head or foot sections.

  17   *      The TBODY end tag may always be safely omitted.

  18   *

  19   * So the DTD is kind of wrong.  The validator is, unfortunately, kind

  20   * of on crack.

  21   *

  22   * The definition changed again in XHTML1.1; and in my opinion, this

  23   * formulation makes the most sense.

  24   *

  25   *      caption?, ( col* | colgroup* ), (( thead?, tfoot?, tbody+ ) | ( tr+ ))

  26   *

  27   * Essentially, we have two modes: thead/tfoot/tbody mode, and tr mode.

  28   * If we encounter a thead, tfoot or tbody, we are placed in the former

  29   * mode, and we *must* wrap any stray tr segments with a tbody. But if

  30   * we don't run into any of them, just have tr tags is OK.

  31   */
  32  class HTMLPurifier_ChildDef_Table extends HTMLPurifier_ChildDef
  33  {
  34      /**

  35       * @type bool

  36       */
  37      public $allow_empty = false;
  38  
  39      /**

  40       * @type string

  41       */
  42      public $type = 'table';
  43  
  44      /**

  45       * @type array

  46       */
  47      public $elements = array(
  48          'tr' => true,
  49          'tbody' => true,
  50          'thead' => true,
  51          'tfoot' => true,
  52          'caption' => true,
  53          'colgroup' => true,
  54          'col' => true
  55      );
  56  
  57      public function __construct()
  58      {
  59      }
  60  
  61      /**

  62       * @param array $children

  63       * @param HTMLPurifier_Config $config

  64       * @param HTMLPurifier_Context $context

  65       * @return array

  66       */
  67      public function validateChildren($children, $config, $context)
  68      {
  69          if (empty($children)) {
  70              return false;
  71          }
  72  
  73          // only one of these elements is allowed in a table

  74          $caption = false;
  75          $thead = false;
  76          $tfoot = false;
  77  
  78          // whitespace

  79          $initial_ws = array();
  80          $after_caption_ws = array();
  81          $after_thead_ws = array();
  82          $after_tfoot_ws = array();
  83  
  84          // as many of these as you want

  85          $cols = array();
  86          $content = array();
  87  
  88          $tbody_mode = false; // if true, then we need to wrap any stray

  89                               // <tr>s with a <tbody>.

  90  
  91          $ws_accum =& $initial_ws;
  92  
  93          foreach ($children as $node) {
  94              if ($node instanceof HTMLPurifier_Node_Comment) {
  95                  $ws_accum[] = $node;
  96                  continue;
  97              }
  98              switch ($node->name) {
  99              case 'tbody':
 100                  $tbody_mode = true;
 101                  // fall through

 102              case 'tr':
 103                  $content[] = $node;
 104                  $ws_accum =& $content;
 105                  break;
 106              case 'caption':
 107                  // there can only be one caption!

 108                  if ($caption !== false)  break;
 109                  $caption = $node;
 110                  $ws_accum =& $after_caption_ws;
 111                  break;
 112              case 'thead':
 113                  $tbody_mode = true;
 114                  // XXX This breaks rendering properties with

 115                  // Firefox, which never floats a <thead> to

 116                  // the top. Ever. (Our scheme will float the

 117                  // first <thead> to the top.)  So maybe

 118                  // <thead>s that are not first should be

 119                  // turned into <tbody>? Very tricky, indeed.

 120                  if ($thead === false) {
 121                      $thead = $node;
 122                      $ws_accum =& $after_thead_ws;
 123                  } else {
 124                      // Oops, there's a second one! What

 125                      // should we do?  Current behavior is to

 126                      // transmutate the first and last entries into

 127                      // tbody tags, and then put into content.

 128                      // Maybe a better idea is to *attach

 129                      // it* to the existing thead or tfoot?

 130                      // We don't do this, because Firefox

 131                      // doesn't float an extra tfoot to the

 132                      // bottom like it does for the first one.

 133                      $node->name = 'tbody';
 134                      $content[] = $node;
 135                      $ws_accum =& $content;
 136                  }
 137                  break;
 138              case 'tfoot':
 139                  // see above for some aveats

 140                  $tbody_mode = true;
 141                  if ($tfoot === false) {
 142                      $tfoot = $node;
 143                      $ws_accum =& $after_tfoot_ws;
 144                  } else {
 145                      $node->name = 'tbody';
 146                      $content[] = $node;
 147                      $ws_accum =& $content;
 148                  }
 149                  break;
 150              case 'colgroup':
 151              case 'col':
 152                  $cols[] = $node;
 153                  $ws_accum =& $cols;
 154                  break;
 155              case '#PCDATA':
 156                  // How is whitespace handled? We treat is as sticky to

 157                  // the *end* of the previous element. So all of the

 158                  // nonsense we have worked on is to keep things

 159                  // together.

 160                  if (!empty($node->is_whitespace)) {
 161                      $ws_accum[] = $node;
 162                  }
 163                  break;
 164              }
 165          }
 166  
 167          if (empty($content)) {
 168              return false;
 169          }
 170  
 171          $ret = $initial_ws;
 172          if ($caption !== false) {
 173              $ret[] = $caption;
 174              $ret = array_merge($ret, $after_caption_ws);
 175          }
 176          if ($cols !== false) {
 177              $ret = array_merge($ret, $cols);
 178          }
 179          if ($thead !== false) {
 180              $ret[] = $thead;
 181              $ret = array_merge($ret, $after_thead_ws);
 182          }
 183          if ($tfoot !== false) {
 184              $ret[] = $tfoot;
 185              $ret = array_merge($ret, $after_tfoot_ws);
 186          }
 187  
 188          if ($tbody_mode) {
 189              // we have to shuffle tr into tbody

 190              $current_tr_tbody = null;
 191  
 192              foreach($content as $node) {
 193                  switch ($node->name) {
 194                  case 'tbody':
 195                      $current_tr_tbody = null;
 196                      $ret[] = $node;
 197                      break;
 198                  case 'tr':
 199                      if ($current_tr_tbody === null) {
 200                          $current_tr_tbody = new HTMLPurifier_Node_Element('tbody');
 201                          $ret[] = $current_tr_tbody;
 202                      }
 203                      $current_tr_tbody->children[] = $node;
 204                      break;
 205                  case '#PCDATA':
 206                      //assert($node->is_whitespace);

 207                      if ($current_tr_tbody === null) {
 208                          $ret[] = $node;
 209                      } else {
 210                          $current_tr_tbody->children[] = $node;
 211                      }
 212                      break;
 213                  }
 214              }
 215          } else {
 216              $ret = array_merge($ret, $content);
 217          }
 218  
 219          return $ret;
 220  
 221      }
 222  }
 223  
 224  // vim: et sw=4 sts=4