[ Index ]

PHP Cross Reference of phpBB-3.3.14-deutsch

title

Body

[close]

/vendor/s9e/text-formatter/src/Plugins/HTMLElements/ -> Configurator.php (source)

   1  <?php
   2  
   3  /**
   4  * @package   s9e\TextFormatter
   5  * @copyright Copyright (c) 2010-2022 The s9e authors
   6  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
   7  */
   8  namespace s9e\TextFormatter\Plugins\HTMLElements;
   9  
  10  use InvalidArgumentException;
  11  use RuntimeException;
  12  use s9e\TextFormatter\Configurator\Helpers\RegexpBuilder;
  13  use s9e\TextFormatter\Configurator\Items\Tag;
  14  use s9e\TextFormatter\Configurator\Items\UnsafeTemplate;
  15  use s9e\TextFormatter\Configurator\JavaScript\Dictionary;
  16  use s9e\TextFormatter\Configurator\Validators\AttributeName;
  17  use s9e\TextFormatter\Configurator\Validators\TagName;
  18  use s9e\TextFormatter\Plugins\ConfiguratorBase;
  19  
  20  class Configurator extends ConfiguratorBase
  21  {
  22      /**
  23      * @var array 2D array using HTML element names as keys, each value being an associative array
  24      *            using HTML attribute names as keys and their alias as values. A special empty entry
  25      *            is used to store the HTML element's alias
  26      */
  27      protected $aliases = [];
  28  
  29      /**
  30      * @var array  Default filter of a few known attributes
  31      *
  32      * It doesn't make much sense to try to declare every known HTML attribute here. Validation is
  33      * not the purpose of this plugin. It does make sense however to declare URL attributes as such,
  34      * so that they are subject to our constraints (disallowed hosts, etc...)
  35      *
  36      * @see scripts/patchHTMLElementConfigurator.php
  37      */
  38      protected $attributeFilters = [
  39          'action'     => '#url',
  40          'cite'       => '#url',
  41          'data'       => '#url',
  42          'formaction' => '#url',
  43          'href'       => '#url',
  44          'icon'       => '#url',
  45          'itemtype'   => '#url',
  46          'longdesc'   => '#url',
  47          'manifest'   => '#url',
  48          'ping'       => '#url',
  49          'poster'     => '#url',
  50          'src'        => '#url'
  51      ];
  52  
  53      /**
  54      * @var array  Hash of allowed HTML elements. Element names are lowercased and used as keys for
  55      *             this array
  56      */
  57      protected $elements = [];
  58  
  59      /**
  60      * @var string Namespace prefix of the tags produced by this plugin's parser
  61      */
  62      protected $prefix = 'html';
  63  
  64      /**
  65      * {@inheritdoc}
  66      */
  67      protected $quickMatch = '<';
  68  
  69      /**
  70      * @var array  Blacklist of elements that are considered unsafe
  71      */
  72      protected $unsafeElements = [
  73          'base',
  74          'embed',
  75          'frame',
  76          'iframe',
  77          'meta',
  78          'object',
  79          'script'
  80      ];
  81  
  82      /**
  83      * @var array  Blacklist of attributes that are considered unsafe, in addition of any attribute
  84      *             whose name starts with "on" such as "onmouseover"
  85      */
  86      protected $unsafeAttributes = [
  87          'style',
  88          'target'
  89      ];
  90  
  91      /**
  92      * Alias the HTML attribute of given HTML element to a given attribute name
  93      *
  94      * NOTE: will *not* create the target attribute
  95      *
  96      * @param  string $elName   Name of the HTML element
  97      * @param  string $attrName Name of the HTML attribute
  98      * @param  string $alias    Alias
  99      * @return void
 100      */
 101  	public function aliasAttribute($elName, $attrName, $alias)
 102      {
 103          $elName   = $this->normalizeElementName($elName);
 104          $attrName = $this->normalizeAttributeName($attrName);
 105  
 106          $this->aliases[$elName][$attrName] = AttributeName::normalize($alias);
 107      }
 108  
 109      /**
 110      * Alias an HTML element to a given tag name
 111      *
 112      * NOTE: will *not* create the target tag
 113      *
 114      * @param  string $elName  Name of the HTML element
 115      * @param  string $tagName Name of the tag
 116      * @return void
 117      */
 118  	public function aliasElement($elName, $tagName)
 119      {
 120          $elName = $this->normalizeElementName($elName);
 121  
 122          $this->aliases[$elName][''] = TagName::normalize($tagName);
 123      }
 124  
 125      /**
 126      * Allow an HTML element to be used
 127      *
 128      * @param  string $elName Name of the element
 129      * @return Tag            Tag that represents this element
 130      */
 131  	public function allowElement($elName)
 132      {
 133          return $this->allowElementWithSafety($elName, false);
 134      }
 135  
 136      /**
 137      * Allow an unsafe HTML element to be used
 138      *
 139      * @param  string $elName Name of the element
 140      * @return Tag            Tag that represents this element
 141      */
 142  	public function allowUnsafeElement($elName)
 143      {
 144          return $this->allowElementWithSafety($elName, true);
 145      }
 146  
 147      /**
 148      * Allow a (potentially unsafe) HTML element to be used
 149      *
 150      * @param  string $elName      Name of the element
 151      * @param  bool   $allowUnsafe Whether to allow unsafe elements
 152      * @return Tag                 Tag that represents this element
 153      */
 154  	protected function allowElementWithSafety($elName, $allowUnsafe)
 155      {
 156          $elName  = $this->normalizeElementName($elName);
 157          $tagName = $this->prefix . ':' . $elName;
 158  
 159          if (!$allowUnsafe && in_array($elName, $this->unsafeElements))
 160          {
 161              throw new RuntimeException("'" . $elName . "' elements are unsafe and are disabled by default. Please use " . __CLASS__ . '::allowUnsafeElement() to bypass this security measure');
 162          }
 163  
 164          // Retrieve or create the tag
 165          $tag = ($this->configurator->tags->exists($tagName))
 166               ? $this->configurator->tags->get($tagName)
 167               : $this->configurator->tags->add($tagName);
 168  
 169          // Rebuild this tag's template
 170          $this->rebuildTemplate($tag, $elName, $allowUnsafe);
 171  
 172          // Record the element name
 173          $this->elements[$elName] = 1;
 174  
 175          return $tag;
 176      }
 177  
 178      /**
 179      * Allow an attribute to be used in an HTML element
 180      *
 181      * @param  string $elName   Name of the element
 182      * @param  string $attrName Name of the attribute
 183      * @return \s9e\Configurator\Items\Attribute
 184      */
 185  	public function allowAttribute($elName, $attrName)
 186      {
 187          return $this->allowAttributeWithSafety($elName, $attrName, false);
 188      }
 189  
 190      /**
 191      * Allow an unsafe attribute to be used in an HTML element
 192      *
 193      * @param  string $elName   Name of the element
 194      * @param  string $attrName Name of the attribute
 195      * @return \s9e\Configurator\Items\Attribute
 196      */
 197  	public function allowUnsafeAttribute($elName, $attrName)
 198      {
 199          return $this->allowAttributeWithSafety($elName, $attrName, true);
 200      }
 201  
 202      /**
 203      * Allow a (potentially unsafe) attribute to be used in an HTML element
 204      *
 205      * @param  string $elName   Name of the element
 206      * @param  string $attrName Name of the attribute
 207      * @param  bool   $allowUnsafe
 208      * @return \s9e\Configurator\Items\Attribute
 209      */
 210  	protected function allowAttributeWithSafety($elName, $attrName, $allowUnsafe)
 211      {
 212          $elName   = $this->normalizeElementName($elName);
 213          $attrName = $this->normalizeAttributeName($attrName);
 214          $tagName  = $this->prefix . ':' . $elName;
 215  
 216          if (!isset($this->elements[$elName]))
 217          {
 218              throw new RuntimeException("Element '" . $elName . "' has not been allowed");
 219          }
 220  
 221          if (!$allowUnsafe)
 222          {
 223              if (substr($attrName, 0, 2) === 'on'
 224               || in_array($attrName, $this->unsafeAttributes))
 225              {
 226                  throw new RuntimeException("'" . $attrName . "' attributes are unsafe and are disabled by default. Please use " . __CLASS__ . '::allowUnsafeAttribute() to bypass this security measure');
 227              }
 228          }
 229  
 230          $tag = $this->configurator->tags->get($tagName);
 231          if (!isset($tag->attributes[$attrName]))
 232          {
 233              $attribute = $tag->attributes->add($attrName);
 234              $attribute->required = false;
 235  
 236              if (isset($this->attributeFilters[$attrName]))
 237              {
 238                  $filterName = $this->attributeFilters[$attrName];
 239                  $filter = $this->configurator->attributeFilters->get($filterName);
 240  
 241                  $attribute->filterChain->append($filter);
 242              }
 243          }
 244  
 245          // Rebuild this tag's template
 246          $this->rebuildTemplate($tag, $elName, $allowUnsafe);
 247  
 248          return $tag->attributes[$attrName];
 249      }
 250  
 251      /**
 252      * Validate and normalize an element name
 253      *
 254      * Accepts any name that would be valid, regardless of whether this element exists in HTML5.
 255      * Might be slightly off as the HTML5 specs don't seem to require it to start with a letter but
 256      * our implementation does.
 257      *
 258      * @link http://dev.w3.org/html5/spec/syntax.html#syntax-tag-name
 259      *
 260      * @param  string $elName Original element name
 261      * @return string         Normalized element name, in lowercase
 262      */
 263  	protected function normalizeElementName($elName)
 264      {
 265          if (!preg_match('#^[a-z][a-z0-9]*$#Di', $elName))
 266          {
 267              throw new InvalidArgumentException("Invalid element name '" . $elName . "'");
 268          }
 269  
 270          return strtolower($elName);
 271      }
 272  
 273      /**
 274      * Validate and normalize an attribute name
 275      *
 276      * More restrictive than the specs but allows all HTML5 attributes and more.
 277      *
 278      * @param  string $attrName Original attribute name
 279      * @return string           Normalized attribute name, in lowercase
 280      */
 281  	protected function normalizeAttributeName($attrName)
 282      {
 283          if (!preg_match('#^[a-z][-\\w]*$#Di', $attrName))
 284          {
 285              throw new InvalidArgumentException("Invalid attribute name '" . $attrName . "'");
 286          }
 287  
 288          return strtolower($attrName);
 289      }
 290  
 291      /**
 292      * Rebuild a tag's template
 293      *
 294      * @param  Tag    $tag         Source tag
 295      * @param  string $elName      Name of the HTML element created by the template
 296      * @param  bool   $allowUnsafe Whether to allow unsafe markup
 297      * @return void
 298      */
 299  	protected function rebuildTemplate(Tag $tag, $elName, $allowUnsafe)
 300      {
 301          $template = '<' . $elName . '>';
 302          foreach ($tag->attributes as $attrName => $attribute)
 303          {
 304              $template .= '<xsl:copy-of select="@' . $attrName . '"/>';
 305          }
 306          $template .= '<xsl:apply-templates/></' . $elName . '>';
 307  
 308          if ($allowUnsafe)
 309          {
 310              $template = new UnsafeTemplate($template);
 311          }
 312  
 313          $tag->setTemplate($template);
 314      }
 315  
 316      /**
 317      * Generate this plugin's config
 318      *
 319      * @return array|null
 320      */
 321  	public function asConfig()
 322      {
 323          if (empty($this->elements) && empty($this->aliases))
 324          {
 325              return;
 326          }
 327  
 328          /**
 329          * Regexp used to match an attributes definition (name + value if applicable)
 330          *
 331          * @link http://dev.w3.org/html5/spec/syntax.html#attributes-0
 332          */
 333          $attrRegexp = '[a-z][-a-z0-9]*(?>\\s*=\\s*(?>"[^"]*"|\'[^\']*\'|[^\\s"\'=<>`]+))?';
 334          $tagRegexp  = RegexpBuilder::fromList(array_merge(
 335              array_keys($this->aliases),
 336              array_keys($this->elements)
 337          ));
 338  
 339          $endTagRegexp   = '/(' . $tagRegexp . ')';
 340          $startTagRegexp = '(' . $tagRegexp . ')((?>\\s+' . $attrRegexp . ')*+)\\s*/?';
 341  
 342          $regexp = '#<(?>' . $endTagRegexp . '|' . $startTagRegexp . ')\\s*>#i';
 343  
 344          $config = [
 345              'quickMatch' => $this->quickMatch,
 346              'prefix'     => $this->prefix,
 347              'regexp'     => $regexp
 348          ];
 349  
 350          if (!empty($this->aliases))
 351          {
 352              // Preserve the aliases array's keys in JavaScript
 353              $config['aliases'] = new Dictionary;
 354              foreach ($this->aliases as $elName => $aliases)
 355              {
 356                  $config['aliases'][$elName] = new Dictionary($aliases);
 357              }
 358          }
 359  
 360          return $config;
 361      }
 362  
 363      /**
 364      * {@inheritdoc}
 365      */
 366  	public function getJSHints()
 367      {
 368          return ['HTMLELEMENTS_HAS_ALIASES' => (int) !empty($this->aliases)];
 369      }
 370  }


Generated: Mon Nov 25 19:05:08 2024 Cross-referenced by PHPXref 0.7.1