|
Moodle
2.2.1
http://www.collinsharper.com
|
00001 <?php 00002 00017 class HTMLPurifier_Config 00018 { 00019 00023 public $version = '4.3.0'; 00024 00029 public $autoFinalize = true; 00030 00031 // protected member variables 00032 00037 protected $serials = array(); 00038 00042 protected $serial; 00043 00047 protected $parser; 00048 00054 public $def; 00055 00059 protected $definitions; 00060 00064 protected $finalized = false; 00065 00069 protected $plist; 00070 00075 private $aliasMode; 00076 00082 public $chatty = true; 00083 00087 private $lock; 00088 00093 public function __construct($definition, $parent = null) { 00094 $parent = $parent ? $parent : $definition->defaultPlist; 00095 $this->plist = new HTMLPurifier_PropertyList($parent); 00096 $this->def = $definition; // keep a copy around for checking 00097 $this->parser = new HTMLPurifier_VarParser_Flexible(); 00098 } 00099 00109 public static function create($config, $schema = null) { 00110 if ($config instanceof HTMLPurifier_Config) { 00111 // pass-through 00112 return $config; 00113 } 00114 if (!$schema) { 00115 $ret = HTMLPurifier_Config::createDefault(); 00116 } else { 00117 $ret = new HTMLPurifier_Config($schema); 00118 } 00119 if (is_string($config)) $ret->loadIni($config); 00120 elseif (is_array($config)) $ret->loadArray($config); 00121 return $ret; 00122 } 00123 00130 public static function inherit(HTMLPurifier_Config $config) { 00131 return new HTMLPurifier_Config($config->def, $config->plist); 00132 } 00133 00138 public static function createDefault() { 00139 $definition = HTMLPurifier_ConfigSchema::instance(); 00140 $config = new HTMLPurifier_Config($definition); 00141 return $config; 00142 } 00143 00148 public function get($key, $a = null) { 00149 if ($a !== null) { 00150 $this->triggerError("Using deprecated API: use \$config->get('$key.$a') instead", E_USER_WARNING); 00151 $key = "$key.$a"; 00152 } 00153 if (!$this->finalized) $this->autoFinalize(); 00154 if (!isset($this->def->info[$key])) { 00155 // can't add % due to SimpleTest bug 00156 $this->triggerError('Cannot retrieve value of undefined directive ' . htmlspecialchars($key), 00157 E_USER_WARNING); 00158 return; 00159 } 00160 if (isset($this->def->info[$key]->isAlias)) { 00161 $d = $this->def->info[$key]; 00162 $this->triggerError('Cannot get value from aliased directive, use real name ' . $d->key, 00163 E_USER_ERROR); 00164 return; 00165 } 00166 if ($this->lock) { 00167 list($ns) = explode('.', $key); 00168 if ($ns !== $this->lock) { 00169 $this->triggerError('Cannot get value of namespace ' . $ns . ' when lock for ' . $this->lock . ' is active, this probably indicates a Definition setup method is accessing directives that are not within its namespace', E_USER_ERROR); 00170 return; 00171 } 00172 } 00173 return $this->plist->get($key); 00174 } 00175 00180 public function getBatch($namespace) { 00181 if (!$this->finalized) $this->autoFinalize(); 00182 $full = $this->getAll(); 00183 if (!isset($full[$namespace])) { 00184 $this->triggerError('Cannot retrieve undefined namespace ' . htmlspecialchars($namespace), 00185 E_USER_WARNING); 00186 return; 00187 } 00188 return $full[$namespace]; 00189 } 00190 00198 public function getBatchSerial($namespace) { 00199 if (empty($this->serials[$namespace])) { 00200 $batch = $this->getBatch($namespace); 00201 unset($batch['DefinitionRev']); 00202 $this->serials[$namespace] = md5(serialize($batch)); 00203 } 00204 return $this->serials[$namespace]; 00205 } 00206 00211 public function getSerial() { 00212 if (empty($this->serial)) { 00213 $this->serial = md5(serialize($this->getAll())); 00214 } 00215 return $this->serial; 00216 } 00217 00222 public function getAll() { 00223 if (!$this->finalized) $this->autoFinalize(); 00224 $ret = array(); 00225 foreach ($this->plist->squash() as $name => $value) { 00226 list($ns, $key) = explode('.', $name, 2); 00227 $ret[$ns][$key] = $value; 00228 } 00229 return $ret; 00230 } 00231 00237 public function set($key, $value, $a = null) { 00238 if (strpos($key, '.') === false) { 00239 $namespace = $key; 00240 $directive = $value; 00241 $value = $a; 00242 $key = "$key.$directive"; 00243 $this->triggerError("Using deprecated API: use \$config->set('$key', ...) instead", E_USER_NOTICE); 00244 } else { 00245 list($namespace) = explode('.', $key); 00246 } 00247 if ($this->isFinalized('Cannot set directive after finalization')) return; 00248 if (!isset($this->def->info[$key])) { 00249 $this->triggerError('Cannot set undefined directive ' . htmlspecialchars($key) . ' to value', 00250 E_USER_WARNING); 00251 return; 00252 } 00253 $def = $this->def->info[$key]; 00254 00255 if (isset($def->isAlias)) { 00256 if ($this->aliasMode) { 00257 $this->triggerError('Double-aliases not allowed, please fix '. 00258 'ConfigSchema bug with' . $key, E_USER_ERROR); 00259 return; 00260 } 00261 $this->aliasMode = true; 00262 $this->set($def->key, $value); 00263 $this->aliasMode = false; 00264 $this->triggerError("$key is an alias, preferred directive name is {$def->key}", E_USER_NOTICE); 00265 return; 00266 } 00267 00268 // Raw type might be negative when using the fully optimized form 00269 // of stdclass, which indicates allow_null == true 00270 $rtype = is_int($def) ? $def : $def->type; 00271 if ($rtype < 0) { 00272 $type = -$rtype; 00273 $allow_null = true; 00274 } else { 00275 $type = $rtype; 00276 $allow_null = isset($def->allow_null); 00277 } 00278 00279 try { 00280 $value = $this->parser->parse($value, $type, $allow_null); 00281 } catch (HTMLPurifier_VarParserException $e) { 00282 $this->triggerError('Value for ' . $key . ' is of invalid type, should be ' . HTMLPurifier_VarParser::getTypeName($type), E_USER_WARNING); 00283 return; 00284 } 00285 if (is_string($value) && is_object($def)) { 00286 // resolve value alias if defined 00287 if (isset($def->aliases[$value])) { 00288 $value = $def->aliases[$value]; 00289 } 00290 // check to see if the value is allowed 00291 if (isset($def->allowed) && !isset($def->allowed[$value])) { 00292 $this->triggerError('Value not supported, valid values are: ' . 00293 $this->_listify($def->allowed), E_USER_WARNING); 00294 return; 00295 } 00296 } 00297 $this->plist->set($key, $value); 00298 00299 // reset definitions if the directives they depend on changed 00300 // this is a very costly process, so it's discouraged 00301 // with finalization 00302 if ($namespace == 'HTML' || $namespace == 'CSS' || $namespace == 'URI') { 00303 $this->definitions[$namespace] = null; 00304 } 00305 00306 $this->serials[$namespace] = false; 00307 } 00308 00312 private function _listify($lookup) { 00313 $list = array(); 00314 foreach ($lookup as $name => $b) $list[] = $name; 00315 return implode(', ', $list); 00316 } 00317 00329 public function getHTMLDefinition($raw = false, $optimized = false) { 00330 return $this->getDefinition('HTML', $raw, $optimized); 00331 } 00332 00344 public function getCSSDefinition($raw = false, $optimized = false) { 00345 return $this->getDefinition('CSS', $raw, $optimized); 00346 } 00347 00359 public function getURIDefinition($raw = false, $optimized = false) { 00360 return $this->getDefinition('URI', $raw, $optimized); 00361 } 00362 00376 public function getDefinition($type, $raw = false, $optimized = false) { 00377 if ($optimized && !$raw) { 00378 throw new HTMLPurifier_Exception("Cannot set optimized = true when raw = false"); 00379 } 00380 if (!$this->finalized) $this->autoFinalize(); 00381 // temporarily suspend locks, so we can handle recursive definition calls 00382 $lock = $this->lock; 00383 $this->lock = null; 00384 $factory = HTMLPurifier_DefinitionCacheFactory::instance(); 00385 $cache = $factory->create($type, $this); 00386 $this->lock = $lock; 00387 if (!$raw) { 00388 // full definition 00389 // --------------- 00390 // check if definition is in memory 00391 if (!empty($this->definitions[$type])) { 00392 $def = $this->definitions[$type]; 00393 // check if the definition is setup 00394 if ($def->setup) { 00395 return $def; 00396 } else { 00397 $def->setup($this); 00398 if ($def->optimized) $cache->add($def, $this); 00399 return $def; 00400 } 00401 } 00402 // check if definition is in cache 00403 $def = $cache->get($this); 00404 if ($def) { 00405 // definition in cache, save to memory and return it 00406 $this->definitions[$type] = $def; 00407 return $def; 00408 } 00409 // initialize it 00410 $def = $this->initDefinition($type); 00411 // set it up 00412 $this->lock = $type; 00413 $def->setup($this); 00414 $this->lock = null; 00415 // save in cache 00416 $cache->add($def, $this); 00417 // return it 00418 return $def; 00419 } else { 00420 // raw definition 00421 // -------------- 00422 // check preconditions 00423 $def = null; 00424 if ($optimized) { 00425 if (is_null($this->get($type . '.DefinitionID'))) { 00426 // fatally error out if definition ID not set 00427 throw new HTMLPurifier_Exception("Cannot retrieve raw version without specifying %$type.DefinitionID"); 00428 } 00429 } 00430 if (!empty($this->definitions[$type])) { 00431 $def = $this->definitions[$type]; 00432 if ($def->setup && !$optimized) { 00433 $extra = $this->chatty ? " (try moving this code block earlier in your initialization)" : ""; 00434 throw new HTMLPurifier_Exception("Cannot retrieve raw definition after it has already been setup" . $extra); 00435 } 00436 if ($def->optimized === null) { 00437 $extra = $this->chatty ? " (try flushing your cache)" : ""; 00438 throw new HTMLPurifier_Exception("Optimization status of definition is unknown" . $extra); 00439 } 00440 if ($def->optimized !== $optimized) { 00441 $msg = $optimized ? "optimized" : "unoptimized"; 00442 $extra = $this->chatty ? " (this backtrace is for the first inconsistent call, which was for a $msg raw definition)" : ""; 00443 throw new HTMLPurifier_Exception("Inconsistent use of optimized and unoptimized raw definition retrievals" . $extra); 00444 } 00445 } 00446 // check if definition was in memory 00447 if ($def) { 00448 if ($def->setup) { 00449 // invariant: $optimized === true (checked above) 00450 return null; 00451 } else { 00452 return $def; 00453 } 00454 } 00455 // if optimized, check if definition was in cache 00456 // (because we do the memory check first, this formulation 00457 // is prone to cache slamming, but I think 00458 // guaranteeing that either /all/ of the raw 00459 // setup code or /none/ of it is run is more important.) 00460 if ($optimized) { 00461 // This code path only gets run once; once we put 00462 // something in $definitions (which is guaranteed by the 00463 // trailing code), we always short-circuit above. 00464 $def = $cache->get($this); 00465 if ($def) { 00466 // save the full definition for later, but don't 00467 // return it yet 00468 $this->definitions[$type] = $def; 00469 return null; 00470 } 00471 } 00472 // check invariants for creation 00473 if (!$optimized) { 00474 if (!is_null($this->get($type . '.DefinitionID'))) { 00475 if ($this->chatty) { 00476 $this->triggerError("Due to a documentation error in previous version of HTML Purifier, your definitions are not being cached. If this is OK, you can remove the %$type.DefinitionRev and %$type.DefinitionID declaration. Otherwise, modify your code to use maybeGetRawDefinition, and test if the returned value is null before making any edits (if it is null, that means that a cached version is available, and no raw operations are necessary). See <a href='http://htmlpurifier.org/docs/enduser-customize.html#optimized'>Customize</a> for more details", E_USER_WARNING); 00477 } else { 00478 $this->triggerError("Useless DefinitionID declaration", E_USER_WARNING); 00479 } 00480 } 00481 } 00482 // initialize it 00483 $def = $this->initDefinition($type); 00484 $def->optimized = $optimized; 00485 return $def; 00486 } 00487 throw new HTMLPurifier_Exception("The impossible happened!"); 00488 } 00489 00490 private function initDefinition($type) { 00491 // quick checks failed, let's create the object 00492 if ($type == 'HTML') { 00493 $def = new HTMLPurifier_HTMLDefinition(); 00494 } elseif ($type == 'CSS') { 00495 $def = new HTMLPurifier_CSSDefinition(); 00496 } elseif ($type == 'URI') { 00497 $def = new HTMLPurifier_URIDefinition(); 00498 } else { 00499 throw new HTMLPurifier_Exception("Definition of $type type not supported"); 00500 } 00501 $this->definitions[$type] = $def; 00502 return $def; 00503 } 00504 00505 public function maybeGetRawDefinition($name) { 00506 return $this->getDefinition($name, true, true); 00507 } 00508 00509 public function maybeGetRawHTMLDefinition() { 00510 return $this->getDefinition('HTML', true, true); 00511 } 00512 00513 public function maybeGetRawCSSDefinition() { 00514 return $this->getDefinition('CSS', true, true); 00515 } 00516 00517 public function maybeGetRawURIDefinition() { 00518 return $this->getDefinition('URI', true, true); 00519 } 00520 00526 public function loadArray($config_array) { 00527 if ($this->isFinalized('Cannot load directives after finalization')) return; 00528 foreach ($config_array as $key => $value) { 00529 $key = str_replace('_', '.', $key); 00530 if (strpos($key, '.') !== false) { 00531 $this->set($key, $value); 00532 } else { 00533 $namespace = $key; 00534 $namespace_values = $value; 00535 foreach ($namespace_values as $directive => $value) { 00536 $this->set($namespace .'.'. $directive, $value); 00537 } 00538 } 00539 } 00540 } 00541 00548 public static function getAllowedDirectivesForForm($allowed, $schema = null) { 00549 if (!$schema) { 00550 $schema = HTMLPurifier_ConfigSchema::instance(); 00551 } 00552 if ($allowed !== true) { 00553 if (is_string($allowed)) $allowed = array($allowed); 00554 $allowed_ns = array(); 00555 $allowed_directives = array(); 00556 $blacklisted_directives = array(); 00557 foreach ($allowed as $ns_or_directive) { 00558 if (strpos($ns_or_directive, '.') !== false) { 00559 // directive 00560 if ($ns_or_directive[0] == '-') { 00561 $blacklisted_directives[substr($ns_or_directive, 1)] = true; 00562 } else { 00563 $allowed_directives[$ns_or_directive] = true; 00564 } 00565 } else { 00566 // namespace 00567 $allowed_ns[$ns_or_directive] = true; 00568 } 00569 } 00570 } 00571 $ret = array(); 00572 foreach ($schema->info as $key => $def) { 00573 list($ns, $directive) = explode('.', $key, 2); 00574 if ($allowed !== true) { 00575 if (isset($blacklisted_directives["$ns.$directive"])) continue; 00576 if (!isset($allowed_directives["$ns.$directive"]) && !isset($allowed_ns[$ns])) continue; 00577 } 00578 if (isset($def->isAlias)) continue; 00579 if ($directive == 'DefinitionID' || $directive == 'DefinitionRev') continue; 00580 $ret[] = array($ns, $directive); 00581 } 00582 return $ret; 00583 } 00584 00594 public static function loadArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null) { 00595 $ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $schema); 00596 $config = HTMLPurifier_Config::create($ret, $schema); 00597 return $config; 00598 } 00599 00604 public function mergeArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true) { 00605 $ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $this->def); 00606 $this->loadArray($ret); 00607 } 00608 00613 public static function prepareArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null) { 00614 if ($index !== false) $array = (isset($array[$index]) && is_array($array[$index])) ? $array[$index] : array(); 00615 $mq = $mq_fix && function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc(); 00616 00617 $allowed = HTMLPurifier_Config::getAllowedDirectivesForForm($allowed, $schema); 00618 $ret = array(); 00619 foreach ($allowed as $key) { 00620 list($ns, $directive) = $key; 00621 $skey = "$ns.$directive"; 00622 if (!empty($array["Null_$skey"])) { 00623 $ret[$ns][$directive] = null; 00624 continue; 00625 } 00626 if (!isset($array[$skey])) continue; 00627 $value = $mq ? stripslashes($array[$skey]) : $array[$skey]; 00628 $ret[$ns][$directive] = $value; 00629 } 00630 return $ret; 00631 } 00632 00637 public function loadIni($filename) { 00638 if ($this->isFinalized('Cannot load directives after finalization')) return; 00639 $array = parse_ini_file($filename, true); 00640 $this->loadArray($array); 00641 } 00642 00647 public function isFinalized($error = false) { 00648 if ($this->finalized && $error) { 00649 $this->triggerError($error, E_USER_ERROR); 00650 } 00651 return $this->finalized; 00652 } 00653 00658 public function autoFinalize() { 00659 if ($this->autoFinalize) { 00660 $this->finalize(); 00661 } else { 00662 $this->plist->squash(true); 00663 } 00664 } 00665 00669 public function finalize() { 00670 $this->finalized = true; 00671 unset($this->parser); 00672 } 00673 00678 protected function triggerError($msg, $no) { 00679 // determine previous stack frame 00680 $extra = ''; 00681 if ($this->chatty) { 00682 $trace = debug_backtrace(); 00683 // zip(tail(trace), trace) -- but PHP is not Haskell har har 00684 for ($i = 0, $c = count($trace); $i < $c - 1; $i++) { 00685 if ($trace[$i + 1]['class'] === 'HTMLPurifier_Config') { 00686 continue; 00687 } 00688 $frame = $trace[$i]; 00689 $extra = " invoked on line {$frame['line']} in file {$frame['file']}"; 00690 break; 00691 } 00692 } 00693 trigger_error($msg . $extra, $no); 00694 } 00695 00700 public function serialize() { 00701 $this->getDefinition('HTML'); 00702 $this->getDefinition('CSS'); 00703 $this->getDefinition('URI'); 00704 return serialize($this); 00705 } 00706 00707 } 00708 00709 // vim: et sw=4 sts=4