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   * Abstract class for a set of proprietary modules that clean up (tidy)
   5   * poorly written HTML.
   6   * @todo Figure out how to protect some of these methods/properties
   7   */
   8  class HTMLPurifier_HTMLModule_Tidy extends HTMLPurifier_HTMLModule
   9  {
  10      /**
  11       * List of supported levels.
  12       * Index zero is a special case "no fixes" level.
  13       * @type array
  14       */
  15      public $levels = array(0 => 'none', 'light', 'medium', 'heavy');
  16  
  17      /**
  18       * Default level to place all fixes in.
  19       * Disabled by default.
  20       * @type string
  21       */
  22      public $defaultLevel = null;
  23  
  24      /**
  25       * Lists of fixes used by getFixesForLevel().
  26       * Format is:
  27       *      HTMLModule_Tidy->fixesForLevel[$level] = array('fix-1', 'fix-2');
  28       * @type array
  29       */
  30      public $fixesForLevel = array(
  31          'light' => array(),
  32          'medium' => array(),
  33          'heavy' => array()
  34      );
  35  
  36      /**
  37       * Lazy load constructs the module by determining the necessary
  38       * fixes to create and then delegating to the populate() function.
  39       * @param HTMLPurifier_Config $config
  40       * @todo Wildcard matching and error reporting when an added or
  41       *       subtracted fix has no effect.
  42       */
  43      public function setup($config)
  44      {
  45          // create fixes, initialize fixesForLevel
  46          $fixes = $this->makeFixes();
  47          $this->makeFixesForLevel($fixes);
  48  
  49          // figure out which fixes to use
  50          $level = $config->get('HTML.TidyLevel');
  51          $fixes_lookup = $this->getFixesForLevel($level);
  52  
  53          // get custom fix declarations: these need namespace processing
  54          $add_fixes = $config->get('HTML.TidyAdd');
  55          $remove_fixes = $config->get('HTML.TidyRemove');
  56  
  57          foreach ($fixes as $name => $fix) {
  58              // needs to be refactored a little to implement globbing
  59              if (isset($remove_fixes[$name]) ||
  60                  (!isset($add_fixes[$name]) && !isset($fixes_lookup[$name]))) {
  61                  unset($fixes[$name]);
  62              }
  63          }
  64  
  65          // populate this module with necessary fixes
  66          $this->populate($fixes);
  67      }
  68  
  69      /**
  70       * Retrieves all fixes per a level, returning fixes for that specific
  71       * level as well as all levels below it.
  72       * @param string $level level identifier, see $levels for valid values
  73       * @return array Lookup up table of fixes
  74       */
  75      public function getFixesForLevel($level)
  76      {
  77          if ($level == $this->levels[0]) {
  78              return array();
  79          }
  80          $activated_levels = array();
  81          for ($i = 1, $c = count($this->levels); $i < $c; $i++) {
  82              $activated_levels[] = $this->levels[$i];
  83              if ($this->levels[$i] == $level) {
  84                  break;
  85              }
  86          }
  87          if ($i == $c) {
  88              trigger_error(
  89                  'Tidy level ' . htmlspecialchars($level) . ' not recognized',
  90                  E_USER_WARNING
  91              );
  92              return array();
  93          }
  94          $ret = array();
  95          foreach ($activated_levels as $level) {
  96              foreach ($this->fixesForLevel[$level] as $fix) {
  97                  $ret[$fix] = true;
  98              }
  99          }
 100          return $ret;
 101      }
 102  
 103      /**
 104       * Dynamically populates the $fixesForLevel member variable using
 105       * the fixes array. It may be custom overloaded, used in conjunction
 106       * with $defaultLevel, or not used at all.
 107       * @param array $fixes
 108       */
 109      public function makeFixesForLevel($fixes)
 110      {
 111          if (!isset($this->defaultLevel)) {
 112              return;
 113          }
 114          if (!isset($this->fixesForLevel[$this->defaultLevel])) {
 115              trigger_error(
 116                  'Default level ' . $this->defaultLevel . ' does not exist',
 117                  E_USER_ERROR
 118              );
 119              return;
 120          }
 121          $this->fixesForLevel[$this->defaultLevel] = array_keys($fixes);
 122      }
 123  
 124      /**
 125       * Populates the module with transforms and other special-case code
 126       * based on a list of fixes passed to it
 127       * @param array $fixes Lookup table of fixes to activate
 128       */
 129      public function populate($fixes)
 130      {
 131          foreach ($fixes as $name => $fix) {
 132              // determine what the fix is for
 133              list($type, $params) = $this->getFixType($name);
 134              switch ($type) {
 135                  case 'attr_transform_pre':
 136                  case 'attr_transform_post':
 137                      $attr = $params['attr'];
 138                      if (isset($params['element'])) {
 139                          $element = $params['element'];
 140                          if (empty($this->info[$element])) {
 141                              $e = $this->addBlankElement($element);
 142                          } else {
 143                              $e = $this->info[$element];
 144                          }
 145                      } else {
 146                          $type = "info_$type";
 147                          $e = $this;
 148                      }
 149                      $e->{$type}[$attr] = $fix;
 150                      break;
 151                  case 'tag_transform':
 152                      $this->info_tag_transform[$params['element']] = $fix;
 153                      break;
 154                  case 'child':
 155                  case 'content_model_type':
 156                      $element = $params['element'];
 157                      if (empty($this->info[$element])) {
 158                          $e = $this->addBlankElement($element);
 159                      } else {
 160                          $e = $this->info[$element];
 161                      }
 162                      $e->$type = $fix;
 163                      break;
 164                  default:
 165                      trigger_error("Fix type $type not supported", E_USER_ERROR);
 166                      break;
 167              }
 168          }
 169      }
 170  
 171      /**
 172       * Parses a fix name and determines what kind of fix it is, as well
 173       * as other information defined by the fix
 174       * @param $name String name of fix
 175       * @return array(string $fix_type, array $fix_parameters)
 176       * @note $fix_parameters is type dependant, see populate() for usage
 177       *       of these parameters
 178       */
 179      public function getFixType($name)
 180      {
 181          // parse it
 182          $property = $attr = null;
 183          if (strpos($name, '#') !== false) {
 184              list($name, $property) = explode('#', $name);
 185          }
 186          if (strpos($name, '@') !== false) {
 187              list($name, $attr) = explode('@', $name);
 188          }
 189  
 190          // figure out the parameters
 191          $params = array();
 192          if ($name !== '') {
 193              $params['element'] = $name;
 194          }
 195          if (!is_null($attr)) {
 196              $params['attr'] = $attr;
 197          }
 198  
 199          // special case: attribute transform
 200          if (!is_null($attr)) {
 201              if (is_null($property)) {
 202                  $property = 'pre';
 203              }
 204              $type = 'attr_transform_' . $property;
 205              return array($type, $params);
 206          }
 207  
 208          // special case: tag transform
 209          if (is_null($property)) {
 210              return array('tag_transform', $params);
 211          }
 212  
 213          return array($property, $params);
 214  
 215      }
 216  
 217      /**
 218       * Defines all fixes the module will perform in a compact
 219       * associative array of fix name to fix implementation.
 220       * @return array
 221       */
 222      public function makeFixes()
 223      {
 224      }
 225  }
 226  
 227  // vim: et sw=4 sts=4