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 310 and 311] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403] [Versions 39 and 311]

   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.13.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 && 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