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   * 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                      // PHP does some weird parsing when I do

 150                      // $e->$type[$attr], so I have to assign a ref.

 151                      $f =& $e->$type;
 152                      $f[$attr] = $fix;
 153                      break;
 154                  case 'tag_transform':
 155                      $this->info_tag_transform[$params['element']] = $fix;
 156                      break;
 157                  case 'child':
 158                  case 'content_model_type':
 159                      $element = $params['element'];
 160                      if (empty($this->info[$element])) {
 161                          $e = $this->addBlankElement($element);
 162                      } else {
 163                          $e = $this->info[$element];
 164                      }
 165                      $e->$type = $fix;
 166                      break;
 167                  default:
 168                      trigger_error("Fix type $type not supported", E_USER_ERROR);
 169                      break;
 170              }
 171          }
 172      }
 173  
 174      /**

 175       * Parses a fix name and determines what kind of fix it is, as well

 176       * as other information defined by the fix

 177       * @param $name String name of fix

 178       * @return array(string $fix_type, array $fix_parameters)

 179       * @note $fix_parameters is type dependant, see populate() for usage

 180       *       of these parameters

 181       */
 182      public function getFixType($name)
 183      {
 184          // parse it

 185          $property = $attr = null;
 186          if (strpos($name, '#') !== false) {
 187              list($name, $property) = explode('#', $name);
 188          }
 189          if (strpos($name, '@') !== false) {
 190              list($name, $attr) = explode('@', $name);
 191          }
 192  
 193          // figure out the parameters

 194          $params = array();
 195          if ($name !== '') {
 196              $params['element'] = $name;
 197          }
 198          if (!is_null($attr)) {
 199              $params['attr'] = $attr;
 200          }
 201  
 202          // special case: attribute transform

 203          if (!is_null($attr)) {
 204              if (is_null($property)) {
 205                  $property = 'pre';
 206              }
 207              $type = 'attr_transform_' . $property;
 208              return array($type, $params);
 209          }
 210  
 211          // special case: tag transform

 212          if (is_null($property)) {
 213              return array('tag_transform', $params);
 214          }
 215  
 216          return array($property, $params);
 217  
 218      }
 219  
 220      /**

 221       * Defines all fixes the module will perform in a compact

 222       * associative array of fix name to fix implementation.

 223       * @return array

 224       */
 225      public function makeFixes()
 226      {
 227      }
 228  }
 229  
 230  // vim: et sw=4 sts=4