Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401]

   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) && $thead === false && $tfoot === false) {
 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