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   * Configuration object that triggers customizable behavior.
   5   *
   6   * @warning This class is strongly defined: that means that the class
   7   *          will fail if an undefined directive is retrieved or set.
   8   *
   9   * @note Many classes that could (although many times don't) use the
  10   *       configuration object make it a mandatory parameter.  This is
  11   *       because a configuration object should always be forwarded,
  12   *       otherwise, you run the risk of missing a parameter and then
  13   *       being stumped when a configuration directive doesn't work.
  14   *
  15   * @todo Reconsider some of the public member variables
  16   */
  17  class HTMLPurifier_Config
  18  {
  19  
  20      /**
  21       * HTML Purifier's version
  22       * @type string
  23       */
  24      public $version = '4.15.0';
  25  
  26      /**
  27       * Whether or not to automatically finalize
  28       * the object if a read operation is done.
  29       * @type bool
  30       */
  31      public $autoFinalize = true;
  32  
  33      // protected member variables
  34  
  35      /**
  36       * Namespace indexed array of serials for specific namespaces.
  37       * @see getSerial() for more info.
  38       * @type string[]
  39       */
  40      protected $serials = array();
  41  
  42      /**
  43       * Serial for entire configuration object.
  44       * @type string
  45       */
  46      protected $serial;
  47  
  48      /**
  49       * Parser for variables.
  50       * @type HTMLPurifier_VarParser_Flexible
  51       */
  52      protected $parser = null;
  53  
  54      /**
  55       * Reference HTMLPurifier_ConfigSchema for value checking.
  56       * @type HTMLPurifier_ConfigSchema
  57       * @note This is public for introspective purposes. Please don't
  58       *       abuse!
  59       */
  60      public $def;
  61  
  62      /**
  63       * Indexed array of definitions.
  64       * @type HTMLPurifier_Definition[]
  65       */
  66      protected $definitions;
  67  
  68      /**
  69       * Whether or not config is finalized.
  70       * @type bool
  71       */
  72      protected $finalized = false;
  73  
  74      /**
  75       * Property list containing configuration directives.
  76       * @type array
  77       */
  78      protected $plist;
  79  
  80      /**
  81       * Whether or not a set is taking place due to an alias lookup.
  82       * @type bool
  83       */
  84      private $aliasMode;
  85  
  86      /**
  87       * Set to false if you do not want line and file numbers in errors.
  88       * (useful when unit testing).  This will also compress some errors
  89       * and exceptions.
  90       * @type bool
  91       */
  92      public $chatty = true;
  93  
  94      /**
  95       * Current lock; only gets to this namespace are allowed.
  96       * @type string
  97       */
  98      private $lock;
  99  
 100      /**
 101       * Constructor
 102       * @param HTMLPurifier_ConfigSchema $definition ConfigSchema that defines
 103       * what directives are allowed.
 104       * @param HTMLPurifier_PropertyList $parent
 105       */
 106      public function __construct($definition, $parent = null)
 107      {
 108          $parent = $parent ? $parent : $definition->defaultPlist;
 109          $this->plist = new HTMLPurifier_PropertyList($parent);
 110          $this->def = $definition; // keep a copy around for checking
 111          $this->parser = new HTMLPurifier_VarParser_Flexible();
 112      }
 113  
 114      /**
 115       * Convenience constructor that creates a config object based on a mixed var
 116       * @param mixed $config Variable that defines the state of the config
 117       *                      object. Can be: a HTMLPurifier_Config() object,
 118       *                      an array of directives based on loadArray(),
 119       *                      or a string filename of an ini file.
 120       * @param HTMLPurifier_ConfigSchema $schema Schema object
 121       * @return HTMLPurifier_Config Configured object
 122       */
 123      public static function create($config, $schema = null)
 124      {
 125          if ($config instanceof HTMLPurifier_Config) {
 126              // pass-through
 127              return $config;
 128          }
 129          if (!$schema) {
 130              $ret = HTMLPurifier_Config::createDefault();
 131          } else {
 132              $ret = new HTMLPurifier_Config($schema);
 133          }
 134          if (is_string($config)) {
 135              $ret->loadIni($config);
 136          } elseif (is_array($config)) $ret->loadArray($config);
 137          return $ret;
 138      }
 139  
 140      /**
 141       * Creates a new config object that inherits from a previous one.
 142       * @param HTMLPurifier_Config $config Configuration object to inherit from.
 143       * @return HTMLPurifier_Config object with $config as its parent.
 144       */
 145      public static function inherit(HTMLPurifier_Config $config)
 146      {
 147          return new HTMLPurifier_Config($config->def, $config->plist);
 148      }
 149  
 150      /**
 151       * Convenience constructor that creates a default configuration object.
 152       * @return HTMLPurifier_Config default object.
 153       */
 154      public static function createDefault()
 155      {
 156          $definition = HTMLPurifier_ConfigSchema::instance();
 157          $config = new HTMLPurifier_Config($definition);
 158          return $config;
 159      }
 160  
 161      /**
 162       * Retrieves a value from the configuration.
 163       *
 164       * @param string $key String key
 165       * @param mixed $a
 166       *
 167       * @return mixed
 168       */
 169      public function get($key, $a = null)
 170      {
 171          if ($a !== null) {
 172              $this->triggerError(
 173                  "Using deprecated API: use \$config->get('$key.$a') instead",
 174                  E_USER_WARNING
 175              );
 176              $key = "$key.$a";
 177          }
 178          if (!$this->finalized) {
 179              $this->autoFinalize();
 180          }
 181          if (!isset($this->def->info[$key])) {
 182              // can't add % due to SimpleTest bug
 183              $this->triggerError(
 184                  'Cannot retrieve value of undefined directive ' . htmlspecialchars($key),
 185                  E_USER_WARNING
 186              );
 187              return;
 188          }
 189          if (isset($this->def->info[$key]->isAlias)) {
 190              $d = $this->def->info[$key];
 191              $this->triggerError(
 192                  'Cannot get value from aliased directive, use real name ' . $d->key,
 193                  E_USER_ERROR
 194              );
 195              return;
 196          }
 197          if ($this->lock) {
 198              list($ns) = explode('.', $key);
 199              if ($ns !== $this->lock) {
 200                  $this->triggerError(
 201                      'Cannot get value of namespace ' . $ns . ' when lock for ' .
 202                      $this->lock .
 203                      ' is active, this probably indicates a Definition setup method ' .
 204                      'is accessing directives that are not within its namespace',
 205                      E_USER_ERROR
 206                  );
 207                  return;
 208              }
 209          }
 210          return $this->plist->get($key);
 211      }
 212  
 213      /**
 214       * Retrieves an array of directives to values from a given namespace
 215       *
 216       * @param string $namespace String namespace
 217       *
 218       * @return array
 219       */
 220      public function getBatch($namespace)
 221      {
 222          if (!$this->finalized) {
 223              $this->autoFinalize();
 224          }
 225          $full = $this->getAll();
 226          if (!isset($full[$namespace])) {
 227              $this->triggerError(
 228                  'Cannot retrieve undefined namespace ' .
 229                  htmlspecialchars($namespace),
 230                  E_USER_WARNING
 231              );
 232              return;
 233          }
 234          return $full[$namespace];
 235      }
 236  
 237      /**
 238       * Returns a SHA-1 signature of a segment of the configuration object
 239       * that uniquely identifies that particular configuration
 240       *
 241       * @param string $namespace Namespace to get serial for
 242       *
 243       * @return string
 244       * @note Revision is handled specially and is removed from the batch
 245       *       before processing!
 246       */
 247      public function getBatchSerial($namespace)
 248      {
 249          if (empty($this->serials[$namespace])) {
 250              $batch = $this->getBatch($namespace);
 251              unset($batch['DefinitionRev']);
 252              $this->serials[$namespace] = sha1(serialize($batch));
 253          }
 254          return $this->serials[$namespace];
 255      }
 256  
 257      /**
 258       * Returns a SHA-1 signature for the entire configuration object
 259       * that uniquely identifies that particular configuration
 260       *
 261       * @return string
 262       */
 263      public function getSerial()
 264      {
 265          if (empty($this->serial)) {
 266              $this->serial = sha1(serialize($this->getAll()));
 267          }
 268          return $this->serial;
 269      }
 270  
 271      /**
 272       * Retrieves all directives, organized by namespace
 273       *
 274       * @warning This is a pretty inefficient function, avoid if you can
 275       */
 276      public function getAll()
 277      {
 278          if (!$this->finalized) {
 279              $this->autoFinalize();
 280          }
 281          $ret = array();
 282          foreach ($this->plist->squash() as $name => $value) {
 283              list($ns, $key) = explode('.', $name, 2);
 284              $ret[$ns][$key] = $value;
 285          }
 286          return $ret;
 287      }
 288  
 289      /**
 290       * Sets a value to configuration.
 291       *
 292       * @param string $key key
 293       * @param mixed $value value
 294       * @param mixed $a
 295       */
 296      public function set($key, $value, $a = null)
 297      {
 298          if (strpos($key, '.') === false) {
 299              $namespace = $key;
 300              $directive = $value;
 301              $value = $a;
 302              $key = "$key.$directive";
 303              $this->triggerError("Using deprecated API: use \$config->set('$key', ...) instead", E_USER_NOTICE);
 304          } else {
 305              list($namespace) = explode('.', $key);
 306          }
 307          if ($this->isFinalized('Cannot set directive after finalization')) {
 308              return;
 309          }
 310          if (!isset($this->def->info[$key])) {
 311              $this->triggerError(
 312                  'Cannot set undefined directive ' . htmlspecialchars($key) . ' to value',
 313                  E_USER_WARNING
 314              );
 315              return;
 316          }
 317          $def = $this->def->info[$key];
 318  
 319          if (isset($def->isAlias)) {
 320              if ($this->aliasMode) {
 321                  $this->triggerError(
 322                      'Double-aliases not allowed, please fix '.
 323                      'ConfigSchema bug with' . $key,
 324                      E_USER_ERROR
 325                  );
 326                  return;
 327              }
 328              $this->aliasMode = true;
 329              $this->set($def->key, $value);
 330              $this->aliasMode = false;
 331              $this->triggerError("$key is an alias, preferred directive name is {$def->key}", E_USER_NOTICE);
 332              return;
 333          }
 334  
 335          // Raw type might be negative when using the fully optimized form
 336          // of stdClass, which indicates allow_null == true
 337          $rtype = is_int($def) ? $def : $def->type;
 338          if ($rtype < 0) {
 339              $type = -$rtype;
 340              $allow_null = true;
 341          } else {
 342              $type = $rtype;
 343              $allow_null = isset($def->allow_null);
 344          }
 345  
 346          try {
 347              $value = $this->parser->parse($value, $type, $allow_null);
 348          } catch (HTMLPurifier_VarParserException $e) {
 349              $this->triggerError(
 350                  'Value for ' . $key . ' is of invalid type, should be ' .
 351                  HTMLPurifier_VarParser::getTypeName($type),
 352                  E_USER_WARNING
 353              );
 354              return;
 355          }
 356          if (is_string($value) && is_object($def)) {
 357              // resolve value alias if defined
 358              if (isset($def->aliases[$value])) {
 359                  $value = $def->aliases[$value];
 360              }
 361              // check to see if the value is allowed
 362              if (isset($def->allowed) && !isset($def->allowed[$value])) {
 363                  $this->triggerError(
 364                      'Value not supported, valid values are: ' .
 365                      $this->_listify($def->allowed),
 366                      E_USER_WARNING
 367                  );
 368                  return;
 369              }
 370          }
 371          $this->plist->set($key, $value);
 372  
 373          // reset definitions if the directives they depend on changed
 374          // this is a very costly process, so it's discouraged
 375          // with finalization
 376          if ($namespace == 'HTML' || $namespace == 'CSS' || $namespace == 'URI') {
 377              $this->definitions[$namespace] = null;
 378          }
 379  
 380          $this->serials[$namespace] = false;
 381      }
 382  
 383      /**
 384       * Convenience function for error reporting
 385       *
 386       * @param array $lookup
 387       *
 388       * @return string
 389       */
 390      private function _listify($lookup)
 391      {
 392          $list = array();
 393          foreach ($lookup as $name => $b) {
 394              $list[] = $name;
 395          }
 396          return implode(', ', $list);
 397      }
 398  
 399      /**
 400       * Retrieves object reference to the HTML definition.
 401       *
 402       * @param bool $raw Return a copy that has not been setup yet. Must be
 403       *             called before it's been setup, otherwise won't work.
 404       * @param bool $optimized If true, this method may return null, to
 405       *             indicate that a cached version of the modified
 406       *             definition object is available and no further edits
 407       *             are necessary.  Consider using
 408       *             maybeGetRawHTMLDefinition, which is more explicitly
 409       *             named, instead.
 410       *
 411       * @return HTMLPurifier_HTMLDefinition|null
 412       */
 413      public function getHTMLDefinition($raw = false, $optimized = false)
 414      {
 415          return $this->getDefinition('HTML', $raw, $optimized);
 416      }
 417  
 418      /**
 419       * Retrieves object reference to the CSS definition
 420       *
 421       * @param bool $raw Return a copy that has not been setup yet. Must be
 422       *             called before it's been setup, otherwise won't work.
 423       * @param bool $optimized If true, this method may return null, to
 424       *             indicate that a cached version of the modified
 425       *             definition object is available and no further edits
 426       *             are necessary.  Consider using
 427       *             maybeGetRawCSSDefinition, which is more explicitly
 428       *             named, instead.
 429       *
 430       * @return HTMLPurifier_CSSDefinition|null
 431       */
 432      public function getCSSDefinition($raw = false, $optimized = false)
 433      {
 434          return $this->getDefinition('CSS', $raw, $optimized);
 435      }
 436  
 437      /**
 438       * Retrieves object reference to the URI definition
 439       *
 440       * @param bool $raw Return a copy that has not been setup yet. Must be
 441       *             called before it's been setup, otherwise won't work.
 442       * @param bool $optimized If true, this method may return null, to
 443       *             indicate that a cached version of the modified
 444       *             definition object is available and no further edits
 445       *             are necessary.  Consider using
 446       *             maybeGetRawURIDefinition, which is more explicitly
 447       *             named, instead.
 448       *
 449       * @return HTMLPurifier_URIDefinition|null
 450       */
 451      public function getURIDefinition($raw = false, $optimized = false)
 452      {
 453          return $this->getDefinition('URI', $raw, $optimized);
 454      }
 455  
 456      /**
 457       * Retrieves a definition
 458       *
 459       * @param string $type Type of definition: HTML, CSS, etc
 460       * @param bool $raw Whether or not definition should be returned raw
 461       * @param bool $optimized Only has an effect when $raw is true.  Whether
 462       *        or not to return null if the result is already present in
 463       *        the cache.  This is off by default for backwards
 464       *        compatibility reasons, but you need to do things this
 465       *        way in order to ensure that caching is done properly.
 466       *        Check out enduser-customize.html for more details.
 467       *        We probably won't ever change this default, as much as the
 468       *        maybe semantics is the "right thing to do."
 469       *
 470       * @throws HTMLPurifier_Exception
 471       * @return HTMLPurifier_Definition|null
 472       */
 473      public function getDefinition($type, $raw = false, $optimized = false)
 474      {
 475          if ($optimized && !$raw) {
 476              throw new HTMLPurifier_Exception("Cannot set optimized = true when raw = false");
 477          }
 478          if (!$this->finalized) {
 479              $this->autoFinalize();
 480          }
 481          // temporarily suspend locks, so we can handle recursive definition calls
 482          $lock = $this->lock;
 483          $this->lock = null;
 484          $factory = HTMLPurifier_DefinitionCacheFactory::instance();
 485          $cache = $factory->create($type, $this);
 486          $this->lock = $lock;
 487          if (!$raw) {
 488              // full definition
 489              // ---------------
 490              // check if definition is in memory
 491              if (!empty($this->definitions[$type])) {
 492                  $def = $this->definitions[$type];
 493                  // check if the definition is setup
 494                  if ($def->setup) {
 495                      return $def;
 496                  } else {
 497                      $def->setup($this);
 498                      if ($def->optimized) {
 499                          $cache->add($def, $this);
 500                      }
 501                      return $def;
 502                  }
 503              }
 504              // check if definition is in cache
 505              $def = $cache->get($this);
 506              if ($def) {
 507                  // definition in cache, save to memory and return it
 508                  $this->definitions[$type] = $def;
 509                  return $def;
 510              }
 511              // initialize it
 512              $def = $this->initDefinition($type);
 513              // set it up
 514              $this->lock = $type;
 515              $def->setup($this);
 516              $this->lock = null;
 517              // save in cache
 518              $cache->add($def, $this);
 519              // return it
 520              return $def;
 521          } else {
 522              // raw definition
 523              // --------------
 524              // check preconditions
 525              $def = null;
 526              if ($optimized) {
 527                  if (is_null($this->get($type . '.DefinitionID'))) {
 528                      // fatally error out if definition ID not set
 529                      throw new HTMLPurifier_Exception(
 530                          "Cannot retrieve raw version without specifying %$type.DefinitionID"
 531                      );
 532                  }
 533              }
 534              if (!empty($this->definitions[$type])) {
 535                  $def = $this->definitions[$type];
 536                  if ($def->setup && !$optimized) {
 537                      $extra = $this->chatty ?
 538                          " (try moving this code block earlier in your initialization)" :
 539                          "";
 540                      throw new HTMLPurifier_Exception(
 541                          "Cannot retrieve raw definition after it has already been setup" .
 542                          $extra
 543                      );
 544                  }
 545                  if ($def->optimized === null) {
 546                      $extra = $this->chatty ? " (try flushing your cache)" : "";
 547                      throw new HTMLPurifier_Exception(
 548                          "Optimization status of definition is unknown" . $extra
 549                      );
 550                  }
 551                  if ($def->optimized !== $optimized) {
 552                      $msg = $optimized ? "optimized" : "unoptimized";
 553                      $extra = $this->chatty ?
 554                          " (this backtrace is for the first inconsistent call, which was for a $msg raw definition)"
 555                          : "";
 556                      throw new HTMLPurifier_Exception(
 557                          "Inconsistent use of optimized and unoptimized raw definition retrievals" . $extra
 558                      );
 559                  }
 560              }
 561              // check if definition was in memory
 562              if ($def) {
 563                  if ($def->setup) {
 564                      // invariant: $optimized === true (checked above)
 565                      return null;
 566                  } else {
 567                      return $def;
 568                  }
 569              }
 570              // if optimized, check if definition was in cache
 571              // (because we do the memory check first, this formulation
 572              // is prone to cache slamming, but I think
 573              // guaranteeing that either /all/ of the raw
 574              // setup code or /none/ of it is run is more important.)
 575              if ($optimized) {
 576                  // This code path only gets run once; once we put
 577                  // something in $definitions (which is guaranteed by the
 578                  // trailing code), we always short-circuit above.
 579                  $def = $cache->get($this);
 580                  if ($def) {
 581                      // save the full definition for later, but don't
 582                      // return it yet
 583                      $this->definitions[$type] = $def;
 584                      return null;
 585                  }
 586              }
 587              // check invariants for creation
 588              if (!$optimized) {
 589                  if (!is_null($this->get($type . '.DefinitionID'))) {
 590                      if ($this->chatty) {
 591                          $this->triggerError(
 592                              'Due to a documentation error in previous version of HTML Purifier, your ' .
 593                              'definitions are not being cached.  If this is OK, you can remove the ' .
 594                              '%$type.DefinitionRev and %$type.DefinitionID declaration.  Otherwise, ' .
 595                              'modify your code to use maybeGetRawDefinition, and test if the returned ' .
 596                              'value is null before making any edits (if it is null, that means that a ' .
 597                              'cached version is available, and no raw operations are necessary).  See ' .
 598                              '<a href="http://htmlpurifier.org/docs/enduser-customize.html#optimized">' .
 599                              'Customize</a> for more details',
 600                              E_USER_WARNING
 601                          );
 602                      } else {
 603                          $this->triggerError(
 604                              "Useless DefinitionID declaration",
 605                              E_USER_WARNING
 606                          );
 607                      }
 608                  }
 609              }
 610              // initialize it
 611              $def = $this->initDefinition($type);
 612              $def->optimized = $optimized;
 613              return $def;
 614          }
 615          throw new HTMLPurifier_Exception("The impossible happened!");
 616      }
 617  
 618      /**
 619       * Initialise definition
 620       *
 621       * @param string $type What type of definition to create
 622       *
 623       * @return HTMLPurifier_CSSDefinition|HTMLPurifier_HTMLDefinition|HTMLPurifier_URIDefinition
 624       * @throws HTMLPurifier_Exception
 625       */
 626      private function initDefinition($type)
 627      {
 628          // quick checks failed, let's create the object
 629          if ($type == 'HTML') {
 630              $def = new HTMLPurifier_HTMLDefinition();
 631          } elseif ($type == 'CSS') {
 632              $def = new HTMLPurifier_CSSDefinition();
 633          } elseif ($type == 'URI') {
 634              $def = new HTMLPurifier_URIDefinition();
 635          } else {
 636              throw new HTMLPurifier_Exception(
 637                  "Definition of $type type not supported"
 638              );
 639          }
 640          $this->definitions[$type] = $def;
 641          return $def;
 642      }
 643  
 644      public function maybeGetRawDefinition($name)
 645      {
 646          return $this->getDefinition($name, true, true);
 647      }
 648  
 649      /**
 650       * @return HTMLPurifier_HTMLDefinition|null
 651       */
 652      public function maybeGetRawHTMLDefinition()
 653      {
 654          return $this->getDefinition('HTML', true, true);
 655      }
 656      
 657      /**
 658       * @return HTMLPurifier_CSSDefinition|null
 659       */
 660      public function maybeGetRawCSSDefinition()
 661      {
 662          return $this->getDefinition('CSS', true, true);
 663      }
 664      
 665      /**
 666       * @return HTMLPurifier_URIDefinition|null
 667       */
 668      public function maybeGetRawURIDefinition()
 669      {
 670          return $this->getDefinition('URI', true, true);
 671      }
 672  
 673      /**
 674       * Loads configuration values from an array with the following structure:
 675       * Namespace.Directive => Value
 676       *
 677       * @param array $config_array Configuration associative array
 678       */
 679      public function loadArray($config_array)
 680      {
 681          if ($this->isFinalized('Cannot load directives after finalization')) {
 682              return;
 683          }
 684          foreach ($config_array as $key => $value) {
 685              $key = str_replace('_', '.', $key);
 686              if (strpos($key, '.') !== false) {
 687                  $this->set($key, $value);
 688              } else {
 689                  $namespace = $key;
 690                  $namespace_values = $value;
 691                  foreach ($namespace_values as $directive => $value2) {
 692                      $this->set($namespace .'.'. $directive, $value2);
 693                  }
 694              }
 695          }
 696      }
 697  
 698      /**
 699       * Returns a list of array(namespace, directive) for all directives
 700       * that are allowed in a web-form context as per an allowed
 701       * namespaces/directives list.
 702       *
 703       * @param array $allowed List of allowed namespaces/directives
 704       * @param HTMLPurifier_ConfigSchema $schema Schema to use, if not global copy
 705       *
 706       * @return array
 707       */
 708      public static function getAllowedDirectivesForForm($allowed, $schema = null)
 709      {
 710          if (!$schema) {
 711              $schema = HTMLPurifier_ConfigSchema::instance();
 712          }
 713          if ($allowed !== true) {
 714              if (is_string($allowed)) {
 715                  $allowed = array($allowed);
 716              }
 717              $allowed_ns = array();
 718              $allowed_directives = array();
 719              $blacklisted_directives = array();
 720              foreach ($allowed as $ns_or_directive) {
 721                  if (strpos($ns_or_directive, '.') !== false) {
 722                      // directive
 723                      if ($ns_or_directive[0] == '-') {
 724                          $blacklisted_directives[substr($ns_or_directive, 1)] = true;
 725                      } else {
 726                          $allowed_directives[$ns_or_directive] = true;
 727                      }
 728                  } else {
 729                      // namespace
 730                      $allowed_ns[$ns_or_directive] = true;
 731                  }
 732              }
 733          }
 734          $ret = array();
 735          foreach ($schema->info as $key => $def) {
 736              list($ns, $directive) = explode('.', $key, 2);
 737              if ($allowed !== true) {
 738                  if (isset($blacklisted_directives["$ns.$directive"])) {
 739                      continue;
 740                  }
 741                  if (!isset($allowed_directives["$ns.$directive"]) && !isset($allowed_ns[$ns])) {
 742                      continue;
 743                  }
 744              }
 745              if (isset($def->isAlias)) {
 746                  continue;
 747              }
 748              if ($directive == 'DefinitionID' || $directive == 'DefinitionRev') {
 749                  continue;
 750              }
 751              $ret[] = array($ns, $directive);
 752          }
 753          return $ret;
 754      }
 755  
 756      /**
 757       * Loads configuration values from $_GET/$_POST that were posted
 758       * via ConfigForm
 759       *
 760       * @param array $array $_GET or $_POST array to import
 761       * @param string|bool $index Index/name that the config variables are in
 762       * @param array|bool $allowed List of allowed namespaces/directives
 763       * @param bool $mq_fix Boolean whether or not to enable magic quotes fix
 764       * @param HTMLPurifier_ConfigSchema $schema Schema to use, if not global copy
 765       *
 766       * @return mixed
 767       */
 768      public static function loadArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null)
 769      {
 770          $ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $schema);
 771          $config = HTMLPurifier_Config::create($ret, $schema);
 772          return $config;
 773      }
 774  
 775      /**
 776       * Merges in configuration values from $_GET/$_POST to object. NOT STATIC.
 777       *
 778       * @param array $array $_GET or $_POST array to import
 779       * @param string|bool $index Index/name that the config variables are in
 780       * @param array|bool $allowed List of allowed namespaces/directives
 781       * @param bool $mq_fix Boolean whether or not to enable magic quotes fix
 782       */
 783      public function mergeArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true)
 784      {
 785           $ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $this->def);
 786           $this->loadArray($ret);
 787      }
 788  
 789      /**
 790       * Prepares an array from a form into something usable for the more
 791       * strict parts of HTMLPurifier_Config
 792       *
 793       * @param array $array $_GET or $_POST array to import
 794       * @param string|bool $index Index/name that the config variables are in
 795       * @param array|bool $allowed List of allowed namespaces/directives
 796       * @param bool $mq_fix Boolean whether or not to enable magic quotes fix
 797       * @param HTMLPurifier_ConfigSchema $schema Schema to use, if not global copy
 798       *
 799       * @return array
 800       */
 801      public static function prepareArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null)
 802      {
 803          if ($index !== false) {
 804              $array = (isset($array[$index]) && is_array($array[$index])) ? $array[$index] : array();
 805          }
 806          $mq = $mq_fix && version_compare(PHP_VERSION, '7.4.0', '<') && function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc();
 807  
 808          $allowed = HTMLPurifier_Config::getAllowedDirectivesForForm($allowed, $schema);
 809          $ret = array();
 810          foreach ($allowed as $key) {
 811              list($ns, $directive) = $key;
 812              $skey = "$ns.$directive";
 813              if (!empty($array["Null_$skey"])) {
 814                  $ret[$ns][$directive] = null;
 815                  continue;
 816              }
 817              if (!isset($array[$skey])) {
 818                  continue;
 819              }
 820              $value = $mq ? stripslashes($array[$skey]) : $array[$skey];
 821              $ret[$ns][$directive] = $value;
 822          }
 823          return $ret;
 824      }
 825  
 826      /**
 827       * Loads configuration values from an ini file
 828       *
 829       * @param string $filename Name of ini file
 830       */
 831      public function loadIni($filename)
 832      {
 833          if ($this->isFinalized('Cannot load directives after finalization')) {
 834              return;
 835          }
 836          $array = parse_ini_file($filename, true);
 837          $this->loadArray($array);
 838      }
 839  
 840      /**
 841       * Checks whether or not the configuration object is finalized.
 842       *
 843       * @param string|bool $error String error message, or false for no error
 844       *
 845       * @return bool
 846       */
 847      public function isFinalized($error = false)
 848      {
 849          if ($this->finalized && $error) {
 850              $this->triggerError($error, E_USER_ERROR);
 851          }
 852          return $this->finalized;
 853      }
 854  
 855      /**
 856       * Finalizes configuration only if auto finalize is on and not
 857       * already finalized
 858       */
 859      public function autoFinalize()
 860      {
 861          if ($this->autoFinalize) {
 862              $this->finalize();
 863          } else {
 864              $this->plist->squash(true);
 865          }
 866      }
 867  
 868      /**
 869       * Finalizes a configuration object, prohibiting further change
 870       */
 871      public function finalize()
 872      {
 873          $this->finalized = true;
 874          $this->parser = null;
 875      }
 876  
 877      /**
 878       * Produces a nicely formatted error message by supplying the
 879       * stack frame information OUTSIDE of HTMLPurifier_Config.
 880       *
 881       * @param string $msg An error message
 882       * @param int $no An error number
 883       */
 884      protected function triggerError($msg, $no)
 885      {
 886          // determine previous stack frame
 887          $extra = '';
 888          if ($this->chatty) {
 889              $trace = debug_backtrace();
 890              // zip(tail(trace), trace) -- but PHP is not Haskell har har
 891              for ($i = 0, $c = count($trace); $i < $c - 1; $i++) {
 892                  // XXX this is not correct on some versions of HTML Purifier
 893                  if (isset($trace[$i + 1]['class']) && $trace[$i + 1]['class'] === 'HTMLPurifier_Config') {
 894                      continue;
 895                  }
 896                  $frame = $trace[$i];
 897                  $extra = " invoked on line {$frame['line']} in file {$frame['file']}";
 898                  break;
 899              }
 900          }
 901          trigger_error($msg . $extra, $no);
 902      }
 903  
 904      /**
 905       * Returns a serialized form of the configuration object that can
 906       * be reconstituted.
 907       *
 908       * @return string
 909       */
 910      public function serialize()
 911      {
 912          $this->getDefinition('HTML');
 913          $this->getDefinition('CSS');
 914          $this->getDefinition('URI');
 915          return serialize($this);
 916      }
 917  
 918  }
 919  
 920  // vim: et sw=4 sts=4