diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..db18d6bb9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +vendor +tmp +openvk.yml +update.pid +update.pid.old +Web/static/js/node_modules \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..1ca2a1ae2 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "Web/static/css/ash_oss_themes"] + path = Web/static/css/ash_oss_themes + url = https://github.com/Defenders08/ash-themes-ovk diff --git a/3rdparty/HTMLPurifier/HTMLPurifier.standalone.php b/3rdparty/HTMLPurifier/HTMLPurifier.standalone.php new file mode 100644 index 000000000..cfdbcc92c --- /dev/null +++ b/3rdparty/HTMLPurifier/HTMLPurifier.standalone.php @@ -0,0 +1,22584 @@ +config = HTMLPurifier_Config::create($config); + $this->strategy = new HTMLPurifier_Strategy_Core(); + } + + /** + * Adds a filter to process the output. First come first serve + * + * @param HTMLPurifier_Filter $filter HTMLPurifier_Filter object + */ + public function addFilter($filter) + { + trigger_error( + 'HTMLPurifier->addFilter() is deprecated, use configuration directives' . + ' in the Filter namespace or Filter.Custom', + E_USER_WARNING + ); + $this->filters[] = $filter; + } + + /** + * Filters an HTML snippet/document to be XSS-free and standards-compliant. + * + * @param string $html String of HTML to purify + * @param HTMLPurifier_Config $config Config object for this operation, + * if omitted, defaults to the config object specified during this + * object's construction. The parameter can also be any type + * that HTMLPurifier_Config::create() supports. + * + * @return string Purified HTML + */ + public function purify($html, $config = null) + { + // :TODO: make the config merge in, instead of replace + $config = $config ? HTMLPurifier_Config::create($config) : $this->config; + + // implementation is partially environment dependant, partially + // configuration dependant + $lexer = HTMLPurifier_Lexer::create($config); + + $context = new HTMLPurifier_Context(); + + // setup HTML generator + $this->generator = new HTMLPurifier_Generator($config, $context); + $context->register('Generator', $this->generator); + + // set up global context variables + if ($config->get('Core.CollectErrors')) { + // may get moved out if other facilities use it + $language_factory = HTMLPurifier_LanguageFactory::instance(); + $language = $language_factory->create($config, $context); + $context->register('Locale', $language); + + $error_collector = new HTMLPurifier_ErrorCollector($context); + $context->register('ErrorCollector', $error_collector); + } + + // setup id_accumulator context, necessary due to the fact that + // AttrValidator can be called from many places + $id_accumulator = HTMLPurifier_IDAccumulator::build($config, $context); + $context->register('IDAccumulator', $id_accumulator); + + $html = HTMLPurifier_Encoder::convertToUTF8($html, $config, $context); + + // setup filters + $filter_flags = $config->getBatch('Filter'); + $custom_filters = $filter_flags['Custom']; + unset($filter_flags['Custom']); + $filters = array(); + foreach ($filter_flags as $filter => $flag) { + if (!$flag) { + continue; + } + if (strpos($filter, '.') !== false) { + continue; + } + $class = "HTMLPurifier_Filter_$filter"; + $filters[] = new $class; + } + foreach ($custom_filters as $filter) { + // maybe "HTMLPurifier_Filter_$filter", but be consistent with AutoFormat + $filters[] = $filter; + } + $filters = array_merge($filters, $this->filters); + // maybe prepare(), but later + + for ($i = 0, $filter_size = count($filters); $i < $filter_size; $i++) { + $html = $filters[$i]->preFilter($html, $config, $context); + } + + // purified HTML + $html = + $this->generator->generateFromTokens( + // list of tokens + $this->strategy->execute( + // list of un-purified tokens + $lexer->tokenizeHTML( + // un-purified HTML + $html, + $config, + $context + ), + $config, + $context + ) + ); + + for ($i = $filter_size - 1; $i >= 0; $i--) { + $html = $filters[$i]->postFilter($html, $config, $context); + } + + $html = HTMLPurifier_Encoder::convertFromUTF8($html, $config, $context); + $this->context =& $context; + return $html; + } + + /** + * Filters an array of HTML snippets + * + * @param string[] $array_of_html Array of html snippets + * @param HTMLPurifier_Config $config Optional config object for this operation. + * See HTMLPurifier::purify() for more details. + * + * @return string[] Array of purified HTML + */ + public function purifyArray($array_of_html, $config = null) + { + $context_array = array(); + foreach($array_of_html as $key=>$value){ + if (is_array($value)) { + $array[$key] = $this->purifyArray($value, $config); + } else { + $array[$key] = $this->purify($value, $config); + } + $context_array[$key] = $this->context; + } + $this->context = $context_array; + return $array; + } + + /** + * Singleton for enforcing just one HTML Purifier in your system + * + * @param HTMLPurifier|HTMLPurifier_Config $prototype Optional prototype + * HTMLPurifier instance to overload singleton with, + * or HTMLPurifier_Config instance to configure the + * generated version with. + * + * @return HTMLPurifier + */ + public static function instance($prototype = null) + { + if (!self::$instance || $prototype) { + if ($prototype instanceof HTMLPurifier) { + self::$instance = $prototype; + } elseif ($prototype) { + self::$instance = new HTMLPurifier($prototype); + } else { + self::$instance = new HTMLPurifier(); + } + } + return self::$instance; + } + + /** + * Singleton for enforcing just one HTML Purifier in your system + * + * @param HTMLPurifier|HTMLPurifier_Config $prototype Optional prototype + * HTMLPurifier instance to overload singleton with, + * or HTMLPurifier_Config instance to configure the + * generated version with. + * + * @return HTMLPurifier + * @note Backwards compatibility, see instance() + */ + public static function getInstance($prototype = null) + { + return HTMLPurifier::instance($prototype); + } +} + + + + + +/** + * Converts a stream of HTMLPurifier_Token into an HTMLPurifier_Node, + * and back again. + * + * @note This transformation is not an equivalence. We mutate the input + * token stream to make it so; see all [MUT] markers in code. + */ +class HTMLPurifier_Arborize +{ + public static function arborize($tokens, $config, $context) { + $definition = $config->getHTMLDefinition(); + $parent = new HTMLPurifier_Token_Start($definition->info_parent); + $stack = array($parent->toNode()); + foreach ($tokens as $token) { + $token->skip = null; // [MUT] + $token->carryover = null; // [MUT] + if ($token instanceof HTMLPurifier_Token_End) { + $token->start = null; // [MUT] + $r = array_pop($stack); + //assert($r->name === $token->name); + //assert(empty($token->attr)); + $r->endCol = $token->col; + $r->endLine = $token->line; + $r->endArmor = $token->armor; + continue; + } + $node = $token->toNode(); + $stack[count($stack)-1]->children[] = $node; + if ($token instanceof HTMLPurifier_Token_Start) { + $stack[] = $node; + } + } + //assert(count($stack) == 1); + return $stack[0]; + } + + public static function flatten($node, $config, $context) { + $level = 0; + $nodes = array($level => new HTMLPurifier_Queue(array($node))); + $closingTokens = array(); + $tokens = array(); + do { + while (!$nodes[$level]->isEmpty()) { + $node = $nodes[$level]->shift(); // FIFO + list($start, $end) = $node->toTokenPair(); + if ($level > 0) { + $tokens[] = $start; + } + if ($end !== NULL) { + $closingTokens[$level][] = $end; + } + if ($node instanceof HTMLPurifier_Node_Element) { + $level++; + $nodes[$level] = new HTMLPurifier_Queue(); + foreach ($node->children as $childNode) { + $nodes[$level]->push($childNode); + } + } + } + $level--; + if ($level && isset($closingTokens[$level])) { + while ($token = array_pop($closingTokens[$level])) { + $tokens[] = $token; + } + } + } while ($level > 0); + return $tokens; + } +} + + + +/** + * Defines common attribute collections that modules reference + */ + +class HTMLPurifier_AttrCollections +{ + + /** + * Associative array of attribute collections, indexed by name. + * @type array + */ + public $info = array(); + + /** + * Performs all expansions on internal data for use by other inclusions + * It also collects all attribute collection extensions from + * modules + * @param HTMLPurifier_AttrTypes $attr_types HTMLPurifier_AttrTypes instance + * @param HTMLPurifier_HTMLModule[] $modules Hash array of HTMLPurifier_HTMLModule members + */ + public function __construct($attr_types, $modules) + { + $this->doConstruct($attr_types, $modules); + } + + public function doConstruct($attr_types, $modules) + { + // load extensions from the modules + foreach ($modules as $module) { + foreach ($module->attr_collections as $coll_i => $coll) { + if (!isset($this->info[$coll_i])) { + $this->info[$coll_i] = array(); + } + foreach ($coll as $attr_i => $attr) { + if ($attr_i === 0 && isset($this->info[$coll_i][$attr_i])) { + // merge in includes + $this->info[$coll_i][$attr_i] = array_merge( + $this->info[$coll_i][$attr_i], + $attr + ); + continue; + } + $this->info[$coll_i][$attr_i] = $attr; + } + } + } + // perform internal expansions and inclusions + foreach ($this->info as $name => $attr) { + // merge attribute collections that include others + $this->performInclusions($this->info[$name]); + // replace string identifiers with actual attribute objects + $this->expandIdentifiers($this->info[$name], $attr_types); + } + } + + /** + * Takes a reference to an attribute associative array and performs + * all inclusions specified by the zero index. + * @param array &$attr Reference to attribute array + */ + public function performInclusions(&$attr) + { + if (!isset($attr[0])) { + return; + } + $merge = $attr[0]; + $seen = array(); // recursion guard + // loop through all the inclusions + for ($i = 0; isset($merge[$i]); $i++) { + if (isset($seen[$merge[$i]])) { + continue; + } + $seen[$merge[$i]] = true; + // foreach attribute of the inclusion, copy it over + if (!isset($this->info[$merge[$i]])) { + continue; + } + foreach ($this->info[$merge[$i]] as $key => $value) { + if (isset($attr[$key])) { + continue; + } // also catches more inclusions + $attr[$key] = $value; + } + if (isset($this->info[$merge[$i]][0])) { + // recursion + $merge = array_merge($merge, $this->info[$merge[$i]][0]); + } + } + unset($attr[0]); + } + + /** + * Expands all string identifiers in an attribute array by replacing + * them with the appropriate values inside HTMLPurifier_AttrTypes + * @param array &$attr Reference to attribute array + * @param HTMLPurifier_AttrTypes $attr_types HTMLPurifier_AttrTypes instance + */ + public function expandIdentifiers(&$attr, $attr_types) + { + // because foreach will process new elements we add, make sure we + // skip duplicates + $processed = array(); + + foreach ($attr as $def_i => $def) { + // skip inclusions + if ($def_i === 0) { + continue; + } + + if (isset($processed[$def_i])) { + continue; + } + + // determine whether or not attribute is required + if ($required = (strpos($def_i, '*') !== false)) { + // rename the definition + unset($attr[$def_i]); + $def_i = trim($def_i, '*'); + $attr[$def_i] = $def; + } + + $processed[$def_i] = true; + + // if we've already got a literal object, move on + if (is_object($def)) { + // preserve previous required + $attr[$def_i]->required = ($required || $attr[$def_i]->required); + continue; + } + + if ($def === false) { + unset($attr[$def_i]); + continue; + } + + if ($t = $attr_types->get($def)) { + $attr[$def_i] = $t; + $attr[$def_i]->required = $required; + } else { + unset($attr[$def_i]); + } + } + } +} + + + + + +/** + * Base class for all validating attribute definitions. + * + * This family of classes forms the core for not only HTML attribute validation, + * but also any sort of string that needs to be validated or cleaned (which + * means CSS properties and composite definitions are defined here too). + * Besides defining (through code) what precisely makes the string valid, + * subclasses are also responsible for cleaning the code if possible. + */ + +abstract class HTMLPurifier_AttrDef +{ + + /** + * Tells us whether or not an HTML attribute is minimized. + * Has no meaning in other contexts. + * @type bool + */ + public $minimized = false; + + /** + * Tells us whether or not an HTML attribute is required. + * Has no meaning in other contexts + * @type bool + */ + public $required = false; + + /** + * Validates and cleans passed string according to a definition. + * + * @param string $string String to be validated and cleaned. + * @param HTMLPurifier_Config $config Mandatory HTMLPurifier_Config object. + * @param HTMLPurifier_Context $context Mandatory HTMLPurifier_Context object. + */ + abstract public function validate($string, $config, $context); + + /** + * Convenience method that parses a string as if it were CDATA. + * + * This method process a string in the manner specified at + * by removing + * leading and trailing whitespace, ignoring line feeds, and replacing + * carriage returns and tabs with spaces. While most useful for HTML + * attributes specified as CDATA, it can also be applied to most CSS + * values. + * + * @note This method is not entirely standards compliant, as trim() removes + * more types of whitespace than specified in the spec. In practice, + * this is rarely a problem, as those extra characters usually have + * already been removed by HTMLPurifier_Encoder. + * + * @warning This processing is inconsistent with XML's whitespace handling + * as specified by section 3.3.3 and referenced XHTML 1.0 section + * 4.7. However, note that we are NOT necessarily + * parsing XML, thus, this behavior may still be correct. We + * assume that newlines have been normalized. + */ + public function parseCDATA($string) + { + $string = trim($string); + $string = str_replace(array("\n", "\t", "\r"), ' ', $string); + return $string; + } + + /** + * Factory method for creating this class from a string. + * @param string $string String construction info + * @return HTMLPurifier_AttrDef Created AttrDef object corresponding to $string + */ + public function make($string) + { + // default implementation, return a flyweight of this object. + // If $string has an effect on the returned object (i.e. you + // need to overload this method), it is best + // to clone or instantiate new copies. (Instantiation is safer.) + return $this; + } + + /** + * Removes spaces from rgb(0, 0, 0) so that shorthand CSS properties work + * properly. THIS IS A HACK! + * @param string $string a CSS colour definition + * @return string + */ + protected function mungeRgb($string) + { + $p = '\s*(\d+(\.\d+)?([%]?))\s*'; + + if (preg_match('/(rgba|hsla)\(/', $string)) { + return preg_replace('/(rgba|hsla)\('.$p.','.$p.','.$p.','.$p.'\)/', '\1(\2,\5,\8,\11)', $string); + } + + return preg_replace('/(rgb|hsl)\('.$p.','.$p.','.$p.'\)/', '\1(\2,\5,\8)', $string); + } + + /** + * Parses a possibly escaped CSS string and returns the "pure" + * version of it. + */ + protected function expandCSSEscape($string) + { + // flexibly parse it + $ret = ''; + for ($i = 0, $c = strlen($string); $i < $c; $i++) { + if ($string[$i] === '\\') { + $i++; + if ($i >= $c) { + $ret .= '\\'; + break; + } + if (ctype_xdigit($string[$i])) { + $code = $string[$i]; + for ($a = 1, $i++; $i < $c && $a < 6; $i++, $a++) { + if (!ctype_xdigit($string[$i])) { + break; + } + $code .= $string[$i]; + } + // We have to be extremely careful when adding + // new characters, to make sure we're not breaking + // the encoding. + $char = HTMLPurifier_Encoder::unichr(hexdec($code)); + if (HTMLPurifier_Encoder::cleanUTF8($char) === '') { + continue; + } + $ret .= $char; + if ($i < $c && trim($string[$i]) !== '') { + $i--; + } + continue; + } + if ($string[$i] === "\n") { + continue; + } + } + $ret .= $string[$i]; + } + return $ret; + } +} + + + + + +/** + * Processes an entire attribute array for corrections needing multiple values. + * + * Occasionally, a certain attribute will need to be removed and popped onto + * another value. Instead of creating a complex return syntax for + * HTMLPurifier_AttrDef, we just pass the whole attribute array to a + * specialized object and have that do the special work. That is the + * family of HTMLPurifier_AttrTransform. + * + * An attribute transformation can be assigned to run before or after + * HTMLPurifier_AttrDef validation. See HTMLPurifier_HTMLDefinition for + * more details. + */ + +abstract class HTMLPurifier_AttrTransform +{ + + /** + * Abstract: makes changes to the attributes dependent on multiple values. + * + * @param array $attr Assoc array of attributes, usually from + * HTMLPurifier_Token_Tag::$attr + * @param HTMLPurifier_Config $config Mandatory HTMLPurifier_Config object. + * @param HTMLPurifier_Context $context Mandatory HTMLPurifier_Context object + * @return array Processed attribute array. + */ + abstract public function transform($attr, $config, $context); + + /** + * Prepends CSS properties to the style attribute, creating the + * attribute if it doesn't exist. + * @param array &$attr Attribute array to process (passed by reference) + * @param string $css CSS to prepend + */ + public function prependCSS(&$attr, $css) + { + $attr['style'] = isset($attr['style']) ? $attr['style'] : ''; + $attr['style'] = $css . $attr['style']; + } + + /** + * Retrieves and removes an attribute + * @param array &$attr Attribute array to process (passed by reference) + * @param mixed $key Key of attribute to confiscate + * @return mixed + */ + public function confiscateAttr(&$attr, $key) + { + if (!isset($attr[$key])) { + return null; + } + $value = $attr[$key]; + unset($attr[$key]); + return $value; + } +} + + + + + +/** + * Provides lookup array of attribute types to HTMLPurifier_AttrDef objects + */ +class HTMLPurifier_AttrTypes +{ + /** + * Lookup array of attribute string identifiers to concrete implementations. + * @type HTMLPurifier_AttrDef[] + */ + protected $info = array(); + + /** + * Constructs the info array, supplying default implementations for attribute + * types. + */ + public function __construct() + { + // XXX This is kind of poor, since we don't actually /clone/ + // instances; instead, we use the supplied make() attribute. So, + // the underlying class must know how to deal with arguments. + // With the old implementation of Enum, that ignored its + // arguments when handling a make dispatch, the IAlign + // definition wouldn't work. + + // pseudo-types, must be instantiated via shorthand + $this->info['Enum'] = new HTMLPurifier_AttrDef_Enum(); + $this->info['Bool'] = new HTMLPurifier_AttrDef_HTML_Bool(); + + $this->info['CDATA'] = new HTMLPurifier_AttrDef_Text(); + $this->info['ID'] = new HTMLPurifier_AttrDef_HTML_ID(); + $this->info['Length'] = new HTMLPurifier_AttrDef_HTML_Length(); + $this->info['MultiLength'] = new HTMLPurifier_AttrDef_HTML_MultiLength(); + $this->info['NMTOKENS'] = new HTMLPurifier_AttrDef_HTML_Nmtokens(); + $this->info['Pixels'] = new HTMLPurifier_AttrDef_HTML_Pixels(); + $this->info['Text'] = new HTMLPurifier_AttrDef_Text(); + $this->info['URI'] = new HTMLPurifier_AttrDef_URI(); + $this->info['LanguageCode'] = new HTMLPurifier_AttrDef_Lang(); + $this->info['Color'] = new HTMLPurifier_AttrDef_HTML_Color(); + $this->info['IAlign'] = self::makeEnum('top,middle,bottom,left,right'); + $this->info['LAlign'] = self::makeEnum('top,bottom,left,right'); + $this->info['FrameTarget'] = new HTMLPurifier_AttrDef_HTML_FrameTarget(); + + // unimplemented aliases + $this->info['ContentType'] = new HTMLPurifier_AttrDef_Text(); + $this->info['ContentTypes'] = new HTMLPurifier_AttrDef_Text(); + $this->info['Charsets'] = new HTMLPurifier_AttrDef_Text(); + $this->info['Character'] = new HTMLPurifier_AttrDef_Text(); + + // "proprietary" types + $this->info['Class'] = new HTMLPurifier_AttrDef_HTML_Class(); + + // number is really a positive integer (one or more digits) + // FIXME: ^^ not always, see start and value of list items + $this->info['Number'] = new HTMLPurifier_AttrDef_Integer(false, false, true); + } + + private static function makeEnum($in) + { + return new HTMLPurifier_AttrDef_Clone(new HTMLPurifier_AttrDef_Enum(explode(',', $in))); + } + + /** + * Retrieves a type + * @param string $type String type name + * @return HTMLPurifier_AttrDef Object AttrDef for type + */ + public function get($type) + { + // determine if there is any extra info tacked on + if (strpos($type, '#') !== false) { + list($type, $string) = explode('#', $type, 2); + } else { + $string = ''; + } + + if (!isset($this->info[$type])) { + trigger_error('Cannot retrieve undefined attribute type ' . $type, E_USER_ERROR); + return; + } + return $this->info[$type]->make($string); + } + + /** + * Sets a new implementation for a type + * @param string $type String type name + * @param HTMLPurifier_AttrDef $impl Object AttrDef for type + */ + public function set($type, $impl) + { + $this->info[$type] = $impl; + } +} + + + + + +/** + * Validates the attributes of a token. Doesn't manage required attributes + * very well. The only reason we factored this out was because RemoveForeignElements + * also needed it besides ValidateAttributes. + */ +class HTMLPurifier_AttrValidator +{ + + /** + * Validates the attributes of a token, mutating it as necessary. + * that has valid tokens + * @param HTMLPurifier_Token $token Token to validate. + * @param HTMLPurifier_Config $config Instance of HTMLPurifier_Config + * @param HTMLPurifier_Context $context Instance of HTMLPurifier_Context + */ + public function validateToken($token, $config, $context) + { + $definition = $config->getHTMLDefinition(); + $e =& $context->get('ErrorCollector', true); + + // initialize IDAccumulator if necessary + $ok =& $context->get('IDAccumulator', true); + if (!$ok) { + $id_accumulator = HTMLPurifier_IDAccumulator::build($config, $context); + $context->register('IDAccumulator', $id_accumulator); + } + + // initialize CurrentToken if necessary + $current_token =& $context->get('CurrentToken', true); + if (!$current_token) { + $context->register('CurrentToken', $token); + } + + if (!$token instanceof HTMLPurifier_Token_Start && + !$token instanceof HTMLPurifier_Token_Empty + ) { + return; + } + + // create alias to global definition array, see also $defs + // DEFINITION CALL + $d_defs = $definition->info_global_attr; + + // don't update token until the very end, to ensure an atomic update + $attr = $token->attr; + + // do global transformations (pre) + // nothing currently utilizes this + foreach ($definition->info_attr_transform_pre as $transform) { + $attr = $transform->transform($o = $attr, $config, $context); + if ($e) { + if ($attr != $o) { + $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr); + } + } + } + + // do local transformations only applicable to this element (pre) + // ex.

to

+ foreach ($definition->info[$token->name]->attr_transform_pre as $transform) { + $attr = $transform->transform($o = $attr, $config, $context); + if ($e) { + if ($attr != $o) { + $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr); + } + } + } + + // create alias to this element's attribute definition array, see + // also $d_defs (global attribute definition array) + // DEFINITION CALL + $defs = $definition->info[$token->name]->attr; + + $attr_key = false; + $context->register('CurrentAttr', $attr_key); + + // iterate through all the attribute keypairs + // Watch out for name collisions: $key has previously been used + foreach ($attr as $attr_key => $value) { + + // call the definition + if (isset($defs[$attr_key])) { + // there is a local definition defined + if ($defs[$attr_key] === false) { + // We've explicitly been told not to allow this element. + // This is usually when there's a global definition + // that must be overridden. + // Theoretically speaking, we could have a + // AttrDef_DenyAll, but this is faster! + $result = false; + } else { + // validate according to the element's definition + $result = $defs[$attr_key]->validate( + $value, + $config, + $context + ); + } + } elseif (isset($d_defs[$attr_key])) { + // there is a global definition defined, validate according + // to the global definition + $result = $d_defs[$attr_key]->validate( + $value, + $config, + $context + ); + } else { + // system never heard of the attribute? DELETE! + $result = false; + } + + // put the results into effect + if ($result === false || $result === null) { + // this is a generic error message that should replaced + // with more specific ones when possible + if ($e) { + $e->send(E_ERROR, 'AttrValidator: Attribute removed'); + } + + // remove the attribute + unset($attr[$attr_key]); + } elseif (is_string($result)) { + // generally, if a substitution is happening, there + // was some sort of implicit correction going on. We'll + // delegate it to the attribute classes to say exactly what. + + // simple substitution + $attr[$attr_key] = $result; + } else { + // nothing happens + } + + // we'd also want slightly more complicated substitution + // involving an array as the return value, + // although we're not sure how colliding attributes would + // resolve (certain ones would be completely overriden, + // others would prepend themselves). + } + + $context->destroy('CurrentAttr'); + + // post transforms + + // global (error reporting untested) + foreach ($definition->info_attr_transform_post as $transform) { + $attr = $transform->transform($o = $attr, $config, $context); + if ($e) { + if ($attr != $o) { + $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr); + } + } + } + + // local (error reporting untested) + foreach ($definition->info[$token->name]->attr_transform_post as $transform) { + $attr = $transform->transform($o = $attr, $config, $context); + if ($e) { + if ($attr != $o) { + $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr); + } + } + } + + $token->attr = $attr; + + // destroy CurrentToken if we made it ourselves + if (!$current_token) { + $context->destroy('CurrentToken'); + } + + } + + +} + + + + + +// constants are slow, so we use as few as possible +if (!defined('HTMLPURIFIER_PREFIX')) { + define('HTMLPURIFIER_PREFIX', dirname(__FILE__) . '/standalone'); + set_include_path(HTMLPURIFIER_PREFIX . PATH_SEPARATOR . get_include_path()); +} + +// accomodations for versions earlier than 5.0.2 +// borrowed from PHP_Compat, LGPL licensed, by Aidan Lister +if (!defined('PHP_EOL')) { + switch (strtoupper(substr(PHP_OS, 0, 3))) { + case 'WIN': + define('PHP_EOL', "\r\n"); + break; + case 'DAR': + define('PHP_EOL', "\r"); + break; + default: + define('PHP_EOL', "\n"); + } +} + +/** + * Bootstrap class that contains meta-functionality for HTML Purifier such as + * the autoload function. + * + * @note + * This class may be used without any other files from HTML Purifier. + */ +class HTMLPurifier_Bootstrap +{ + + /** + * Autoload function for HTML Purifier + * @param string $class Class to load + * @return bool + */ + public static function autoload($class) + { + $file = HTMLPurifier_Bootstrap::getPath($class); + if (!$file) { + return false; + } + // Technically speaking, it should be ok and more efficient to + // just do 'require', but Antonio Parraga reports that with + // Zend extensions such as Zend debugger and APC, this invariant + // may be broken. Since we have efficient alternatives, pay + // the cost here and avoid the bug. + require_once HTMLPURIFIER_PREFIX . '/' . $file; + return true; + } + + /** + * Returns the path for a specific class. + * @param string $class Class path to get + * @return string + */ + public static function getPath($class) + { + if (strncmp('HTMLPurifier', $class, 12) !== 0) { + return false; + } + // Custom implementations + if (strncmp('HTMLPurifier_Language_', $class, 22) === 0) { + $code = str_replace('_', '-', substr($class, 22)); + $file = 'HTMLPurifier/Language/classes/' . $code . '.php'; + } else { + $file = str_replace('_', '/', $class) . '.php'; + } + if (!file_exists(HTMLPURIFIER_PREFIX . '/' . $file)) { + return false; + } + return $file; + } + + /** + * "Pre-registers" our autoloader on the SPL stack. + */ + public static function registerAutoload() + { + $autoload = array('HTMLPurifier_Bootstrap', 'autoload'); + if (($funcs = spl_autoload_functions()) === false) { + spl_autoload_register($autoload); + } elseif (function_exists('spl_autoload_unregister')) { + if (version_compare(PHP_VERSION, '5.3.0', '>=')) { + // prepend flag exists, no need for shenanigans + spl_autoload_register($autoload, true, true); + } else { + $buggy = version_compare(PHP_VERSION, '5.2.11', '<'); + $compat = version_compare(PHP_VERSION, '5.1.2', '<=') && + version_compare(PHP_VERSION, '5.1.0', '>='); + foreach ($funcs as $func) { + if ($buggy && is_array($func)) { + // :TRICKY: There are some compatibility issues and some + // places where we need to error out + $reflector = new ReflectionMethod($func[0], $func[1]); + if (!$reflector->isStatic()) { + throw new Exception( + 'HTML Purifier autoloader registrar is not compatible + with non-static object methods due to PHP Bug #44144; + Please do not use HTMLPurifier.autoload.php (or any + file that includes this file); instead, place the code: + spl_autoload_register(array(\'HTMLPurifier_Bootstrap\', \'autoload\')) + after your own autoloaders.' + ); + } + // Suprisingly, spl_autoload_register supports the + // Class::staticMethod callback format, although call_user_func doesn't + if ($compat) { + $func = implode('::', $func); + } + } + spl_autoload_unregister($func); + } + spl_autoload_register($autoload); + foreach ($funcs as $func) { + spl_autoload_register($func); + } + } + } + } +} + + + + + +/** + * Super-class for definition datatype objects, implements serialization + * functions for the class. + */ +abstract class HTMLPurifier_Definition +{ + + /** + * Has setup() been called yet? + * @type bool + */ + public $setup = false; + + /** + * If true, write out the final definition object to the cache after + * setup. This will be true only if all invocations to get a raw + * definition object are also optimized. This does not cause file + * system thrashing because on subsequent calls the cached object + * is used and any writes to the raw definition object are short + * circuited. See enduser-customize.html for the high-level + * picture. + * @type bool + */ + public $optimized = null; + + /** + * What type of definition is it? + * @type string + */ + public $type; + + /** + * Sets up the definition object into the final form, something + * not done by the constructor + * @param HTMLPurifier_Config $config + */ + abstract protected function doSetup($config); + + /** + * Setup function that aborts if already setup + * @param HTMLPurifier_Config $config + */ + public function setup($config) + { + if ($this->setup) { + return; + } + $this->setup = true; + $this->doSetup($config); + } +} + + + + + +/** + * Defines allowed CSS attributes and what their values are. + * @see HTMLPurifier_HTMLDefinition + */ +class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition +{ + + public $type = 'CSS'; + + /** + * Assoc array of attribute name to definition object. + * @type HTMLPurifier_AttrDef[] + */ + public $info = array(); + + /** + * Constructs the info array. The meat of this class. + * @param HTMLPurifier_Config $config + */ + protected function doSetup($config) + { + $this->info['text-align'] = new HTMLPurifier_AttrDef_Enum( + array('left', 'right', 'center', 'justify'), + false + ); + + $border_style = + $this->info['border-bottom-style'] = + $this->info['border-right-style'] = + $this->info['border-left-style'] = + $this->info['border-top-style'] = new HTMLPurifier_AttrDef_Enum( + array( + 'none', + 'hidden', + 'dotted', + 'dashed', + 'solid', + 'double', + 'groove', + 'ridge', + 'inset', + 'outset' + ), + false + ); + + $this->info['border-style'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_style); + + $this->info['clear'] = new HTMLPurifier_AttrDef_Enum( + array('none', 'left', 'right', 'both'), + false + ); + $this->info['float'] = new HTMLPurifier_AttrDef_Enum( + array('none', 'left', 'right'), + false + ); + $this->info['font-style'] = new HTMLPurifier_AttrDef_Enum( + array('normal', 'italic', 'oblique'), + false + ); + $this->info['font-variant'] = new HTMLPurifier_AttrDef_Enum( + array('normal', 'small-caps'), + false + ); + + $uri_or_none = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_Enum(array('none')), + new HTMLPurifier_AttrDef_CSS_URI() + ) + ); + + $this->info['list-style-position'] = new HTMLPurifier_AttrDef_Enum( + array('inside', 'outside'), + false + ); + $this->info['list-style-type'] = new HTMLPurifier_AttrDef_Enum( + array( + 'disc', + 'circle', + 'square', + 'decimal', + 'lower-roman', + 'upper-roman', + 'lower-alpha', + 'upper-alpha', + 'none' + ), + false + ); + $this->info['list-style-image'] = $uri_or_none; + + $this->info['list-style'] = new HTMLPurifier_AttrDef_CSS_ListStyle($config); + + $this->info['text-transform'] = new HTMLPurifier_AttrDef_Enum( + array('capitalize', 'uppercase', 'lowercase', 'none'), + false + ); + $this->info['color'] = new HTMLPurifier_AttrDef_CSS_Color(); + + $this->info['background-image'] = $uri_or_none; + $this->info['background-repeat'] = new HTMLPurifier_AttrDef_Enum( + array('repeat', 'repeat-x', 'repeat-y', 'no-repeat') + ); + $this->info['background-attachment'] = new HTMLPurifier_AttrDef_Enum( + array('scroll', 'fixed') + ); + $this->info['background-position'] = new HTMLPurifier_AttrDef_CSS_BackgroundPosition(); + + $border_color = + $this->info['border-top-color'] = + $this->info['border-bottom-color'] = + $this->info['border-left-color'] = + $this->info['border-right-color'] = + $this->info['background-color'] = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_Enum(array('transparent')), + new HTMLPurifier_AttrDef_CSS_Color() + ) + ); + + $this->info['background'] = new HTMLPurifier_AttrDef_CSS_Background($config); + + $this->info['border-color'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_color); + + $border_width = + $this->info['border-top-width'] = + $this->info['border-bottom-width'] = + $this->info['border-left-width'] = + $this->info['border-right-width'] = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_Enum(array('thin', 'medium', 'thick')), + new HTMLPurifier_AttrDef_CSS_Length('0') //disallow negative + ) + ); + + $this->info['border-width'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_width); + + $this->info['letter-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_Enum(array('normal')), + new HTMLPurifier_AttrDef_CSS_Length() + ) + ); + + $this->info['word-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_Enum(array('normal')), + new HTMLPurifier_AttrDef_CSS_Length() + ) + ); + + $this->info['font-size'] = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_Enum( + array( + 'xx-small', + 'x-small', + 'small', + 'medium', + 'large', + 'x-large', + 'xx-large', + 'larger', + 'smaller' + ) + ), + new HTMLPurifier_AttrDef_CSS_Percentage(), + new HTMLPurifier_AttrDef_CSS_Length() + ) + ); + + $this->info['line-height'] = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_Enum(array('normal')), + new HTMLPurifier_AttrDef_CSS_Number(true), // no negatives + new HTMLPurifier_AttrDef_CSS_Length('0'), + new HTMLPurifier_AttrDef_CSS_Percentage(true) + ) + ); + + $margin = + $this->info['margin-top'] = + $this->info['margin-bottom'] = + $this->info['margin-left'] = + $this->info['margin-right'] = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_CSS_Length(), + new HTMLPurifier_AttrDef_CSS_Percentage(), + new HTMLPurifier_AttrDef_Enum(array('auto')) + ) + ); + + $this->info['margin'] = new HTMLPurifier_AttrDef_CSS_Multiple($margin); + + // non-negative + $padding = + $this->info['padding-top'] = + $this->info['padding-bottom'] = + $this->info['padding-left'] = + $this->info['padding-right'] = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_CSS_Length('0'), + new HTMLPurifier_AttrDef_CSS_Percentage(true) + ) + ); + + $this->info['padding'] = new HTMLPurifier_AttrDef_CSS_Multiple($padding); + + $this->info['text-indent'] = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_CSS_Length(), + new HTMLPurifier_AttrDef_CSS_Percentage() + ) + ); + + $trusted_wh = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_CSS_Length('0'), + new HTMLPurifier_AttrDef_CSS_Percentage(true), + new HTMLPurifier_AttrDef_Enum(array('auto', 'initial', 'inherit')) + ) + ); + $trusted_min_wh = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_CSS_Length('0'), + new HTMLPurifier_AttrDef_CSS_Percentage(true), + new HTMLPurifier_AttrDef_Enum(array('initial', 'inherit')) + ) + ); + $trusted_max_wh = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_CSS_Length('0'), + new HTMLPurifier_AttrDef_CSS_Percentage(true), + new HTMLPurifier_AttrDef_Enum(array('none', 'initial', 'inherit')) + ) + ); + $max = $config->get('CSS.MaxImgLength'); + + $this->info['width'] = + $this->info['height'] = + $max === null ? + $trusted_wh : + new HTMLPurifier_AttrDef_Switch( + 'img', + // For img tags: + new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_CSS_Length('0', $max), + new HTMLPurifier_AttrDef_Enum(array('auto')) + ) + ), + // For everyone else: + $trusted_wh + ); + $this->info['min-width'] = + $this->info['min-height'] = + $max === null ? + $trusted_min_wh : + new HTMLPurifier_AttrDef_Switch( + 'img', + // For img tags: + new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_CSS_Length('0', $max), + new HTMLPurifier_AttrDef_Enum(array('initial', 'inherit')) + ) + ), + // For everyone else: + $trusted_min_wh + ); + $this->info['max-width'] = + $this->info['max-height'] = + $max === null ? + $trusted_max_wh : + new HTMLPurifier_AttrDef_Switch( + 'img', + // For img tags: + new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_CSS_Length('0', $max), + new HTMLPurifier_AttrDef_Enum(array('none', 'initial', 'inherit')) + ) + ), + // For everyone else: + $trusted_max_wh + ); + + $this->info['text-decoration'] = new HTMLPurifier_AttrDef_CSS_TextDecoration(); + + $this->info['font-family'] = new HTMLPurifier_AttrDef_CSS_FontFamily(); + + // this could use specialized code + $this->info['font-weight'] = new HTMLPurifier_AttrDef_Enum( + array( + 'normal', + 'bold', + 'bolder', + 'lighter', + '100', + '200', + '300', + '400', + '500', + '600', + '700', + '800', + '900' + ), + false + ); + + // MUST be called after other font properties, as it references + // a CSSDefinition object + $this->info['font'] = new HTMLPurifier_AttrDef_CSS_Font($config); + + // same here + $this->info['border'] = + $this->info['border-bottom'] = + $this->info['border-top'] = + $this->info['border-left'] = + $this->info['border-right'] = new HTMLPurifier_AttrDef_CSS_Border($config); + + $this->info['border-collapse'] = new HTMLPurifier_AttrDef_Enum( + array('collapse', 'separate') + ); + + $this->info['caption-side'] = new HTMLPurifier_AttrDef_Enum( + array('top', 'bottom') + ); + + $this->info['table-layout'] = new HTMLPurifier_AttrDef_Enum( + array('auto', 'fixed') + ); + + $this->info['vertical-align'] = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_Enum( + array( + 'baseline', + 'sub', + 'super', + 'top', + 'text-top', + 'middle', + 'bottom', + 'text-bottom' + ) + ), + new HTMLPurifier_AttrDef_CSS_Length(), + new HTMLPurifier_AttrDef_CSS_Percentage() + ) + ); + + $this->info['border-spacing'] = new HTMLPurifier_AttrDef_CSS_Multiple(new HTMLPurifier_AttrDef_CSS_Length(), 2); + + // These CSS properties don't work on many browsers, but we live + // in THE FUTURE! + $this->info['white-space'] = new HTMLPurifier_AttrDef_Enum( + array('nowrap', 'normal', 'pre', 'pre-wrap', 'pre-line') + ); + + if ($config->get('CSS.Proprietary')) { + $this->doSetupProprietary($config); + } + + if ($config->get('CSS.AllowTricky')) { + $this->doSetupTricky($config); + } + + if ($config->get('CSS.Trusted')) { + $this->doSetupTrusted($config); + } + + $allow_important = $config->get('CSS.AllowImportant'); + // wrap all attr-defs with decorator that handles !important + foreach ($this->info as $k => $v) { + $this->info[$k] = new HTMLPurifier_AttrDef_CSS_ImportantDecorator($v, $allow_important); + } + + $this->setupConfigStuff($config); + } + + /** + * @param HTMLPurifier_Config $config + */ + protected function doSetupProprietary($config) + { + // Internet Explorer only scrollbar colors + $this->info['scrollbar-arrow-color'] = new HTMLPurifier_AttrDef_CSS_Color(); + $this->info['scrollbar-base-color'] = new HTMLPurifier_AttrDef_CSS_Color(); + $this->info['scrollbar-darkshadow-color'] = new HTMLPurifier_AttrDef_CSS_Color(); + $this->info['scrollbar-face-color'] = new HTMLPurifier_AttrDef_CSS_Color(); + $this->info['scrollbar-highlight-color'] = new HTMLPurifier_AttrDef_CSS_Color(); + $this->info['scrollbar-shadow-color'] = new HTMLPurifier_AttrDef_CSS_Color(); + + // vendor specific prefixes of opacity + $this->info['-moz-opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue(); + $this->info['-khtml-opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue(); + + // only opacity, for now + $this->info['filter'] = new HTMLPurifier_AttrDef_CSS_Filter(); + + // more CSS3 + $this->info['page-break-after'] = + $this->info['page-break-before'] = new HTMLPurifier_AttrDef_Enum( + array( + 'auto', + 'always', + 'avoid', + 'left', + 'right' + ) + ); + $this->info['page-break-inside'] = new HTMLPurifier_AttrDef_Enum(array('auto', 'avoid')); + + $border_radius = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_CSS_Percentage(true), // disallow negative + new HTMLPurifier_AttrDef_CSS_Length('0') // disallow negative + )); + + $this->info['border-top-left-radius'] = + $this->info['border-top-right-radius'] = + $this->info['border-bottom-right-radius'] = + $this->info['border-bottom-left-radius'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_radius, 2); + // TODO: support SLASH syntax + $this->info['border-radius'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_radius, 4); + + } + + /** + * @param HTMLPurifier_Config $config + */ + protected function doSetupTricky($config) + { + $this->info['display'] = new HTMLPurifier_AttrDef_Enum( + array( + 'inline', + 'block', + 'list-item', + 'run-in', + 'compact', + 'marker', + 'table', + 'inline-block', + 'inline-table', + 'table-row-group', + 'table-header-group', + 'table-footer-group', + 'table-row', + 'table-column-group', + 'table-column', + 'table-cell', + 'table-caption', + 'none' + ) + ); + $this->info['visibility'] = new HTMLPurifier_AttrDef_Enum( + array('visible', 'hidden', 'collapse') + ); + $this->info['overflow'] = new HTMLPurifier_AttrDef_Enum(array('visible', 'hidden', 'auto', 'scroll')); + $this->info['opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue(); + } + + /** + * @param HTMLPurifier_Config $config + */ + protected function doSetupTrusted($config) + { + $this->info['position'] = new HTMLPurifier_AttrDef_Enum( + array('static', 'relative', 'absolute', 'fixed') + ); + $this->info['top'] = + $this->info['left'] = + $this->info['right'] = + $this->info['bottom'] = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_CSS_Length(), + new HTMLPurifier_AttrDef_CSS_Percentage(), + new HTMLPurifier_AttrDef_Enum(array('auto')), + ) + ); + $this->info['z-index'] = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_Integer(), + new HTMLPurifier_AttrDef_Enum(array('auto')), + ) + ); + } + + /** + * Performs extra config-based processing. Based off of + * HTMLPurifier_HTMLDefinition. + * @param HTMLPurifier_Config $config + * @todo Refactor duplicate elements into common class (probably using + * composition, not inheritance). + */ + protected function setupConfigStuff($config) + { + // setup allowed elements + $support = "(for information on implementing this, see the " . + "support forums) "; + $allowed_properties = $config->get('CSS.AllowedProperties'); + if ($allowed_properties !== null) { + foreach ($this->info as $name => $d) { + if (!isset($allowed_properties[$name])) { + unset($this->info[$name]); + } + unset($allowed_properties[$name]); + } + // emit errors + foreach ($allowed_properties as $name => $d) { + // :TODO: Is this htmlspecialchars() call really necessary? + $name = htmlspecialchars($name); + trigger_error("Style attribute '$name' is not supported $support", E_USER_WARNING); + } + } + + $forbidden_properties = $config->get('CSS.ForbiddenProperties'); + if ($forbidden_properties !== null) { + foreach ($this->info as $name => $d) { + if (isset($forbidden_properties[$name])) { + unset($this->info[$name]); + } + } + } + } +} + + + + + +/** + * Defines allowed child nodes and validates nodes against it. + */ +abstract class HTMLPurifier_ChildDef +{ + /** + * Type of child definition, usually right-most part of class name lowercase. + * Used occasionally in terms of context. + * @type string + */ + public $type; + + /** + * Indicates whether or not an empty array of children is okay. + * + * This is necessary for redundant checking when changes affecting + * a child node may cause a parent node to now be disallowed. + * @type bool + */ + public $allow_empty; + + /** + * Lookup array of all elements that this definition could possibly allow. + * @type array + */ + public $elements = array(); + + /** + * Get lookup of tag names that should not close this element automatically. + * All other elements will do so. + * @param HTMLPurifier_Config $config HTMLPurifier_Config object + * @return array + */ + public function getAllowedElements($config) + { + return $this->elements; + } + + /** + * Validates nodes according to definition and returns modification. + * + * @param HTMLPurifier_Node[] $children Array of HTMLPurifier_Node + * @param HTMLPurifier_Config $config HTMLPurifier_Config object + * @param HTMLPurifier_Context $context HTMLPurifier_Context object + * @return bool|array true to leave nodes as is, false to remove parent node, array of replacement children + */ + abstract public function validateChildren($children, $config, $context); +} + + + + + +/** + * Configuration object that triggers customizable behavior. + * + * @warning This class is strongly defined: that means that the class + * will fail if an undefined directive is retrieved or set. + * + * @note Many classes that could (although many times don't) use the + * configuration object make it a mandatory parameter. This is + * because a configuration object should always be forwarded, + * otherwise, you run the risk of missing a parameter and then + * being stumped when a configuration directive doesn't work. + * + * @todo Reconsider some of the public member variables + */ +class HTMLPurifier_Config +{ + + /** + * HTML Purifier's version + * @type string + */ + public $version = '4.12.0'; + + /** + * Whether or not to automatically finalize + * the object if a read operation is done. + * @type bool + */ + public $autoFinalize = true; + + // protected member variables + + /** + * Namespace indexed array of serials for specific namespaces. + * @see getSerial() for more info. + * @type string[] + */ + protected $serials = array(); + + /** + * Serial for entire configuration object. + * @type string + */ + protected $serial; + + /** + * Parser for variables. + * @type HTMLPurifier_VarParser_Flexible + */ + protected $parser = null; + + /** + * Reference HTMLPurifier_ConfigSchema for value checking. + * @type HTMLPurifier_ConfigSchema + * @note This is public for introspective purposes. Please don't + * abuse! + */ + public $def; + + /** + * Indexed array of definitions. + * @type HTMLPurifier_Definition[] + */ + protected $definitions; + + /** + * Whether or not config is finalized. + * @type bool + */ + protected $finalized = false; + + /** + * Property list containing configuration directives. + * @type array + */ + protected $plist; + + /** + * Whether or not a set is taking place due to an alias lookup. + * @type bool + */ + private $aliasMode; + + /** + * Set to false if you do not want line and file numbers in errors. + * (useful when unit testing). This will also compress some errors + * and exceptions. + * @type bool + */ + public $chatty = true; + + /** + * Current lock; only gets to this namespace are allowed. + * @type string + */ + private $lock; + + /** + * Constructor + * @param HTMLPurifier_ConfigSchema $definition ConfigSchema that defines + * what directives are allowed. + * @param HTMLPurifier_PropertyList $parent + */ + public function __construct($definition, $parent = null) + { + $parent = $parent ? $parent : $definition->defaultPlist; + $this->plist = new HTMLPurifier_PropertyList($parent); + $this->def = $definition; // keep a copy around for checking + $this->parser = new HTMLPurifier_VarParser_Flexible(); + } + + /** + * Convenience constructor that creates a config object based on a mixed var + * @param mixed $config Variable that defines the state of the config + * object. Can be: a HTMLPurifier_Config() object, + * an array of directives based on loadArray(), + * or a string filename of an ini file. + * @param HTMLPurifier_ConfigSchema $schema Schema object + * @return HTMLPurifier_Config Configured object + */ + public static function create($config, $schema = null) + { + if ($config instanceof HTMLPurifier_Config) { + // pass-through + return $config; + } + if (!$schema) { + $ret = HTMLPurifier_Config::createDefault(); + } else { + $ret = new HTMLPurifier_Config($schema); + } + if (is_string($config)) { + $ret->loadIni($config); + } elseif (is_array($config)) $ret->loadArray($config); + return $ret; + } + + /** + * Creates a new config object that inherits from a previous one. + * @param HTMLPurifier_Config $config Configuration object to inherit from. + * @return HTMLPurifier_Config object with $config as its parent. + */ + public static function inherit(HTMLPurifier_Config $config) + { + return new HTMLPurifier_Config($config->def, $config->plist); + } + + /** + * Convenience constructor that creates a default configuration object. + * @return HTMLPurifier_Config default object. + */ + public static function createDefault() + { + $definition = HTMLPurifier_ConfigSchema::instance(); + $config = new HTMLPurifier_Config($definition); + return $config; + } + + /** + * Retrieves a value from the configuration. + * + * @param string $key String key + * @param mixed $a + * + * @return mixed + */ + public function get($key, $a = null) + { + if ($a !== null) { + $this->triggerError( + "Using deprecated API: use \$config->get('$key.$a') instead", + E_USER_WARNING + ); + $key = "$key.$a"; + } + if (!$this->finalized) { + $this->autoFinalize(); + } + if (!isset($this->def->info[$key])) { + // can't add % due to SimpleTest bug + $this->triggerError( + 'Cannot retrieve value of undefined directive ' . htmlspecialchars($key), + E_USER_WARNING + ); + return; + } + if (isset($this->def->info[$key]->isAlias)) { + $d = $this->def->info[$key]; + $this->triggerError( + 'Cannot get value from aliased directive, use real name ' . $d->key, + E_USER_ERROR + ); + return; + } + if ($this->lock) { + list($ns) = explode('.', $key); + if ($ns !== $this->lock) { + $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 + ); + return; + } + } + return $this->plist->get($key); + } + + /** + * Retrieves an array of directives to values from a given namespace + * + * @param string $namespace String namespace + * + * @return array + */ + public function getBatch($namespace) + { + if (!$this->finalized) { + $this->autoFinalize(); + } + $full = $this->getAll(); + if (!isset($full[$namespace])) { + $this->triggerError( + 'Cannot retrieve undefined namespace ' . + htmlspecialchars($namespace), + E_USER_WARNING + ); + return; + } + return $full[$namespace]; + } + + /** + * Returns a SHA-1 signature of a segment of the configuration object + * that uniquely identifies that particular configuration + * + * @param string $namespace Namespace to get serial for + * + * @return string + * @note Revision is handled specially and is removed from the batch + * before processing! + */ + public function getBatchSerial($namespace) + { + if (empty($this->serials[$namespace])) { + $batch = $this->getBatch($namespace); + unset($batch['DefinitionRev']); + $this->serials[$namespace] = sha1(serialize($batch)); + } + return $this->serials[$namespace]; + } + + /** + * Returns a SHA-1 signature for the entire configuration object + * that uniquely identifies that particular configuration + * + * @return string + */ + public function getSerial() + { + if (empty($this->serial)) { + $this->serial = sha1(serialize($this->getAll())); + } + return $this->serial; + } + + /** + * Retrieves all directives, organized by namespace + * + * @warning This is a pretty inefficient function, avoid if you can + */ + public function getAll() + { + if (!$this->finalized) { + $this->autoFinalize(); + } + $ret = array(); + foreach ($this->plist->squash() as $name => $value) { + list($ns, $key) = explode('.', $name, 2); + $ret[$ns][$key] = $value; + } + return $ret; + } + + /** + * Sets a value to configuration. + * + * @param string $key key + * @param mixed $value value + * @param mixed $a + */ + public function set($key, $value, $a = null) + { + if (strpos($key, '.') === false) { + $namespace = $key; + $directive = $value; + $value = $a; + $key = "$key.$directive"; + $this->triggerError("Using deprecated API: use \$config->set('$key', ...) instead", E_USER_NOTICE); + } else { + list($namespace) = explode('.', $key); + } + if ($this->isFinalized('Cannot set directive after finalization')) { + return; + } + if (!isset($this->def->info[$key])) { + $this->triggerError( + 'Cannot set undefined directive ' . htmlspecialchars($key) . ' to value', + E_USER_WARNING + ); + return; + } + $def = $this->def->info[$key]; + + if (isset($def->isAlias)) { + if ($this->aliasMode) { + $this->triggerError( + 'Double-aliases not allowed, please fix '. + 'ConfigSchema bug with' . $key, + E_USER_ERROR + ); + return; + } + $this->aliasMode = true; + $this->set($def->key, $value); + $this->aliasMode = false; + $this->triggerError("$key is an alias, preferred directive name is {$def->key}", E_USER_NOTICE); + return; + } + + // Raw type might be negative when using the fully optimized form + // of stdClass, which indicates allow_null == true + $rtype = is_int($def) ? $def : $def->type; + if ($rtype < 0) { + $type = -$rtype; + $allow_null = true; + } else { + $type = $rtype; + $allow_null = isset($def->allow_null); + } + + try { + $value = $this->parser->parse($value, $type, $allow_null); + } catch (HTMLPurifier_VarParserException $e) { + $this->triggerError( + 'Value for ' . $key . ' is of invalid type, should be ' . + HTMLPurifier_VarParser::getTypeName($type), + E_USER_WARNING + ); + return; + } + if (is_string($value) && is_object($def)) { + // resolve value alias if defined + if (isset($def->aliases[$value])) { + $value = $def->aliases[$value]; + } + // check to see if the value is allowed + if (isset($def->allowed) && !isset($def->allowed[$value])) { + $this->triggerError( + 'Value not supported, valid values are: ' . + $this->_listify($def->allowed), + E_USER_WARNING + ); + return; + } + } + $this->plist->set($key, $value); + + // reset definitions if the directives they depend on changed + // this is a very costly process, so it's discouraged + // with finalization + if ($namespace == 'HTML' || $namespace == 'CSS' || $namespace == 'URI') { + $this->definitions[$namespace] = null; + } + + $this->serials[$namespace] = false; + } + + /** + * Convenience function for error reporting + * + * @param array $lookup + * + * @return string + */ + private function _listify($lookup) + { + $list = array(); + foreach ($lookup as $name => $b) { + $list[] = $name; + } + return implode(', ', $list); + } + + /** + * Retrieves object reference to the HTML definition. + * + * @param bool $raw Return a copy that has not been setup yet. Must be + * called before it's been setup, otherwise won't work. + * @param bool $optimized If true, this method may return null, to + * indicate that a cached version of the modified + * definition object is available and no further edits + * are necessary. Consider using + * maybeGetRawHTMLDefinition, which is more explicitly + * named, instead. + * + * @return HTMLPurifier_HTMLDefinition + */ + public function getHTMLDefinition($raw = false, $optimized = false) + { + return $this->getDefinition('HTML', $raw, $optimized); + } + + /** + * Retrieves object reference to the CSS definition + * + * @param bool $raw Return a copy that has not been setup yet. Must be + * called before it's been setup, otherwise won't work. + * @param bool $optimized If true, this method may return null, to + * indicate that a cached version of the modified + * definition object is available and no further edits + * are necessary. Consider using + * maybeGetRawCSSDefinition, which is more explicitly + * named, instead. + * + * @return HTMLPurifier_CSSDefinition + */ + public function getCSSDefinition($raw = false, $optimized = false) + { + return $this->getDefinition('CSS', $raw, $optimized); + } + + /** + * Retrieves object reference to the URI definition + * + * @param bool $raw Return a copy that has not been setup yet. Must be + * called before it's been setup, otherwise won't work. + * @param bool $optimized If true, this method may return null, to + * indicate that a cached version of the modified + * definition object is available and no further edits + * are necessary. Consider using + * maybeGetRawURIDefinition, which is more explicitly + * named, instead. + * + * @return HTMLPurifier_URIDefinition + */ + public function getURIDefinition($raw = false, $optimized = false) + { + return $this->getDefinition('URI', $raw, $optimized); + } + + /** + * Retrieves a definition + * + * @param string $type Type of definition: HTML, CSS, etc + * @param bool $raw Whether or not definition should be returned raw + * @param bool $optimized Only has an effect when $raw is true. Whether + * or not to return null if the result is already present in + * the cache. This is off by default for backwards + * compatibility reasons, but you need to do things this + * way in order to ensure that caching is done properly. + * Check out enduser-customize.html for more details. + * We probably won't ever change this default, as much as the + * maybe semantics is the "right thing to do." + * + * @throws HTMLPurifier_Exception + * @return HTMLPurifier_Definition + */ + public function getDefinition($type, $raw = false, $optimized = false) + { + if ($optimized && !$raw) { + throw new HTMLPurifier_Exception("Cannot set optimized = true when raw = false"); + } + if (!$this->finalized) { + $this->autoFinalize(); + } + // temporarily suspend locks, so we can handle recursive definition calls + $lock = $this->lock; + $this->lock = null; + $factory = HTMLPurifier_DefinitionCacheFactory::instance(); + $cache = $factory->create($type, $this); + $this->lock = $lock; + if (!$raw) { + // full definition + // --------------- + // check if definition is in memory + if (!empty($this->definitions[$type])) { + $def = $this->definitions[$type]; + // check if the definition is setup + if ($def->setup) { + return $def; + } else { + $def->setup($this); + if ($def->optimized) { + $cache->add($def, $this); + } + return $def; + } + } + // check if definition is in cache + $def = $cache->get($this); + if ($def) { + // definition in cache, save to memory and return it + $this->definitions[$type] = $def; + return $def; + } + // initialize it + $def = $this->initDefinition($type); + // set it up + $this->lock = $type; + $def->setup($this); + $this->lock = null; + // save in cache + $cache->add($def, $this); + // return it + return $def; + } else { + // raw definition + // -------------- + // check preconditions + $def = null; + if ($optimized) { + if (is_null($this->get($type . '.DefinitionID'))) { + // fatally error out if definition ID not set + throw new HTMLPurifier_Exception( + "Cannot retrieve raw version without specifying %$type.DefinitionID" + ); + } + } + if (!empty($this->definitions[$type])) { + $def = $this->definitions[$type]; + if ($def->setup && !$optimized) { + $extra = $this->chatty ? + " (try moving this code block earlier in your initialization)" : + ""; + throw new HTMLPurifier_Exception( + "Cannot retrieve raw definition after it has already been setup" . + $extra + ); + } + if ($def->optimized === null) { + $extra = $this->chatty ? " (try flushing your cache)" : ""; + throw new HTMLPurifier_Exception( + "Optimization status of definition is unknown" . $extra + ); + } + if ($def->optimized !== $optimized) { + $msg = $optimized ? "optimized" : "unoptimized"; + $extra = $this->chatty ? + " (this backtrace is for the first inconsistent call, which was for a $msg raw definition)" + : ""; + throw new HTMLPurifier_Exception( + "Inconsistent use of optimized and unoptimized raw definition retrievals" . $extra + ); + } + } + // check if definition was in memory + if ($def) { + if ($def->setup) { + // invariant: $optimized === true (checked above) + return null; + } else { + return $def; + } + } + // if optimized, check if definition was in cache + // (because we do the memory check first, this formulation + // is prone to cache slamming, but I think + // guaranteeing that either /all/ of the raw + // setup code or /none/ of it is run is more important.) + if ($optimized) { + // This code path only gets run once; once we put + // something in $definitions (which is guaranteed by the + // trailing code), we always short-circuit above. + $def = $cache->get($this); + if ($def) { + // save the full definition for later, but don't + // return it yet + $this->definitions[$type] = $def; + return null; + } + } + // check invariants for creation + if (!$optimized) { + if (!is_null($this->get($type . '.DefinitionID'))) { + if ($this->chatty) { + $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 ' . + '' . + 'Customize for more details', + E_USER_WARNING + ); + } else { + $this->triggerError( + "Useless DefinitionID declaration", + E_USER_WARNING + ); + } + } + } + // initialize it + $def = $this->initDefinition($type); + $def->optimized = $optimized; + return $def; + } + throw new HTMLPurifier_Exception("The impossible happened!"); + } + + /** + * Initialise definition + * + * @param string $type What type of definition to create + * + * @return HTMLPurifier_CSSDefinition|HTMLPurifier_HTMLDefinition|HTMLPurifier_URIDefinition + * @throws HTMLPurifier_Exception + */ + private function initDefinition($type) + { + // quick checks failed, let's create the object + if ($type == 'HTML') { + $def = new HTMLPurifier_HTMLDefinition(); + } elseif ($type == 'CSS') { + $def = new HTMLPurifier_CSSDefinition(); + } elseif ($type == 'URI') { + $def = new HTMLPurifier_URIDefinition(); + } else { + throw new HTMLPurifier_Exception( + "Definition of $type type not supported" + ); + } + $this->definitions[$type] = $def; + return $def; + } + + public function maybeGetRawDefinition($name) + { + return $this->getDefinition($name, true, true); + } + + /** + * @return HTMLPurifier_HTMLDefinition + */ + public function maybeGetRawHTMLDefinition() + { + return $this->getDefinition('HTML', true, true); + } + + /** + * @return HTMLPurifier_CSSDefinition + */ + public function maybeGetRawCSSDefinition() + { + return $this->getDefinition('CSS', true, true); + } + + /** + * @return HTMLPurifier_URIDefinition + */ + public function maybeGetRawURIDefinition() + { + return $this->getDefinition('URI', true, true); + } + + /** + * Loads configuration values from an array with the following structure: + * Namespace.Directive => Value + * + * @param array $config_array Configuration associative array + */ + public function loadArray($config_array) + { + if ($this->isFinalized('Cannot load directives after finalization')) { + return; + } + foreach ($config_array as $key => $value) { + $key = str_replace('_', '.', $key); + if (strpos($key, '.') !== false) { + $this->set($key, $value); + } else { + $namespace = $key; + $namespace_values = $value; + foreach ($namespace_values as $directive => $value2) { + $this->set($namespace .'.'. $directive, $value2); + } + } + } + } + + /** + * Returns a list of array(namespace, directive) for all directives + * that are allowed in a web-form context as per an allowed + * namespaces/directives list. + * + * @param array $allowed List of allowed namespaces/directives + * @param HTMLPurifier_ConfigSchema $schema Schema to use, if not global copy + * + * @return array + */ + public static function getAllowedDirectivesForForm($allowed, $schema = null) + { + if (!$schema) { + $schema = HTMLPurifier_ConfigSchema::instance(); + } + if ($allowed !== true) { + if (is_string($allowed)) { + $allowed = array($allowed); + } + $allowed_ns = array(); + $allowed_directives = array(); + $blacklisted_directives = array(); + foreach ($allowed as $ns_or_directive) { + if (strpos($ns_or_directive, '.') !== false) { + // directive + if ($ns_or_directive[0] == '-') { + $blacklisted_directives[substr($ns_or_directive, 1)] = true; + } else { + $allowed_directives[$ns_or_directive] = true; + } + } else { + // namespace + $allowed_ns[$ns_or_directive] = true; + } + } + } + $ret = array(); + foreach ($schema->info as $key => $def) { + list($ns, $directive) = explode('.', $key, 2); + if ($allowed !== true) { + if (isset($blacklisted_directives["$ns.$directive"])) { + continue; + } + if (!isset($allowed_directives["$ns.$directive"]) && !isset($allowed_ns[$ns])) { + continue; + } + } + if (isset($def->isAlias)) { + continue; + } + if ($directive == 'DefinitionID' || $directive == 'DefinitionRev') { + continue; + } + $ret[] = array($ns, $directive); + } + return $ret; + } + + /** + * Loads configuration values from $_GET/$_POST that were posted + * via ConfigForm + * + * @param array $array $_GET or $_POST array to import + * @param string|bool $index Index/name that the config variables are in + * @param array|bool $allowed List of allowed namespaces/directives + * @param bool $mq_fix Boolean whether or not to enable magic quotes fix + * @param HTMLPurifier_ConfigSchema $schema Schema to use, if not global copy + * + * @return mixed + */ + public static function loadArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null) + { + $ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $schema); + $config = HTMLPurifier_Config::create($ret, $schema); + return $config; + } + + /** + * Merges in configuration values from $_GET/$_POST to object. NOT STATIC. + * + * @param array $array $_GET or $_POST array to import + * @param string|bool $index Index/name that the config variables are in + * @param array|bool $allowed List of allowed namespaces/directives + * @param bool $mq_fix Boolean whether or not to enable magic quotes fix + */ + public function mergeArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true) + { + $ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $this->def); + $this->loadArray($ret); + } + + /** + * Prepares an array from a form into something usable for the more + * strict parts of HTMLPurifier_Config + * + * @param array $array $_GET or $_POST array to import + * @param string|bool $index Index/name that the config variables are in + * @param array|bool $allowed List of allowed namespaces/directives + * @param bool $mq_fix Boolean whether or not to enable magic quotes fix + * @param HTMLPurifier_ConfigSchema $schema Schema to use, if not global copy + * + * @return array + */ + public static function prepareArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null) + { + if ($index !== false) { + $array = (isset($array[$index]) && is_array($array[$index])) ? $array[$index] : array(); + } + $mq = $mq_fix && function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc(); + + $allowed = HTMLPurifier_Config::getAllowedDirectivesForForm($allowed, $schema); + $ret = array(); + foreach ($allowed as $key) { + list($ns, $directive) = $key; + $skey = "$ns.$directive"; + if (!empty($array["Null_$skey"])) { + $ret[$ns][$directive] = null; + continue; + } + if (!isset($array[$skey])) { + continue; + } + $value = $mq ? stripslashes($array[$skey]) : $array[$skey]; + $ret[$ns][$directive] = $value; + } + return $ret; + } + + /** + * Loads configuration values from an ini file + * + * @param string $filename Name of ini file + */ + public function loadIni($filename) + { + if ($this->isFinalized('Cannot load directives after finalization')) { + return; + } + $array = parse_ini_file($filename, true); + $this->loadArray($array); + } + + /** + * Checks whether or not the configuration object is finalized. + * + * @param string|bool $error String error message, or false for no error + * + * @return bool + */ + public function isFinalized($error = false) + { + if ($this->finalized && $error) { + $this->triggerError($error, E_USER_ERROR); + } + return $this->finalized; + } + + /** + * Finalizes configuration only if auto finalize is on and not + * already finalized + */ + public function autoFinalize() + { + if ($this->autoFinalize) { + $this->finalize(); + } else { + $this->plist->squash(true); + } + } + + /** + * Finalizes a configuration object, prohibiting further change + */ + public function finalize() + { + $this->finalized = true; + $this->parser = null; + } + + /** + * Produces a nicely formatted error message by supplying the + * stack frame information OUTSIDE of HTMLPurifier_Config. + * + * @param string $msg An error message + * @param int $no An error number + */ + protected function triggerError($msg, $no) + { + // determine previous stack frame + $extra = ''; + if ($this->chatty) { + $trace = debug_backtrace(); + // zip(tail(trace), trace) -- but PHP is not Haskell har har + for ($i = 0, $c = count($trace); $i < $c - 1; $i++) { + // XXX this is not correct on some versions of HTML Purifier + if (isset($trace[$i + 1]['class']) && $trace[$i + 1]['class'] === 'HTMLPurifier_Config') { + continue; + } + $frame = $trace[$i]; + $extra = " invoked on line {$frame['line']} in file {$frame['file']}"; + break; + } + } + trigger_error($msg . $extra, $no); + } + + /** + * Returns a serialized form of the configuration object that can + * be reconstituted. + * + * @return string + */ + public function serialize() + { + $this->getDefinition('HTML'); + $this->getDefinition('CSS'); + $this->getDefinition('URI'); + return serialize($this); + } + +} + + + + + +/** + * Configuration definition, defines directives and their defaults. + */ +class HTMLPurifier_ConfigSchema +{ + /** + * Defaults of the directives and namespaces. + * @type array + * @note This shares the exact same structure as HTMLPurifier_Config::$conf + */ + public $defaults = array(); + + /** + * The default property list. Do not edit this property list. + * @type array + */ + public $defaultPlist; + + /** + * Definition of the directives. + * The structure of this is: + * + * array( + * 'Namespace' => array( + * 'Directive' => new stdClass(), + * ) + * ) + * + * The stdClass may have the following properties: + * + * - If isAlias isn't set: + * - type: Integer type of directive, see HTMLPurifier_VarParser for definitions + * - allow_null: If set, this directive allows null values + * - aliases: If set, an associative array of value aliases to real values + * - allowed: If set, a lookup array of allowed (string) values + * - If isAlias is set: + * - namespace: Namespace this directive aliases to + * - name: Directive name this directive aliases to + * + * In certain degenerate cases, stdClass will actually be an integer. In + * that case, the value is equivalent to an stdClass with the type + * property set to the integer. If the integer is negative, type is + * equal to the absolute value of integer, and allow_null is true. + * + * This class is friendly with HTMLPurifier_Config. If you need introspection + * about the schema, you're better of using the ConfigSchema_Interchange, + * which uses more memory but has much richer information. + * @type array + */ + public $info = array(); + + /** + * Application-wide singleton + * @type HTMLPurifier_ConfigSchema + */ + protected static $singleton; + + public function __construct() + { + $this->defaultPlist = new HTMLPurifier_PropertyList(); + } + + /** + * Unserializes the default ConfigSchema. + * @return HTMLPurifier_ConfigSchema + */ + public static function makeFromSerial() + { + $contents = file_get_contents(HTMLPURIFIER_PREFIX . '/HTMLPurifier/ConfigSchema/schema.ser'); + $r = unserialize($contents); + if (!$r) { + $hash = sha1($contents); + trigger_error("Unserialization of configuration schema failed, sha1 of file was $hash", E_USER_ERROR); + } + return $r; + } + + /** + * Retrieves an instance of the application-wide configuration definition. + * @param HTMLPurifier_ConfigSchema $prototype + * @return HTMLPurifier_ConfigSchema + */ + public static function instance($prototype = null) + { + if ($prototype !== null) { + HTMLPurifier_ConfigSchema::$singleton = $prototype; + } elseif (HTMLPurifier_ConfigSchema::$singleton === null || $prototype === true) { + HTMLPurifier_ConfigSchema::$singleton = HTMLPurifier_ConfigSchema::makeFromSerial(); + } + return HTMLPurifier_ConfigSchema::$singleton; + } + + /** + * Defines a directive for configuration + * @warning Will fail of directive's namespace is defined. + * @warning This method's signature is slightly different from the legacy + * define() static method! Beware! + * @param string $key Name of directive + * @param mixed $default Default value of directive + * @param string $type Allowed type of the directive. See + * HTMLPurifier_VarParser::$types for allowed values + * @param bool $allow_null Whether or not to allow null values + */ + public function add($key, $default, $type, $allow_null) + { + $obj = new stdClass(); + $obj->type = is_int($type) ? $type : HTMLPurifier_VarParser::$types[$type]; + if ($allow_null) { + $obj->allow_null = true; + } + $this->info[$key] = $obj; + $this->defaults[$key] = $default; + $this->defaultPlist->set($key, $default); + } + + /** + * Defines a directive value alias. + * + * Directive value aliases are convenient for developers because it lets + * them set a directive to several values and get the same result. + * @param string $key Name of Directive + * @param array $aliases Hash of aliased values to the real alias + */ + public function addValueAliases($key, $aliases) + { + if (!isset($this->info[$key]->aliases)) { + $this->info[$key]->aliases = array(); + } + foreach ($aliases as $alias => $real) { + $this->info[$key]->aliases[$alias] = $real; + } + } + + /** + * Defines a set of allowed values for a directive. + * @warning This is slightly different from the corresponding static + * method definition. + * @param string $key Name of directive + * @param array $allowed Lookup array of allowed values + */ + public function addAllowedValues($key, $allowed) + { + $this->info[$key]->allowed = $allowed; + } + + /** + * Defines a directive alias for backwards compatibility + * @param string $key Directive that will be aliased + * @param string $new_key Directive that the alias will be to + */ + public function addAlias($key, $new_key) + { + $obj = new stdClass; + $obj->key = $new_key; + $obj->isAlias = true; + $this->info[$key] = $obj; + } + + /** + * Replaces any stdClass that only has the type property with type integer. + */ + public function postProcess() + { + foreach ($this->info as $key => $v) { + if (count((array) $v) == 1) { + $this->info[$key] = $v->type; + } elseif (count((array) $v) == 2 && isset($v->allow_null)) { + $this->info[$key] = -$v->type; + } + } + } +} + + + + + +/** + * @todo Unit test + */ +class HTMLPurifier_ContentSets +{ + + /** + * List of content set strings (pipe separators) indexed by name. + * @type array + */ + public $info = array(); + + /** + * List of content set lookups (element => true) indexed by name. + * @type array + * @note This is in HTMLPurifier_HTMLDefinition->info_content_sets + */ + public $lookup = array(); + + /** + * Synchronized list of defined content sets (keys of info). + * @type array + */ + protected $keys = array(); + /** + * Synchronized list of defined content values (values of info). + * @type array + */ + protected $values = array(); + + /** + * Merges in module's content sets, expands identifiers in the content + * sets and populates the keys, values and lookup member variables. + * @param HTMLPurifier_HTMLModule[] $modules List of HTMLPurifier_HTMLModule + */ + public function __construct($modules) + { + if (!is_array($modules)) { + $modules = array($modules); + } + // populate content_sets based on module hints + // sorry, no way of overloading + foreach ($modules as $module) { + foreach ($module->content_sets as $key => $value) { + $temp = $this->convertToLookup($value); + if (isset($this->lookup[$key])) { + // add it into the existing content set + $this->lookup[$key] = array_merge($this->lookup[$key], $temp); + } else { + $this->lookup[$key] = $temp; + } + } + } + $old_lookup = false; + while ($old_lookup !== $this->lookup) { + $old_lookup = $this->lookup; + foreach ($this->lookup as $i => $set) { + $add = array(); + foreach ($set as $element => $x) { + if (isset($this->lookup[$element])) { + $add += $this->lookup[$element]; + unset($this->lookup[$i][$element]); + } + } + $this->lookup[$i] += $add; + } + } + + foreach ($this->lookup as $key => $lookup) { + $this->info[$key] = implode(' | ', array_keys($lookup)); + } + $this->keys = array_keys($this->info); + $this->values = array_values($this->info); + } + + /** + * Accepts a definition; generates and assigns a ChildDef for it + * @param HTMLPurifier_ElementDef $def HTMLPurifier_ElementDef reference + * @param HTMLPurifier_HTMLModule $module Module that defined the ElementDef + */ + public function generateChildDef(&$def, $module) + { + if (!empty($def->child)) { // already done! + return; + } + $content_model = $def->content_model; + if (is_string($content_model)) { + // Assume that $this->keys is alphanumeric + $def->content_model = preg_replace_callback( + '/\b(' . implode('|', $this->keys) . ')\b/', + array($this, 'generateChildDefCallback'), + $content_model + ); + //$def->content_model = str_replace( + // $this->keys, $this->values, $content_model); + } + $def->child = $this->getChildDef($def, $module); + } + + public function generateChildDefCallback($matches) + { + return $this->info[$matches[0]]; + } + + /** + * Instantiates a ChildDef based on content_model and content_model_type + * member variables in HTMLPurifier_ElementDef + * @note This will also defer to modules for custom HTMLPurifier_ChildDef + * subclasses that need content set expansion + * @param HTMLPurifier_ElementDef $def HTMLPurifier_ElementDef to have ChildDef extracted + * @param HTMLPurifier_HTMLModule $module Module that defined the ElementDef + * @return HTMLPurifier_ChildDef corresponding to ElementDef + */ + public function getChildDef($def, $module) + { + $value = $def->content_model; + if (is_object($value)) { + trigger_error( + 'Literal object child definitions should be stored in '. + 'ElementDef->child not ElementDef->content_model', + E_USER_NOTICE + ); + return $value; + } + switch ($def->content_model_type) { + case 'required': + return new HTMLPurifier_ChildDef_Required($value); + case 'optional': + return new HTMLPurifier_ChildDef_Optional($value); + case 'empty': + return new HTMLPurifier_ChildDef_Empty(); + case 'custom': + return new HTMLPurifier_ChildDef_Custom($value); + } + // defer to its module + $return = false; + if ($module->defines_child_def) { // save a func call + $return = $module->getChildDef($def); + } + if ($return !== false) { + return $return; + } + // error-out + trigger_error( + 'Could not determine which ChildDef class to instantiate', + E_USER_ERROR + ); + return false; + } + + /** + * Converts a string list of elements separated by pipes into + * a lookup array. + * @param string $string List of elements + * @return array Lookup array of elements + */ + protected function convertToLookup($string) + { + $array = explode('|', str_replace(' ', '', $string)); + $ret = array(); + foreach ($array as $k) { + $ret[$k] = true; + } + return $ret; + } +} + + + + + +/** + * Registry object that contains information about the current context. + * @warning Is a bit buggy when variables are set to null: it thinks + * they don't exist! So use false instead, please. + * @note Since the variables Context deals with may not be objects, + * references are very important here! Do not remove! + */ +class HTMLPurifier_Context +{ + + /** + * Private array that stores the references. + * @type array + */ + private $_storage = array(); + + /** + * Registers a variable into the context. + * @param string $name String name + * @param mixed $ref Reference to variable to be registered + */ + public function register($name, &$ref) + { + if (array_key_exists($name, $this->_storage)) { + trigger_error( + "Name $name produces collision, cannot re-register", + E_USER_ERROR + ); + return; + } + $this->_storage[$name] =& $ref; + } + + /** + * Retrieves a variable reference from the context. + * @param string $name String name + * @param bool $ignore_error Boolean whether or not to ignore error + * @return mixed + */ + public function &get($name, $ignore_error = false) + { + if (!array_key_exists($name, $this->_storage)) { + if (!$ignore_error) { + trigger_error( + "Attempted to retrieve non-existent variable $name", + E_USER_ERROR + ); + } + $var = null; // so we can return by reference + return $var; + } + return $this->_storage[$name]; + } + + /** + * Destroys a variable in the context. + * @param string $name String name + */ + public function destroy($name) + { + if (!array_key_exists($name, $this->_storage)) { + trigger_error( + "Attempted to destroy non-existent variable $name", + E_USER_ERROR + ); + return; + } + unset($this->_storage[$name]); + } + + /** + * Checks whether or not the variable exists. + * @param string $name String name + * @return bool + */ + public function exists($name) + { + return array_key_exists($name, $this->_storage); + } + + /** + * Loads a series of variables from an associative array + * @param array $context_array Assoc array of variables to load + */ + public function loadArray($context_array) + { + foreach ($context_array as $key => $discard) { + $this->register($key, $context_array[$key]); + } + } +} + + + + + +/** + * Abstract class representing Definition cache managers that implements + * useful common methods and is a factory. + * @todo Create a separate maintenance file advanced users can use to + * cache their custom HTMLDefinition, which can be loaded + * via a configuration directive + * @todo Implement memcached + */ +abstract class HTMLPurifier_DefinitionCache +{ + /** + * @type string + */ + public $type; + + /** + * @param string $type Type of definition objects this instance of the + * cache will handle. + */ + public function __construct($type) + { + $this->type = $type; + } + + /** + * Generates a unique identifier for a particular configuration + * @param HTMLPurifier_Config $config Instance of HTMLPurifier_Config + * @return string + */ + public function generateKey($config) + { + return $config->version . ',' . // possibly replace with function calls + $config->getBatchSerial($this->type) . ',' . + $config->get($this->type . '.DefinitionRev'); + } + + /** + * Tests whether or not a key is old with respect to the configuration's + * version and revision number. + * @param string $key Key to test + * @param HTMLPurifier_Config $config Instance of HTMLPurifier_Config to test against + * @return bool + */ + public function isOld($key, $config) + { + if (substr_count($key, ',') < 2) { + return true; + } + list($version, $hash, $revision) = explode(',', $key, 3); + $compare = version_compare($version, $config->version); + // version mismatch, is always old + if ($compare != 0) { + return true; + } + // versions match, ids match, check revision number + if ($hash == $config->getBatchSerial($this->type) && + $revision < $config->get($this->type . '.DefinitionRev')) { + return true; + } + return false; + } + + /** + * Checks if a definition's type jives with the cache's type + * @note Throws an error on failure + * @param HTMLPurifier_Definition $def Definition object to check + * @return bool true if good, false if not + */ + public function checkDefType($def) + { + if ($def->type !== $this->type) { + trigger_error("Cannot use definition of type {$def->type} in cache for {$this->type}"); + return false; + } + return true; + } + + /** + * Adds a definition object to the cache + * @param HTMLPurifier_Definition $def + * @param HTMLPurifier_Config $config + */ + abstract public function add($def, $config); + + /** + * Unconditionally saves a definition object to the cache + * @param HTMLPurifier_Definition $def + * @param HTMLPurifier_Config $config + */ + abstract public function set($def, $config); + + /** + * Replace an object in the cache + * @param HTMLPurifier_Definition $def + * @param HTMLPurifier_Config $config + */ + abstract public function replace($def, $config); + + /** + * Retrieves a definition object from the cache + * @param HTMLPurifier_Config $config + */ + abstract public function get($config); + + /** + * Removes a definition object to the cache + * @param HTMLPurifier_Config $config + */ + abstract public function remove($config); + + /** + * Clears all objects from cache + * @param HTMLPurifier_Config $config + */ + abstract public function flush($config); + + /** + * Clears all expired (older version or revision) objects from cache + * @note Be careful implementing this method as flush. Flush must + * not interfere with other Definition types, and cleanup() + * should not be repeatedly called by userland code. + * @param HTMLPurifier_Config $config + */ + abstract public function cleanup($config); +} + + + + + +/** + * Responsible for creating definition caches. + */ +class HTMLPurifier_DefinitionCacheFactory +{ + /** + * @type array + */ + protected $caches = array('Serializer' => array()); + + /** + * @type array + */ + protected $implementations = array(); + + /** + * @type HTMLPurifier_DefinitionCache_Decorator[] + */ + protected $decorators = array(); + + /** + * Initialize default decorators + */ + public function setup() + { + $this->addDecorator('Cleanup'); + } + + /** + * Retrieves an instance of global definition cache factory. + * @param HTMLPurifier_DefinitionCacheFactory $prototype + * @return HTMLPurifier_DefinitionCacheFactory + */ + public static function instance($prototype = null) + { + static $instance; + if ($prototype !== null) { + $instance = $prototype; + } elseif ($instance === null || $prototype === true) { + $instance = new HTMLPurifier_DefinitionCacheFactory(); + $instance->setup(); + } + return $instance; + } + + /** + * Registers a new definition cache object + * @param string $short Short name of cache object, for reference + * @param string $long Full class name of cache object, for construction + */ + public function register($short, $long) + { + $this->implementations[$short] = $long; + } + + /** + * Factory method that creates a cache object based on configuration + * @param string $type Name of definitions handled by cache + * @param HTMLPurifier_Config $config Config instance + * @return mixed + */ + public function create($type, $config) + { + $method = $config->get('Cache.DefinitionImpl'); + if ($method === null) { + return new HTMLPurifier_DefinitionCache_Null($type); + } + if (!empty($this->caches[$method][$type])) { + return $this->caches[$method][$type]; + } + if (isset($this->implementations[$method]) && + class_exists($class = $this->implementations[$method], false)) { + $cache = new $class($type); + } else { + if ($method != 'Serializer') { + trigger_error("Unrecognized DefinitionCache $method, using Serializer instead", E_USER_WARNING); + } + $cache = new HTMLPurifier_DefinitionCache_Serializer($type); + } + foreach ($this->decorators as $decorator) { + $new_cache = $decorator->decorate($cache); + // prevent infinite recursion in PHP 4 + unset($cache); + $cache = $new_cache; + } + $this->caches[$method][$type] = $cache; + return $this->caches[$method][$type]; + } + + /** + * Registers a decorator to add to all new cache objects + * @param HTMLPurifier_DefinitionCache_Decorator|string $decorator An instance or the name of a decorator + */ + public function addDecorator($decorator) + { + if (is_string($decorator)) { + $class = "HTMLPurifier_DefinitionCache_Decorator_$decorator"; + $decorator = new $class; + } + $this->decorators[$decorator->name] = $decorator; + } +} + + + + + +/** + * Represents a document type, contains information on which modules + * need to be loaded. + * @note This class is inspected by Printer_HTMLDefinition->renderDoctype. + * If structure changes, please update that function. + */ +class HTMLPurifier_Doctype +{ + /** + * Full name of doctype + * @type string + */ + public $name; + + /** + * List of standard modules (string identifiers or literal objects) + * that this doctype uses + * @type array + */ + public $modules = array(); + + /** + * List of modules to use for tidying up code + * @type array + */ + public $tidyModules = array(); + + /** + * Is the language derived from XML (i.e. XHTML)? + * @type bool + */ + public $xml = true; + + /** + * List of aliases for this doctype + * @type array + */ + public $aliases = array(); + + /** + * Public DTD identifier + * @type string + */ + public $dtdPublic; + + /** + * System DTD identifier + * @type string + */ + public $dtdSystem; + + public function __construct( + $name = null, + $xml = true, + $modules = array(), + $tidyModules = array(), + $aliases = array(), + $dtd_public = null, + $dtd_system = null + ) { + $this->name = $name; + $this->xml = $xml; + $this->modules = $modules; + $this->tidyModules = $tidyModules; + $this->aliases = $aliases; + $this->dtdPublic = $dtd_public; + $this->dtdSystem = $dtd_system; + } +} + + + + + +class HTMLPurifier_DoctypeRegistry +{ + + /** + * Hash of doctype names to doctype objects. + * @type array + */ + protected $doctypes; + + /** + * Lookup table of aliases to real doctype names. + * @type array + */ + protected $aliases; + + /** + * Registers a doctype to the registry + * @note Accepts a fully-formed doctype object, or the + * parameters for constructing a doctype object + * @param string $doctype Name of doctype or literal doctype object + * @param bool $xml + * @param array $modules Modules doctype will load + * @param array $tidy_modules Modules doctype will load for certain modes + * @param array $aliases Alias names for doctype + * @param string $dtd_public + * @param string $dtd_system + * @return HTMLPurifier_Doctype Editable registered doctype + */ + public function register( + $doctype, + $xml = true, + $modules = array(), + $tidy_modules = array(), + $aliases = array(), + $dtd_public = null, + $dtd_system = null + ) { + if (!is_array($modules)) { + $modules = array($modules); + } + if (!is_array($tidy_modules)) { + $tidy_modules = array($tidy_modules); + } + if (!is_array($aliases)) { + $aliases = array($aliases); + } + if (!is_object($doctype)) { + $doctype = new HTMLPurifier_Doctype( + $doctype, + $xml, + $modules, + $tidy_modules, + $aliases, + $dtd_public, + $dtd_system + ); + } + $this->doctypes[$doctype->name] = $doctype; + $name = $doctype->name; + // hookup aliases + foreach ($doctype->aliases as $alias) { + if (isset($this->doctypes[$alias])) { + continue; + } + $this->aliases[$alias] = $name; + } + // remove old aliases + if (isset($this->aliases[$name])) { + unset($this->aliases[$name]); + } + return $doctype; + } + + /** + * Retrieves reference to a doctype of a certain name + * @note This function resolves aliases + * @note When possible, use the more fully-featured make() + * @param string $doctype Name of doctype + * @return HTMLPurifier_Doctype Editable doctype object + */ + public function get($doctype) + { + if (isset($this->aliases[$doctype])) { + $doctype = $this->aliases[$doctype]; + } + if (!isset($this->doctypes[$doctype])) { + trigger_error('Doctype ' . htmlspecialchars($doctype) . ' does not exist', E_USER_ERROR); + $anon = new HTMLPurifier_Doctype($doctype); + return $anon; + } + return $this->doctypes[$doctype]; + } + + /** + * Creates a doctype based on a configuration object, + * will perform initialization on the doctype + * @note Use this function to get a copy of doctype that config + * can hold on to (this is necessary in order to tell + * Generator whether or not the current document is XML + * based or not). + * @param HTMLPurifier_Config $config + * @return HTMLPurifier_Doctype + */ + public function make($config) + { + return clone $this->get($this->getDoctypeFromConfig($config)); + } + + /** + * Retrieves the doctype from the configuration object + * @param HTMLPurifier_Config $config + * @return string + */ + public function getDoctypeFromConfig($config) + { + // recommended test + $doctype = $config->get('HTML.Doctype'); + if (!empty($doctype)) { + return $doctype; + } + $doctype = $config->get('HTML.CustomDoctype'); + if (!empty($doctype)) { + return $doctype; + } + // backwards-compatibility + if ($config->get('HTML.XHTML')) { + $doctype = 'XHTML 1.0'; + } else { + $doctype = 'HTML 4.01'; + } + if ($config->get('HTML.Strict')) { + $doctype .= ' Strict'; + } else { + $doctype .= ' Transitional'; + } + return $doctype; + } +} + + + + + +/** + * Structure that stores an HTML element definition. Used by + * HTMLPurifier_HTMLDefinition and HTMLPurifier_HTMLModule. + * @note This class is inspected by HTMLPurifier_Printer_HTMLDefinition. + * Please update that class too. + * @warning If you add new properties to this class, you MUST update + * the mergeIn() method. + */ +class HTMLPurifier_ElementDef +{ + /** + * Does the definition work by itself, or is it created solely + * for the purpose of merging into another definition? + * @type bool + */ + public $standalone = true; + + /** + * Associative array of attribute name to HTMLPurifier_AttrDef. + * @type array + * @note Before being processed by HTMLPurifier_AttrCollections + * when modules are finalized during + * HTMLPurifier_HTMLDefinition->setup(), this array may also + * contain an array at index 0 that indicates which attribute + * collections to load into the full array. It may also + * contain string indentifiers in lieu of HTMLPurifier_AttrDef, + * see HTMLPurifier_AttrTypes on how they are expanded during + * HTMLPurifier_HTMLDefinition->setup() processing. + */ + public $attr = array(); + + // XXX: Design note: currently, it's not possible to override + // previously defined AttrTransforms without messing around with + // the final generated config. This is by design; a previous version + // used an associated list of attr_transform, but it was extremely + // easy to accidentally override other attribute transforms by + // forgetting to specify an index (and just using 0.) While we + // could check this by checking the index number and complaining, + // there is a second problem which is that it is not at all easy to + // tell when something is getting overridden. Combine this with a + // codebase where this isn't really being used, and it's perfect for + // nuking. + + /** + * List of tags HTMLPurifier_AttrTransform to be done before validation. + * @type array + */ + public $attr_transform_pre = array(); + + /** + * List of tags HTMLPurifier_AttrTransform to be done after validation. + * @type array + */ + public $attr_transform_post = array(); + + /** + * HTMLPurifier_ChildDef of this tag. + * @type HTMLPurifier_ChildDef + */ + public $child; + + /** + * Abstract string representation of internal ChildDef rules. + * @see HTMLPurifier_ContentSets for how this is parsed and then transformed + * into an HTMLPurifier_ChildDef. + * @warning This is a temporary variable that is not available after + * being processed by HTMLDefinition + * @type string + */ + public $content_model; + + /** + * Value of $child->type, used to determine which ChildDef to use, + * used in combination with $content_model. + * @warning This must be lowercase + * @warning This is a temporary variable that is not available after + * being processed by HTMLDefinition + * @type string + */ + public $content_model_type; + + /** + * Does the element have a content model (#PCDATA | Inline)*? This + * is important for chameleon ins and del processing in + * HTMLPurifier_ChildDef_Chameleon. Dynamically set: modules don't + * have to worry about this one. + * @type bool + */ + public $descendants_are_inline = false; + + /** + * List of the names of required attributes this element has. + * Dynamically populated by HTMLPurifier_HTMLDefinition::getElement() + * @type array + */ + public $required_attr = array(); + + /** + * Lookup table of tags excluded from all descendants of this tag. + * @type array + * @note SGML permits exclusions for all descendants, but this is + * not possible with DTDs or XML Schemas. W3C has elected to + * use complicated compositions of content_models to simulate + * exclusion for children, but we go the simpler, SGML-style + * route of flat-out exclusions, which correctly apply to + * all descendants and not just children. Note that the XHTML + * Modularization Abstract Modules are blithely unaware of such + * distinctions. + */ + public $excludes = array(); + + /** + * This tag is explicitly auto-closed by the following tags. + * @type array + */ + public $autoclose = array(); + + /** + * If a foreign element is found in this element, test if it is + * allowed by this sub-element; if it is, instead of closing the + * current element, place it inside this element. + * @type string + */ + public $wrap; + + /** + * Whether or not this is a formatting element affected by the + * "Active Formatting Elements" algorithm. + * @type bool + */ + public $formatting; + + /** + * Low-level factory constructor for creating new standalone element defs + */ + public static function create($content_model, $content_model_type, $attr) + { + $def = new HTMLPurifier_ElementDef(); + $def->content_model = $content_model; + $def->content_model_type = $content_model_type; + $def->attr = $attr; + return $def; + } + + /** + * Merges the values of another element definition into this one. + * Values from the new element def take precedence if a value is + * not mergeable. + * @param HTMLPurifier_ElementDef $def + */ + public function mergeIn($def) + { + // later keys takes precedence + foreach ($def->attr as $k => $v) { + if ($k === 0) { + // merge in the includes + // sorry, no way to override an include + foreach ($v as $v2) { + $this->attr[0][] = $v2; + } + continue; + } + if ($v === false) { + if (isset($this->attr[$k])) { + unset($this->attr[$k]); + } + continue; + } + $this->attr[$k] = $v; + } + $this->_mergeAssocArray($this->excludes, $def->excludes); + $this->attr_transform_pre = array_merge($this->attr_transform_pre, $def->attr_transform_pre); + $this->attr_transform_post = array_merge($this->attr_transform_post, $def->attr_transform_post); + + if (!empty($def->content_model)) { + $this->content_model = + str_replace("#SUPER", $this->content_model, $def->content_model); + $this->child = false; + } + if (!empty($def->content_model_type)) { + $this->content_model_type = $def->content_model_type; + $this->child = false; + } + if (!is_null($def->child)) { + $this->child = $def->child; + } + if (!is_null($def->formatting)) { + $this->formatting = $def->formatting; + } + if ($def->descendants_are_inline) { + $this->descendants_are_inline = $def->descendants_are_inline; + } + } + + /** + * Merges one array into another, removes values which equal false + * @param $a1 Array by reference that is merged into + * @param $a2 Array that merges into $a1 + */ + private function _mergeAssocArray(&$a1, $a2) + { + foreach ($a2 as $k => $v) { + if ($v === false) { + if (isset($a1[$k])) { + unset($a1[$k]); + } + continue; + } + $a1[$k] = $v; + } + } +} + + + + + +/** + * A UTF-8 specific character encoder that handles cleaning and transforming. + * @note All functions in this class should be static. + */ +class HTMLPurifier_Encoder +{ + + /** + * Constructor throws fatal error if you attempt to instantiate class + */ + private function __construct() + { + trigger_error('Cannot instantiate encoder, call methods statically', E_USER_ERROR); + } + + /** + * Error-handler that mutes errors, alternative to shut-up operator. + */ + public static function muteErrorHandler() + { + } + + /** + * iconv wrapper which mutes errors, but doesn't work around bugs. + * @param string $in Input encoding + * @param string $out Output encoding + * @param string $text The text to convert + * @return string + */ + public static function unsafeIconv($in, $out, $text) + { + set_error_handler(array('HTMLPurifier_Encoder', 'muteErrorHandler')); + $r = iconv($in, $out, $text); + restore_error_handler(); + return $r; + } + + /** + * iconv wrapper which mutes errors and works around bugs. + * @param string $in Input encoding + * @param string $out Output encoding + * @param string $text The text to convert + * @param int $max_chunk_size + * @return string + */ + public static function iconv($in, $out, $text, $max_chunk_size = 8000) + { + $code = self::testIconvTruncateBug(); + if ($code == self::ICONV_OK) { + return self::unsafeIconv($in, $out, $text); + } elseif ($code == self::ICONV_TRUNCATES) { + // we can only work around this if the input character set + // is utf-8 + if ($in == 'utf-8') { + if ($max_chunk_size < 4) { + trigger_error('max_chunk_size is too small', E_USER_WARNING); + return false; + } + // split into 8000 byte chunks, but be careful to handle + // multibyte boundaries properly + if (($c = strlen($text)) <= $max_chunk_size) { + return self::unsafeIconv($in, $out, $text); + } + $r = ''; + $i = 0; + while (true) { + if ($i + $max_chunk_size >= $c) { + $r .= self::unsafeIconv($in, $out, substr($text, $i)); + break; + } + // wibble the boundary + if (0x80 != (0xC0 & ord($text[$i + $max_chunk_size]))) { + $chunk_size = $max_chunk_size; + } elseif (0x80 != (0xC0 & ord($text[$i + $max_chunk_size - 1]))) { + $chunk_size = $max_chunk_size - 1; + } elseif (0x80 != (0xC0 & ord($text[$i + $max_chunk_size - 2]))) { + $chunk_size = $max_chunk_size - 2; + } elseif (0x80 != (0xC0 & ord($text[$i + $max_chunk_size - 3]))) { + $chunk_size = $max_chunk_size - 3; + } else { + return false; // rather confusing UTF-8... + } + $chunk = substr($text, $i, $chunk_size); // substr doesn't mind overlong lengths + $r .= self::unsafeIconv($in, $out, $chunk); + $i += $chunk_size; + } + return $r; + } else { + return false; + } + } else { + return false; + } + } + + /** + * Cleans a UTF-8 string for well-formedness and SGML validity + * + * It will parse according to UTF-8 and return a valid UTF8 string, with + * non-SGML codepoints excluded. + * + * Specifically, it will permit: + * \x{9}\x{A}\x{D}\x{20}-\x{7E}\x{A0}-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF} + * Source: https://www.w3.org/TR/REC-xml/#NT-Char + * Arguably this function should be modernized to the HTML5 set + * of allowed characters: + * https://www.w3.org/TR/html5/syntax.html#preprocessing-the-input-stream + * which simultaneously expand and restrict the set of allowed characters. + * + * @param string $str The string to clean + * @param bool $force_php + * @return string + * + * @note Just for reference, the non-SGML code points are 0 to 31 and + * 127 to 159, inclusive. However, we allow code points 9, 10 + * and 13, which are the tab, line feed and carriage return + * respectively. 128 and above the code points map to multibyte + * UTF-8 representations. + * + * @note Fallback code adapted from utf8ToUnicode by Henri Sivonen and + * hsivonen@iki.fi at under the + * LGPL license. Notes on what changed are inside, but in general, + * the original code transformed UTF-8 text into an array of integer + * Unicode codepoints. Understandably, transforming that back to + * a string would be somewhat expensive, so the function was modded to + * directly operate on the string. However, this discourages code + * reuse, and the logic enumerated here would be useful for any + * function that needs to be able to understand UTF-8 characters. + * As of right now, only smart lossless character encoding converters + * would need that, and I'm probably not going to implement them. + */ + public static function cleanUTF8($str, $force_php = false) + { + // UTF-8 validity is checked since PHP 4.3.5 + // This is an optimization: if the string is already valid UTF-8, no + // need to do PHP stuff. 99% of the time, this will be the case. + if (preg_match( + '/^[\x{9}\x{A}\x{D}\x{20}-\x{7E}\x{A0}-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}]*$/Du', + $str + )) { + return $str; + } + + $mState = 0; // cached expected number of octets after the current octet + // until the beginning of the next UTF8 character sequence + $mUcs4 = 0; // cached Unicode character + $mBytes = 1; // cached expected number of octets in the current sequence + + // original code involved an $out that was an array of Unicode + // codepoints. Instead of having to convert back into UTF-8, we've + // decided to directly append valid UTF-8 characters onto a string + // $out once they're done. $char accumulates raw bytes, while $mUcs4 + // turns into the Unicode code point, so there's some redundancy. + + $out = ''; + $char = ''; + + $len = strlen($str); + for ($i = 0; $i < $len; $i++) { + $in = ord($str[$i]); + $char .= $str[$i]; // append byte to char + if (0 == $mState) { + // When mState is zero we expect either a US-ASCII character + // or a multi-octet sequence. + if (0 == (0x80 & ($in))) { + // US-ASCII, pass straight through. + if (($in <= 31 || $in == 127) && + !($in == 9 || $in == 13 || $in == 10) // save \r\t\n + ) { + // control characters, remove + } else { + $out .= $char; + } + // reset + $char = ''; + $mBytes = 1; + } elseif (0xC0 == (0xE0 & ($in))) { + // First octet of 2 octet sequence + $mUcs4 = ($in); + $mUcs4 = ($mUcs4 & 0x1F) << 6; + $mState = 1; + $mBytes = 2; + } elseif (0xE0 == (0xF0 & ($in))) { + // First octet of 3 octet sequence + $mUcs4 = ($in); + $mUcs4 = ($mUcs4 & 0x0F) << 12; + $mState = 2; + $mBytes = 3; + } elseif (0xF0 == (0xF8 & ($in))) { + // First octet of 4 octet sequence + $mUcs4 = ($in); + $mUcs4 = ($mUcs4 & 0x07) << 18; + $mState = 3; + $mBytes = 4; + } elseif (0xF8 == (0xFC & ($in))) { + // First octet of 5 octet sequence. + // + // This is illegal because the encoded codepoint must be + // either: + // (a) not the shortest form or + // (b) outside the Unicode range of 0-0x10FFFF. + // Rather than trying to resynchronize, we will carry on + // until the end of the sequence and let the later error + // handling code catch it. + $mUcs4 = ($in); + $mUcs4 = ($mUcs4 & 0x03) << 24; + $mState = 4; + $mBytes = 5; + } elseif (0xFC == (0xFE & ($in))) { + // First octet of 6 octet sequence, see comments for 5 + // octet sequence. + $mUcs4 = ($in); + $mUcs4 = ($mUcs4 & 1) << 30; + $mState = 5; + $mBytes = 6; + } else { + // Current octet is neither in the US-ASCII range nor a + // legal first octet of a multi-octet sequence. + $mState = 0; + $mUcs4 = 0; + $mBytes = 1; + $char = ''; + } + } else { + // When mState is non-zero, we expect a continuation of the + // multi-octet sequence + if (0x80 == (0xC0 & ($in))) { + // Legal continuation. + $shift = ($mState - 1) * 6; + $tmp = $in; + $tmp = ($tmp & 0x0000003F) << $shift; + $mUcs4 |= $tmp; + + if (0 == --$mState) { + // End of the multi-octet sequence. mUcs4 now contains + // the final Unicode codepoint to be output + + // Check for illegal sequences and codepoints. + + // From Unicode 3.1, non-shortest form is illegal + if (((2 == $mBytes) && ($mUcs4 < 0x0080)) || + ((3 == $mBytes) && ($mUcs4 < 0x0800)) || + ((4 == $mBytes) && ($mUcs4 < 0x10000)) || + (4 < $mBytes) || + // From Unicode 3.2, surrogate characters = illegal + (($mUcs4 & 0xFFFFF800) == 0xD800) || + // Codepoints outside the Unicode range are illegal + ($mUcs4 > 0x10FFFF) + ) { + + } elseif (0xFEFF != $mUcs4 && // omit BOM + // check for valid Char unicode codepoints + ( + 0x9 == $mUcs4 || + 0xA == $mUcs4 || + 0xD == $mUcs4 || + (0x20 <= $mUcs4 && 0x7E >= $mUcs4) || + // 7F-9F is not strictly prohibited by XML, + // but it is non-SGML, and thus we don't allow it + (0xA0 <= $mUcs4 && 0xD7FF >= $mUcs4) || + (0xE000 <= $mUcs4 && 0xFFFD >= $mUcs4) || + (0x10000 <= $mUcs4 && 0x10FFFF >= $mUcs4) + ) + ) { + $out .= $char; + } + // initialize UTF8 cache (reset) + $mState = 0; + $mUcs4 = 0; + $mBytes = 1; + $char = ''; + } + } else { + // ((0xC0 & (*in) != 0x80) && (mState != 0)) + // Incomplete multi-octet sequence. + // used to result in complete fail, but we'll reset + $mState = 0; + $mUcs4 = 0; + $mBytes = 1; + $char =''; + } + } + } + return $out; + } + + /** + * Translates a Unicode codepoint into its corresponding UTF-8 character. + * @note Based on Feyd's function at + * , + * which is in public domain. + * @note While we're going to do code point parsing anyway, a good + * optimization would be to refuse to translate code points that + * are non-SGML characters. However, this could lead to duplication. + * @note This is very similar to the unichr function in + * maintenance/generate-entity-file.php (although this is superior, + * due to its sanity checks). + */ + + // +----------+----------+----------+----------+ + // | 33222222 | 22221111 | 111111 | | + // | 10987654 | 32109876 | 54321098 | 76543210 | bit + // +----------+----------+----------+----------+ + // | | | | 0xxxxxxx | 1 byte 0x00000000..0x0000007F + // | | | 110yyyyy | 10xxxxxx | 2 byte 0x00000080..0x000007FF + // | | 1110zzzz | 10yyyyyy | 10xxxxxx | 3 byte 0x00000800..0x0000FFFF + // | 11110www | 10wwzzzz | 10yyyyyy | 10xxxxxx | 4 byte 0x00010000..0x0010FFFF + // +----------+----------+----------+----------+ + // | 00000000 | 00011111 | 11111111 | 11111111 | Theoretical upper limit of legal scalars: 2097151 (0x001FFFFF) + // | 00000000 | 00010000 | 11111111 | 11111111 | Defined upper limit of legal scalar codes + // +----------+----------+----------+----------+ + + public static function unichr($code) + { + if ($code > 1114111 or $code < 0 or + ($code >= 55296 and $code <= 57343) ) { + // bits are set outside the "valid" range as defined + // by UNICODE 4.1.0 + return ''; + } + + $x = $y = $z = $w = 0; + if ($code < 128) { + // regular ASCII character + $x = $code; + } else { + // set up bits for UTF-8 + $x = ($code & 63) | 128; + if ($code < 2048) { + $y = (($code & 2047) >> 6) | 192; + } else { + $y = (($code & 4032) >> 6) | 128; + if ($code < 65536) { + $z = (($code >> 12) & 15) | 224; + } else { + $z = (($code >> 12) & 63) | 128; + $w = (($code >> 18) & 7) | 240; + } + } + } + // set up the actual character + $ret = ''; + if ($w) { + $ret .= chr($w); + } + if ($z) { + $ret .= chr($z); + } + if ($y) { + $ret .= chr($y); + } + $ret .= chr($x); + + return $ret; + } + + /** + * @return bool + */ + public static function iconvAvailable() + { + static $iconv = null; + if ($iconv === null) { + $iconv = function_exists('iconv') && self::testIconvTruncateBug() != self::ICONV_UNUSABLE; + } + return $iconv; + } + + /** + * Convert a string to UTF-8 based on configuration. + * @param string $str The string to convert + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return string + */ + public static function convertToUTF8($str, $config, $context) + { + $encoding = $config->get('Core.Encoding'); + if ($encoding === 'utf-8') { + return $str; + } + static $iconv = null; + if ($iconv === null) { + $iconv = self::iconvAvailable(); + } + if ($iconv && !$config->get('Test.ForceNoIconv')) { + // unaffected by bugs, since UTF-8 support all characters + $str = self::unsafeIconv($encoding, 'utf-8//IGNORE', $str); + if ($str === false) { + // $encoding is not a valid encoding + trigger_error('Invalid encoding ' . $encoding, E_USER_ERROR); + return ''; + } + // If the string is bjorked by Shift_JIS or a similar encoding + // that doesn't support all of ASCII, convert the naughty + // characters to their true byte-wise ASCII/UTF-8 equivalents. + $str = strtr($str, self::testEncodingSupportsASCII($encoding)); + return $str; + } elseif ($encoding === 'iso-8859-1') { + $str = utf8_encode($str); + return $str; + } + $bug = HTMLPurifier_Encoder::testIconvTruncateBug(); + if ($bug == self::ICONV_OK) { + trigger_error('Encoding not supported, please install iconv', E_USER_ERROR); + } else { + trigger_error( + 'You have a buggy version of iconv, see https://bugs.php.net/bug.php?id=48147 ' . + 'and http://sourceware.org/bugzilla/show_bug.cgi?id=13541', + E_USER_ERROR + ); + } + } + + /** + * Converts a string from UTF-8 based on configuration. + * @param string $str The string to convert + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return string + * @note Currently, this is a lossy conversion, with unexpressable + * characters being omitted. + */ + public static function convertFromUTF8($str, $config, $context) + { + $encoding = $config->get('Core.Encoding'); + if ($escape = $config->get('Core.EscapeNonASCIICharacters')) { + $str = self::convertToASCIIDumbLossless($str); + } + if ($encoding === 'utf-8') { + return $str; + } + static $iconv = null; + if ($iconv === null) { + $iconv = self::iconvAvailable(); + } + if ($iconv && !$config->get('Test.ForceNoIconv')) { + // Undo our previous fix in convertToUTF8, otherwise iconv will barf + $ascii_fix = self::testEncodingSupportsASCII($encoding); + if (!$escape && !empty($ascii_fix)) { + $clear_fix = array(); + foreach ($ascii_fix as $utf8 => $native) { + $clear_fix[$utf8] = ''; + } + $str = strtr($str, $clear_fix); + } + $str = strtr($str, array_flip($ascii_fix)); + // Normal stuff + $str = self::iconv('utf-8', $encoding . '//IGNORE', $str); + return $str; + } elseif ($encoding === 'iso-8859-1') { + $str = utf8_decode($str); + return $str; + } + trigger_error('Encoding not supported', E_USER_ERROR); + // You might be tempted to assume that the ASCII representation + // might be OK, however, this is *not* universally true over all + // encodings. So we take the conservative route here, rather + // than forcibly turn on %Core.EscapeNonASCIICharacters + } + + /** + * Lossless (character-wise) conversion of HTML to ASCII + * @param string $str UTF-8 string to be converted to ASCII + * @return string ASCII encoded string with non-ASCII character entity-ized + * @warning Adapted from MediaWiki, claiming fair use: this is a common + * algorithm. If you disagree with this license fudgery, + * implement it yourself. + * @note Uses decimal numeric entities since they are best supported. + * @note This is a DUMB function: it has no concept of keeping + * character entities that the projected character encoding + * can allow. We could possibly implement a smart version + * but that would require it to also know which Unicode + * codepoints the charset supported (not an easy task). + * @note Sort of with cleanUTF8() but it assumes that $str is + * well-formed UTF-8 + */ + public static function convertToASCIIDumbLossless($str) + { + $bytesleft = 0; + $result = ''; + $working = 0; + $len = strlen($str); + for ($i = 0; $i < $len; $i++) { + $bytevalue = ord($str[$i]); + if ($bytevalue <= 0x7F) { //0xxx xxxx + $result .= chr($bytevalue); + $bytesleft = 0; + } elseif ($bytevalue <= 0xBF) { //10xx xxxx + $working = $working << 6; + $working += ($bytevalue & 0x3F); + $bytesleft--; + if ($bytesleft <= 0) { + $result .= "&#" . $working . ";"; + } + } elseif ($bytevalue <= 0xDF) { //110x xxxx + $working = $bytevalue & 0x1F; + $bytesleft = 1; + } elseif ($bytevalue <= 0xEF) { //1110 xxxx + $working = $bytevalue & 0x0F; + $bytesleft = 2; + } else { //1111 0xxx + $working = $bytevalue & 0x07; + $bytesleft = 3; + } + } + return $result; + } + + /** No bugs detected in iconv. */ + const ICONV_OK = 0; + + /** Iconv truncates output if converting from UTF-8 to another + * character set with //IGNORE, and a non-encodable character is found */ + const ICONV_TRUNCATES = 1; + + /** Iconv does not support //IGNORE, making it unusable for + * transcoding purposes */ + const ICONV_UNUSABLE = 2; + + /** + * glibc iconv has a known bug where it doesn't handle the magic + * //IGNORE stanza correctly. In particular, rather than ignore + * characters, it will return an EILSEQ after consuming some number + * of characters, and expect you to restart iconv as if it were + * an E2BIG. Old versions of PHP did not respect the errno, and + * returned the fragment, so as a result you would see iconv + * mysteriously truncating output. We can work around this by + * manually chopping our input into segments of about 8000 + * characters, as long as PHP ignores the error code. If PHP starts + * paying attention to the error code, iconv becomes unusable. + * + * @return int Error code indicating severity of bug. + */ + public static function testIconvTruncateBug() + { + static $code = null; + if ($code === null) { + // better not use iconv, otherwise infinite loop! + $r = self::unsafeIconv('utf-8', 'ascii//IGNORE', "\xCE\xB1" . str_repeat('a', 9000)); + if ($r === false) { + $code = self::ICONV_UNUSABLE; + } elseif (($c = strlen($r)) < 9000) { + $code = self::ICONV_TRUNCATES; + } elseif ($c > 9000) { + trigger_error( + 'Your copy of iconv is extremely buggy. Please notify HTML Purifier maintainers: ' . + 'include your iconv version as per phpversion()', + E_USER_ERROR + ); + } else { + $code = self::ICONV_OK; + } + } + return $code; + } + + /** + * This expensive function tests whether or not a given character + * encoding supports ASCII. 7/8-bit encodings like Shift_JIS will + * fail this test, and require special processing. Variable width + * encodings shouldn't ever fail. + * + * @param string $encoding Encoding name to test, as per iconv format + * @param bool $bypass Whether or not to bypass the precompiled arrays. + * @return Array of UTF-8 characters to their corresponding ASCII, + * which can be used to "undo" any overzealous iconv action. + */ + public static function testEncodingSupportsASCII($encoding, $bypass = false) + { + // All calls to iconv here are unsafe, proof by case analysis: + // If ICONV_OK, no difference. + // If ICONV_TRUNCATE, all calls involve one character inputs, + // so bug is not triggered. + // If ICONV_UNUSABLE, this call is irrelevant + static $encodings = array(); + if (!$bypass) { + if (isset($encodings[$encoding])) { + return $encodings[$encoding]; + } + $lenc = strtolower($encoding); + switch ($lenc) { + case 'shift_jis': + return array("\xC2\xA5" => '\\', "\xE2\x80\xBE" => '~'); + case 'johab': + return array("\xE2\x82\xA9" => '\\'); + } + if (strpos($lenc, 'iso-8859-') === 0) { + return array(); + } + } + $ret = array(); + if (self::unsafeIconv('UTF-8', $encoding, 'a') === false) { + return false; + } + for ($i = 0x20; $i <= 0x7E; $i++) { // all printable ASCII chars + $c = chr($i); // UTF-8 char + $r = self::unsafeIconv('UTF-8', "$encoding//IGNORE", $c); // initial conversion + if ($r === '' || + // This line is needed for iconv implementations that do not + // omit characters that do not exist in the target character set + ($r === $c && self::unsafeIconv($encoding, 'UTF-8//IGNORE', $r) !== $c) + ) { + // Reverse engineer: what's the UTF-8 equiv of this byte + // sequence? This assumes that there's no variable width + // encoding that doesn't support ASCII. + $ret[self::unsafeIconv($encoding, 'UTF-8//IGNORE', $c)] = $c; + } + } + $encodings[$encoding] = $ret; + return $ret; + } +} + + + + + +/** + * Object that provides entity lookup table from entity name to character + */ +class HTMLPurifier_EntityLookup +{ + /** + * Assoc array of entity name to character represented. + * @type array + */ + public $table; + + /** + * Sets up the entity lookup table from the serialized file contents. + * @param bool $file + * @note The serialized contents are versioned, but were generated + * using the maintenance script generate_entity_file.php + * @warning This is not in constructor to help enforce the Singleton + */ + public function setup($file = false) + { + if (!$file) { + $file = HTMLPURIFIER_PREFIX . '/HTMLPurifier/EntityLookup/entities.ser'; + } + $this->table = unserialize(file_get_contents($file)); + } + + /** + * Retrieves sole instance of the object. + * @param bool|HTMLPurifier_EntityLookup $prototype Optional prototype of custom lookup table to overload with. + * @return HTMLPurifier_EntityLookup + */ + public static function instance($prototype = false) + { + // no references, since PHP doesn't copy unless modified + static $instance = null; + if ($prototype) { + $instance = $prototype; + } elseif (!$instance) { + $instance = new HTMLPurifier_EntityLookup(); + $instance->setup(); + } + return $instance; + } +} + + + + + +// if want to implement error collecting here, we'll need to use some sort +// of global data (probably trigger_error) because it's impossible to pass +// $config or $context to the callback functions. + +/** + * Handles referencing and derefencing character entities + */ +class HTMLPurifier_EntityParser +{ + + /** + * Reference to entity lookup table. + * @type HTMLPurifier_EntityLookup + */ + protected $_entity_lookup; + + /** + * Callback regex string for entities in text. + * @type string + */ + protected $_textEntitiesRegex; + + /** + * Callback regex string for entities in attributes. + * @type string + */ + protected $_attrEntitiesRegex; + + /** + * Tests if the beginning of a string is a semi-optional regex + */ + protected $_semiOptionalPrefixRegex; + + public function __construct() { + // From + // http://stackoverflow.com/questions/15532252/why-is-reg-being-rendered-as-without-the-bounding-semicolon + $semi_optional = "quot|QUOT|lt|LT|gt|GT|amp|AMP|AElig|Aacute|Acirc|Agrave|Aring|Atilde|Auml|COPY|Ccedil|ETH|Eacute|Ecirc|Egrave|Euml|Iacute|Icirc|Igrave|Iuml|Ntilde|Oacute|Ocirc|Ograve|Oslash|Otilde|Ouml|REG|THORN|Uacute|Ucirc|Ugrave|Uuml|Yacute|aacute|acirc|acute|aelig|agrave|aring|atilde|auml|brvbar|ccedil|cedil|cent|copy|curren|deg|divide|eacute|ecirc|egrave|eth|euml|frac12|frac14|frac34|iacute|icirc|iexcl|igrave|iquest|iuml|laquo|macr|micro|middot|nbsp|not|ntilde|oacute|ocirc|ograve|ordf|ordm|oslash|otilde|ouml|para|plusmn|pound|raquo|reg|sect|shy|sup1|sup2|sup3|szlig|thorn|times|uacute|ucirc|ugrave|uml|uuml|yacute|yen|yuml"; + + // NB: three empty captures to put the fourth match in the right + // place + $this->_semiOptionalPrefixRegex = "/&()()()($semi_optional)/"; + + $this->_textEntitiesRegex = + '/&(?:'. + // hex + '[#]x([a-fA-F0-9]+);?|'. + // dec + '[#]0*(\d+);?|'. + // string (mandatory semicolon) + // NB: order matters: match semicolon preferentially + '([A-Za-z_:][A-Za-z0-9.\-_:]*);|'. + // string (optional semicolon) + "($semi_optional)". + ')/'; + + $this->_attrEntitiesRegex = + '/&(?:'. + // hex + '[#]x([a-fA-F0-9]+);?|'. + // dec + '[#]0*(\d+);?|'. + // string (mandatory semicolon) + // NB: order matters: match semicolon preferentially + '([A-Za-z_:][A-Za-z0-9.\-_:]*);|'. + // string (optional semicolon) + // don't match if trailing is equals or alphanumeric (URL + // like) + "($semi_optional)(?![=;A-Za-z0-9])". + ')/'; + + } + + /** + * Substitute entities with the parsed equivalents. Use this on + * textual data in an HTML document (as opposed to attributes.) + * + * @param string $string String to have entities parsed. + * @return string Parsed string. + */ + public function substituteTextEntities($string) + { + return preg_replace_callback( + $this->_textEntitiesRegex, + array($this, 'entityCallback'), + $string + ); + } + + /** + * Substitute entities with the parsed equivalents. Use this on + * attribute contents in documents. + * + * @param string $string String to have entities parsed. + * @return string Parsed string. + */ + public function substituteAttrEntities($string) + { + return preg_replace_callback( + $this->_attrEntitiesRegex, + array($this, 'entityCallback'), + $string + ); + } + + /** + * Callback function for substituteNonSpecialEntities() that does the work. + * + * @param array $matches PCRE matches array, with 0 the entire match, and + * either index 1, 2 or 3 set with a hex value, dec value, + * or string (respectively). + * @return string Replacement string. + */ + + protected function entityCallback($matches) + { + $entity = $matches[0]; + $hex_part = @$matches[1]; + $dec_part = @$matches[2]; + $named_part = empty($matches[3]) ? (empty($matches[4]) ? "" : $matches[4]) : $matches[3]; + if ($hex_part !== NULL && $hex_part !== "") { + return HTMLPurifier_Encoder::unichr(hexdec($hex_part)); + } elseif ($dec_part !== NULL && $dec_part !== "") { + return HTMLPurifier_Encoder::unichr((int) $dec_part); + } else { + if (!$this->_entity_lookup) { + $this->_entity_lookup = HTMLPurifier_EntityLookup::instance(); + } + if (isset($this->_entity_lookup->table[$named_part])) { + return $this->_entity_lookup->table[$named_part]; + } else { + // exact match didn't match anything, so test if + // any of the semicolon optional match the prefix. + // Test that this is an EXACT match is important to + // prevent infinite loop + if (!empty($matches[3])) { + return preg_replace_callback( + $this->_semiOptionalPrefixRegex, + array($this, 'entityCallback'), + $entity + ); + } + return $entity; + } + } + } + + // LEGACY CODE BELOW + + /** + * Callback regex string for parsing entities. + * @type string + */ + protected $_substituteEntitiesRegex = + '/&(?:[#]x([a-fA-F0-9]+)|[#]0*(\d+)|([A-Za-z_:][A-Za-z0-9.\-_:]*));?/'; + // 1. hex 2. dec 3. string (XML style) + + /** + * Decimal to parsed string conversion table for special entities. + * @type array + */ + protected $_special_dec2str = + array( + 34 => '"', + 38 => '&', + 39 => "'", + 60 => '<', + 62 => '>' + ); + + /** + * Stripped entity names to decimal conversion table for special entities. + * @type array + */ + protected $_special_ent2dec = + array( + 'quot' => 34, + 'amp' => 38, + 'lt' => 60, + 'gt' => 62 + ); + + /** + * Substitutes non-special entities with their parsed equivalents. Since + * running this whenever you have parsed character is t3h 5uck, we run + * it before everything else. + * + * @param string $string String to have non-special entities parsed. + * @return string Parsed string. + */ + public function substituteNonSpecialEntities($string) + { + // it will try to detect missing semicolons, but don't rely on it + return preg_replace_callback( + $this->_substituteEntitiesRegex, + array($this, 'nonSpecialEntityCallback'), + $string + ); + } + + /** + * Callback function for substituteNonSpecialEntities() that does the work. + * + * @param array $matches PCRE matches array, with 0 the entire match, and + * either index 1, 2 or 3 set with a hex value, dec value, + * or string (respectively). + * @return string Replacement string. + */ + + protected function nonSpecialEntityCallback($matches) + { + // replaces all but big five + $entity = $matches[0]; + $is_num = (@$matches[0][1] === '#'); + if ($is_num) { + $is_hex = (@$entity[2] === 'x'); + $code = $is_hex ? hexdec($matches[1]) : (int) $matches[2]; + // abort for special characters + if (isset($this->_special_dec2str[$code])) { + return $entity; + } + return HTMLPurifier_Encoder::unichr($code); + } else { + if (isset($this->_special_ent2dec[$matches[3]])) { + return $entity; + } + if (!$this->_entity_lookup) { + $this->_entity_lookup = HTMLPurifier_EntityLookup::instance(); + } + if (isset($this->_entity_lookup->table[$matches[3]])) { + return $this->_entity_lookup->table[$matches[3]]; + } else { + return $entity; + } + } + } + + /** + * Substitutes only special entities with their parsed equivalents. + * + * @notice We try to avoid calling this function because otherwise, it + * would have to be called a lot (for every parsed section). + * + * @param string $string String to have non-special entities parsed. + * @return string Parsed string. + */ + public function substituteSpecialEntities($string) + { + return preg_replace_callback( + $this->_substituteEntitiesRegex, + array($this, 'specialEntityCallback'), + $string + ); + } + + /** + * Callback function for substituteSpecialEntities() that does the work. + * + * This callback has same syntax as nonSpecialEntityCallback(). + * + * @param array $matches PCRE-style matches array, with 0 the entire match, and + * either index 1, 2 or 3 set with a hex value, dec value, + * or string (respectively). + * @return string Replacement string. + */ + protected function specialEntityCallback($matches) + { + $entity = $matches[0]; + $is_num = (@$matches[0][1] === '#'); + if ($is_num) { + $is_hex = (@$entity[2] === 'x'); + $int = $is_hex ? hexdec($matches[1]) : (int) $matches[2]; + return isset($this->_special_dec2str[$int]) ? + $this->_special_dec2str[$int] : + $entity; + } else { + return isset($this->_special_ent2dec[$matches[3]]) ? + $this->_special_dec2str[$this->_special_ent2dec[$matches[3]]] : + $entity; + } + } +} + + + + + +/** + * Error collection class that enables HTML Purifier to report HTML + * problems back to the user + */ +class HTMLPurifier_ErrorCollector +{ + + /** + * Identifiers for the returned error array. These are purposely numeric + * so list() can be used. + */ + const LINENO = 0; + const SEVERITY = 1; + const MESSAGE = 2; + const CHILDREN = 3; + + /** + * @type array + */ + protected $errors; + + /** + * @type array + */ + protected $_current; + + /** + * @type array + */ + protected $_stacks = array(array()); + + /** + * @type HTMLPurifier_Language + */ + protected $locale; + + /** + * @type HTMLPurifier_Generator + */ + protected $generator; + + /** + * @type HTMLPurifier_Context + */ + protected $context; + + /** + * @type array + */ + protected $lines = array(); + + /** + * @param HTMLPurifier_Context $context + */ + public function __construct($context) + { + $this->locale =& $context->get('Locale'); + $this->context = $context; + $this->_current =& $this->_stacks[0]; + $this->errors =& $this->_stacks[0]; + } + + /** + * Sends an error message to the collector for later use + * @param int $severity Error severity, PHP error style (don't use E_USER_) + * @param string $msg Error message text + */ + public function send($severity, $msg) + { + $args = array(); + if (func_num_args() > 2) { + $args = func_get_args(); + array_shift($args); + unset($args[0]); + } + + $token = $this->context->get('CurrentToken', true); + $line = $token ? $token->line : $this->context->get('CurrentLine', true); + $col = $token ? $token->col : $this->context->get('CurrentCol', true); + $attr = $this->context->get('CurrentAttr', true); + + // perform special substitutions, also add custom parameters + $subst = array(); + if (!is_null($token)) { + $args['CurrentToken'] = $token; + } + if (!is_null($attr)) { + $subst['$CurrentAttr.Name'] = $attr; + if (isset($token->attr[$attr])) { + $subst['$CurrentAttr.Value'] = $token->attr[$attr]; + } + } + + if (empty($args)) { + $msg = $this->locale->getMessage($msg); + } else { + $msg = $this->locale->formatMessage($msg, $args); + } + + if (!empty($subst)) { + $msg = strtr($msg, $subst); + } + + // (numerically indexed) + $error = array( + self::LINENO => $line, + self::SEVERITY => $severity, + self::MESSAGE => $msg, + self::CHILDREN => array() + ); + $this->_current[] = $error; + + // NEW CODE BELOW ... + // Top-level errors are either: + // TOKEN type, if $value is set appropriately, or + // "syntax" type, if $value is null + $new_struct = new HTMLPurifier_ErrorStruct(); + $new_struct->type = HTMLPurifier_ErrorStruct::TOKEN; + if ($token) { + $new_struct->value = clone $token; + } + if (is_int($line) && is_int($col)) { + if (isset($this->lines[$line][$col])) { + $struct = $this->lines[$line][$col]; + } else { + $struct = $this->lines[$line][$col] = $new_struct; + } + // These ksorts may present a performance problem + ksort($this->lines[$line], SORT_NUMERIC); + } else { + if (isset($this->lines[-1])) { + $struct = $this->lines[-1]; + } else { + $struct = $this->lines[-1] = $new_struct; + } + } + ksort($this->lines, SORT_NUMERIC); + + // Now, check if we need to operate on a lower structure + if (!empty($attr)) { + $struct = $struct->getChild(HTMLPurifier_ErrorStruct::ATTR, $attr); + if (!$struct->value) { + $struct->value = array($attr, 'PUT VALUE HERE'); + } + } + if (!empty($cssprop)) { + $struct = $struct->getChild(HTMLPurifier_ErrorStruct::CSSPROP, $cssprop); + if (!$struct->value) { + // if we tokenize CSS this might be a little more difficult to do + $struct->value = array($cssprop, 'PUT VALUE HERE'); + } + } + + // Ok, structs are all setup, now time to register the error + $struct->addError($severity, $msg); + } + + /** + * Retrieves raw error data for custom formatter to use + */ + public function getRaw() + { + return $this->errors; + } + + /** + * Default HTML formatting implementation for error messages + * @param HTMLPurifier_Config $config Configuration, vital for HTML output nature + * @param array $errors Errors array to display; used for recursion. + * @return string + */ + public function getHTMLFormatted($config, $errors = null) + { + $ret = array(); + + $this->generator = new HTMLPurifier_Generator($config, $this->context); + if ($errors === null) { + $errors = $this->errors; + } + + // 'At line' message needs to be removed + + // generation code for new structure goes here. It needs to be recursive. + foreach ($this->lines as $line => $col_array) { + if ($line == -1) { + continue; + } + foreach ($col_array as $col => $struct) { + $this->_renderStruct($ret, $struct, $line, $col); + } + } + if (isset($this->lines[-1])) { + $this->_renderStruct($ret, $this->lines[-1]); + } + + if (empty($errors)) { + return '

' . $this->locale->getMessage('ErrorCollector: No errors') . '

'; + } else { + return ''; + } + + } + + private function _renderStruct(&$ret, $struct, $line = null, $col = null) + { + $stack = array($struct); + $context_stack = array(array()); + while ($current = array_pop($stack)) { + $context = array_pop($context_stack); + foreach ($current->errors as $error) { + list($severity, $msg) = $error; + $string = ''; + $string .= '
'; + // W3C uses an icon to indicate the severity of the error. + $error = $this->locale->getErrorName($severity); + $string .= "$error "; + if (!is_null($line) && !is_null($col)) { + $string .= "Line $line, Column $col: "; + } else { + $string .= 'End of Document: '; + } + $string .= '' . $this->generator->escape($msg) . ' '; + $string .= '
'; + // Here, have a marker for the character on the column appropriate. + // Be sure to clip extremely long lines. + //$string .= '
';
+                //$string .= '';
+                //$string .= '
'; + $ret[] = $string; + } + foreach ($current->children as $array) { + $context[] = $current; + $stack = array_merge($stack, array_reverse($array, true)); + for ($i = count($array); $i > 0; $i--) { + $context_stack[] = $context; + } + } + } + } +} + + + + + +/** + * Records errors for particular segments of an HTML document such as tokens, + * attributes or CSS properties. They can contain error structs (which apply + * to components of what they represent), but their main purpose is to hold + * errors applying to whatever struct is being used. + */ +class HTMLPurifier_ErrorStruct +{ + + /** + * Possible values for $children first-key. Note that top-level structures + * are automatically token-level. + */ + const TOKEN = 0; + const ATTR = 1; + const CSSPROP = 2; + + /** + * Type of this struct. + * @type string + */ + public $type; + + /** + * Value of the struct we are recording errors for. There are various + * values for this: + * - TOKEN: Instance of HTMLPurifier_Token + * - ATTR: array('attr-name', 'value') + * - CSSPROP: array('prop-name', 'value') + * @type mixed + */ + public $value; + + /** + * Errors registered for this structure. + * @type array + */ + public $errors = array(); + + /** + * Child ErrorStructs that are from this structure. For example, a TOKEN + * ErrorStruct would contain ATTR ErrorStructs. This is a multi-dimensional + * array in structure: [TYPE]['identifier'] + * @type array + */ + public $children = array(); + + /** + * @param string $type + * @param string $id + * @return mixed + */ + public function getChild($type, $id) + { + if (!isset($this->children[$type][$id])) { + $this->children[$type][$id] = new HTMLPurifier_ErrorStruct(); + $this->children[$type][$id]->type = $type; + } + return $this->children[$type][$id]; + } + + /** + * @param int $severity + * @param string $message + */ + public function addError($severity, $message) + { + $this->errors[] = array($severity, $message); + } +} + + + + + +/** + * Global exception class for HTML Purifier; any exceptions we throw + * are from here. + */ +class HTMLPurifier_Exception extends Exception +{ + +} + + + + + +/** + * Represents a pre or post processing filter on HTML Purifier's output + * + * Sometimes, a little ad-hoc fixing of HTML has to be done before + * it gets sent through HTML Purifier: you can use filters to acheive + * this effect. For instance, YouTube videos can be preserved using + * this manner. You could have used a decorator for this task, but + * PHP's support for them is not terribly robust, so we're going + * to just loop through the filters. + * + * Filters should be exited first in, last out. If there are three filters, + * named 1, 2 and 3, the order of execution should go 1->preFilter, + * 2->preFilter, 3->preFilter, purify, 3->postFilter, 2->postFilter, + * 1->postFilter. + * + * @note Methods are not declared abstract as it is perfectly legitimate + * for an implementation not to want anything to happen on a step + */ + +class HTMLPurifier_Filter +{ + + /** + * Name of the filter for identification purposes. + * @type string + */ + public $name; + + /** + * Pre-processor function, handles HTML before HTML Purifier + * @param string $html + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return string + */ + public function preFilter($html, $config, $context) + { + return $html; + } + + /** + * Post-processor function, handles HTML after HTML Purifier + * @param string $html + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return string + */ + public function postFilter($html, $config, $context) + { + return $html; + } +} + + + + + +/** + * Generates HTML from tokens. + * @todo Refactor interface so that configuration/context is determined + * upon instantiation, no need for messy generateFromTokens() calls + * @todo Make some of the more internal functions protected, and have + * unit tests work around that + */ +class HTMLPurifier_Generator +{ + + /** + * Whether or not generator should produce XML output. + * @type bool + */ + private $_xhtml = true; + + /** + * :HACK: Whether or not generator should comment the insides of #i', '', $html); + } + + return $html; + } + + /** + * Takes a string of HTML (fragment or document) and returns the content + * @todo Consider making protected + */ + public function extractBody($html) + { + $matches = array(); + $result = preg_match('|(.*?)]*>(.*)|is', $html, $matches); + if ($result) { + // Make sure it's not in a comment + $comment_start = strrpos($matches[1], ''); + if ($comment_start === false || + ($comment_end !== false && $comment_end > $comment_start)) { + return $matches[2]; + } + } + return $html; + } +} + + + + + +/** + * Abstract base node class that all others inherit from. + * + * Why do we not use the DOM extension? (1) It is not always available, + * (2) it has funny constraints on the data it can represent, + * whereas we want a maximally flexible representation, and (3) its + * interface is a bit cumbersome. + */ +abstract class HTMLPurifier_Node +{ + /** + * Line number of the start token in the source document + * @type int + */ + public $line; + + /** + * Column number of the start token in the source document. Null if unknown. + * @type int + */ + public $col; + + /** + * Lookup array of processing that this token is exempt from. + * Currently, valid values are "ValidateAttributes". + * @type array + */ + public $armor = array(); + + /** + * When true, this node should be ignored as non-existent. + * + * Who is responsible for ignoring dead nodes? FixNesting is + * responsible for removing them before passing on to child + * validators. + */ + public $dead = false; + + /** + * Returns a pair of start and end tokens, where the end token + * is null if it is not necessary. Does not include children. + * @type array + */ + abstract public function toTokenPair(); +} + + + + + +/** + * Class that handles operations involving percent-encoding in URIs. + * + * @warning + * Be careful when reusing instances of PercentEncoder. The object + * you use for normalize() SHOULD NOT be used for encode(), or + * vice-versa. + */ +class HTMLPurifier_PercentEncoder +{ + + /** + * Reserved characters to preserve when using encode(). + * @type array + */ + protected $preserve = array(); + + /** + * String of characters that should be preserved while using encode(). + * @param bool $preserve + */ + public function __construct($preserve = false) + { + // unreserved letters, ought to const-ify + for ($i = 48; $i <= 57; $i++) { // digits + $this->preserve[$i] = true; + } + for ($i = 65; $i <= 90; $i++) { // upper-case + $this->preserve[$i] = true; + } + for ($i = 97; $i <= 122; $i++) { // lower-case + $this->preserve[$i] = true; + } + $this->preserve[45] = true; // Dash - + $this->preserve[46] = true; // Period . + $this->preserve[95] = true; // Underscore _ + $this->preserve[126]= true; // Tilde ~ + + // extra letters not to escape + if ($preserve !== false) { + for ($i = 0, $c = strlen($preserve); $i < $c; $i++) { + $this->preserve[ord($preserve[$i])] = true; + } + } + } + + /** + * Our replacement for urlencode, it encodes all non-reserved characters, + * as well as any extra characters that were instructed to be preserved. + * @note + * Assumes that the string has already been normalized, making any + * and all percent escape sequences valid. Percents will not be + * re-escaped, regardless of their status in $preserve + * @param string $string String to be encoded + * @return string Encoded string. + */ + public function encode($string) + { + $ret = ''; + for ($i = 0, $c = strlen($string); $i < $c; $i++) { + if ($string[$i] !== '%' && !isset($this->preserve[$int = ord($string[$i])])) { + $ret .= '%' . sprintf('%02X', $int); + } else { + $ret .= $string[$i]; + } + } + return $ret; + } + + /** + * Fix up percent-encoding by decoding unreserved characters and normalizing. + * @warning This function is affected by $preserve, even though the + * usual desired behavior is for this not to preserve those + * characters. Be careful when reusing instances of PercentEncoder! + * @param string $string String to normalize + * @return string + */ + public function normalize($string) + { + if ($string == '') { + return ''; + } + $parts = explode('%', $string); + $ret = array_shift($parts); + foreach ($parts as $part) { + $length = strlen($part); + if ($length < 2) { + $ret .= '%25' . $part; + continue; + } + $encoding = substr($part, 0, 2); + $text = substr($part, 2); + if (!ctype_xdigit($encoding)) { + $ret .= '%25' . $part; + continue; + } + $int = hexdec($encoding); + if (isset($this->preserve[$int])) { + $ret .= chr($int) . $text; + continue; + } + $encoding = strtoupper($encoding); + $ret .= '%' . $encoding . $text; + } + return $ret; + } +} + + + + + +/** + * Generic property list implementation + */ +class HTMLPurifier_PropertyList +{ + /** + * Internal data-structure for properties. + * @type array + */ + protected $data = array(); + + /** + * Parent plist. + * @type HTMLPurifier_PropertyList + */ + protected $parent; + + /** + * Cache. + * @type array + */ + protected $cache; + + /** + * @param HTMLPurifier_PropertyList $parent Parent plist + */ + public function __construct($parent = null) + { + $this->parent = $parent; + } + + /** + * Recursively retrieves the value for a key + * @param string $name + * @throws HTMLPurifier_Exception + */ + public function get($name) + { + if ($this->has($name)) { + return $this->data[$name]; + } + // possible performance bottleneck, convert to iterative if necessary + if ($this->parent) { + return $this->parent->get($name); + } + throw new HTMLPurifier_Exception("Key '$name' not found"); + } + + /** + * Sets the value of a key, for this plist + * @param string $name + * @param mixed $value + */ + public function set($name, $value) + { + $this->data[$name] = $value; + } + + /** + * Returns true if a given key exists + * @param string $name + * @return bool + */ + public function has($name) + { + return array_key_exists($name, $this->data); + } + + /** + * Resets a value to the value of it's parent, usually the default. If + * no value is specified, the entire plist is reset. + * @param string $name + */ + public function reset($name = null) + { + if ($name == null) { + $this->data = array(); + } else { + unset($this->data[$name]); + } + } + + /** + * Squashes this property list and all of its property lists into a single + * array, and returns the array. This value is cached by default. + * @param bool $force If true, ignores the cache and regenerates the array. + * @return array + */ + public function squash($force = false) + { + if ($this->cache !== null && !$force) { + return $this->cache; + } + if ($this->parent) { + return $this->cache = array_merge($this->parent->squash($force), $this->data); + } else { + return $this->cache = $this->data; + } + } + + /** + * Returns the parent plist. + * @return HTMLPurifier_PropertyList + */ + public function getParent() + { + return $this->parent; + } + + /** + * Sets the parent plist. + * @param HTMLPurifier_PropertyList $plist Parent plist + */ + public function setParent($plist) + { + $this->parent = $plist; + } +} + + + + + +/** + * Property list iterator. Do not instantiate this class directly. + */ +class HTMLPurifier_PropertyListIterator extends FilterIterator +{ + + /** + * @type int + */ + protected $l; + /** + * @type string + */ + protected $filter; + + /** + * @param Iterator $iterator Array of data to iterate over + * @param string $filter Optional prefix to only allow values of + */ + public function __construct(Iterator $iterator, $filter = null) + { + parent::__construct($iterator); + $this->l = strlen($filter); + $this->filter = $filter; + } + + /** + * @return bool + */ + public function accept() + { + $key = $this->getInnerIterator()->key(); + if (strncmp($key, $this->filter, $this->l) !== 0) { + return false; + } + return true; + } +} + + + + + +/** + * A simple array-backed queue, based off of the classic Okasaki + * persistent amortized queue. The basic idea is to maintain two + * stacks: an input stack and an output stack. When the output + * stack runs out, reverse the input stack and use it as the output + * stack. + * + * We don't use the SPL implementation because it's only supported + * on PHP 5.3 and later. + * + * Exercise: Prove that push/pop on this queue take amortized O(1) time. + * + * Exercise: Extend this queue to be a deque, while preserving amortized + * O(1) time. Some care must be taken on rebalancing to avoid quadratic + * behaviour caused by repeatedly shuffling data from the input stack + * to the output stack and back. + */ +class HTMLPurifier_Queue { + private $input; + private $output; + + public function __construct($input = array()) { + $this->input = $input; + $this->output = array(); + } + + /** + * Shifts an element off the front of the queue. + */ + public function shift() { + if (empty($this->output)) { + $this->output = array_reverse($this->input); + $this->input = array(); + } + if (empty($this->output)) { + return NULL; + } + return array_pop($this->output); + } + + /** + * Pushes an element onto the front of the queue. + */ + public function push($x) { + array_push($this->input, $x); + } + + /** + * Checks if it's empty. + */ + public function isEmpty() { + return empty($this->input) && empty($this->output); + } +} + + + +/** + * Supertype for classes that define a strategy for modifying/purifying tokens. + * + * While HTMLPurifier's core purpose is fixing HTML into something proper, + * strategies provide plug points for extra configuration or even extra + * features, such as custom tags, custom parsing of text, etc. + */ + + +abstract class HTMLPurifier_Strategy +{ + + /** + * Executes the strategy on the tokens. + * + * @param HTMLPurifier_Token[] $tokens Array of HTMLPurifier_Token objects to be operated on. + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return HTMLPurifier_Token[] Processed array of token objects. + */ + abstract public function execute($tokens, $config, $context); +} + + + + + +/** + * This is in almost every respect equivalent to an array except + * that it keeps track of which keys were accessed. + * + * @warning For the sake of backwards compatibility with early versions + * of PHP 5, you must not use the $hash[$key] syntax; if you do + * our version of offsetGet is never called. + */ +class HTMLPurifier_StringHash extends ArrayObject +{ + /** + * @type array + */ + protected $accessed = array(); + + /** + * Retrieves a value, and logs the access. + * @param mixed $index + * @return mixed + */ + public function offsetGet($index) + { + $this->accessed[$index] = true; + return parent::offsetGet($index); + } + + /** + * Returns a lookup array of all array indexes that have been accessed. + * @return array in form array($index => true). + */ + public function getAccessed() + { + return $this->accessed; + } + + /** + * Resets the access array. + */ + public function resetAccessed() + { + $this->accessed = array(); + } +} + + + + + +/** + * Parses string hash files. File format is as such: + * + * DefaultKeyValue + * KEY: Value + * KEY2: Value2 + * --MULTILINE-KEY-- + * Multiline + * value. + * + * Which would output something similar to: + * + * array( + * 'ID' => 'DefaultKeyValue', + * 'KEY' => 'Value', + * 'KEY2' => 'Value2', + * 'MULTILINE-KEY' => "Multiline\nvalue.\n", + * ) + * + * We use this as an easy to use file-format for configuration schema + * files, but the class itself is usage agnostic. + * + * You can use ---- to forcibly terminate parsing of a single string-hash; + * this marker is used in multi string-hashes to delimit boundaries. + */ +class HTMLPurifier_StringHashParser +{ + + /** + * @type string + */ + public $default = 'ID'; + + /** + * Parses a file that contains a single string-hash. + * @param string $file + * @return array + */ + public function parseFile($file) + { + if (!file_exists($file)) { + return false; + } + $fh = fopen($file, 'r'); + if (!$fh) { + return false; + } + $ret = $this->parseHandle($fh); + fclose($fh); + return $ret; + } + + /** + * Parses a file that contains multiple string-hashes delimited by '----' + * @param string $file + * @return array + */ + public function parseMultiFile($file) + { + if (!file_exists($file)) { + return false; + } + $ret = array(); + $fh = fopen($file, 'r'); + if (!$fh) { + return false; + } + while (!feof($fh)) { + $ret[] = $this->parseHandle($fh); + } + fclose($fh); + return $ret; + } + + /** + * Internal parser that acepts a file handle. + * @note While it's possible to simulate in-memory parsing by using + * custom stream wrappers, if such a use-case arises we should + * factor out the file handle into its own class. + * @param resource $fh File handle with pointer at start of valid string-hash + * block. + * @return array + */ + protected function parseHandle($fh) + { + $state = false; + $single = false; + $ret = array(); + do { + $line = fgets($fh); + if ($line === false) { + break; + } + $line = rtrim($line, "\n\r"); + if (!$state && $line === '') { + continue; + } + if ($line === '----') { + break; + } + if (strncmp('--#', $line, 3) === 0) { + // Comment + continue; + } elseif (strncmp('--', $line, 2) === 0) { + // Multiline declaration + $state = trim($line, '- '); + if (!isset($ret[$state])) { + $ret[$state] = ''; + } + continue; + } elseif (!$state) { + $single = true; + if (strpos($line, ':') !== false) { + // Single-line declaration + list($state, $line) = explode(':', $line, 2); + $line = trim($line); + } else { + // Use default declaration + $state = $this->default; + } + } + if ($single) { + $ret[$state] = $line; + $single = false; + $state = false; + } else { + $ret[$state] .= "$line\n"; + } + } while (!feof($fh)); + return $ret; + } +} + + + + + +/** + * Defines a mutation of an obsolete tag into a valid tag. + */ +abstract class HTMLPurifier_TagTransform +{ + + /** + * Tag name to transform the tag to. + * @type string + */ + public $transform_to; + + /** + * Transforms the obsolete tag into the valid tag. + * @param HTMLPurifier_Token_Tag $tag Tag to be transformed. + * @param HTMLPurifier_Config $config Mandatory HTMLPurifier_Config object + * @param HTMLPurifier_Context $context Mandatory HTMLPurifier_Context object + */ + abstract public function transform($tag, $config, $context); + + /** + * Prepends CSS properties to the style attribute, creating the + * attribute if it doesn't exist. + * @warning Copied over from AttrTransform, be sure to keep in sync + * @param array $attr Attribute array to process (passed by reference) + * @param string $css CSS to prepend + */ + protected function prependCSS(&$attr, $css) + { + $attr['style'] = isset($attr['style']) ? $attr['style'] : ''; + $attr['style'] = $css . $attr['style']; + } +} + + + + + +/** + * Abstract base token class that all others inherit from. + */ +abstract class HTMLPurifier_Token +{ + /** + * Line number node was on in source document. Null if unknown. + * @type int + */ + public $line; + + /** + * Column of line node was on in source document. Null if unknown. + * @type int + */ + public $col; + + /** + * Lookup array of processing that this token is exempt from. + * Currently, valid values are "ValidateAttributes" and + * "MakeWellFormed_TagClosedError" + * @type array + */ + public $armor = array(); + + /** + * Used during MakeWellFormed. See Note [Injector skips] + * @type + */ + public $skip; + + /** + * @type + */ + public $rewind; + + /** + * @type + */ + public $carryover; + + /** + * @param string $n + * @return null|string + */ + public function __get($n) + { + if ($n === 'type') { + trigger_error('Deprecated type property called; use instanceof', E_USER_NOTICE); + switch (get_class($this)) { + case 'HTMLPurifier_Token_Start': + return 'start'; + case 'HTMLPurifier_Token_Empty': + return 'empty'; + case 'HTMLPurifier_Token_End': + return 'end'; + case 'HTMLPurifier_Token_Text': + return 'text'; + case 'HTMLPurifier_Token_Comment': + return 'comment'; + default: + return null; + } + } + } + + /** + * Sets the position of the token in the source document. + * @param int $l + * @param int $c + */ + public function position($l = null, $c = null) + { + $this->line = $l; + $this->col = $c; + } + + /** + * Convenience function for DirectLex settings line/col position. + * @param int $l + * @param int $c + */ + public function rawPosition($l, $c) + { + if ($c === -1) { + $l++; + } + $this->line = $l; + $this->col = $c; + } + + /** + * Converts a token into its corresponding node. + */ + abstract public function toNode(); +} + + + + + +/** + * Factory for token generation. + * + * @note Doing some benchmarking indicates that the new operator is much + * slower than the clone operator (even discounting the cost of the + * constructor). This class is for that optimization. + * Other then that, there's not much point as we don't + * maintain parallel HTMLPurifier_Token hierarchies (the main reason why + * you'd want to use an abstract factory). + * @todo Port DirectLex to use this + */ +class HTMLPurifier_TokenFactory +{ + // p stands for prototype + + /** + * @type HTMLPurifier_Token_Start + */ + private $p_start; + + /** + * @type HTMLPurifier_Token_End + */ + private $p_end; + + /** + * @type HTMLPurifier_Token_Empty + */ + private $p_empty; + + /** + * @type HTMLPurifier_Token_Text + */ + private $p_text; + + /** + * @type HTMLPurifier_Token_Comment + */ + private $p_comment; + + /** + * Generates blank prototypes for cloning. + */ + public function __construct() + { + $this->p_start = new HTMLPurifier_Token_Start('', array()); + $this->p_end = new HTMLPurifier_Token_End(''); + $this->p_empty = new HTMLPurifier_Token_Empty('', array()); + $this->p_text = new HTMLPurifier_Token_Text(''); + $this->p_comment = new HTMLPurifier_Token_Comment(''); + } + + /** + * Creates a HTMLPurifier_Token_Start. + * @param string $name Tag name + * @param array $attr Associative array of attributes + * @return HTMLPurifier_Token_Start Generated HTMLPurifier_Token_Start + */ + public function createStart($name, $attr = array()) + { + $p = clone $this->p_start; + $p->__construct($name, $attr); + return $p; + } + + /** + * Creates a HTMLPurifier_Token_End. + * @param string $name Tag name + * @return HTMLPurifier_Token_End Generated HTMLPurifier_Token_End + */ + public function createEnd($name) + { + $p = clone $this->p_end; + $p->__construct($name); + return $p; + } + + /** + * Creates a HTMLPurifier_Token_Empty. + * @param string $name Tag name + * @param array $attr Associative array of attributes + * @return HTMLPurifier_Token_Empty Generated HTMLPurifier_Token_Empty + */ + public function createEmpty($name, $attr = array()) + { + $p = clone $this->p_empty; + $p->__construct($name, $attr); + return $p; + } + + /** + * Creates a HTMLPurifier_Token_Text. + * @param string $data Data of text token + * @return HTMLPurifier_Token_Text Generated HTMLPurifier_Token_Text + */ + public function createText($data) + { + $p = clone $this->p_text; + $p->__construct($data); + return $p; + } + + /** + * Creates a HTMLPurifier_Token_Comment. + * @param string $data Data of comment token + * @return HTMLPurifier_Token_Comment Generated HTMLPurifier_Token_Comment + */ + public function createComment($data) + { + $p = clone $this->p_comment; + $p->__construct($data); + return $p; + } +} + + + + + +/** + * HTML Purifier's internal representation of a URI. + * @note + * Internal data-structures are completely escaped. If the data needs + * to be used in a non-URI context (which is very unlikely), be sure + * to decode it first. The URI may not necessarily be well-formed until + * validate() is called. + */ +class HTMLPurifier_URI +{ + /** + * @type string + */ + public $scheme; + + /** + * @type string + */ + public $userinfo; + + /** + * @type string + */ + public $host; + + /** + * @type int + */ + public $port; + + /** + * @type string + */ + public $path; + + /** + * @type string + */ + public $query; + + /** + * @type string + */ + public $fragment; + + /** + * @param string $scheme + * @param string $userinfo + * @param string $host + * @param int $port + * @param string $path + * @param string $query + * @param string $fragment + * @note Automatically normalizes scheme and port + */ + public function __construct($scheme, $userinfo, $host, $port, $path, $query, $fragment) + { + $this->scheme = is_null($scheme) || ctype_lower($scheme) ? $scheme : strtolower($scheme); + $this->userinfo = $userinfo; + $this->host = $host; + $this->port = is_null($port) ? $port : (int)$port; + $this->path = $path; + $this->query = $query; + $this->fragment = $fragment; + } + + /** + * Retrieves a scheme object corresponding to the URI's scheme/default + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return HTMLPurifier_URIScheme Scheme object appropriate for validating this URI + */ + public function getSchemeObj($config, $context) + { + $registry = HTMLPurifier_URISchemeRegistry::instance(); + if ($this->scheme !== null) { + $scheme_obj = $registry->getScheme($this->scheme, $config, $context); + if (!$scheme_obj) { + return false; + } // invalid scheme, clean it out + } else { + // no scheme: retrieve the default one + $def = $config->getDefinition('URI'); + $scheme_obj = $def->getDefaultScheme($config, $context); + if (!$scheme_obj) { + if ($def->defaultScheme !== null) { + // something funky happened to the default scheme object + trigger_error( + 'Default scheme object "' . $def->defaultScheme . '" was not readable', + E_USER_WARNING + ); + } // suppress error if it's null + return false; + } + } + return $scheme_obj; + } + + /** + * Generic validation method applicable for all schemes. May modify + * this URI in order to get it into a compliant form. + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool True if validation/filtering succeeds, false if failure + */ + public function validate($config, $context) + { + // ABNF definitions from RFC 3986 + $chars_sub_delims = '!$&\'()*+,;='; + $chars_gen_delims = ':/?#[]@'; + $chars_pchar = $chars_sub_delims . ':@'; + + // validate host + if (!is_null($this->host)) { + $host_def = new HTMLPurifier_AttrDef_URI_Host(); + $this->host = $host_def->validate($this->host, $config, $context); + if ($this->host === false) { + $this->host = null; + } + } + + // validate scheme + // NOTE: It's not appropriate to check whether or not this + // scheme is in our registry, since a URIFilter may convert a + // URI that we don't allow into one we do. So instead, we just + // check if the scheme can be dropped because there is no host + // and it is our default scheme. + if (!is_null($this->scheme) && is_null($this->host) || $this->host === '') { + // support for relative paths is pretty abysmal when the + // scheme is present, so axe it when possible + $def = $config->getDefinition('URI'); + if ($def->defaultScheme === $this->scheme) { + $this->scheme = null; + } + } + + // validate username + if (!is_null($this->userinfo)) { + $encoder = new HTMLPurifier_PercentEncoder($chars_sub_delims . ':'); + $this->userinfo = $encoder->encode($this->userinfo); + } + + // validate port + if (!is_null($this->port)) { + if ($this->port < 1 || $this->port > 65535) { + $this->port = null; + } + } + + // validate path + $segments_encoder = new HTMLPurifier_PercentEncoder($chars_pchar . '/'); + if (!is_null($this->host)) { // this catches $this->host === '' + // path-abempty (hier and relative) + // http://www.example.com/my/path + // //www.example.com/my/path (looks odd, but works, and + // recognized by most browsers) + // (this set is valid or invalid on a scheme by scheme + // basis, so we'll deal with it later) + // file:///my/path + // ///my/path + $this->path = $segments_encoder->encode($this->path); + } elseif ($this->path !== '') { + if ($this->path[0] === '/') { + // path-absolute (hier and relative) + // http:/my/path + // /my/path + if (strlen($this->path) >= 2 && $this->path[1] === '/') { + // This could happen if both the host gets stripped + // out + // http://my/path + // //my/path + $this->path = ''; + } else { + $this->path = $segments_encoder->encode($this->path); + } + } elseif (!is_null($this->scheme)) { + // path-rootless (hier) + // http:my/path + // Short circuit evaluation means we don't need to check nz + $this->path = $segments_encoder->encode($this->path); + } else { + // path-noscheme (relative) + // my/path + // (once again, not checking nz) + $segment_nc_encoder = new HTMLPurifier_PercentEncoder($chars_sub_delims . '@'); + $c = strpos($this->path, '/'); + if ($c !== false) { + $this->path = + $segment_nc_encoder->encode(substr($this->path, 0, $c)) . + $segments_encoder->encode(substr($this->path, $c)); + } else { + $this->path = $segment_nc_encoder->encode($this->path); + } + } + } else { + // path-empty (hier and relative) + $this->path = ''; // just to be safe + } + + // qf = query and fragment + $qf_encoder = new HTMLPurifier_PercentEncoder($chars_pchar . '/?'); + + if (!is_null($this->query)) { + $this->query = $qf_encoder->encode($this->query); + } + + if (!is_null($this->fragment)) { + $this->fragment = $qf_encoder->encode($this->fragment); + } + return true; + } + + /** + * Convert URI back to string + * @return string URI appropriate for output + */ + public function toString() + { + // reconstruct authority + $authority = null; + // there is a rendering difference between a null authority + // (http:foo-bar) and an empty string authority + // (http:///foo-bar). + if (!is_null($this->host)) { + $authority = ''; + if (!is_null($this->userinfo)) { + $authority .= $this->userinfo . '@'; + } + $authority .= $this->host; + if (!is_null($this->port)) { + $authority .= ':' . $this->port; + } + } + + // Reconstruct the result + // One might wonder about parsing quirks from browsers after + // this reconstruction. Unfortunately, parsing behavior depends + // on what *scheme* was employed (file:///foo is handled *very* + // differently than http:///foo), so unfortunately we have to + // defer to the schemes to do the right thing. + $result = ''; + if (!is_null($this->scheme)) { + $result .= $this->scheme . ':'; + } + if (!is_null($authority)) { + $result .= '//' . $authority; + } + $result .= $this->path; + if (!is_null($this->query)) { + $result .= '?' . $this->query; + } + if (!is_null($this->fragment)) { + $result .= '#' . $this->fragment; + } + + return $result; + } + + /** + * Returns true if this URL might be considered a 'local' URL given + * the current context. This is true when the host is null, or + * when it matches the host supplied to the configuration. + * + * Note that this does not do any scheme checking, so it is mostly + * only appropriate for metadata that doesn't care about protocol + * security. isBenign is probably what you actually want. + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function isLocal($config, $context) + { + if ($this->host === null) { + return true; + } + $uri_def = $config->getDefinition('URI'); + if ($uri_def->host === $this->host) { + return true; + } + return false; + } + + /** + * Returns true if this URL should be considered a 'benign' URL, + * that is: + * + * - It is a local URL (isLocal), and + * - It has a equal or better level of security + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function isBenign($config, $context) + { + if (!$this->isLocal($config, $context)) { + return false; + } + + $scheme_obj = $this->getSchemeObj($config, $context); + if (!$scheme_obj) { + return false; + } // conservative approach + + $current_scheme_obj = $config->getDefinition('URI')->getDefaultScheme($config, $context); + if ($current_scheme_obj->secure) { + if (!$scheme_obj->secure) { + return false; + } + } + return true; + } +} + + + + + +class HTMLPurifier_URIDefinition extends HTMLPurifier_Definition +{ + + public $type = 'URI'; + protected $filters = array(); + protected $postFilters = array(); + protected $registeredFilters = array(); + + /** + * HTMLPurifier_URI object of the base specified at %URI.Base + */ + public $base; + + /** + * String host to consider "home" base, derived off of $base + */ + public $host; + + /** + * Name of default scheme based on %URI.DefaultScheme and %URI.Base + */ + public $defaultScheme; + + public function __construct() + { + $this->registerFilter(new HTMLPurifier_URIFilter_DisableExternal()); + $this->registerFilter(new HTMLPurifier_URIFilter_DisableExternalResources()); + $this->registerFilter(new HTMLPurifier_URIFilter_DisableResources()); + $this->registerFilter(new HTMLPurifier_URIFilter_HostBlacklist()); + $this->registerFilter(new HTMLPurifier_URIFilter_SafeIframe()); + $this->registerFilter(new HTMLPurifier_URIFilter_MakeAbsolute()); + $this->registerFilter(new HTMLPurifier_URIFilter_Munge()); + } + + public function registerFilter($filter) + { + $this->registeredFilters[$filter->name] = $filter; + } + + public function addFilter($filter, $config) + { + $r = $filter->prepare($config); + if ($r === false) return; // null is ok, for backwards compat + if ($filter->post) { + $this->postFilters[$filter->name] = $filter; + } else { + $this->filters[$filter->name] = $filter; + } + } + + protected function doSetup($config) + { + $this->setupMemberVariables($config); + $this->setupFilters($config); + } + + protected function setupFilters($config) + { + foreach ($this->registeredFilters as $name => $filter) { + if ($filter->always_load) { + $this->addFilter($filter, $config); + } else { + $conf = $config->get('URI.' . $name); + if ($conf !== false && $conf !== null) { + $this->addFilter($filter, $config); + } + } + } + unset($this->registeredFilters); + } + + protected function setupMemberVariables($config) + { + $this->host = $config->get('URI.Host'); + $base_uri = $config->get('URI.Base'); + if (!is_null($base_uri)) { + $parser = new HTMLPurifier_URIParser(); + $this->base = $parser->parse($base_uri); + $this->defaultScheme = $this->base->scheme; + if (is_null($this->host)) $this->host = $this->base->host; + } + if (is_null($this->defaultScheme)) $this->defaultScheme = $config->get('URI.DefaultScheme'); + } + + public function getDefaultScheme($config, $context) + { + return HTMLPurifier_URISchemeRegistry::instance()->getScheme($this->defaultScheme, $config, $context); + } + + public function filter(&$uri, $config, $context) + { + foreach ($this->filters as $name => $f) { + $result = $f->filter($uri, $config, $context); + if (!$result) return false; + } + return true; + } + + public function postFilter(&$uri, $config, $context) + { + foreach ($this->postFilters as $name => $f) { + $result = $f->filter($uri, $config, $context); + if (!$result) return false; + } + return true; + } + +} + + + + + +/** + * Chainable filters for custom URI processing. + * + * These filters can perform custom actions on a URI filter object, + * including transformation or blacklisting. A filter named Foo + * must have a corresponding configuration directive %URI.Foo, + * unless always_load is specified to be true. + * + * The following contexts may be available while URIFilters are being + * processed: + * + * - EmbeddedURI: true if URI is an embedded resource that will + * be loaded automatically on page load + * - CurrentToken: a reference to the token that is currently + * being processed + * - CurrentAttr: the name of the attribute that is currently being + * processed + * - CurrentCSSProperty: the name of the CSS property that is + * currently being processed (if applicable) + * + * @warning This filter is called before scheme object validation occurs. + * Make sure, if you require a specific scheme object, you + * you check that it exists. This allows filters to convert + * proprietary URI schemes into regular ones. + */ +abstract class HTMLPurifier_URIFilter +{ + + /** + * Unique identifier of filter. + * @type string + */ + public $name; + + /** + * True if this filter should be run after scheme validation. + * @type bool + */ + public $post = false; + + /** + * True if this filter should always be loaded. + * This permits a filter to be named Foo without the corresponding + * %URI.Foo directive existing. + * @type bool + */ + public $always_load = false; + + /** + * Performs initialization for the filter. If the filter returns + * false, this means that it shouldn't be considered active. + * @param HTMLPurifier_Config $config + * @return bool + */ + public function prepare($config) + { + return true; + } + + /** + * Filter a URI object + * @param HTMLPurifier_URI $uri Reference to URI object variable + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool Whether or not to continue processing: false indicates + * URL is no good, true indicates continue processing. Note that + * all changes are committed directly on the URI object + */ + abstract public function filter(&$uri, $config, $context); +} + + + + + +/** + * Parses a URI into the components and fragment identifier as specified + * by RFC 3986. + */ +class HTMLPurifier_URIParser +{ + + /** + * Instance of HTMLPurifier_PercentEncoder to do normalization with. + */ + protected $percentEncoder; + + public function __construct() + { + $this->percentEncoder = new HTMLPurifier_PercentEncoder(); + } + + /** + * Parses a URI. + * @param $uri string URI to parse + * @return HTMLPurifier_URI representation of URI. This representation has + * not been validated yet and may not conform to RFC. + */ + public function parse($uri) + { + $uri = $this->percentEncoder->normalize($uri); + + // Regexp is as per Appendix B. + // Note that ["<>] are an addition to the RFC's recommended + // characters, because they represent external delimeters. + $r_URI = '!'. + '(([a-zA-Z0-9\.\+\-]+):)?'. // 2. Scheme + '(//([^/?#"<>]*))?'. // 4. Authority + '([^?#"<>]*)'. // 5. Path + '(\?([^#"<>]*))?'. // 7. Query + '(#([^"<>]*))?'. // 8. Fragment + '!'; + + $matches = array(); + $result = preg_match($r_URI, $uri, $matches); + + if (!$result) return false; // *really* invalid URI + + // seperate out parts + $scheme = !empty($matches[1]) ? $matches[2] : null; + $authority = !empty($matches[3]) ? $matches[4] : null; + $path = $matches[5]; // always present, can be empty + $query = !empty($matches[6]) ? $matches[7] : null; + $fragment = !empty($matches[8]) ? $matches[9] : null; + + // further parse authority + if ($authority !== null) { + $r_authority = "/^((.+?)@)?(\[[^\]]+\]|[^:]*)(:(\d*))?/"; + $matches = array(); + preg_match($r_authority, $authority, $matches); + $userinfo = !empty($matches[1]) ? $matches[2] : null; + $host = !empty($matches[3]) ? $matches[3] : ''; + $port = !empty($matches[4]) ? (int) $matches[5] : null; + } else { + $port = $host = $userinfo = null; + } + + return new HTMLPurifier_URI( + $scheme, $userinfo, $host, $port, $path, $query, $fragment); + } + +} + + + + + +/** + * Validator for the components of a URI for a specific scheme + */ +abstract class HTMLPurifier_URIScheme +{ + + /** + * Scheme's default port (integer). If an explicit port number is + * specified that coincides with the default port, it will be + * elided. + * @type int + */ + public $default_port = null; + + /** + * Whether or not URIs of this scheme are locatable by a browser + * http and ftp are accessible, while mailto and news are not. + * @type bool + */ + public $browsable = false; + + /** + * Whether or not data transmitted over this scheme is encrypted. + * https is secure, http is not. + * @type bool + */ + public $secure = false; + + /** + * Whether or not the URI always uses , resolves edge cases + * with making relative URIs absolute + * @type bool + */ + public $hierarchical = false; + + /** + * Whether or not the URI may omit a hostname when the scheme is + * explicitly specified, ala file:///path/to/file. As of writing, + * 'file' is the only scheme that browsers support his properly. + * @type bool + */ + public $may_omit_host = false; + + /** + * Validates the components of a URI for a specific scheme. + * @param HTMLPurifier_URI $uri Reference to a HTMLPurifier_URI object + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool success or failure + */ + abstract public function doValidate(&$uri, $config, $context); + + /** + * Public interface for validating components of a URI. Performs a + * bunch of default actions. Don't overload this method. + * @param HTMLPurifier_URI $uri Reference to a HTMLPurifier_URI object + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool success or failure + */ + public function validate(&$uri, $config, $context) + { + if ($this->default_port == $uri->port) { + $uri->port = null; + } + // kludge: browsers do funny things when the scheme but not the + // authority is set + if (!$this->may_omit_host && + // if the scheme is present, a missing host is always in error + (!is_null($uri->scheme) && ($uri->host === '' || is_null($uri->host))) || + // if the scheme is not present, a *blank* host is in error, + // since this translates into '///path' which most browsers + // interpret as being 'http://path'. + (is_null($uri->scheme) && $uri->host === '') + ) { + do { + if (is_null($uri->scheme)) { + if (substr($uri->path, 0, 2) != '//') { + $uri->host = null; + break; + } + // URI is '////path', so we cannot nullify the + // host to preserve semantics. Try expanding the + // hostname instead (fall through) + } + // first see if we can manually insert a hostname + $host = $config->get('URI.Host'); + if (!is_null($host)) { + $uri->host = $host; + } else { + // we can't do anything sensible, reject the URL. + return false; + } + } while (false); + } + return $this->doValidate($uri, $config, $context); + } +} + + + + + +/** + * Registry for retrieving specific URI scheme validator objects. + */ +class HTMLPurifier_URISchemeRegistry +{ + + /** + * Retrieve sole instance of the registry. + * @param HTMLPurifier_URISchemeRegistry $prototype Optional prototype to overload sole instance with, + * or bool true to reset to default registry. + * @return HTMLPurifier_URISchemeRegistry + * @note Pass a registry object $prototype with a compatible interface and + * the function will copy it and return it all further times. + */ + public static function instance($prototype = null) + { + static $instance = null; + if ($prototype !== null) { + $instance = $prototype; + } elseif ($instance === null || $prototype == true) { + $instance = new HTMLPurifier_URISchemeRegistry(); + } + return $instance; + } + + /** + * Cache of retrieved schemes. + * @type HTMLPurifier_URIScheme[] + */ + protected $schemes = array(); + + /** + * Retrieves a scheme validator object + * @param string $scheme String scheme name like http or mailto + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return HTMLPurifier_URIScheme + */ + public function getScheme($scheme, $config, $context) + { + if (!$config) { + $config = HTMLPurifier_Config::createDefault(); + } + + // important, otherwise attacker could include arbitrary file + $allowed_schemes = $config->get('URI.AllowedSchemes'); + if (!$config->get('URI.OverrideAllowedSchemes') && + !isset($allowed_schemes[$scheme]) + ) { + return; + } + + if (isset($this->schemes[$scheme])) { + return $this->schemes[$scheme]; + } + if (!isset($allowed_schemes[$scheme])) { + return; + } + + $class = 'HTMLPurifier_URIScheme_' . $scheme; + if (!class_exists($class)) { + return; + } + $this->schemes[$scheme] = new $class(); + return $this->schemes[$scheme]; + } + + /** + * Registers a custom scheme to the cache, bypassing reflection. + * @param string $scheme Scheme name + * @param HTMLPurifier_URIScheme $scheme_obj + */ + public function register($scheme, $scheme_obj) + { + $this->schemes[$scheme] = $scheme_obj; + } +} + + + + + +/** + * Class for converting between different unit-lengths as specified by + * CSS. + */ +class HTMLPurifier_UnitConverter +{ + + const ENGLISH = 1; + const METRIC = 2; + const DIGITAL = 3; + + /** + * Units information array. Units are grouped into measuring systems + * (English, Metric), and are assigned an integer representing + * the conversion factor between that unit and the smallest unit in + * the system. Numeric indexes are actually magical constants that + * encode conversion data from one system to the next, with a O(n^2) + * constraint on memory (this is generally not a problem, since + * the number of measuring systems is small.) + */ + protected static $units = array( + self::ENGLISH => array( + 'px' => 3, // This is as per CSS 2.1 and Firefox. Your mileage may vary + 'pt' => 4, + 'pc' => 48, + 'in' => 288, + self::METRIC => array('pt', '0.352777778', 'mm'), + ), + self::METRIC => array( + 'mm' => 1, + 'cm' => 10, + self::ENGLISH => array('mm', '2.83464567', 'pt'), + ), + ); + + /** + * Minimum bcmath precision for output. + * @type int + */ + protected $outputPrecision; + + /** + * Bcmath precision for internal calculations. + * @type int + */ + protected $internalPrecision; + + /** + * Whether or not BCMath is available. + * @type bool + */ + private $bcmath; + + public function __construct($output_precision = 4, $internal_precision = 10, $force_no_bcmath = false) + { + $this->outputPrecision = $output_precision; + $this->internalPrecision = $internal_precision; + $this->bcmath = !$force_no_bcmath && function_exists('bcmul'); + } + + /** + * Converts a length object of one unit into another unit. + * @param HTMLPurifier_Length $length + * Instance of HTMLPurifier_Length to convert. You must validate() + * it before passing it here! + * @param string $to_unit + * Unit to convert to. + * @return HTMLPurifier_Length|bool + * @note + * About precision: This conversion function pays very special + * attention to the incoming precision of values and attempts + * to maintain a number of significant figure. Results are + * fairly accurate up to nine digits. Some caveats: + * - If a number is zero-padded as a result of this significant + * figure tracking, the zeroes will be eliminated. + * - If a number contains less than four sigfigs ($outputPrecision) + * and this causes some decimals to be excluded, those + * decimals will be added on. + */ + public function convert($length, $to_unit) + { + if (!$length->isValid()) { + return false; + } + + $n = $length->getN(); + $unit = $length->getUnit(); + + if ($n === '0' || $unit === false) { + return new HTMLPurifier_Length('0', false); + } + + $state = $dest_state = false; + foreach (self::$units as $k => $x) { + if (isset($x[$unit])) { + $state = $k; + } + if (isset($x[$to_unit])) { + $dest_state = $k; + } + } + if (!$state || !$dest_state) { + return false; + } + + // Some calculations about the initial precision of the number; + // this will be useful when we need to do final rounding. + $sigfigs = $this->getSigFigs($n); + if ($sigfigs < $this->outputPrecision) { + $sigfigs = $this->outputPrecision; + } + + // BCMath's internal precision deals only with decimals. Use + // our default if the initial number has no decimals, or increase + // it by how ever many decimals, thus, the number of guard digits + // will always be greater than or equal to internalPrecision. + $log = (int)floor(log(abs($n), 10)); + $cp = ($log < 0) ? $this->internalPrecision - $log : $this->internalPrecision; // internal precision + + for ($i = 0; $i < 2; $i++) { + + // Determine what unit IN THIS SYSTEM we need to convert to + if ($dest_state === $state) { + // Simple conversion + $dest_unit = $to_unit; + } else { + // Convert to the smallest unit, pending a system shift + $dest_unit = self::$units[$state][$dest_state][0]; + } + + // Do the conversion if necessary + if ($dest_unit !== $unit) { + $factor = $this->div(self::$units[$state][$unit], self::$units[$state][$dest_unit], $cp); + $n = $this->mul($n, $factor, $cp); + $unit = $dest_unit; + } + + // Output was zero, so bail out early. Shouldn't ever happen. + if ($n === '') { + $n = '0'; + $unit = $to_unit; + break; + } + + // It was a simple conversion, so bail out + if ($dest_state === $state) { + break; + } + + if ($i !== 0) { + // Conversion failed! Apparently, the system we forwarded + // to didn't have this unit. This should never happen! + return false; + } + + // Pre-condition: $i == 0 + + // Perform conversion to next system of units + $n = $this->mul($n, self::$units[$state][$dest_state][1], $cp); + $unit = self::$units[$state][$dest_state][2]; + $state = $dest_state; + + // One more loop around to convert the unit in the new system. + + } + + // Post-condition: $unit == $to_unit + if ($unit !== $to_unit) { + return false; + } + + // Useful for debugging: + //echo "
n";
+        //echo "$n\nsigfigs = $sigfigs\nnew_log = $new_log\nlog = $log\nrp = $rp\n
\n"; + + $n = $this->round($n, $sigfigs); + if (strpos($n, '.') !== false) { + $n = rtrim($n, '0'); + } + $n = rtrim($n, '.'); + + return new HTMLPurifier_Length($n, $unit); + } + + /** + * Returns the number of significant figures in a string number. + * @param string $n Decimal number + * @return int number of sigfigs + */ + public function getSigFigs($n) + { + $n = ltrim($n, '0+-'); + $dp = strpos($n, '.'); // decimal position + if ($dp === false) { + $sigfigs = strlen(rtrim($n, '0')); + } else { + $sigfigs = strlen(ltrim($n, '0.')); // eliminate extra decimal character + if ($dp !== 0) { + $sigfigs--; + } + } + return $sigfigs; + } + + /** + * Adds two numbers, using arbitrary precision when available. + * @param string $s1 + * @param string $s2 + * @param int $scale + * @return string + */ + private function add($s1, $s2, $scale) + { + if ($this->bcmath) { + return bcadd($s1, $s2, $scale); + } else { + return $this->scale((float)$s1 + (float)$s2, $scale); + } + } + + /** + * Multiples two numbers, using arbitrary precision when available. + * @param string $s1 + * @param string $s2 + * @param int $scale + * @return string + */ + private function mul($s1, $s2, $scale) + { + if ($this->bcmath) { + return bcmul($s1, $s2, $scale); + } else { + return $this->scale((float)$s1 * (float)$s2, $scale); + } + } + + /** + * Divides two numbers, using arbitrary precision when available. + * @param string $s1 + * @param string $s2 + * @param int $scale + * @return string + */ + private function div($s1, $s2, $scale) + { + if ($this->bcmath) { + return bcdiv($s1, $s2, $scale); + } else { + return $this->scale((float)$s1 / (float)$s2, $scale); + } + } + + /** + * Rounds a number according to the number of sigfigs it should have, + * using arbitrary precision when available. + * @param float $n + * @param int $sigfigs + * @return string + */ + private function round($n, $sigfigs) + { + $new_log = (int)floor(log(abs($n), 10)); // Number of digits left of decimal - 1 + $rp = $sigfigs - $new_log - 1; // Number of decimal places needed + $neg = $n < 0 ? '-' : ''; // Negative sign + if ($this->bcmath) { + if ($rp >= 0) { + $n = bcadd($n, $neg . '0.' . str_repeat('0', $rp) . '5', $rp + 1); + $n = bcdiv($n, '1', $rp); + } else { + // This algorithm partially depends on the standardized + // form of numbers that comes out of bcmath. + $n = bcadd($n, $neg . '5' . str_repeat('0', $new_log - $sigfigs), 0); + $n = substr($n, 0, $sigfigs + strlen($neg)) . str_repeat('0', $new_log - $sigfigs + 1); + } + return $n; + } else { + return $this->scale(round($n, $sigfigs - $new_log - 1), $rp + 1); + } + } + + /** + * Scales a float to $scale digits right of decimal point, like BCMath. + * @param float $r + * @param int $scale + * @return string + */ + private function scale($r, $scale) + { + if ($scale < 0) { + // The f sprintf type doesn't support negative numbers, so we + // need to cludge things manually. First get the string. + $r = sprintf('%.0f', (float)$r); + // Due to floating point precision loss, $r will more than likely + // look something like 4652999999999.9234. We grab one more digit + // than we need to precise from $r and then use that to round + // appropriately. + $precise = (string)round(substr($r, 0, strlen($r) + $scale), -1); + // Now we return it, truncating the zero that was rounded off. + return substr($precise, 0, -1) . str_repeat('0', -$scale + 1); + } + return sprintf('%.' . $scale . 'f', (float)$r); + } +} + + + + + +/** + * Parses string representations into their corresponding native PHP + * variable type. The base implementation does a simple type-check. + */ +class HTMLPurifier_VarParser +{ + + const C_STRING = 1; + const ISTRING = 2; + const TEXT = 3; + const ITEXT = 4; + const C_INT = 5; + const C_FLOAT = 6; + const C_BOOL = 7; + const LOOKUP = 8; + const ALIST = 9; + const HASH = 10; + const C_MIXED = 11; + + /** + * Lookup table of allowed types. Mainly for backwards compatibility, but + * also convenient for transforming string type names to the integer constants. + */ + public static $types = array( + 'string' => self::C_STRING, + 'istring' => self::ISTRING, + 'text' => self::TEXT, + 'itext' => self::ITEXT, + 'int' => self::C_INT, + 'float' => self::C_FLOAT, + 'bool' => self::C_BOOL, + 'lookup' => self::LOOKUP, + 'list' => self::ALIST, + 'hash' => self::HASH, + 'mixed' => self::C_MIXED + ); + + /** + * Lookup table of types that are string, and can have aliases or + * allowed value lists. + */ + public static $stringTypes = array( + self::C_STRING => true, + self::ISTRING => true, + self::TEXT => true, + self::ITEXT => true, + ); + + /** + * Validate a variable according to type. + * It may return NULL as a valid type if $allow_null is true. + * + * @param mixed $var Variable to validate + * @param int $type Type of variable, see HTMLPurifier_VarParser->types + * @param bool $allow_null Whether or not to permit null as a value + * @return string Validated and type-coerced variable + * @throws HTMLPurifier_VarParserException + */ + final public function parse($var, $type, $allow_null = false) + { + if (is_string($type)) { + if (!isset(HTMLPurifier_VarParser::$types[$type])) { + throw new HTMLPurifier_VarParserException("Invalid type '$type'"); + } else { + $type = HTMLPurifier_VarParser::$types[$type]; + } + } + $var = $this->parseImplementation($var, $type, $allow_null); + if ($allow_null && $var === null) { + return null; + } + // These are basic checks, to make sure nothing horribly wrong + // happened in our implementations. + switch ($type) { + case (self::C_STRING): + case (self::ISTRING): + case (self::TEXT): + case (self::ITEXT): + if (!is_string($var)) { + break; + } + if ($type == self::ISTRING || $type == self::ITEXT) { + $var = strtolower($var); + } + return $var; + case (self::C_INT): + if (!is_int($var)) { + break; + } + return $var; + case (self::C_FLOAT): + if (!is_float($var)) { + break; + } + return $var; + case (self::C_BOOL): + if (!is_bool($var)) { + break; + } + return $var; + case (self::LOOKUP): + case (self::ALIST): + case (self::HASH): + if (!is_array($var)) { + break; + } + if ($type === self::LOOKUP) { + foreach ($var as $k) { + if ($k !== true) { + $this->error('Lookup table contains value other than true'); + } + } + } elseif ($type === self::ALIST) { + $keys = array_keys($var); + if (array_keys($keys) !== $keys) { + $this->error('Indices for list are not uniform'); + } + } + return $var; + case (self::C_MIXED): + return $var; + default: + $this->errorInconsistent(get_class($this), $type); + } + $this->errorGeneric($var, $type); + } + + /** + * Actually implements the parsing. Base implementation does not + * do anything to $var. Subclasses should overload this! + * @param mixed $var + * @param int $type + * @param bool $allow_null + * @return string + */ + protected function parseImplementation($var, $type, $allow_null) + { + return $var; + } + + /** + * Throws an exception. + * @throws HTMLPurifier_VarParserException + */ + protected function error($msg) + { + throw new HTMLPurifier_VarParserException($msg); + } + + /** + * Throws an inconsistency exception. + * @note This should not ever be called. It would be called if we + * extend the allowed values of HTMLPurifier_VarParser without + * updating subclasses. + * @param string $class + * @param int $type + * @throws HTMLPurifier_Exception + */ + protected function errorInconsistent($class, $type) + { + throw new HTMLPurifier_Exception( + "Inconsistency in $class: " . HTMLPurifier_VarParser::getTypeName($type) . + " not implemented" + ); + } + + /** + * Generic error for if a type didn't work. + * @param mixed $var + * @param int $type + */ + protected function errorGeneric($var, $type) + { + $vtype = gettype($var); + $this->error("Expected type " . HTMLPurifier_VarParser::getTypeName($type) . ", got $vtype"); + } + + /** + * @param int $type + * @return string + */ + public static function getTypeName($type) + { + static $lookup; + if (!$lookup) { + // Lazy load the alternative lookup table + $lookup = array_flip(HTMLPurifier_VarParser::$types); + } + if (!isset($lookup[$type])) { + return 'unknown'; + } + return $lookup[$type]; + } +} + + + + + +/** + * Exception type for HTMLPurifier_VarParser + */ +class HTMLPurifier_VarParserException extends HTMLPurifier_Exception +{ + +} + + + + + +/** + * A zipper is a purely-functional data structure which contains + * a focus that can be efficiently manipulated. It is known as + * a "one-hole context". This mutable variant implements a zipper + * for a list as a pair of two arrays, laid out as follows: + * + * Base list: 1 2 3 4 [ ] 6 7 8 9 + * Front list: 1 2 3 4 + * Back list: 9 8 7 6 + * + * User is expected to keep track of the "current element" and properly + * fill it back in as necessary. (ToDo: Maybe it's more user friendly + * to implicitly track the current element?) + * + * Nota bene: the current class gets confused if you try to store NULLs + * in the list. + */ + +class HTMLPurifier_Zipper +{ + public $front, $back; + + public function __construct($front, $back) { + $this->front = $front; + $this->back = $back; + } + + /** + * Creates a zipper from an array, with a hole in the + * 0-index position. + * @param Array to zipper-ify. + * @return Tuple of zipper and element of first position. + */ + static public function fromArray($array) { + $z = new self(array(), array_reverse($array)); + $t = $z->delete(); // delete the "dummy hole" + return array($z, $t); + } + + /** + * Convert zipper back into a normal array, optionally filling in + * the hole with a value. (Usually you should supply a $t, unless you + * are at the end of the array.) + */ + public function toArray($t = NULL) { + $a = $this->front; + if ($t !== NULL) $a[] = $t; + for ($i = count($this->back)-1; $i >= 0; $i--) { + $a[] = $this->back[$i]; + } + return $a; + } + + /** + * Move hole to the next element. + * @param $t Element to fill hole with + * @return Original contents of new hole. + */ + public function next($t) { + if ($t !== NULL) array_push($this->front, $t); + return empty($this->back) ? NULL : array_pop($this->back); + } + + /** + * Iterated hole advancement. + * @param $t Element to fill hole with + * @param $i How many forward to advance hole + * @return Original contents of new hole, i away + */ + public function advance($t, $n) { + for ($i = 0; $i < $n; $i++) { + $t = $this->next($t); + } + return $t; + } + + /** + * Move hole to the previous element + * @param $t Element to fill hole with + * @return Original contents of new hole. + */ + public function prev($t) { + if ($t !== NULL) array_push($this->back, $t); + return empty($this->front) ? NULL : array_pop($this->front); + } + + /** + * Delete contents of current hole, shifting hole to + * next element. + * @return Original contents of new hole. + */ + public function delete() { + return empty($this->back) ? NULL : array_pop($this->back); + } + + /** + * Returns true if we are at the end of the list. + * @return bool + */ + public function done() { + return empty($this->back); + } + + /** + * Insert element before hole. + * @param Element to insert + */ + public function insertBefore($t) { + if ($t !== NULL) array_push($this->front, $t); + } + + /** + * Insert element after hole. + * @param Element to insert + */ + public function insertAfter($t) { + if ($t !== NULL) array_push($this->back, $t); + } + + /** + * Splice in multiple elements at hole. Functional specification + * in terms of array_splice: + * + * $arr1 = $arr; + * $old1 = array_splice($arr1, $i, $delete, $replacement); + * + * list($z, $t) = HTMLPurifier_Zipper::fromArray($arr); + * $t = $z->advance($t, $i); + * list($old2, $t) = $z->splice($t, $delete, $replacement); + * $arr2 = $z->toArray($t); + * + * assert($old1 === $old2); + * assert($arr1 === $arr2); + * + * NB: the absolute index location after this operation is + * *unchanged!* + * + * @param Current contents of hole. + */ + public function splice($t, $delete, $replacement) { + // delete + $old = array(); + $r = $t; + for ($i = $delete; $i > 0; $i--) { + $old[] = $r; + $r = $this->delete(); + } + // insert + for ($i = count($replacement)-1; $i >= 0; $i--) { + $this->insertAfter($r); + $r = $replacement[$i]; + } + return array($old, $r); + } +} + + + +/** + * Validates the HTML attribute style, otherwise known as CSS. + * @note We don't implement the whole CSS specification, so it might be + * difficult to reuse this component in the context of validating + * actual stylesheet declarations. + * @note If we were really serious about validating the CSS, we would + * tokenize the styles and then parse the tokens. Obviously, we + * are not doing that. Doing that could seriously harm performance, + * but would make these components a lot more viable for a CSS + * filtering solution. + */ +class HTMLPurifier_AttrDef_CSS extends HTMLPurifier_AttrDef +{ + + /** + * @param string $css + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($css, $config, $context) + { + $css = $this->parseCDATA($css); + + $definition = $config->getCSSDefinition(); + $allow_duplicates = $config->get("CSS.AllowDuplicates"); + + + // According to the CSS2.1 spec, the places where a + // non-delimiting semicolon can appear are in strings + // escape sequences. So here is some dumb hack to + // handle quotes. + $len = strlen($css); + $accum = ""; + $declarations = array(); + $quoted = false; + for ($i = 0; $i < $len; $i++) { + $c = strcspn($css, ";'\"", $i); + $accum .= substr($css, $i, $c); + $i += $c; + if ($i == $len) break; + $d = $css[$i]; + if ($quoted) { + $accum .= $d; + if ($d == $quoted) { + $quoted = false; + } + } else { + if ($d == ";") { + $declarations[] = $accum; + $accum = ""; + } else { + $accum .= $d; + $quoted = $d; + } + } + } + if ($accum != "") $declarations[] = $accum; + + $propvalues = array(); + $new_declarations = ''; + + /** + * Name of the current CSS property being validated. + */ + $property = false; + $context->register('CurrentCSSProperty', $property); + + foreach ($declarations as $declaration) { + if (!$declaration) { + continue; + } + if (!strpos($declaration, ':')) { + continue; + } + list($property, $value) = explode(':', $declaration, 2); + $property = trim($property); + $value = trim($value); + $ok = false; + do { + if (isset($definition->info[$property])) { + $ok = true; + break; + } + if (ctype_lower($property)) { + break; + } + $property = strtolower($property); + if (isset($definition->info[$property])) { + $ok = true; + break; + } + } while (0); + if (!$ok) { + continue; + } + // inefficient call, since the validator will do this again + if (strtolower(trim($value)) !== 'inherit') { + // inherit works for everything (but only on the base property) + $result = $definition->info[$property]->validate( + $value, + $config, + $context + ); + } else { + $result = 'inherit'; + } + if ($result === false) { + continue; + } + if ($allow_duplicates) { + $new_declarations .= "$property:$result;"; + } else { + $propvalues[$property] = $result; + } + } + + $context->destroy('CurrentCSSProperty'); + + // procedure does not write the new CSS simultaneously, so it's + // slightly inefficient, but it's the only way of getting rid of + // duplicates. Perhaps config to optimize it, but not now. + + foreach ($propvalues as $prop => $value) { + $new_declarations .= "$prop:$value;"; + } + + return $new_declarations ? $new_declarations : false; + + } + +} + + + + + +/** + * Dummy AttrDef that mimics another AttrDef, BUT it generates clones + * with make. + */ +class HTMLPurifier_AttrDef_Clone extends HTMLPurifier_AttrDef +{ + /** + * What we're cloning. + * @type HTMLPurifier_AttrDef + */ + protected $clone; + + /** + * @param HTMLPurifier_AttrDef $clone + */ + public function __construct($clone) + { + $this->clone = $clone; + } + + /** + * @param string $v + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($v, $config, $context) + { + return $this->clone->validate($v, $config, $context); + } + + /** + * @param string $string + * @return HTMLPurifier_AttrDef + */ + public function make($string) + { + return clone $this->clone; + } +} + + + + + +// Enum = Enumerated +/** + * Validates a keyword against a list of valid values. + * @warning The case-insensitive compare of this function uses PHP's + * built-in strtolower and ctype_lower functions, which may + * cause problems with international comparisons + */ +class HTMLPurifier_AttrDef_Enum extends HTMLPurifier_AttrDef +{ + + /** + * Lookup table of valid values. + * @type array + * @todo Make protected + */ + public $valid_values = array(); + + /** + * Bool indicating whether or not enumeration is case sensitive. + * @note In general this is always case insensitive. + */ + protected $case_sensitive = false; // values according to W3C spec + + /** + * @param array $valid_values List of valid values + * @param bool $case_sensitive Whether or not case sensitive + */ + public function __construct($valid_values = array(), $case_sensitive = false) + { + $this->valid_values = array_flip($valid_values); + $this->case_sensitive = $case_sensitive; + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + $string = trim($string); + if (!$this->case_sensitive) { + // we may want to do full case-insensitive libraries + $string = ctype_lower($string) ? $string : strtolower($string); + } + $result = isset($this->valid_values[$string]); + + return $result ? $string : false; + } + + /** + * @param string $string In form of comma-delimited list of case-insensitive + * valid values. Example: "foo,bar,baz". Prepend "s:" to make + * case sensitive + * @return HTMLPurifier_AttrDef_Enum + */ + public function make($string) + { + if (strlen($string) > 2 && $string[0] == 's' && $string[1] == ':') { + $string = substr($string, 2); + $sensitive = true; + } else { + $sensitive = false; + } + $values = explode(',', $string); + return new HTMLPurifier_AttrDef_Enum($values, $sensitive); + } +} + + + + + +/** + * Validates an integer. + * @note While this class was modeled off the CSS definition, no currently + * allowed CSS uses this type. The properties that do are: widows, + * orphans, z-index, counter-increment, counter-reset. Some of the + * HTML attributes, however, find use for a non-negative version of this. + */ +class HTMLPurifier_AttrDef_Integer extends HTMLPurifier_AttrDef +{ + + /** + * Whether or not negative values are allowed. + * @type bool + */ + protected $negative = true; + + /** + * Whether or not zero is allowed. + * @type bool + */ + protected $zero = true; + + /** + * Whether or not positive values are allowed. + * @type bool + */ + protected $positive = true; + + /** + * @param $negative Bool indicating whether or not negative values are allowed + * @param $zero Bool indicating whether or not zero is allowed + * @param $positive Bool indicating whether or not positive values are allowed + */ + public function __construct($negative = true, $zero = true, $positive = true) + { + $this->negative = $negative; + $this->zero = $zero; + $this->positive = $positive; + } + + /** + * @param string $integer + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($integer, $config, $context) + { + $integer = $this->parseCDATA($integer); + if ($integer === '') { + return false; + } + + // we could possibly simply typecast it to integer, but there are + // certain fringe cases that must not return an integer. + + // clip leading sign + if ($this->negative && $integer[0] === '-') { + $digits = substr($integer, 1); + if ($digits === '0') { + $integer = '0'; + } // rm minus sign for zero + } elseif ($this->positive && $integer[0] === '+') { + $digits = $integer = substr($integer, 1); // rm unnecessary plus + } else { + $digits = $integer; + } + + // test if it's numeric + if (!ctype_digit($digits)) { + return false; + } + + // perform scope tests + if (!$this->zero && $integer == 0) { + return false; + } + if (!$this->positive && $integer > 0) { + return false; + } + if (!$this->negative && $integer < 0) { + return false; + } + + return $integer; + } +} + + + + + +/** + * Validates the HTML attribute lang, effectively a language code. + * @note Built according to RFC 3066, which obsoleted RFC 1766 + */ +class HTMLPurifier_AttrDef_Lang extends HTMLPurifier_AttrDef +{ + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + $string = trim($string); + if (!$string) { + return false; + } + + $subtags = explode('-', $string); + $num_subtags = count($subtags); + + if ($num_subtags == 0) { // sanity check + return false; + } + + // process primary subtag : $subtags[0] + $length = strlen($subtags[0]); + switch ($length) { + case 0: + return false; + case 1: + if (!($subtags[0] == 'x' || $subtags[0] == 'i')) { + return false; + } + break; + case 2: + case 3: + if (!ctype_alpha($subtags[0])) { + return false; + } elseif (!ctype_lower($subtags[0])) { + $subtags[0] = strtolower($subtags[0]); + } + break; + default: + return false; + } + + $new_string = $subtags[0]; + if ($num_subtags == 1) { + return $new_string; + } + + // process second subtag : $subtags[1] + $length = strlen($subtags[1]); + if ($length == 0 || ($length == 1 && $subtags[1] != 'x') || $length > 8 || !ctype_alnum($subtags[1])) { + return $new_string; + } + if (!ctype_lower($subtags[1])) { + $subtags[1] = strtolower($subtags[1]); + } + + $new_string .= '-' . $subtags[1]; + if ($num_subtags == 2) { + return $new_string; + } + + // process all other subtags, index 2 and up + for ($i = 2; $i < $num_subtags; $i++) { + $length = strlen($subtags[$i]); + if ($length == 0 || $length > 8 || !ctype_alnum($subtags[$i])) { + return $new_string; + } + if (!ctype_lower($subtags[$i])) { + $subtags[$i] = strtolower($subtags[$i]); + } + $new_string .= '-' . $subtags[$i]; + } + return $new_string; + } +} + + + + + +/** + * Decorator that, depending on a token, switches between two definitions. + */ +class HTMLPurifier_AttrDef_Switch +{ + + /** + * @type string + */ + protected $tag; + + /** + * @type HTMLPurifier_AttrDef + */ + protected $withTag; + + /** + * @type HTMLPurifier_AttrDef + */ + protected $withoutTag; + + /** + * @param string $tag Tag name to switch upon + * @param HTMLPurifier_AttrDef $with_tag Call if token matches tag + * @param HTMLPurifier_AttrDef $without_tag Call if token doesn't match, or there is no token + */ + public function __construct($tag, $with_tag, $without_tag) + { + $this->tag = $tag; + $this->withTag = $with_tag; + $this->withoutTag = $without_tag; + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + $token = $context->get('CurrentToken', true); + if (!$token || $token->name !== $this->tag) { + return $this->withoutTag->validate($string, $config, $context); + } else { + return $this->withTag->validate($string, $config, $context); + } + } +} + + + + + +/** + * Validates arbitrary text according to the HTML spec. + */ +class HTMLPurifier_AttrDef_Text extends HTMLPurifier_AttrDef +{ + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + return $this->parseCDATA($string); + } +} + + + + + +/** + * Validates a URI as defined by RFC 3986. + * @note Scheme-specific mechanics deferred to HTMLPurifier_URIScheme + */ +class HTMLPurifier_AttrDef_URI extends HTMLPurifier_AttrDef +{ + + /** + * @type HTMLPurifier_URIParser + */ + protected $parser; + + /** + * @type bool + */ + protected $embedsResource; + + /** + * @param bool $embeds_resource Does the URI here result in an extra HTTP request? + */ + public function __construct($embeds_resource = false) + { + $this->parser = new HTMLPurifier_URIParser(); + $this->embedsResource = (bool)$embeds_resource; + } + + /** + * @param string $string + * @return HTMLPurifier_AttrDef_URI + */ + public function make($string) + { + $embeds = ($string === 'embedded'); + return new HTMLPurifier_AttrDef_URI($embeds); + } + + /** + * @param string $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($uri, $config, $context) + { + if ($config->get('URI.Disable')) { + return false; + } + + $uri = $this->parseCDATA($uri); + + // parse the URI + $uri = $this->parser->parse($uri); + if ($uri === false) { + return false; + } + + // add embedded flag to context for validators + $context->register('EmbeddedURI', $this->embedsResource); + + $ok = false; + do { + + // generic validation + $result = $uri->validate($config, $context); + if (!$result) { + break; + } + + // chained filtering + $uri_def = $config->getDefinition('URI'); + $result = $uri_def->filter($uri, $config, $context); + if (!$result) { + break; + } + + // scheme-specific validation + $scheme_obj = $uri->getSchemeObj($config, $context); + if (!$scheme_obj) { + break; + } + if ($this->embedsResource && !$scheme_obj->browsable) { + break; + } + $result = $scheme_obj->validate($uri, $config, $context); + if (!$result) { + break; + } + + // Post chained filtering + $result = $uri_def->postFilter($uri, $config, $context); + if (!$result) { + break; + } + + // survived gauntlet + $ok = true; + + } while (false); + + $context->destroy('EmbeddedURI'); + if (!$ok) { + return false; + } + // back to string + return $uri->toString(); + } +} + + + + + +/** + * Validates a number as defined by the CSS spec. + */ +class HTMLPurifier_AttrDef_CSS_Number extends HTMLPurifier_AttrDef +{ + + /** + * Indicates whether or not only positive values are allowed. + * @type bool + */ + protected $non_negative = false; + + /** + * @param bool $non_negative indicates whether negatives are forbidden + */ + public function __construct($non_negative = false) + { + $this->non_negative = $non_negative; + } + + /** + * @param string $number + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return string|bool + * @warning Some contexts do not pass $config, $context. These + * variables should not be used without checking HTMLPurifier_Length + */ + public function validate($number, $config, $context) + { + $number = $this->parseCDATA($number); + + if ($number === '') { + return false; + } + if ($number === '0') { + return '0'; + } + + $sign = ''; + switch ($number[0]) { + case '-': + if ($this->non_negative) { + return false; + } + $sign = '-'; + case '+': + $number = substr($number, 1); + } + + if (ctype_digit($number)) { + $number = ltrim($number, '0'); + return $number ? $sign . $number : '0'; + } + + // Period is the only non-numeric character allowed + if (strpos($number, '.') === false) { + return false; + } + + list($left, $right) = explode('.', $number, 2); + + if ($left === '' && $right === '') { + return false; + } + if ($left !== '' && !ctype_digit($left)) { + return false; + } + + $left = ltrim($left, '0'); + $right = rtrim($right, '0'); + + if ($right === '') { + return $left ? $sign . $left : '0'; + } elseif (!ctype_digit($right)) { + return false; + } + return $sign . $left . '.' . $right; + } +} + + + + + +class HTMLPurifier_AttrDef_CSS_AlphaValue extends HTMLPurifier_AttrDef_CSS_Number +{ + + public function __construct() + { + parent::__construct(false); // opacity is non-negative, but we will clamp it + } + + /** + * @param string $number + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return string + */ + public function validate($number, $config, $context) + { + $result = parent::validate($number, $config, $context); + if ($result === false) { + return $result; + } + $float = (float)$result; + if ($float < 0.0) { + $result = '0'; + } + if ($float > 1.0) { + $result = '1'; + } + return $result; + } +} + + + + + +/** + * Validates shorthand CSS property background. + * @warning Does not support url tokens that have internal spaces. + */ +class HTMLPurifier_AttrDef_CSS_Background extends HTMLPurifier_AttrDef +{ + + /** + * Local copy of component validators. + * @type HTMLPurifier_AttrDef[] + * @note See HTMLPurifier_AttrDef_Font::$info for a similar impl. + */ + protected $info; + + /** + * @param HTMLPurifier_Config $config + */ + public function __construct($config) + { + $def = $config->getCSSDefinition(); + $this->info['background-color'] = $def->info['background-color']; + $this->info['background-image'] = $def->info['background-image']; + $this->info['background-repeat'] = $def->info['background-repeat']; + $this->info['background-attachment'] = $def->info['background-attachment']; + $this->info['background-position'] = $def->info['background-position']; + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + // regular pre-processing + $string = $this->parseCDATA($string); + if ($string === '') { + return false; + } + + // munge rgb() decl if necessary + $string = $this->mungeRgb($string); + + // assumes URI doesn't have spaces in it + $bits = explode(' ', $string); // bits to process + + $caught = array(); + $caught['color'] = false; + $caught['image'] = false; + $caught['repeat'] = false; + $caught['attachment'] = false; + $caught['position'] = false; + + $i = 0; // number of catches + + foreach ($bits as $bit) { + if ($bit === '') { + continue; + } + foreach ($caught as $key => $status) { + if ($key != 'position') { + if ($status !== false) { + continue; + } + $r = $this->info['background-' . $key]->validate($bit, $config, $context); + } else { + $r = $bit; + } + if ($r === false) { + continue; + } + if ($key == 'position') { + if ($caught[$key] === false) { + $caught[$key] = ''; + } + $caught[$key] .= $r . ' '; + } else { + $caught[$key] = $r; + } + $i++; + break; + } + } + + if (!$i) { + return false; + } + if ($caught['position'] !== false) { + $caught['position'] = $this->info['background-position']-> + validate($caught['position'], $config, $context); + } + + $ret = array(); + foreach ($caught as $value) { + if ($value === false) { + continue; + } + $ret[] = $value; + } + + if (empty($ret)) { + return false; + } + return implode(' ', $ret); + } +} + + + + + +/* W3C says: + [ // adjective and number must be in correct order, even if + // you could switch them without introducing ambiguity. + // some browsers support that syntax + [ + | | left | center | right + ] + [ + | | top | center | bottom + ]? + ] | + [ // this signifies that the vertical and horizontal adjectives + // can be arbitrarily ordered, however, there can only be two, + // one of each, or none at all + [ + left | center | right + ] || + [ + top | center | bottom + ] + ] + top, left = 0% + center, (none) = 50% + bottom, right = 100% +*/ + +/* QuirksMode says: + keyword + length/percentage must be ordered correctly, as per W3C + + Internet Explorer and Opera, however, support arbitrary ordering. We + should fix it up. + + Minor issue though, not strictly necessary. +*/ + +// control freaks may appreciate the ability to convert these to +// percentages or something, but it's not necessary + +/** + * Validates the value of background-position. + */ +class HTMLPurifier_AttrDef_CSS_BackgroundPosition extends HTMLPurifier_AttrDef +{ + + /** + * @type HTMLPurifier_AttrDef_CSS_Length + */ + protected $length; + + /** + * @type HTMLPurifier_AttrDef_CSS_Percentage + */ + protected $percentage; + + public function __construct() + { + $this->length = new HTMLPurifier_AttrDef_CSS_Length(); + $this->percentage = new HTMLPurifier_AttrDef_CSS_Percentage(); + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + $string = $this->parseCDATA($string); + $bits = explode(' ', $string); + + $keywords = array(); + $keywords['h'] = false; // left, right + $keywords['v'] = false; // top, bottom + $keywords['ch'] = false; // center (first word) + $keywords['cv'] = false; // center (second word) + $measures = array(); + + $i = 0; + + $lookup = array( + 'top' => 'v', + 'bottom' => 'v', + 'left' => 'h', + 'right' => 'h', + 'center' => 'c' + ); + + foreach ($bits as $bit) { + if ($bit === '') { + continue; + } + + // test for keyword + $lbit = ctype_lower($bit) ? $bit : strtolower($bit); + if (isset($lookup[$lbit])) { + $status = $lookup[$lbit]; + if ($status == 'c') { + if ($i == 0) { + $status = 'ch'; + } else { + $status = 'cv'; + } + } + $keywords[$status] = $lbit; + $i++; + } + + // test for length + $r = $this->length->validate($bit, $config, $context); + if ($r !== false) { + $measures[] = $r; + $i++; + } + + // test for percentage + $r = $this->percentage->validate($bit, $config, $context); + if ($r !== false) { + $measures[] = $r; + $i++; + } + } + + if (!$i) { + return false; + } // no valid values were caught + + $ret = array(); + + // first keyword + if ($keywords['h']) { + $ret[] = $keywords['h']; + } elseif ($keywords['ch']) { + $ret[] = $keywords['ch']; + $keywords['cv'] = false; // prevent re-use: center = center center + } elseif (count($measures)) { + $ret[] = array_shift($measures); + } + + if ($keywords['v']) { + $ret[] = $keywords['v']; + } elseif ($keywords['cv']) { + $ret[] = $keywords['cv']; + } elseif (count($measures)) { + $ret[] = array_shift($measures); + } + + if (empty($ret)) { + return false; + } + return implode(' ', $ret); + } +} + + + + + +/** + * Validates the border property as defined by CSS. + */ +class HTMLPurifier_AttrDef_CSS_Border extends HTMLPurifier_AttrDef +{ + + /** + * Local copy of properties this property is shorthand for. + * @type HTMLPurifier_AttrDef[] + */ + protected $info = array(); + + /** + * @param HTMLPurifier_Config $config + */ + public function __construct($config) + { + $def = $config->getCSSDefinition(); + $this->info['border-width'] = $def->info['border-width']; + $this->info['border-style'] = $def->info['border-style']; + $this->info['border-top-color'] = $def->info['border-top-color']; + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + $string = $this->parseCDATA($string); + $string = $this->mungeRgb($string); + $bits = explode(' ', $string); + $done = array(); // segments we've finished + $ret = ''; // return value + foreach ($bits as $bit) { + foreach ($this->info as $propname => $validator) { + if (isset($done[$propname])) { + continue; + } + $r = $validator->validate($bit, $config, $context); + if ($r !== false) { + $ret .= $r . ' '; + $done[$propname] = true; + break; + } + } + } + return rtrim($ret); + } +} + + + + + +/** + * Validates Color as defined by CSS. + */ +class HTMLPurifier_AttrDef_CSS_Color extends HTMLPurifier_AttrDef +{ + + /** + * @type HTMLPurifier_AttrDef_CSS_AlphaValue + */ + protected $alpha; + + public function __construct() + { + $this->alpha = new HTMLPurifier_AttrDef_CSS_AlphaValue(); + } + + /** + * @param string $color + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($color, $config, $context) + { + static $colors = null; + if ($colors === null) { + $colors = $config->get('Core.ColorKeywords'); + } + + $color = trim($color); + if ($color === '') { + return false; + } + + $lower = strtolower($color); + if (isset($colors[$lower])) { + return $colors[$lower]; + } + + if (preg_match('#(rgb|rgba|hsl|hsla)\(#', $color, $matches) === 1) { + $length = strlen($color); + if (strpos($color, ')') !== $length - 1) { + return false; + } + + // get used function : rgb, rgba, hsl or hsla + $function = $matches[1]; + + $parameters_size = 3; + $alpha_channel = false; + if (substr($function, -1) === 'a') { + $parameters_size = 4; + $alpha_channel = true; + } + + /* + * Allowed types for values : + * parameter_position => [type => max_value] + */ + $allowed_types = array( + 1 => array('percentage' => 100, 'integer' => 255), + 2 => array('percentage' => 100, 'integer' => 255), + 3 => array('percentage' => 100, 'integer' => 255), + ); + $allow_different_types = false; + + if (strpos($function, 'hsl') !== false) { + $allowed_types = array( + 1 => array('integer' => 360), + 2 => array('percentage' => 100), + 3 => array('percentage' => 100), + ); + $allow_different_types = true; + } + + $values = trim(str_replace($function, '', $color), ' ()'); + + $parts = explode(',', $values); + if (count($parts) !== $parameters_size) { + return false; + } + + $type = false; + $new_parts = array(); + $i = 0; + + foreach ($parts as $part) { + $i++; + $part = trim($part); + + if ($part === '') { + return false; + } + + // different check for alpha channel + if ($alpha_channel === true && $i === count($parts)) { + $result = $this->alpha->validate($part, $config, $context); + + if ($result === false) { + return false; + } + + $new_parts[] = (string)$result; + continue; + } + + if (substr($part, -1) === '%') { + $current_type = 'percentage'; + } else { + $current_type = 'integer'; + } + + if (!array_key_exists($current_type, $allowed_types[$i])) { + return false; + } + + if (!$type) { + $type = $current_type; + } + + if ($allow_different_types === false && $type != $current_type) { + return false; + } + + $max_value = $allowed_types[$i][$current_type]; + + if ($current_type == 'integer') { + // Return value between range 0 -> $max_value + $new_parts[] = (int)max(min($part, $max_value), 0); + } elseif ($current_type == 'percentage') { + $new_parts[] = (float)max(min(rtrim($part, '%'), $max_value), 0) . '%'; + } + } + + $new_values = implode(',', $new_parts); + + $color = $function . '(' . $new_values . ')'; + } else { + // hexadecimal handling + if ($color[0] === '#') { + $hex = substr($color, 1); + } else { + $hex = $color; + $color = '#' . $color; + } + $length = strlen($hex); + if ($length !== 3 && $length !== 6) { + return false; + } + if (!ctype_xdigit($hex)) { + return false; + } + } + return $color; + } + +} + + + + + +/** + * Allows multiple validators to attempt to validate attribute. + * + * Composite is just what it sounds like: a composite of many validators. + * This means that multiple HTMLPurifier_AttrDef objects will have a whack + * at the string. If one of them passes, that's what is returned. This is + * especially useful for CSS values, which often are a choice between + * an enumerated set of predefined values or a flexible data type. + */ +class HTMLPurifier_AttrDef_CSS_Composite extends HTMLPurifier_AttrDef +{ + + /** + * List of objects that may process strings. + * @type HTMLPurifier_AttrDef[] + * @todo Make protected + */ + public $defs; + + /** + * @param HTMLPurifier_AttrDef[] $defs List of HTMLPurifier_AttrDef objects + */ + public function __construct($defs) + { + $this->defs = $defs; + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + foreach ($this->defs as $i => $def) { + $result = $this->defs[$i]->validate($string, $config, $context); + if ($result !== false) { + return $result; + } + } + return false; + } +} + + + + + +/** + * Decorator which enables CSS properties to be disabled for specific elements. + */ +class HTMLPurifier_AttrDef_CSS_DenyElementDecorator extends HTMLPurifier_AttrDef +{ + /** + * @type HTMLPurifier_AttrDef + */ + public $def; + /** + * @type string + */ + public $element; + + /** + * @param HTMLPurifier_AttrDef $def Definition to wrap + * @param string $element Element to deny + */ + public function __construct($def, $element) + { + $this->def = $def; + $this->element = $element; + } + + /** + * Checks if CurrentToken is set and equal to $this->element + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + $token = $context->get('CurrentToken', true); + if ($token && $token->name == $this->element) { + return false; + } + return $this->def->validate($string, $config, $context); + } +} + + + + + +/** + * Microsoft's proprietary filter: CSS property + * @note Currently supports the alpha filter. In the future, this will + * probably need an extensible framework + */ +class HTMLPurifier_AttrDef_CSS_Filter extends HTMLPurifier_AttrDef +{ + /** + * @type HTMLPurifier_AttrDef_Integer + */ + protected $intValidator; + + public function __construct() + { + $this->intValidator = new HTMLPurifier_AttrDef_Integer(); + } + + /** + * @param string $value + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($value, $config, $context) + { + $value = $this->parseCDATA($value); + if ($value === 'none') { + return $value; + } + // if we looped this we could support multiple filters + $function_length = strcspn($value, '('); + $function = trim(substr($value, 0, $function_length)); + if ($function !== 'alpha' && + $function !== 'Alpha' && + $function !== 'progid:DXImageTransform.Microsoft.Alpha' + ) { + return false; + } + $cursor = $function_length + 1; + $parameters_length = strcspn($value, ')', $cursor); + $parameters = substr($value, $cursor, $parameters_length); + $params = explode(',', $parameters); + $ret_params = array(); + $lookup = array(); + foreach ($params as $param) { + list($key, $value) = explode('=', $param); + $key = trim($key); + $value = trim($value); + if (isset($lookup[$key])) { + continue; + } + if ($key !== 'opacity') { + continue; + } + $value = $this->intValidator->validate($value, $config, $context); + if ($value === false) { + continue; + } + $int = (int)$value; + if ($int > 100) { + $value = '100'; + } + if ($int < 0) { + $value = '0'; + } + $ret_params[] = "$key=$value"; + $lookup[$key] = true; + } + $ret_parameters = implode(',', $ret_params); + $ret_function = "$function($ret_parameters)"; + return $ret_function; + } +} + + + + + +/** + * Validates shorthand CSS property font. + */ +class HTMLPurifier_AttrDef_CSS_Font extends HTMLPurifier_AttrDef +{ + + /** + * Local copy of validators + * @type HTMLPurifier_AttrDef[] + * @note If we moved specific CSS property definitions to their own + * classes instead of having them be assembled at run time by + * CSSDefinition, this wouldn't be necessary. We'd instantiate + * our own copies. + */ + protected $info = array(); + + /** + * @param HTMLPurifier_Config $config + */ + public function __construct($config) + { + $def = $config->getCSSDefinition(); + $this->info['font-style'] = $def->info['font-style']; + $this->info['font-variant'] = $def->info['font-variant']; + $this->info['font-weight'] = $def->info['font-weight']; + $this->info['font-size'] = $def->info['font-size']; + $this->info['line-height'] = $def->info['line-height']; + $this->info['font-family'] = $def->info['font-family']; + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + static $system_fonts = array( + 'caption' => true, + 'icon' => true, + 'menu' => true, + 'message-box' => true, + 'small-caption' => true, + 'status-bar' => true + ); + + // regular pre-processing + $string = $this->parseCDATA($string); + if ($string === '') { + return false; + } + + // check if it's one of the keywords + $lowercase_string = strtolower($string); + if (isset($system_fonts[$lowercase_string])) { + return $lowercase_string; + } + + $bits = explode(' ', $string); // bits to process + $stage = 0; // this indicates what we're looking for + $caught = array(); // which stage 0 properties have we caught? + $stage_1 = array('font-style', 'font-variant', 'font-weight'); + $final = ''; // output + + for ($i = 0, $size = count($bits); $i < $size; $i++) { + if ($bits[$i] === '') { + continue; + } + switch ($stage) { + case 0: // attempting to catch font-style, font-variant or font-weight + foreach ($stage_1 as $validator_name) { + if (isset($caught[$validator_name])) { + continue; + } + $r = $this->info[$validator_name]->validate( + $bits[$i], + $config, + $context + ); + if ($r !== false) { + $final .= $r . ' '; + $caught[$validator_name] = true; + break; + } + } + // all three caught, continue on + if (count($caught) >= 3) { + $stage = 1; + } + if ($r !== false) { + break; + } + case 1: // attempting to catch font-size and perhaps line-height + $found_slash = false; + if (strpos($bits[$i], '/') !== false) { + list($font_size, $line_height) = + explode('/', $bits[$i]); + if ($line_height === '') { + // ooh, there's a space after the slash! + $line_height = false; + $found_slash = true; + } + } else { + $font_size = $bits[$i]; + $line_height = false; + } + $r = $this->info['font-size']->validate( + $font_size, + $config, + $context + ); + if ($r !== false) { + $final .= $r; + // attempt to catch line-height + if ($line_height === false) { + // we need to scroll forward + for ($j = $i + 1; $j < $size; $j++) { + if ($bits[$j] === '') { + continue; + } + if ($bits[$j] === '/') { + if ($found_slash) { + return false; + } else { + $found_slash = true; + continue; + } + } + $line_height = $bits[$j]; + break; + } + } else { + // slash already found + $found_slash = true; + $j = $i; + } + if ($found_slash) { + $i = $j; + $r = $this->info['line-height']->validate( + $line_height, + $config, + $context + ); + if ($r !== false) { + $final .= '/' . $r; + } + } + $final .= ' '; + $stage = 2; + break; + } + return false; + case 2: // attempting to catch font-family + $font_family = + implode(' ', array_slice($bits, $i, $size - $i)); + $r = $this->info['font-family']->validate( + $font_family, + $config, + $context + ); + if ($r !== false) { + $final .= $r . ' '; + // processing completed successfully + return rtrim($final); + } + return false; + } + } + return false; + } +} + + + + + +/** + * Validates a font family list according to CSS spec + */ +class HTMLPurifier_AttrDef_CSS_FontFamily extends HTMLPurifier_AttrDef +{ + + protected $mask = null; + + public function __construct() + { + $this->mask = '_- '; + for ($c = 'a'; $c <= 'z'; $c++) { + $this->mask .= $c; + } + for ($c = 'A'; $c <= 'Z'; $c++) { + $this->mask .= $c; + } + for ($c = '0'; $c <= '9'; $c++) { + $this->mask .= $c; + } // cast-y, but should be fine + // special bytes used by UTF-8 + for ($i = 0x80; $i <= 0xFF; $i++) { + // We don't bother excluding invalid bytes in this range, + // because the our restriction of well-formed UTF-8 will + // prevent these from ever occurring. + $this->mask .= chr($i); + } + + /* + PHP's internal strcspn implementation is + O(length of string * length of mask), making it inefficient + for large masks. However, it's still faster than + preg_match 8) + for (p = s1;;) { + spanp = s2; + do { + if (*spanp == c || p == s1_end) { + return p - s1; + } + } while (spanp++ < (s2_end - 1)); + c = *++p; + } + */ + // possible optimization: invert the mask. + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + static $generic_names = array( + 'serif' => true, + 'sans-serif' => true, + 'monospace' => true, + 'fantasy' => true, + 'cursive' => true + ); + $allowed_fonts = $config->get('CSS.AllowedFonts'); + + // assume that no font names contain commas in them + $fonts = explode(',', $string); + $final = ''; + foreach ($fonts as $font) { + $font = trim($font); + if ($font === '') { + continue; + } + // match a generic name + if (isset($generic_names[$font])) { + if ($allowed_fonts === null || isset($allowed_fonts[$font])) { + $final .= $font . ', '; + } + continue; + } + // match a quoted name + if ($font[0] === '"' || $font[0] === "'") { + $length = strlen($font); + if ($length <= 2) { + continue; + } + $quote = $font[0]; + if ($font[$length - 1] !== $quote) { + continue; + } + $font = substr($font, 1, $length - 2); + } + + $font = $this->expandCSSEscape($font); + + // $font is a pure representation of the font name + + if ($allowed_fonts !== null && !isset($allowed_fonts[$font])) { + continue; + } + + if (ctype_alnum($font) && $font !== '') { + // very simple font, allow it in unharmed + $final .= $font . ', '; + continue; + } + + // bugger out on whitespace. form feed (0C) really + // shouldn't show up regardless + $font = str_replace(array("\n", "\t", "\r", "\x0C"), ' ', $font); + + // Here, there are various classes of characters which need + // to be treated differently: + // - Alphanumeric characters are essentially safe. We + // handled these above. + // - Spaces require quoting, though most parsers will do + // the right thing if there aren't any characters that + // can be misinterpreted + // - Dashes rarely occur, but they fairly unproblematic + // for parsing/rendering purposes. + // The above characters cover the majority of Western font + // names. + // - Arbitrary Unicode characters not in ASCII. Because + // most parsers give little thought to Unicode, treatment + // of these codepoints is basically uniform, even for + // punctuation-like codepoints. These characters can + // show up in non-Western pages and are supported by most + // major browsers, for example: "MS 明朝" is a + // legitimate font-name + // . See + // the CSS3 spec for more examples: + // + // You can see live samples of these on the Internet: + // + // However, most of these fonts have ASCII equivalents: + // for example, 'MS Mincho', and it's considered + // professional to use ASCII font names instead of + // Unicode font names. Thanks Takeshi Terada for + // providing this information. + // The following characters, to my knowledge, have not been + // used to name font names. + // - Single quote. While theoretically you might find a + // font name that has a single quote in its name (serving + // as an apostrophe, e.g. Dave's Scribble), I haven't + // been able to find any actual examples of this. + // Internet Explorer's cssText translation (which I + // believe is invoked by innerHTML) normalizes any + // quoting to single quotes, and fails to escape single + // quotes. (Note that this is not IE's behavior for all + // CSS properties, just some sort of special casing for + // font-family). So a single quote *cannot* be used + // safely in the font-family context if there will be an + // innerHTML/cssText translation. Note that Firefox 3.x + // does this too. + // - Double quote. In IE, these get normalized to + // single-quotes, no matter what the encoding. (Fun + // fact, in IE8, the 'content' CSS property gained + // support, where they special cased to preserve encoded + // double quotes, but still translate unadorned double + // quotes into single quotes.) So, because their + // fixpoint behavior is identical to single quotes, they + // cannot be allowed either. Firefox 3.x displays + // single-quote style behavior. + // - Backslashes are reduced by one (so \\ -> \) every + // iteration, so they cannot be used safely. This shows + // up in IE7, IE8 and FF3 + // - Semicolons, commas and backticks are handled properly. + // - The rest of the ASCII punctuation is handled properly. + // We haven't checked what browsers do to unadorned + // versions, but this is not important as long as the + // browser doesn't /remove/ surrounding quotes (as IE does + // for HTML). + // + // With these results in hand, we conclude that there are + // various levels of safety: + // - Paranoid: alphanumeric, spaces and dashes(?) + // - International: Paranoid + non-ASCII Unicode + // - Edgy: Everything except quotes, backslashes + // - NoJS: Standards compliance, e.g. sod IE. Note that + // with some judicious character escaping (since certain + // types of escaping doesn't work) this is theoretically + // OK as long as innerHTML/cssText is not called. + // We believe that international is a reasonable default + // (that we will implement now), and once we do more + // extensive research, we may feel comfortable with dropping + // it down to edgy. + + // Edgy: alphanumeric, spaces, dashes, underscores and Unicode. Use of + // str(c)spn assumes that the string was already well formed + // Unicode (which of course it is). + if (strspn($font, $this->mask) !== strlen($font)) { + continue; + } + + // Historical: + // In the absence of innerHTML/cssText, these ugly + // transforms don't pose a security risk (as \\ and \" + // might--these escapes are not supported by most browsers). + // We could try to be clever and use single-quote wrapping + // when there is a double quote present, but I have choosen + // not to implement that. (NOTE: you can reduce the amount + // of escapes by one depending on what quoting style you use) + // $font = str_replace('\\', '\\5C ', $font); + // $font = str_replace('"', '\\22 ', $font); + // $font = str_replace("'", '\\27 ', $font); + + // font possibly with spaces, requires quoting + $final .= "'$font', "; + } + $final = rtrim($final, ', '); + if ($final === '') { + return false; + } + return $final; + } + +} + + + + + +/** + * Validates based on {ident} CSS grammar production + */ +class HTMLPurifier_AttrDef_CSS_Ident extends HTMLPurifier_AttrDef +{ + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + $string = trim($string); + + // early abort: '' and '0' (strings that convert to false) are invalid + if (!$string) { + return false; + } + + $pattern = '/^(-?[A-Za-z_][A-Za-z_\-0-9]*)$/'; + if (!preg_match($pattern, $string)) { + return false; + } + return $string; + } +} + + + + + +/** + * Decorator which enables !important to be used in CSS values. + */ +class HTMLPurifier_AttrDef_CSS_ImportantDecorator extends HTMLPurifier_AttrDef +{ + /** + * @type HTMLPurifier_AttrDef + */ + public $def; + /** + * @type bool + */ + public $allow; + + /** + * @param HTMLPurifier_AttrDef $def Definition to wrap + * @param bool $allow Whether or not to allow !important + */ + public function __construct($def, $allow = false) + { + $this->def = $def; + $this->allow = $allow; + } + + /** + * Intercepts and removes !important if necessary + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + // test for ! and important tokens + $string = trim($string); + $is_important = false; + // :TODO: optimization: test directly for !important and ! important + if (strlen($string) >= 9 && substr($string, -9) === 'important') { + $temp = rtrim(substr($string, 0, -9)); + // use a temp, because we might want to restore important + if (strlen($temp) >= 1 && substr($temp, -1) === '!') { + $string = rtrim(substr($temp, 0, -1)); + $is_important = true; + } + } + $string = $this->def->validate($string, $config, $context); + if ($this->allow && $is_important) { + $string .= ' !important'; + } + return $string; + } +} + + + + + +/** + * Represents a Length as defined by CSS. + */ +class HTMLPurifier_AttrDef_CSS_Length extends HTMLPurifier_AttrDef +{ + + /** + * @type HTMLPurifier_Length|string + */ + protected $min; + + /** + * @type HTMLPurifier_Length|string + */ + protected $max; + + /** + * @param HTMLPurifier_Length|string $min Minimum length, or null for no bound. String is also acceptable. + * @param HTMLPurifier_Length|string $max Maximum length, or null for no bound. String is also acceptable. + */ + public function __construct($min = null, $max = null) + { + $this->min = $min !== null ? HTMLPurifier_Length::make($min) : null; + $this->max = $max !== null ? HTMLPurifier_Length::make($max) : null; + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + $string = $this->parseCDATA($string); + + // Optimizations + if ($string === '') { + return false; + } + if ($string === '0') { + return '0'; + } + if (strlen($string) === 1) { + return false; + } + + $length = HTMLPurifier_Length::make($string); + if (!$length->isValid()) { + return false; + } + + if ($this->min) { + $c = $length->compareTo($this->min); + if ($c === false) { + return false; + } + if ($c < 0) { + return false; + } + } + if ($this->max) { + $c = $length->compareTo($this->max); + if ($c === false) { + return false; + } + if ($c > 0) { + return false; + } + } + return $length->toString(); + } +} + + + + + +/** + * Validates shorthand CSS property list-style. + * @warning Does not support url tokens that have internal spaces. + */ +class HTMLPurifier_AttrDef_CSS_ListStyle extends HTMLPurifier_AttrDef +{ + + /** + * Local copy of validators. + * @type HTMLPurifier_AttrDef[] + * @note See HTMLPurifier_AttrDef_CSS_Font::$info for a similar impl. + */ + protected $info; + + /** + * @param HTMLPurifier_Config $config + */ + public function __construct($config) + { + $def = $config->getCSSDefinition(); + $this->info['list-style-type'] = $def->info['list-style-type']; + $this->info['list-style-position'] = $def->info['list-style-position']; + $this->info['list-style-image'] = $def->info['list-style-image']; + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + // regular pre-processing + $string = $this->parseCDATA($string); + if ($string === '') { + return false; + } + + // assumes URI doesn't have spaces in it + $bits = explode(' ', strtolower($string)); // bits to process + + $caught = array(); + $caught['type'] = false; + $caught['position'] = false; + $caught['image'] = false; + + $i = 0; // number of catches + $none = false; + + foreach ($bits as $bit) { + if ($i >= 3) { + return; + } // optimization bit + if ($bit === '') { + continue; + } + foreach ($caught as $key => $status) { + if ($status !== false) { + continue; + } + $r = $this->info['list-style-' . $key]->validate($bit, $config, $context); + if ($r === false) { + continue; + } + if ($r === 'none') { + if ($none) { + continue; + } else { + $none = true; + } + if ($key == 'image') { + continue; + } + } + $caught[$key] = $r; + $i++; + break; + } + } + + if (!$i) { + return false; + } + + $ret = array(); + + // construct type + if ($caught['type']) { + $ret[] = $caught['type']; + } + + // construct image + if ($caught['image']) { + $ret[] = $caught['image']; + } + + // construct position + if ($caught['position']) { + $ret[] = $caught['position']; + } + + if (empty($ret)) { + return false; + } + return implode(' ', $ret); + } +} + + + + + +/** + * Framework class for strings that involve multiple values. + * + * Certain CSS properties such as border-width and margin allow multiple + * lengths to be specified. This class can take a vanilla border-width + * definition and multiply it, usually into a max of four. + * + * @note Even though the CSS specification isn't clear about it, inherit + * can only be used alone: it will never manifest as part of a multi + * shorthand declaration. Thus, this class does not allow inherit. + */ +class HTMLPurifier_AttrDef_CSS_Multiple extends HTMLPurifier_AttrDef +{ + /** + * Instance of component definition to defer validation to. + * @type HTMLPurifier_AttrDef + * @todo Make protected + */ + public $single; + + /** + * Max number of values allowed. + * @todo Make protected + */ + public $max; + + /** + * @param HTMLPurifier_AttrDef $single HTMLPurifier_AttrDef to multiply + * @param int $max Max number of values allowed (usually four) + */ + public function __construct($single, $max = 4) + { + $this->single = $single; + $this->max = $max; + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + $string = $this->mungeRgb($this->parseCDATA($string)); + if ($string === '') { + return false; + } + $parts = explode(' ', $string); // parseCDATA replaced \r, \t and \n + $length = count($parts); + $final = ''; + for ($i = 0, $num = 0; $i < $length && $num < $this->max; $i++) { + if (ctype_space($parts[$i])) { + continue; + } + $result = $this->single->validate($parts[$i], $config, $context); + if ($result !== false) { + $final .= $result . ' '; + $num++; + } + } + if ($final === '') { + return false; + } + return rtrim($final); + } +} + + + + + +/** + * Validates a Percentage as defined by the CSS spec. + */ +class HTMLPurifier_AttrDef_CSS_Percentage extends HTMLPurifier_AttrDef +{ + + /** + * Instance to defer number validation to. + * @type HTMLPurifier_AttrDef_CSS_Number + */ + protected $number_def; + + /** + * @param bool $non_negative Whether to forbid negative values + */ + public function __construct($non_negative = false) + { + $this->number_def = new HTMLPurifier_AttrDef_CSS_Number($non_negative); + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + $string = $this->parseCDATA($string); + + if ($string === '') { + return false; + } + $length = strlen($string); + if ($length === 1) { + return false; + } + if ($string[$length - 1] !== '%') { + return false; + } + + $number = substr($string, 0, $length - 1); + $number = $this->number_def->validate($number, $config, $context); + + if ($number === false) { + return false; + } + return "$number%"; + } +} + + + + + +/** + * Validates the value for the CSS property text-decoration + * @note This class could be generalized into a version that acts sort of + * like Enum except you can compound the allowed values. + */ +class HTMLPurifier_AttrDef_CSS_TextDecoration extends HTMLPurifier_AttrDef +{ + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + static $allowed_values = array( + 'line-through' => true, + 'overline' => true, + 'underline' => true, + ); + + $string = strtolower($this->parseCDATA($string)); + + if ($string === 'none') { + return $string; + } + + $parts = explode(' ', $string); + $final = ''; + foreach ($parts as $part) { + if (isset($allowed_values[$part])) { + $final .= $part . ' '; + } + } + $final = rtrim($final); + if ($final === '') { + return false; + } + return $final; + } +} + + + + + +/** + * Validates a URI in CSS syntax, which uses url('http://example.com') + * @note While theoretically speaking a URI in a CSS document could + * be non-embedded, as of CSS2 there is no such usage so we're + * generalizing it. This may need to be changed in the future. + * @warning Since HTMLPurifier_AttrDef_CSS blindly uses semicolons as + * the separator, you cannot put a literal semicolon in + * in the URI. Try percent encoding it, in that case. + */ +class HTMLPurifier_AttrDef_CSS_URI extends HTMLPurifier_AttrDef_URI +{ + + public function __construct() + { + parent::__construct(true); // always embedded + } + + /** + * @param string $uri_string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($uri_string, $config, $context) + { + // parse the URI out of the string and then pass it onto + // the parent object + + $uri_string = $this->parseCDATA($uri_string); + if (strpos($uri_string, 'url(') !== 0) { + return false; + } + $uri_string = substr($uri_string, 4); + if (strlen($uri_string) == 0) { + return false; + } + $new_length = strlen($uri_string) - 1; + if ($uri_string[$new_length] != ')') { + return false; + } + $uri = trim(substr($uri_string, 0, $new_length)); + + if (!empty($uri) && ($uri[0] == "'" || $uri[0] == '"')) { + $quote = $uri[0]; + $new_length = strlen($uri) - 1; + if ($uri[$new_length] !== $quote) { + return false; + } + $uri = substr($uri, 1, $new_length - 1); + } + + $uri = $this->expandCSSEscape($uri); + + $result = parent::validate($uri, $config, $context); + + if ($result === false) { + return false; + } + + // extra sanity check; should have been done by URI + $result = str_replace(array('"', "\\", "\n", "\x0c", "\r"), "", $result); + + // suspicious characters are ()'; we're going to percent encode + // them for safety. + $result = str_replace(array('(', ')', "'"), array('%28', '%29', '%27'), $result); + + // there's an extra bug where ampersands lose their escaping on + // an innerHTML cycle, so a very unlucky query parameter could + // then change the meaning of the URL. Unfortunately, there's + // not much we can do about that... + return "url(\"$result\")"; + } +} + + + + + +/** + * Validates a boolean attribute + */ +class HTMLPurifier_AttrDef_HTML_Bool extends HTMLPurifier_AttrDef +{ + + /** + * @type string + */ + protected $name; + + /** + * @type bool + */ + public $minimized = true; + + /** + * @param bool|string $name + */ + public function __construct($name = false) + { + $this->name = $name; + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + return $this->name; + } + + /** + * @param string $string Name of attribute + * @return HTMLPurifier_AttrDef_HTML_Bool + */ + public function make($string) + { + return new HTMLPurifier_AttrDef_HTML_Bool($string); + } +} + + + + + +/** + * Validates contents based on NMTOKENS attribute type. + */ +class HTMLPurifier_AttrDef_HTML_Nmtokens extends HTMLPurifier_AttrDef +{ + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + $string = trim($string); + + // early abort: '' and '0' (strings that convert to false) are invalid + if (!$string) { + return false; + } + + $tokens = $this->split($string, $config, $context); + $tokens = $this->filter($tokens, $config, $context); + if (empty($tokens)) { + return false; + } + return implode(' ', $tokens); + } + + /** + * Splits a space separated list of tokens into its constituent parts. + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + protected function split($string, $config, $context) + { + // OPTIMIZABLE! + // do the preg_match, capture all subpatterns for reformulation + + // we don't support U+00A1 and up codepoints or + // escaping because I don't know how to do that with regexps + // and plus it would complicate optimization efforts (you never + // see that anyway). + $pattern = '/(?:(?<=\s)|\A)' . // look behind for space or string start + '((?:--|-?[A-Za-z_])[A-Za-z_\-0-9]*)' . + '(?:(?=\s)|\z)/'; // look ahead for space or string end + preg_match_all($pattern, $string, $matches); + return $matches[1]; + } + + /** + * Template method for removing certain tokens based on arbitrary criteria. + * @note If we wanted to be really functional, we'd do an array_filter + * with a callback. But... we're not. + * @param array $tokens + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + protected function filter($tokens, $config, $context) + { + return $tokens; + } +} + + + + + +/** + * Implements special behavior for class attribute (normally NMTOKENS) + */ +class HTMLPurifier_AttrDef_HTML_Class extends HTMLPurifier_AttrDef_HTML_Nmtokens +{ + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + protected function split($string, $config, $context) + { + // really, this twiddle should be lazy loaded + $name = $config->getDefinition('HTML')->doctype->name; + if ($name == "XHTML 1.1" || $name == "XHTML 2.0") { + return parent::split($string, $config, $context); + } else { + return preg_split('/\s+/', $string); + } + } + + /** + * @param array $tokens + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + protected function filter($tokens, $config, $context) + { + $allowed = $config->get('Attr.AllowedClasses'); + $forbidden = $config->get('Attr.ForbiddenClasses'); + $ret = array(); + foreach ($tokens as $token) { + if (($allowed === null || isset($allowed[$token])) && + !isset($forbidden[$token]) && + // We need this O(n) check because of PHP's array + // implementation that casts -0 to 0. + !in_array($token, $ret, true) + ) { + $ret[] = $token; + } + } + return $ret; + } +} + + + +/** + * Validates a color according to the HTML spec. + */ +class HTMLPurifier_AttrDef_HTML_Color extends HTMLPurifier_AttrDef +{ + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + static $colors = null; + if ($colors === null) { + $colors = $config->get('Core.ColorKeywords'); + } + + $string = trim($string); + + if (empty($string)) { + return false; + } + $lower = strtolower($string); + if (isset($colors[$lower])) { + return $colors[$lower]; + } + if ($string[0] === '#') { + $hex = substr($string, 1); + } else { + $hex = $string; + } + + $length = strlen($hex); + if ($length !== 3 && $length !== 6) { + return false; + } + if (!ctype_xdigit($hex)) { + return false; + } + if ($length === 3) { + $hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2]; + } + return "#$hex"; + } +} + + + + + +/** + * Special-case enum attribute definition that lazy loads allowed frame targets + */ +class HTMLPurifier_AttrDef_HTML_FrameTarget extends HTMLPurifier_AttrDef_Enum +{ + + /** + * @type array + */ + public $valid_values = false; // uninitialized value + + /** + * @type bool + */ + protected $case_sensitive = false; + + public function __construct() + { + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + if ($this->valid_values === false) { + $this->valid_values = $config->get('Attr.AllowedFrameTargets'); + } + return parent::validate($string, $config, $context); + } +} + + + + + +/** + * Validates the HTML attribute ID. + * @warning Even though this is the id processor, it + * will ignore the directive Attr:IDBlacklist, since it will only + * go according to the ID accumulator. Since the accumulator is + * automatically generated, it will have already absorbed the + * blacklist. If you're hacking around, make sure you use load()! + */ + +class HTMLPurifier_AttrDef_HTML_ID extends HTMLPurifier_AttrDef +{ + + // selector is NOT a valid thing to use for IDREFs, because IDREFs + // *must* target IDs that exist, whereas selector #ids do not. + + /** + * Determines whether or not we're validating an ID in a CSS + * selector context. + * @type bool + */ + protected $selector; + + /** + * @param bool $selector + */ + public function __construct($selector = false) + { + $this->selector = $selector; + } + + /** + * @param string $id + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($id, $config, $context) + { + if (!$this->selector && !$config->get('Attr.EnableID')) { + return false; + } + + $id = trim($id); // trim it first + + if ($id === '') { + return false; + } + + $prefix = $config->get('Attr.IDPrefix'); + if ($prefix !== '') { + $prefix .= $config->get('Attr.IDPrefixLocal'); + // prevent re-appending the prefix + if (strpos($id, $prefix) !== 0) { + $id = $prefix . $id; + } + } elseif ($config->get('Attr.IDPrefixLocal') !== '') { + trigger_error( + '%Attr.IDPrefixLocal cannot be used unless ' . + '%Attr.IDPrefix is set', + E_USER_WARNING + ); + } + + if (!$this->selector) { + $id_accumulator =& $context->get('IDAccumulator'); + if (isset($id_accumulator->ids[$id])) { + return false; + } + } + + // we purposely avoid using regex, hopefully this is faster + + if ($config->get('Attr.ID.HTML5') === true) { + if (preg_match('/[\t\n\x0b\x0c ]/', $id)) { + return false; + } + } else { + if (ctype_alpha($id)) { + // OK + } else { + if (!ctype_alpha(@$id[0])) { + return false; + } + // primitive style of regexps, I suppose + $trim = trim( + $id, + 'A..Za..z0..9:-._' + ); + if ($trim !== '') { + return false; + } + } + } + + $regexp = $config->get('Attr.IDBlacklistRegexp'); + if ($regexp && preg_match($regexp, $id)) { + return false; + } + + if (!$this->selector) { + $id_accumulator->add($id); + } + + // if no change was made to the ID, return the result + // else, return the new id if stripping whitespace made it + // valid, or return false. + return $id; + } +} + + + + + +/** + * Validates an integer representation of pixels according to the HTML spec. + */ +class HTMLPurifier_AttrDef_HTML_Pixels extends HTMLPurifier_AttrDef +{ + + /** + * @type int + */ + protected $max; + + /** + * @param int $max + */ + public function __construct($max = null) + { + $this->max = $max; + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + $string = trim($string); + if ($string === '0') { + return $string; + } + if ($string === '') { + return false; + } + $length = strlen($string); + if (substr($string, $length - 2) == 'px') { + $string = substr($string, 0, $length - 2); + } + if (!is_numeric($string)) { + return false; + } + $int = (int)$string; + + if ($int < 0) { + return '0'; + } + + // upper-bound value, extremely high values can + // crash operating systems, see + // WARNING, above link WILL crash you if you're using Windows + + if ($this->max !== null && $int > $this->max) { + return (string)$this->max; + } + return (string)$int; + } + + /** + * @param string $string + * @return HTMLPurifier_AttrDef + */ + public function make($string) + { + if ($string === '') { + $max = null; + } else { + $max = (int)$string; + } + $class = get_class($this); + return new $class($max); + } +} + + + + + +/** + * Validates the HTML type length (not to be confused with CSS's length). + * + * This accepts integer pixels or percentages as lengths for certain + * HTML attributes. + */ + +class HTMLPurifier_AttrDef_HTML_Length extends HTMLPurifier_AttrDef_HTML_Pixels +{ + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + $string = trim($string); + if ($string === '') { + return false; + } + + $parent_result = parent::validate($string, $config, $context); + if ($parent_result !== false) { + return $parent_result; + } + + $length = strlen($string); + $last_char = $string[$length - 1]; + + if ($last_char !== '%') { + return false; + } + + $points = substr($string, 0, $length - 1); + + if (!is_numeric($points)) { + return false; + } + + $points = (int)$points; + + if ($points < 0) { + return '0%'; + } + if ($points > 100) { + return '100%'; + } + return ((string)$points) . '%'; + } +} + + + + + +/** + * Validates a rel/rev link attribute against a directive of allowed values + * @note We cannot use Enum because link types allow multiple + * values. + * @note Assumes link types are ASCII text + */ +class HTMLPurifier_AttrDef_HTML_LinkTypes extends HTMLPurifier_AttrDef +{ + + /** + * Name config attribute to pull. + * @type string + */ + protected $name; + + /** + * @param string $name + */ + public function __construct($name) + { + $configLookup = array( + 'rel' => 'AllowedRel', + 'rev' => 'AllowedRev' + ); + if (!isset($configLookup[$name])) { + trigger_error( + 'Unrecognized attribute name for link ' . + 'relationship.', + E_USER_ERROR + ); + return; + } + $this->name = $configLookup[$name]; + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + $allowed = $config->get('Attr.' . $this->name); + if (empty($allowed)) { + return false; + } + + $string = $this->parseCDATA($string); + $parts = explode(' ', $string); + + // lookup to prevent duplicates + $ret_lookup = array(); + foreach ($parts as $part) { + $part = strtolower(trim($part)); + if (!isset($allowed[$part])) { + continue; + } + $ret_lookup[$part] = true; + } + + if (empty($ret_lookup)) { + return false; + } + $string = implode(' ', array_keys($ret_lookup)); + return $string; + } +} + + + + + +/** + * Validates a MultiLength as defined by the HTML spec. + * + * A multilength is either a integer (pixel count), a percentage, or + * a relative number. + */ +class HTMLPurifier_AttrDef_HTML_MultiLength extends HTMLPurifier_AttrDef_HTML_Length +{ + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + $string = trim($string); + if ($string === '') { + return false; + } + + $parent_result = parent::validate($string, $config, $context); + if ($parent_result !== false) { + return $parent_result; + } + + $length = strlen($string); + $last_char = $string[$length - 1]; + + if ($last_char !== '*') { + return false; + } + + $int = substr($string, 0, $length - 1); + + if ($int == '') { + return '*'; + } + if (!is_numeric($int)) { + return false; + } + + $int = (int)$int; + if ($int < 0) { + return false; + } + if ($int == 0) { + return '0'; + } + if ($int == 1) { + return '*'; + } + return ((string)$int) . '*'; + } +} + + + + + +abstract class HTMLPurifier_AttrDef_URI_Email extends HTMLPurifier_AttrDef +{ + + /** + * Unpacks a mailbox into its display-name and address + * @param string $string + * @return mixed + */ + public function unpack($string) + { + // needs to be implemented + } + +} + +// sub-implementations + + + + + +/** + * Validates a host according to the IPv4, IPv6 and DNS (future) specifications. + */ +class HTMLPurifier_AttrDef_URI_Host extends HTMLPurifier_AttrDef +{ + + /** + * IPv4 sub-validator. + * @type HTMLPurifier_AttrDef_URI_IPv4 + */ + protected $ipv4; + + /** + * IPv6 sub-validator. + * @type HTMLPurifier_AttrDef_URI_IPv6 + */ + protected $ipv6; + + public function __construct() + { + $this->ipv4 = new HTMLPurifier_AttrDef_URI_IPv4(); + $this->ipv6 = new HTMLPurifier_AttrDef_URI_IPv6(); + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + $length = strlen($string); + // empty hostname is OK; it's usually semantically equivalent: + // the default host as defined by a URI scheme is used: + // + // If the URI scheme defines a default for host, then that + // default applies when the host subcomponent is undefined + // or when the registered name is empty (zero length). + if ($string === '') { + return ''; + } + if ($length > 1 && $string[0] === '[' && $string[$length - 1] === ']') { + //IPv6 + $ip = substr($string, 1, $length - 2); + $valid = $this->ipv6->validate($ip, $config, $context); + if ($valid === false) { + return false; + } + return '[' . $valid . ']'; + } + + // need to do checks on unusual encodings too + $ipv4 = $this->ipv4->validate($string, $config, $context); + if ($ipv4 !== false) { + return $ipv4; + } + + // A regular domain name. + + // This doesn't match I18N domain names, but we don't have proper IRI support, + // so force users to insert Punycode. + + // There is not a good sense in which underscores should be + // allowed, since it's technically not! (And if you go as + // far to allow everything as specified by the DNS spec... + // well, that's literally everything, modulo some space limits + // for the components and the overall name (which, by the way, + // we are NOT checking!). So we (arbitrarily) decide this: + // let's allow underscores wherever we would have allowed + // hyphens, if they are enabled. This is a pretty good match + // for browser behavior, for example, a large number of browsers + // cannot handle foo_.example.com, but foo_bar.example.com is + // fairly well supported. + $underscore = $config->get('Core.AllowHostnameUnderscore') ? '_' : ''; + + // Based off of RFC 1738, but amended so that + // as per RFC 3696, the top label need only not be all numeric. + // The productions describing this are: + $a = '[a-z]'; // alpha + $an = '[a-z0-9]'; // alphanum + $and = "[a-z0-9-$underscore]"; // alphanum | "-" + // domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum + $domainlabel = "$an(?:$and*$an)?"; + // AMENDED as per RFC 3696 + // toplabel = alphanum | alphanum *( alphanum | "-" ) alphanum + // side condition: not all numeric + $toplabel = "$an(?:$and*$an)?"; + // hostname = *( domainlabel "." ) toplabel [ "." ] + if (preg_match("/^(?:$domainlabel\.)*($toplabel)\.?$/i", $string, $matches)) { + if (!ctype_digit($matches[1])) { + return $string; + } + } + + // PHP 5.3 and later support this functionality natively + if (function_exists('idn_to_ascii')) { + if (defined('IDNA_NONTRANSITIONAL_TO_ASCII') && defined('INTL_IDNA_VARIANT_UTS46')) { + $string = idn_to_ascii($string, IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46); + } else { + $string = idn_to_ascii($string); + } + + // If we have Net_IDNA2 support, we can support IRIs by + // punycoding them. (This is the most portable thing to do, + // since otherwise we have to assume browsers support + } elseif ($config->get('Core.EnableIDNA')) { + $idna = new Net_IDNA2(array('encoding' => 'utf8', 'overlong' => false, 'strict' => true)); + // we need to encode each period separately + $parts = explode('.', $string); + try { + $new_parts = array(); + foreach ($parts as $part) { + $encodable = false; + for ($i = 0, $c = strlen($part); $i < $c; $i++) { + if (ord($part[$i]) > 0x7a) { + $encodable = true; + break; + } + } + if (!$encodable) { + $new_parts[] = $part; + } else { + $new_parts[] = $idna->encode($part); + } + } + $string = implode('.', $new_parts); + } catch (Exception $e) { + // XXX error reporting + } + } + // Try again + if (preg_match("/^($domainlabel\.)*$toplabel\.?$/i", $string)) { + return $string; + } + return false; + } +} + + + + + +/** + * Validates an IPv4 address + * @author Feyd @ forums.devnetwork.net (public domain) + */ +class HTMLPurifier_AttrDef_URI_IPv4 extends HTMLPurifier_AttrDef +{ + + /** + * IPv4 regex, protected so that IPv6 can reuse it. + * @type string + */ + protected $ip4; + + /** + * @param string $aIP + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($aIP, $config, $context) + { + if (!$this->ip4) { + $this->_loadRegex(); + } + + if (preg_match('#^' . $this->ip4 . '$#s', $aIP)) { + return $aIP; + } + return false; + } + + /** + * Lazy load function to prevent regex from being stuffed in + * cache. + */ + protected function _loadRegex() + { + $oct = '(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]|[0-9])'; // 0-255 + $this->ip4 = "(?:{$oct}\\.{$oct}\\.{$oct}\\.{$oct})"; + } +} + + + + + +/** + * Validates an IPv6 address. + * @author Feyd @ forums.devnetwork.net (public domain) + * @note This function requires brackets to have been removed from address + * in URI. + */ +class HTMLPurifier_AttrDef_URI_IPv6 extends HTMLPurifier_AttrDef_URI_IPv4 +{ + + /** + * @param string $aIP + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($aIP, $config, $context) + { + if (!$this->ip4) { + $this->_loadRegex(); + } + + $original = $aIP; + + $hex = '[0-9a-fA-F]'; + $blk = '(?:' . $hex . '{1,4})'; + $pre = '(?:/(?:12[0-8]|1[0-1][0-9]|[1-9][0-9]|[0-9]))'; // /0 - /128 + + // prefix check + if (strpos($aIP, '/') !== false) { + if (preg_match('#' . $pre . '$#s', $aIP, $find)) { + $aIP = substr($aIP, 0, 0 - strlen($find[0])); + unset($find); + } else { + return false; + } + } + + // IPv4-compatiblity check + if (preg_match('#(?<=:' . ')' . $this->ip4 . '$#s', $aIP, $find)) { + $aIP = substr($aIP, 0, 0 - strlen($find[0])); + $ip = explode('.', $find[0]); + $ip = array_map('dechex', $ip); + $aIP .= $ip[0] . $ip[1] . ':' . $ip[2] . $ip[3]; + unset($find, $ip); + } + + // compression check + $aIP = explode('::', $aIP); + $c = count($aIP); + if ($c > 2) { + return false; + } elseif ($c == 2) { + list($first, $second) = $aIP; + $first = explode(':', $first); + $second = explode(':', $second); + + if (count($first) + count($second) > 8) { + return false; + } + + while (count($first) < 8) { + array_push($first, '0'); + } + + array_splice($first, 8 - count($second), 8, $second); + $aIP = $first; + unset($first, $second); + } else { + $aIP = explode(':', $aIP[0]); + } + $c = count($aIP); + + if ($c != 8) { + return false; + } + + // All the pieces should be 16-bit hex strings. Are they? + foreach ($aIP as $piece) { + if (!preg_match('#^[0-9a-fA-F]{4}$#s', sprintf('%04s', $piece))) { + return false; + } + } + return $original; + } +} + + + + + +/** + * Primitive email validation class based on the regexp found at + * http://www.regular-expressions.info/email.html + */ +class HTMLPurifier_AttrDef_URI_Email_SimpleCheck extends HTMLPurifier_AttrDef_URI_Email +{ + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + // no support for named mailboxes i.e. "Bob " + // that needs more percent encoding to be done + if ($string == '') { + return false; + } + $string = trim($string); + $result = preg_match('/^[A-Z0-9._%-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i', $string); + return $result ? $string : false; + } +} + + + + + +/** + * Pre-transform that changes proprietary background attribute to CSS. + */ +class HTMLPurifier_AttrTransform_Background extends HTMLPurifier_AttrTransform +{ + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + if (!isset($attr['background'])) { + return $attr; + } + + $background = $this->confiscateAttr($attr, 'background'); + // some validation should happen here + + $this->prependCSS($attr, "background-image:url($background);"); + return $attr; + } +} + + + + + +// this MUST be placed in post, as it assumes that any value in dir is valid + +/** + * Post-trasnform that ensures that bdo tags have the dir attribute set. + */ +class HTMLPurifier_AttrTransform_BdoDir extends HTMLPurifier_AttrTransform +{ + + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + if (isset($attr['dir'])) { + return $attr; + } + $attr['dir'] = $config->get('Attr.DefaultTextDir'); + return $attr; + } +} + + + + + +/** + * Pre-transform that changes deprecated bgcolor attribute to CSS. + */ +class HTMLPurifier_AttrTransform_BgColor extends HTMLPurifier_AttrTransform +{ + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + if (!isset($attr['bgcolor'])) { + return $attr; + } + + $bgcolor = $this->confiscateAttr($attr, 'bgcolor'); + // some validation should happen here + + $this->prependCSS($attr, "background-color:$bgcolor;"); + return $attr; + } +} + + + + + +/** + * Pre-transform that changes converts a boolean attribute to fixed CSS + */ +class HTMLPurifier_AttrTransform_BoolToCSS extends HTMLPurifier_AttrTransform +{ + /** + * Name of boolean attribute that is trigger. + * @type string + */ + protected $attr; + + /** + * CSS declarations to add to style, needs trailing semicolon. + * @type string + */ + protected $css; + + /** + * @param string $attr attribute name to convert from + * @param string $css CSS declarations to add to style (needs semicolon) + */ + public function __construct($attr, $css) + { + $this->attr = $attr; + $this->css = $css; + } + + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + if (!isset($attr[$this->attr])) { + return $attr; + } + unset($attr[$this->attr]); + $this->prependCSS($attr, $this->css); + return $attr; + } +} + + + + + +/** + * Pre-transform that changes deprecated border attribute to CSS. + */ +class HTMLPurifier_AttrTransform_Border extends HTMLPurifier_AttrTransform +{ + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + if (!isset($attr['border'])) { + return $attr; + } + $border_width = $this->confiscateAttr($attr, 'border'); + // some validation should happen here + $this->prependCSS($attr, "border:{$border_width}px solid;"); + return $attr; + } +} + + + + + +/** + * Generic pre-transform that converts an attribute with a fixed number of + * values (enumerated) to CSS. + */ +class HTMLPurifier_AttrTransform_EnumToCSS extends HTMLPurifier_AttrTransform +{ + /** + * Name of attribute to transform from. + * @type string + */ + protected $attr; + + /** + * Lookup array of attribute values to CSS. + * @type array + */ + protected $enumToCSS = array(); + + /** + * Case sensitivity of the matching. + * @type bool + * @warning Currently can only be guaranteed to work with ASCII + * values. + */ + protected $caseSensitive = false; + + /** + * @param string $attr Attribute name to transform from + * @param array $enum_to_css Lookup array of attribute values to CSS + * @param bool $case_sensitive Case sensitivity indicator, default false + */ + public function __construct($attr, $enum_to_css, $case_sensitive = false) + { + $this->attr = $attr; + $this->enumToCSS = $enum_to_css; + $this->caseSensitive = (bool)$case_sensitive; + } + + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + if (!isset($attr[$this->attr])) { + return $attr; + } + + $value = trim($attr[$this->attr]); + unset($attr[$this->attr]); + + if (!$this->caseSensitive) { + $value = strtolower($value); + } + + if (!isset($this->enumToCSS[$value])) { + return $attr; + } + $this->prependCSS($attr, $this->enumToCSS[$value]); + return $attr; + } +} + + + + + +// must be called POST validation + +/** + * Transform that supplies default values for the src and alt attributes + * in img tags, as well as prevents the img tag from being removed + * because of a missing alt tag. This needs to be registered as both + * a pre and post attribute transform. + */ +class HTMLPurifier_AttrTransform_ImgRequired extends HTMLPurifier_AttrTransform +{ + + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + $src = true; + if (!isset($attr['src'])) { + if ($config->get('Core.RemoveInvalidImg')) { + return $attr; + } + $attr['src'] = $config->get('Attr.DefaultInvalidImage'); + $src = false; + } + + if (!isset($attr['alt'])) { + if ($src) { + $alt = $config->get('Attr.DefaultImageAlt'); + if ($alt === null) { + $attr['alt'] = basename($attr['src']); + } else { + $attr['alt'] = $alt; + } + } else { + $attr['alt'] = $config->get('Attr.DefaultInvalidImageAlt'); + } + } + return $attr; + } +} + + + + + +/** + * Pre-transform that changes deprecated hspace and vspace attributes to CSS + */ +class HTMLPurifier_AttrTransform_ImgSpace extends HTMLPurifier_AttrTransform +{ + /** + * @type string + */ + protected $attr; + + /** + * @type array + */ + protected $css = array( + 'hspace' => array('left', 'right'), + 'vspace' => array('top', 'bottom') + ); + + /** + * @param string $attr + */ + public function __construct($attr) + { + $this->attr = $attr; + if (!isset($this->css[$attr])) { + trigger_error(htmlspecialchars($attr) . ' is not valid space attribute'); + } + } + + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + if (!isset($attr[$this->attr])) { + return $attr; + } + + $width = $this->confiscateAttr($attr, $this->attr); + // some validation could happen here + + if (!isset($this->css[$this->attr])) { + return $attr; + } + + $style = ''; + foreach ($this->css[$this->attr] as $suffix) { + $property = "margin-$suffix"; + $style .= "$property:{$width}px;"; + } + $this->prependCSS($attr, $style); + return $attr; + } +} + + + + + +/** + * Performs miscellaneous cross attribute validation and filtering for + * input elements. This is meant to be a post-transform. + */ +class HTMLPurifier_AttrTransform_Input extends HTMLPurifier_AttrTransform +{ + /** + * @type HTMLPurifier_AttrDef_HTML_Pixels + */ + protected $pixels; + + public function __construct() + { + $this->pixels = new HTMLPurifier_AttrDef_HTML_Pixels(); + } + + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + if (!isset($attr['type'])) { + $t = 'text'; + } else { + $t = strtolower($attr['type']); + } + if (isset($attr['checked']) && $t !== 'radio' && $t !== 'checkbox') { + unset($attr['checked']); + } + if (isset($attr['maxlength']) && $t !== 'text' && $t !== 'password') { + unset($attr['maxlength']); + } + if (isset($attr['size']) && $t !== 'text' && $t !== 'password') { + $result = $this->pixels->validate($attr['size'], $config, $context); + if ($result === false) { + unset($attr['size']); + } else { + $attr['size'] = $result; + } + } + if (isset($attr['src']) && $t !== 'image') { + unset($attr['src']); + } + if (!isset($attr['value']) && ($t === 'radio' || $t === 'checkbox')) { + $attr['value'] = ''; + } + return $attr; + } +} + + + + + +/** + * Post-transform that copies lang's value to xml:lang (and vice-versa) + * @note Theoretically speaking, this could be a pre-transform, but putting + * post is more efficient. + */ +class HTMLPurifier_AttrTransform_Lang extends HTMLPurifier_AttrTransform +{ + + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + $lang = isset($attr['lang']) ? $attr['lang'] : false; + $xml_lang = isset($attr['xml:lang']) ? $attr['xml:lang'] : false; + + if ($lang !== false && $xml_lang === false) { + $attr['xml:lang'] = $lang; + } elseif ($xml_lang !== false) { + $attr['lang'] = $xml_lang; + } + return $attr; + } +} + + + + + +/** + * Class for handling width/height length attribute transformations to CSS + */ +class HTMLPurifier_AttrTransform_Length extends HTMLPurifier_AttrTransform +{ + + /** + * @type string + */ + protected $name; + + /** + * @type string + */ + protected $cssName; + + public function __construct($name, $css_name = null) + { + $this->name = $name; + $this->cssName = $css_name ? $css_name : $name; + } + + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + if (!isset($attr[$this->name])) { + return $attr; + } + $length = $this->confiscateAttr($attr, $this->name); + if (ctype_digit($length)) { + $length .= 'px'; + } + $this->prependCSS($attr, $this->cssName . ":$length;"); + return $attr; + } +} + + + + + +/** + * Pre-transform that changes deprecated name attribute to ID if necessary + */ +class HTMLPurifier_AttrTransform_Name extends HTMLPurifier_AttrTransform +{ + + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + // Abort early if we're using relaxed definition of name + if ($config->get('HTML.Attr.Name.UseCDATA')) { + return $attr; + } + if (!isset($attr['name'])) { + return $attr; + } + $id = $this->confiscateAttr($attr, 'name'); + if (isset($attr['id'])) { + return $attr; + } + $attr['id'] = $id; + return $attr; + } +} + + + + + +/** + * Post-transform that performs validation to the name attribute; if + * it is present with an equivalent id attribute, it is passed through; + * otherwise validation is performed. + */ +class HTMLPurifier_AttrTransform_NameSync extends HTMLPurifier_AttrTransform +{ + + public function __construct() + { + $this->idDef = new HTMLPurifier_AttrDef_HTML_ID(); + } + + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + if (!isset($attr['name'])) { + return $attr; + } + $name = $attr['name']; + if (isset($attr['id']) && $attr['id'] === $name) { + return $attr; + } + $result = $this->idDef->validate($name, $config, $context); + if ($result === false) { + unset($attr['name']); + } else { + $attr['name'] = $result; + } + return $attr; + } +} + + + + + +// must be called POST validation + +/** + * Adds rel="nofollow" to all outbound links. This transform is + * only attached if Attr.Nofollow is TRUE. + */ +class HTMLPurifier_AttrTransform_Nofollow extends HTMLPurifier_AttrTransform +{ + /** + * @type HTMLPurifier_URIParser + */ + private $parser; + + public function __construct() + { + $this->parser = new HTMLPurifier_URIParser(); + } + + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + if (!isset($attr['href'])) { + return $attr; + } + + // XXX Kind of inefficient + $url = $this->parser->parse($attr['href']); + $scheme = $url->getSchemeObj($config, $context); + + if ($scheme->browsable && !$url->isLocal($config, $context)) { + if (isset($attr['rel'])) { + $rels = explode(' ', $attr['rel']); + if (!in_array('nofollow', $rels)) { + $rels[] = 'nofollow'; + } + $attr['rel'] = implode(' ', $rels); + } else { + $attr['rel'] = 'nofollow'; + } + } + return $attr; + } +} + + + + + +class HTMLPurifier_AttrTransform_SafeEmbed extends HTMLPurifier_AttrTransform +{ + /** + * @type string + */ + public $name = "SafeEmbed"; + + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + $attr['allowscriptaccess'] = 'never'; + $attr['allownetworking'] = 'internal'; + $attr['type'] = 'application/x-shockwave-flash'; + return $attr; + } +} + + + + + +/** + * Writes default type for all objects. Currently only supports flash. + */ +class HTMLPurifier_AttrTransform_SafeObject extends HTMLPurifier_AttrTransform +{ + /** + * @type string + */ + public $name = "SafeObject"; + + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + if (!isset($attr['type'])) { + $attr['type'] = 'application/x-shockwave-flash'; + } + return $attr; + } +} + + + + + +/** + * Validates name/value pairs in param tags to be used in safe objects. This + * will only allow name values it recognizes, and pre-fill certain attributes + * with required values. + * + * @note + * This class only supports Flash. In the future, Quicktime support + * may be added. + * + * @warning + * This class expects an injector to add the necessary parameters tags. + */ +class HTMLPurifier_AttrTransform_SafeParam extends HTMLPurifier_AttrTransform +{ + /** + * @type string + */ + public $name = "SafeParam"; + + /** + * @type HTMLPurifier_AttrDef_URI + */ + private $uri; + + public function __construct() + { + $this->uri = new HTMLPurifier_AttrDef_URI(true); // embedded + $this->wmode = new HTMLPurifier_AttrDef_Enum(array('window', 'opaque', 'transparent')); + } + + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + // If we add support for other objects, we'll need to alter the + // transforms. + switch ($attr['name']) { + // application/x-shockwave-flash + // Keep this synchronized with Injector/SafeObject.php + case 'allowScriptAccess': + $attr['value'] = 'never'; + break; + case 'allowNetworking': + $attr['value'] = 'internal'; + break; + case 'allowFullScreen': + if ($config->get('HTML.FlashAllowFullScreen')) { + $attr['value'] = ($attr['value'] == 'true') ? 'true' : 'false'; + } else { + $attr['value'] = 'false'; + } + break; + case 'wmode': + $attr['value'] = $this->wmode->validate($attr['value'], $config, $context); + break; + case 'movie': + case 'src': + $attr['name'] = "movie"; + $attr['value'] = $this->uri->validate($attr['value'], $config, $context); + break; + case 'flashvars': + // we're going to allow arbitrary inputs to the SWF, on + // the reasoning that it could only hack the SWF, not us. + break; + // add other cases to support other param name/value pairs + default: + $attr['name'] = $attr['value'] = null; + } + return $attr; + } +} + + + + + +/** + * Implements required attribute stipulation for )#si', + array($this, 'scriptCallback'), + $html + ); + } + + $html = $this->normalize($html, $config, $context); + + $cursor = 0; // our location in the text + $inside_tag = false; // whether or not we're parsing the inside of a tag + $array = array(); // result array + + // This is also treated to mean maintain *column* numbers too + $maintain_line_numbers = $config->get('Core.MaintainLineNumbers'); + + if ($maintain_line_numbers === null) { + // automatically determine line numbering by checking + // if error collection is on + $maintain_line_numbers = $config->get('Core.CollectErrors'); + } + + if ($maintain_line_numbers) { + $current_line = 1; + $current_col = 0; + $length = strlen($html); + } else { + $current_line = false; + $current_col = false; + $length = false; + } + $context->register('CurrentLine', $current_line); + $context->register('CurrentCol', $current_col); + $nl = "\n"; + // how often to manually recalculate. This will ALWAYS be right, + // but it's pretty wasteful. Set to 0 to turn off + $synchronize_interval = $config->get('Core.DirectLexLineNumberSyncInterval'); + + $e = false; + if ($config->get('Core.CollectErrors')) { + $e =& $context->get('ErrorCollector'); + } + + // for testing synchronization + $loops = 0; + + while (++$loops) { + // $cursor is either at the start of a token, or inside of + // a tag (i.e. there was a < immediately before it), as indicated + // by $inside_tag + + if ($maintain_line_numbers) { + // $rcursor, however, is always at the start of a token. + $rcursor = $cursor - (int)$inside_tag; + + // Column number is cheap, so we calculate it every round. + // We're interested at the *end* of the newline string, so + // we need to add strlen($nl) == 1 to $nl_pos before subtracting it + // from our "rcursor" position. + $nl_pos = strrpos($html, $nl, $rcursor - $length); + $current_col = $rcursor - (is_bool($nl_pos) ? 0 : $nl_pos + 1); + + // recalculate lines + if ($synchronize_interval && // synchronization is on + $cursor > 0 && // cursor is further than zero + $loops % $synchronize_interval === 0) { // time to synchronize! + $current_line = 1 + $this->substrCount($html, $nl, 0, $cursor); + } + } + + $position_next_lt = strpos($html, '<', $cursor); + $position_next_gt = strpos($html, '>', $cursor); + + // triggers on "asdf" but not "asdf " + // special case to set up context + if ($position_next_lt === $cursor) { + $inside_tag = true; + $cursor++; + } + + if (!$inside_tag && $position_next_lt !== false) { + // We are not inside tag and there still is another tag to parse + $token = new + HTMLPurifier_Token_Text( + $this->parseText( + substr( + $html, + $cursor, + $position_next_lt - $cursor + ), $config + ) + ); + if ($maintain_line_numbers) { + $token->rawPosition($current_line, $current_col); + $current_line += $this->substrCount($html, $nl, $cursor, $position_next_lt - $cursor); + } + $array[] = $token; + $cursor = $position_next_lt + 1; + $inside_tag = true; + continue; + } elseif (!$inside_tag) { + // We are not inside tag but there are no more tags + // If we're already at the end, break + if ($cursor === strlen($html)) { + break; + } + // Create Text of rest of string + $token = new + HTMLPurifier_Token_Text( + $this->parseText( + substr( + $html, + $cursor + ), $config + ) + ); + if ($maintain_line_numbers) { + $token->rawPosition($current_line, $current_col); + } + $array[] = $token; + break; + } elseif ($inside_tag && $position_next_gt !== false) { + // We are in tag and it is well formed + // Grab the internals of the tag + $strlen_segment = $position_next_gt - $cursor; + + if ($strlen_segment < 1) { + // there's nothing to process! + $token = new HTMLPurifier_Token_Text('<'); + $cursor++; + continue; + } + + $segment = substr($html, $cursor, $strlen_segment); + + if ($segment === false) { + // somehow, we attempted to access beyond the end of + // the string, defense-in-depth, reported by Nate Abele + break; + } + + // Check if it's a comment + if (substr($segment, 0, 3) === '!--') { + // re-determine segment length, looking for --> + $position_comment_end = strpos($html, '-->', $cursor); + if ($position_comment_end === false) { + // uh oh, we have a comment that extends to + // infinity. Can't be helped: set comment + // end position to end of string + if ($e) { + $e->send(E_WARNING, 'Lexer: Unclosed comment'); + } + $position_comment_end = strlen($html); + $end = true; + } else { + $end = false; + } + $strlen_segment = $position_comment_end - $cursor; + $segment = substr($html, $cursor, $strlen_segment); + $token = new + HTMLPurifier_Token_Comment( + substr( + $segment, + 3, + $strlen_segment - 3 + ) + ); + if ($maintain_line_numbers) { + $token->rawPosition($current_line, $current_col); + $current_line += $this->substrCount($html, $nl, $cursor, $strlen_segment); + } + $array[] = $token; + $cursor = $end ? $position_comment_end : $position_comment_end + 3; + $inside_tag = false; + continue; + } + + // Check if it's an end tag + $is_end_tag = (strpos($segment, '/') === 0); + if ($is_end_tag) { + $type = substr($segment, 1); + $token = new HTMLPurifier_Token_End($type); + if ($maintain_line_numbers) { + $token->rawPosition($current_line, $current_col); + $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor); + } + $array[] = $token; + $inside_tag = false; + $cursor = $position_next_gt + 1; + continue; + } + + // Check leading character is alnum, if not, we may + // have accidently grabbed an emoticon. Translate into + // text and go our merry way + if (!ctype_alpha($segment[0])) { + // XML: $segment[0] !== '_' && $segment[0] !== ':' + if ($e) { + $e->send(E_NOTICE, 'Lexer: Unescaped lt'); + } + $token = new HTMLPurifier_Token_Text('<'); + if ($maintain_line_numbers) { + $token->rawPosition($current_line, $current_col); + $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor); + } + $array[] = $token; + $inside_tag = false; + continue; + } + + // Check if it is explicitly self closing, if so, remove + // trailing slash. Remember, we could have a tag like
, so + // any later token processing scripts must convert improperly + // classified EmptyTags from StartTags. + $is_self_closing = (strrpos($segment, '/') === $strlen_segment - 1); + if ($is_self_closing) { + $strlen_segment--; + $segment = substr($segment, 0, $strlen_segment); + } + + // Check if there are any attributes + $position_first_space = strcspn($segment, $this->_whitespace); + + if ($position_first_space >= $strlen_segment) { + if ($is_self_closing) { + $token = new HTMLPurifier_Token_Empty($segment); + } else { + $token = new HTMLPurifier_Token_Start($segment); + } + if ($maintain_line_numbers) { + $token->rawPosition($current_line, $current_col); + $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor); + } + $array[] = $token; + $inside_tag = false; + $cursor = $position_next_gt + 1; + continue; + } + + // Grab out all the data + $type = substr($segment, 0, $position_first_space); + $attribute_string = + trim( + substr( + $segment, + $position_first_space + ) + ); + if ($attribute_string) { + $attr = $this->parseAttributeString( + $attribute_string, + $config, + $context + ); + } else { + $attr = array(); + } + + if ($is_self_closing) { + $token = new HTMLPurifier_Token_Empty($type, $attr); + } else { + $token = new HTMLPurifier_Token_Start($type, $attr); + } + if ($maintain_line_numbers) { + $token->rawPosition($current_line, $current_col); + $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor); + } + $array[] = $token; + $cursor = $position_next_gt + 1; + $inside_tag = false; + continue; + } else { + // inside tag, but there's no ending > sign + if ($e) { + $e->send(E_WARNING, 'Lexer: Missing gt'); + } + $token = new + HTMLPurifier_Token_Text( + '<' . + $this->parseText( + substr($html, $cursor), $config + ) + ); + if ($maintain_line_numbers) { + $token->rawPosition($current_line, $current_col); + } + // no cursor scroll? Hmm... + $array[] = $token; + break; + } + break; + } + + $context->destroy('CurrentLine'); + $context->destroy('CurrentCol'); + return $array; + } + + /** + * PHP 5.0.x compatible substr_count that implements offset and length + * @param string $haystack + * @param string $needle + * @param int $offset + * @param int $length + * @return int + */ + protected function substrCount($haystack, $needle, $offset, $length) + { + static $oldVersion; + if ($oldVersion === null) { + $oldVersion = version_compare(PHP_VERSION, '5.1', '<'); + } + if ($oldVersion) { + $haystack = substr($haystack, $offset, $length); + return substr_count($haystack, $needle); + } else { + return substr_count($haystack, $needle, $offset, $length); + } + } + + /** + * Takes the inside of an HTML tag and makes an assoc array of attributes. + * + * @param string $string Inside of tag excluding name. + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array Assoc array of attributes. + */ + public function parseAttributeString($string, $config, $context) + { + $string = (string)$string; // quick typecast + + if ($string == '') { + return array(); + } // no attributes + + $e = false; + if ($config->get('Core.CollectErrors')) { + $e =& $context->get('ErrorCollector'); + } + + // let's see if we can abort as quickly as possible + // one equal sign, no spaces => one attribute + $num_equal = substr_count($string, '='); + $has_space = strpos($string, ' '); + if ($num_equal === 0 && !$has_space) { + // bool attribute + return array($string => $string); + } elseif ($num_equal === 1 && !$has_space) { + // only one attribute + list($key, $quoted_value) = explode('=', $string); + $quoted_value = trim($quoted_value); + if (!$key) { + if ($e) { + $e->send(E_ERROR, 'Lexer: Missing attribute key'); + } + return array(); + } + if (!$quoted_value) { + return array($key => ''); + } + $first_char = @$quoted_value[0]; + $last_char = @$quoted_value[strlen($quoted_value) - 1]; + + $same_quote = ($first_char == $last_char); + $open_quote = ($first_char == '"' || $first_char == "'"); + + if ($same_quote && $open_quote) { + // well behaved + $value = substr($quoted_value, 1, strlen($quoted_value) - 2); + } else { + // not well behaved + if ($open_quote) { + if ($e) { + $e->send(E_ERROR, 'Lexer: Missing end quote'); + } + $value = substr($quoted_value, 1); + } else { + $value = $quoted_value; + } + } + if ($value === false) { + $value = ''; + } + return array($key => $this->parseAttr($value, $config)); + } + + // setup loop environment + $array = array(); // return assoc array of attributes + $cursor = 0; // current position in string (moves forward) + $size = strlen($string); // size of the string (stays the same) + + // if we have unquoted attributes, the parser expects a terminating + // space, so let's guarantee that there's always a terminating space. + $string .= ' '; + + $old_cursor = -1; + while ($cursor < $size) { + if ($old_cursor >= $cursor) { + throw new Exception("Infinite loop detected"); + } + $old_cursor = $cursor; + + $cursor += ($value = strspn($string, $this->_whitespace, $cursor)); + // grab the key + + $key_begin = $cursor; //we're currently at the start of the key + + // scroll past all characters that are the key (not whitespace or =) + $cursor += strcspn($string, $this->_whitespace . '=', $cursor); + + $key_end = $cursor; // now at the end of the key + + $key = substr($string, $key_begin, $key_end - $key_begin); + + if (!$key) { + if ($e) { + $e->send(E_ERROR, 'Lexer: Missing attribute key'); + } + $cursor += 1 + strcspn($string, $this->_whitespace, $cursor + 1); // prevent infinite loop + continue; // empty key + } + + // scroll past all whitespace + $cursor += strspn($string, $this->_whitespace, $cursor); + + if ($cursor >= $size) { + $array[$key] = $key; + break; + } + + // if the next character is an equal sign, we've got a regular + // pair, otherwise, it's a bool attribute + $first_char = @$string[$cursor]; + + if ($first_char == '=') { + // key="value" + + $cursor++; + $cursor += strspn($string, $this->_whitespace, $cursor); + + if ($cursor === false) { + $array[$key] = ''; + break; + } + + // we might be in front of a quote right now + + $char = @$string[$cursor]; + + if ($char == '"' || $char == "'") { + // it's quoted, end bound is $char + $cursor++; + $value_begin = $cursor; + $cursor = strpos($string, $char, $cursor); + $value_end = $cursor; + } else { + // it's not quoted, end bound is whitespace + $value_begin = $cursor; + $cursor += strcspn($string, $this->_whitespace, $cursor); + $value_end = $cursor; + } + + // we reached a premature end + if ($cursor === false) { + $cursor = $size; + $value_end = $cursor; + } + + $value = substr($string, $value_begin, $value_end - $value_begin); + if ($value === false) { + $value = ''; + } + $array[$key] = $this->parseAttr($value, $config); + $cursor++; + } else { + // boolattr + if ($key !== '') { + $array[$key] = $key; + } else { + // purely theoretical + if ($e) { + $e->send(E_ERROR, 'Lexer: Missing attribute key'); + } + } + } + } + return $array; + } +} + + + + + +/** + * Concrete comment node class. + */ +class HTMLPurifier_Node_Comment extends HTMLPurifier_Node +{ + /** + * Character data within comment. + * @type string + */ + public $data; + + /** + * @type bool + */ + public $is_whitespace = true; + + /** + * Transparent constructor. + * + * @param string $data String comment data. + * @param int $line + * @param int $col + */ + public function __construct($data, $line = null, $col = null) + { + $this->data = $data; + $this->line = $line; + $this->col = $col; + } + + public function toTokenPair() { + return array(new HTMLPurifier_Token_Comment($this->data, $this->line, $this->col), null); + } +} + + + +/** + * Concrete element node class. + */ +class HTMLPurifier_Node_Element extends HTMLPurifier_Node +{ + /** + * The lower-case name of the tag, like 'a', 'b' or 'blockquote'. + * + * @note Strictly speaking, XML tags are case sensitive, so we shouldn't + * be lower-casing them, but these tokens cater to HTML tags, which are + * insensitive. + * @type string + */ + public $name; + + /** + * Associative array of the node's attributes. + * @type array + */ + public $attr = array(); + + /** + * List of child elements. + * @type array + */ + public $children = array(); + + /** + * Does this use the form or the form, i.e. + * is it a pair of start/end tokens or an empty token. + * @bool + */ + public $empty = false; + + public $endCol = null, $endLine = null, $endArmor = array(); + + public function __construct($name, $attr = array(), $line = null, $col = null, $armor = array()) { + $this->name = $name; + $this->attr = $attr; + $this->line = $line; + $this->col = $col; + $this->armor = $armor; + } + + public function toTokenPair() { + // XXX inefficiency here, normalization is not necessary + if ($this->empty) { + return array(new HTMLPurifier_Token_Empty($this->name, $this->attr, $this->line, $this->col, $this->armor), null); + } else { + $start = new HTMLPurifier_Token_Start($this->name, $this->attr, $this->line, $this->col, $this->armor); + $end = new HTMLPurifier_Token_End($this->name, array(), $this->endLine, $this->endCol, $this->endArmor); + //$end->start = $start; + return array($start, $end); + } + } +} + + + + +/** + * Concrete text token class. + * + * Text tokens comprise of regular parsed character data (PCDATA) and raw + * character data (from the CDATA sections). Internally, their + * data is parsed with all entities expanded. Surprisingly, the text token + * does have a "tag name" called #PCDATA, which is how the DTD represents it + * in permissible child nodes. + */ +class HTMLPurifier_Node_Text extends HTMLPurifier_Node +{ + + /** + * PCDATA tag name compatible with DTD, see + * HTMLPurifier_ChildDef_Custom for details. + * @type string + */ + public $name = '#PCDATA'; + + /** + * @type string + */ + public $data; + /**< Parsed character data of text. */ + + /** + * @type bool + */ + public $is_whitespace; + + /**< Bool indicating if node is whitespace. */ + + /** + * Constructor, accepts data and determines if it is whitespace. + * @param string $data String parsed character data. + * @param int $line + * @param int $col + */ + public function __construct($data, $is_whitespace, $line = null, $col = null) + { + $this->data = $data; + $this->is_whitespace = $is_whitespace; + $this->line = $line; + $this->col = $col; + } + + public function toTokenPair() { + return array(new HTMLPurifier_Token_Text($this->data, $this->line, $this->col), null); + } +} + + + + + +/** + * Composite strategy that runs multiple strategies on tokens. + */ +abstract class HTMLPurifier_Strategy_Composite extends HTMLPurifier_Strategy +{ + + /** + * List of strategies to run tokens through. + * @type HTMLPurifier_Strategy[] + */ + protected $strategies = array(); + + /** + * @param HTMLPurifier_Token[] $tokens + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return HTMLPurifier_Token[] + */ + public function execute($tokens, $config, $context) + { + foreach ($this->strategies as $strategy) { + $tokens = $strategy->execute($tokens, $config, $context); + } + return $tokens; + } +} + + + + + +/** + * Core strategy composed of the big four strategies. + */ +class HTMLPurifier_Strategy_Core extends HTMLPurifier_Strategy_Composite +{ + public function __construct() + { + $this->strategies[] = new HTMLPurifier_Strategy_RemoveForeignElements(); + $this->strategies[] = new HTMLPurifier_Strategy_MakeWellFormed(); + $this->strategies[] = new HTMLPurifier_Strategy_FixNesting(); + $this->strategies[] = new HTMLPurifier_Strategy_ValidateAttributes(); + } +} + + + + + +/** + * Takes a well formed list of tokens and fixes their nesting. + * + * HTML elements dictate which elements are allowed to be their children, + * for example, you can't have a p tag in a span tag. Other elements have + * much more rigorous definitions: tables, for instance, require a specific + * order for their elements. There are also constraints not expressible by + * document type definitions, such as the chameleon nature of ins/del + * tags and global child exclusions. + * + * The first major objective of this strategy is to iterate through all + * the nodes and determine whether or not their children conform to the + * element's definition. If they do not, the child definition may + * optionally supply an amended list of elements that is valid or + * require that the entire node be deleted (and the previous node + * rescanned). + * + * The second objective is to ensure that explicitly excluded elements of + * an element do not appear in its children. Code that accomplishes this + * task is pervasive through the strategy, though the two are distinct tasks + * and could, theoretically, be seperated (although it's not recommended). + * + * @note Whether or not unrecognized children are silently dropped or + * translated into text depends on the child definitions. + * + * @todo Enable nodes to be bubbled out of the structure. This is + * easier with our new algorithm. + */ + +class HTMLPurifier_Strategy_FixNesting extends HTMLPurifier_Strategy +{ + + /** + * @param HTMLPurifier_Token[] $tokens + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array|HTMLPurifier_Token[] + */ + public function execute($tokens, $config, $context) + { + + //####################################################################// + // Pre-processing + + // O(n) pass to convert to a tree, so that we can efficiently + // refer to substrings + $top_node = HTMLPurifier_Arborize::arborize($tokens, $config, $context); + + // get a copy of the HTML definition + $definition = $config->getHTMLDefinition(); + + $excludes_enabled = !$config->get('Core.DisableExcludes'); + + // setup the context variable 'IsInline', for chameleon processing + // is 'false' when we are not inline, 'true' when it must always + // be inline, and an integer when it is inline for a certain + // branch of the document tree + $is_inline = $definition->info_parent_def->descendants_are_inline; + $context->register('IsInline', $is_inline); + + // setup error collector + $e =& $context->get('ErrorCollector', true); + + //####################################################################// + // Loop initialization + + // stack that contains all elements that are excluded + // it is organized by parent elements, similar to $stack, + // but it is only populated when an element with exclusions is + // processed, i.e. there won't be empty exclusions. + $exclude_stack = array($definition->info_parent_def->excludes); + + // variable that contains the start token while we are processing + // nodes. This enables error reporting to do its job + $node = $top_node; + // dummy token + list($token, $d) = $node->toTokenPair(); + $context->register('CurrentNode', $node); + $context->register('CurrentToken', $token); + + //####################################################################// + // Loop + + // We need to implement a post-order traversal iteratively, to + // avoid running into stack space limits. This is pretty tricky + // to reason about, so we just manually stack-ify the recursive + // variant: + // + // function f($node) { + // foreach ($node->children as $child) { + // f($child); + // } + // validate($node); + // } + // + // Thus, we will represent a stack frame as array($node, + // $is_inline, stack of children) + // e.g. array_reverse($node->children) - already processed + // children. + + $parent_def = $definition->info_parent_def; + $stack = array( + array($top_node, + $parent_def->descendants_are_inline, + $parent_def->excludes, // exclusions + 0) + ); + + while (!empty($stack)) { + list($node, $is_inline, $excludes, $ix) = array_pop($stack); + // recursive call + $go = false; + $def = empty($stack) ? $definition->info_parent_def : $definition->info[$node->name]; + while (isset($node->children[$ix])) { + $child = $node->children[$ix++]; + if ($child instanceof HTMLPurifier_Node_Element) { + $go = true; + $stack[] = array($node, $is_inline, $excludes, $ix); + $stack[] = array($child, + // ToDo: I don't think it matters if it's def or + // child_def, but double check this... + $is_inline || $def->descendants_are_inline, + empty($def->excludes) ? $excludes + : array_merge($excludes, $def->excludes), + 0); + break; + } + }; + if ($go) continue; + list($token, $d) = $node->toTokenPair(); + // base case + if ($excludes_enabled && isset($excludes[$node->name])) { + $node->dead = true; + if ($e) $e->send(E_ERROR, 'Strategy_FixNesting: Node excluded'); + } else { + // XXX I suppose it would be slightly more efficient to + // avoid the allocation here and have children + // strategies handle it + $children = array(); + foreach ($node->children as $child) { + if (!$child->dead) $children[] = $child; + } + $result = $def->child->validateChildren($children, $config, $context); + if ($result === true) { + // nop + $node->children = $children; + } elseif ($result === false) { + $node->dead = true; + if ($e) $e->send(E_ERROR, 'Strategy_FixNesting: Node removed'); + } else { + $node->children = $result; + if ($e) { + // XXX This will miss mutations of internal nodes. Perhaps defer to the child validators + if (empty($result) && !empty($children)) { + $e->send(E_ERROR, 'Strategy_FixNesting: Node contents removed'); + } else if ($result != $children) { + $e->send(E_WARNING, 'Strategy_FixNesting: Node reorganized'); + } + } + } + } + } + + //####################################################################// + // Post-processing + + // remove context variables + $context->destroy('IsInline'); + $context->destroy('CurrentNode'); + $context->destroy('CurrentToken'); + + //####################################################################// + // Return + + return HTMLPurifier_Arborize::flatten($node, $config, $context); + } +} + + + + + +/** + * Takes tokens makes them well-formed (balance end tags, etc.) + * + * Specification of the armor attributes this strategy uses: + * + * - MakeWellFormed_TagClosedError: This armor field is used to + * suppress tag closed errors for certain tokens [TagClosedSuppress], + * in particular, if a tag was generated automatically by HTML + * Purifier, we may rely on our infrastructure to close it for us + * and shouldn't report an error to the user [TagClosedAuto]. + */ +class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy +{ + + /** + * Array stream of tokens being processed. + * @type HTMLPurifier_Token[] + */ + protected $tokens; + + /** + * Current token. + * @type HTMLPurifier_Token + */ + protected $token; + + /** + * Zipper managing the true state. + * @type HTMLPurifier_Zipper + */ + protected $zipper; + + /** + * Current nesting of elements. + * @type array + */ + protected $stack; + + /** + * Injectors active in this stream processing. + * @type HTMLPurifier_Injector[] + */ + protected $injectors; + + /** + * Current instance of HTMLPurifier_Config. + * @type HTMLPurifier_Config + */ + protected $config; + + /** + * Current instance of HTMLPurifier_Context. + * @type HTMLPurifier_Context + */ + protected $context; + + /** + * @param HTMLPurifier_Token[] $tokens + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return HTMLPurifier_Token[] + * @throws HTMLPurifier_Exception + */ + public function execute($tokens, $config, $context) + { + $definition = $config->getHTMLDefinition(); + + // local variables + $generator = new HTMLPurifier_Generator($config, $context); + $escape_invalid_tags = $config->get('Core.EscapeInvalidTags'); + // used for autoclose early abortion + $global_parent_allowed_elements = $definition->info_parent_def->child->getAllowedElements($config); + $e = $context->get('ErrorCollector', true); + $i = false; // injector index + list($zipper, $token) = HTMLPurifier_Zipper::fromArray($tokens); + if ($token === NULL) { + return array(); + } + $reprocess = false; // whether or not to reprocess the same token + $stack = array(); + + // member variables + $this->stack =& $stack; + $this->tokens =& $tokens; + $this->token =& $token; + $this->zipper =& $zipper; + $this->config = $config; + $this->context = $context; + + // context variables + $context->register('CurrentNesting', $stack); + $context->register('InputZipper', $zipper); + $context->register('CurrentToken', $token); + + // -- begin INJECTOR -- + + $this->injectors = array(); + + $injectors = $config->getBatch('AutoFormat'); + $def_injectors = $definition->info_injector; + $custom_injectors = $injectors['Custom']; + unset($injectors['Custom']); // special case + foreach ($injectors as $injector => $b) { + // XXX: Fix with a legitimate lookup table of enabled filters + if (strpos($injector, '.') !== false) { + continue; + } + $injector = "HTMLPurifier_Injector_$injector"; + if (!$b) { + continue; + } + $this->injectors[] = new $injector; + } + foreach ($def_injectors as $injector) { + // assumed to be objects + $this->injectors[] = $injector; + } + foreach ($custom_injectors as $injector) { + if (!$injector) { + continue; + } + if (is_string($injector)) { + $injector = "HTMLPurifier_Injector_$injector"; + $injector = new $injector; + } + $this->injectors[] = $injector; + } + + // give the injectors references to the definition and context + // variables for performance reasons + foreach ($this->injectors as $ix => $injector) { + $error = $injector->prepare($config, $context); + if (!$error) { + continue; + } + array_splice($this->injectors, $ix, 1); // rm the injector + trigger_error("Cannot enable {$injector->name} injector because $error is not allowed", E_USER_WARNING); + } + + // -- end INJECTOR -- + + // a note on reprocessing: + // In order to reduce code duplication, whenever some code needs + // to make HTML changes in order to make things "correct", the + // new HTML gets sent through the purifier, regardless of its + // status. This means that if we add a start token, because it + // was totally necessary, we don't have to update nesting; we just + // punt ($reprocess = true; continue;) and it does that for us. + + // isset is in loop because $tokens size changes during loop exec + for (;; + // only increment if we don't need to reprocess + $reprocess ? $reprocess = false : $token = $zipper->next($token)) { + + // check for a rewind + if (is_int($i)) { + // possibility: disable rewinding if the current token has a + // rewind set on it already. This would offer protection from + // infinite loop, but might hinder some advanced rewinding. + $rewind_offset = $this->injectors[$i]->getRewindOffset(); + if (is_int($rewind_offset)) { + for ($j = 0; $j < $rewind_offset; $j++) { + if (empty($zipper->front)) break; + $token = $zipper->prev($token); + // indicate that other injectors should not process this token, + // but we need to reprocess it. See Note [Injector skips] + unset($token->skip[$i]); + $token->rewind = $i; + if ($token instanceof HTMLPurifier_Token_Start) { + array_pop($this->stack); + } elseif ($token instanceof HTMLPurifier_Token_End) { + $this->stack[] = $token->start; + } + } + } + $i = false; + } + + // handle case of document end + if ($token === NULL) { + // kill processing if stack is empty + if (empty($this->stack)) { + break; + } + + // peek + $top_nesting = array_pop($this->stack); + $this->stack[] = $top_nesting; + + // send error [TagClosedSuppress] + if ($e && !isset($top_nesting->armor['MakeWellFormed_TagClosedError'])) { + $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag closed by document end', $top_nesting); + } + + // append, don't splice, since this is the end + $token = new HTMLPurifier_Token_End($top_nesting->name); + + // punt! + $reprocess = true; + continue; + } + + //echo '
'; printZipper($zipper, $token);//printTokens($this->stack); + //flush(); + + // quick-check: if it's not a tag, no need to process + if (empty($token->is_tag)) { + if ($token instanceof HTMLPurifier_Token_Text) { + foreach ($this->injectors as $i => $injector) { + if (isset($token->skip[$i])) { + // See Note [Injector skips] + continue; + } + if ($token->rewind !== null && $token->rewind !== $i) { + continue; + } + // XXX fuckup + $r = $token; + $injector->handleText($r); + $token = $this->processToken($r, $i); + $reprocess = true; + break; + } + } + // another possibility is a comment + continue; + } + + if (isset($definition->info[$token->name])) { + $type = $definition->info[$token->name]->child->type; + } else { + $type = false; // Type is unknown, treat accordingly + } + + // quick tag checks: anything that's *not* an end tag + $ok = false; + if ($type === 'empty' && $token instanceof HTMLPurifier_Token_Start) { + // claims to be a start tag but is empty + $token = new HTMLPurifier_Token_Empty( + $token->name, + $token->attr, + $token->line, + $token->col, + $token->armor + ); + $ok = true; + } elseif ($type && $type !== 'empty' && $token instanceof HTMLPurifier_Token_Empty) { + // claims to be empty but really is a start tag + // NB: this assignment is required + $old_token = $token; + $token = new HTMLPurifier_Token_End($token->name); + $token = $this->insertBefore( + new HTMLPurifier_Token_Start($old_token->name, $old_token->attr, $old_token->line, $old_token->col, $old_token->armor) + ); + // punt (since we had to modify the input stream in a non-trivial way) + $reprocess = true; + continue; + } elseif ($token instanceof HTMLPurifier_Token_Empty) { + // real empty token + $ok = true; + } elseif ($token instanceof HTMLPurifier_Token_Start) { + // start tag + + // ...unless they also have to close their parent + if (!empty($this->stack)) { + + // Performance note: you might think that it's rather + // inefficient, recalculating the autoclose information + // for every tag that a token closes (since when we + // do an autoclose, we push a new token into the + // stream and then /process/ that, before + // re-processing this token.) But this is + // necessary, because an injector can make an + // arbitrary transformations to the autoclosing + // tokens we introduce, so things may have changed + // in the meantime. Also, doing the inefficient thing is + // "easy" to reason about (for certain perverse definitions + // of "easy") + + $parent = array_pop($this->stack); + $this->stack[] = $parent; + + $parent_def = null; + $parent_elements = null; + $autoclose = false; + if (isset($definition->info[$parent->name])) { + $parent_def = $definition->info[$parent->name]; + $parent_elements = $parent_def->child->getAllowedElements($config); + $autoclose = !isset($parent_elements[$token->name]); + } + + if ($autoclose && $definition->info[$token->name]->wrap) { + // Check if an element can be wrapped by another + // element to make it valid in a context (for + // example,
      needs a
    • in between) + $wrapname = $definition->info[$token->name]->wrap; + $wrapdef = $definition->info[$wrapname]; + $elements = $wrapdef->child->getAllowedElements($config); + if (isset($elements[$token->name]) && isset($parent_elements[$wrapname])) { + $newtoken = new HTMLPurifier_Token_Start($wrapname); + $token = $this->insertBefore($newtoken); + $reprocess = true; + continue; + } + } + + $carryover = false; + if ($autoclose && $parent_def->formatting) { + $carryover = true; + } + + if ($autoclose) { + // check if this autoclose is doomed to fail + // (this rechecks $parent, which his harmless) + $autoclose_ok = isset($global_parent_allowed_elements[$token->name]); + if (!$autoclose_ok) { + foreach ($this->stack as $ancestor) { + $elements = $definition->info[$ancestor->name]->child->getAllowedElements($config); + if (isset($elements[$token->name])) { + $autoclose_ok = true; + break; + } + if ($definition->info[$token->name]->wrap) { + $wrapname = $definition->info[$token->name]->wrap; + $wrapdef = $definition->info[$wrapname]; + $wrap_elements = $wrapdef->child->getAllowedElements($config); + if (isset($wrap_elements[$token->name]) && isset($elements[$wrapname])) { + $autoclose_ok = true; + break; + } + } + } + } + if ($autoclose_ok) { + // errors need to be updated + $new_token = new HTMLPurifier_Token_End($parent->name); + $new_token->start = $parent; + // [TagClosedSuppress] + if ($e && !isset($parent->armor['MakeWellFormed_TagClosedError'])) { + if (!$carryover) { + $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag auto closed', $parent); + } else { + $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag carryover', $parent); + } + } + if ($carryover) { + $element = clone $parent; + // [TagClosedAuto] + $element->armor['MakeWellFormed_TagClosedError'] = true; + $element->carryover = true; + $token = $this->processToken(array($new_token, $token, $element)); + } else { + $token = $this->insertBefore($new_token); + } + } else { + $token = $this->remove(); + } + $reprocess = true; + continue; + } + + } + $ok = true; + } + + if ($ok) { + foreach ($this->injectors as $i => $injector) { + if (isset($token->skip[$i])) { + // See Note [Injector skips] + continue; + } + if ($token->rewind !== null && $token->rewind !== $i) { + continue; + } + $r = $token; + $injector->handleElement($r); + $token = $this->processToken($r, $i); + $reprocess = true; + break; + } + if (!$reprocess) { + // ah, nothing interesting happened; do normal processing + if ($token instanceof HTMLPurifier_Token_Start) { + $this->stack[] = $token; + } elseif ($token instanceof HTMLPurifier_Token_End) { + throw new HTMLPurifier_Exception( + 'Improper handling of end tag in start code; possible error in MakeWellFormed' + ); + } + } + continue; + } + + // sanity check: we should be dealing with a closing tag + if (!$token instanceof HTMLPurifier_Token_End) { + throw new HTMLPurifier_Exception('Unaccounted for tag token in input stream, bug in HTML Purifier'); + } + + // make sure that we have something open + if (empty($this->stack)) { + if ($escape_invalid_tags) { + if ($e) { + $e->send(E_WARNING, 'Strategy_MakeWellFormed: Unnecessary end tag to text'); + } + $token = new HTMLPurifier_Token_Text($generator->generateFromToken($token)); + } else { + if ($e) { + $e->send(E_WARNING, 'Strategy_MakeWellFormed: Unnecessary end tag removed'); + } + $token = $this->remove(); + } + $reprocess = true; + continue; + } + + // first, check for the simplest case: everything closes neatly. + // Eventually, everything passes through here; if there are problems + // we modify the input stream accordingly and then punt, so that + // the tokens get processed again. + $current_parent = array_pop($this->stack); + if ($current_parent->name == $token->name) { + $token->start = $current_parent; + foreach ($this->injectors as $i => $injector) { + if (isset($token->skip[$i])) { + // See Note [Injector skips] + continue; + } + if ($token->rewind !== null && $token->rewind !== $i) { + continue; + } + $r = $token; + $injector->handleEnd($r); + $token = $this->processToken($r, $i); + $this->stack[] = $current_parent; + $reprocess = true; + break; + } + continue; + } + + // okay, so we're trying to close the wrong tag + + // undo the pop previous pop + $this->stack[] = $current_parent; + + // scroll back the entire nest, trying to find our tag. + // (feature could be to specify how far you'd like to go) + $size = count($this->stack); + // -2 because -1 is the last element, but we already checked that + $skipped_tags = false; + for ($j = $size - 2; $j >= 0; $j--) { + if ($this->stack[$j]->name == $token->name) { + $skipped_tags = array_slice($this->stack, $j); + break; + } + } + + // we didn't find the tag, so remove + if ($skipped_tags === false) { + if ($escape_invalid_tags) { + if ($e) { + $e->send(E_WARNING, 'Strategy_MakeWellFormed: Stray end tag to text'); + } + $token = new HTMLPurifier_Token_Text($generator->generateFromToken($token)); + } else { + if ($e) { + $e->send(E_WARNING, 'Strategy_MakeWellFormed: Stray end tag removed'); + } + $token = $this->remove(); + } + $reprocess = true; + continue; + } + + // do errors, in REVERSE $j order: a,b,c with + $c = count($skipped_tags); + if ($e) { + for ($j = $c - 1; $j > 0; $j--) { + // notice we exclude $j == 0, i.e. the current ending tag, from + // the errors... [TagClosedSuppress] + if (!isset($skipped_tags[$j]->armor['MakeWellFormed_TagClosedError'])) { + $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag closed by element end', $skipped_tags[$j]); + } + } + } + + // insert tags, in FORWARD $j order: c,b,a with + $replace = array($token); + for ($j = 1; $j < $c; $j++) { + // ...as well as from the insertions + $new_token = new HTMLPurifier_Token_End($skipped_tags[$j]->name); + $new_token->start = $skipped_tags[$j]; + array_unshift($replace, $new_token); + if (isset($definition->info[$new_token->name]) && $definition->info[$new_token->name]->formatting) { + // [TagClosedAuto] + $element = clone $skipped_tags[$j]; + $element->carryover = true; + $element->armor['MakeWellFormed_TagClosedError'] = true; + $replace[] = $element; + } + } + $token = $this->processToken($replace); + $reprocess = true; + continue; + } + + $context->destroy('CurrentToken'); + $context->destroy('CurrentNesting'); + $context->destroy('InputZipper'); + + unset($this->injectors, $this->stack, $this->tokens); + return $zipper->toArray($token); + } + + /** + * Processes arbitrary token values for complicated substitution patterns. + * In general: + * + * If $token is an array, it is a list of tokens to substitute for the + * current token. These tokens then get individually processed. If there + * is a leading integer in the list, that integer determines how many + * tokens from the stream should be removed. + * + * If $token is a regular token, it is swapped with the current token. + * + * If $token is false, the current token is deleted. + * + * If $token is an integer, that number of tokens (with the first token + * being the current one) will be deleted. + * + * @param HTMLPurifier_Token|array|int|bool $token Token substitution value + * @param HTMLPurifier_Injector|int $injector Injector that performed the substitution; default is if + * this is not an injector related operation. + * @throws HTMLPurifier_Exception + */ + protected function processToken($token, $injector = -1) + { + // Zend OpCache miscompiles $token = array($token), so + // avoid this pattern. See: https://github.com/ezyang/htmlpurifier/issues/108 + + // normalize forms of token + if (is_object($token)) { + $tmp = $token; + $token = array(1, $tmp); + } + if (is_int($token)) { + $tmp = $token; + $token = array($tmp); + } + if ($token === false) { + $token = array(1); + } + if (!is_array($token)) { + throw new HTMLPurifier_Exception('Invalid token type from injector'); + } + if (!is_int($token[0])) { + array_unshift($token, 1); + } + if ($token[0] === 0) { + throw new HTMLPurifier_Exception('Deleting zero tokens is not valid'); + } + + // $token is now an array with the following form: + // array(number nodes to delete, new node 1, new node 2, ...) + + $delete = array_shift($token); + list($old, $r) = $this->zipper->splice($this->token, $delete, $token); + + if ($injector > -1) { + // See Note [Injector skips] + // Determine appropriate skips. Here's what the code does: + // *If* we deleted one or more tokens, copy the skips + // of those tokens into the skips of the new tokens (in $token). + // Also, mark the newly inserted tokens as having come from + // $injector. + $oldskip = isset($old[0]) ? $old[0]->skip : array(); + foreach ($token as $object) { + $object->skip = $oldskip; + $object->skip[$injector] = true; + } + } + + return $r; + + } + + /** + * Inserts a token before the current token. Cursor now points to + * this token. You must reprocess after this. + * @param HTMLPurifier_Token $token + */ + private function insertBefore($token) + { + // NB not $this->zipper->insertBefore(), due to positioning + // differences + $splice = $this->zipper->splice($this->token, 0, array($token)); + + return $splice[1]; + } + + /** + * Removes current token. Cursor now points to new token occupying previously + * occupied space. You must reprocess after this. + */ + private function remove() + { + return $this->zipper->delete(); + } +} + +// Note [Injector skips] +// ~~~~~~~~~~~~~~~~~~~~~ +// When I originally designed this class, the idea behind the 'skip' +// property of HTMLPurifier_Token was to help avoid infinite loops +// in injector processing. For example, suppose you wrote an injector +// that bolded swear words. Naively, you might write it so that +// whenever you saw ****, you replaced it with ****. +// +// When this happens, we will reprocess all of the tokens with the +// other injectors. Now there is an opportunity for infinite loop: +// if we rerun the swear-word injector on these tokens, we might +// see **** and then reprocess again to get +// **** ad infinitum. +// +// Thus, the idea of a skip is that once we process a token with +// an injector, we mark all of those tokens as having "come from" +// the injector, and we never run the injector again on these +// tokens. +// +// There were two more complications, however: +// +// - With HTMLPurifier_Injector_RemoveEmpty, we noticed that if +// you had , after you removed the , you +// really would like this injector to go back and reprocess +// the tag, discovering that it is now empty and can be +// removed. So we reintroduced the possibility of infinite looping +// by adding a "rewind" function, which let you go back to an +// earlier point in the token stream and reprocess it with injectors. +// Needless to say, we need to UN-skip the token so it gets +// reprocessed. +// +// - Suppose that you successfuly process a token, replace it with +// one with your skip mark, but now another injector wants to +// process the skipped token with another token. Should you continue +// to skip that new token, or reprocess it? If you reprocess, +// you can end up with an infinite loop where one injector converts +// to , and then another injector converts it back. So +// we inherit the skips, but for some reason, I thought that we +// should inherit the skip from the first token of the token +// that we deleted. Why? Well, it seems to work OK. +// +// If I were to redesign this functionality, I would absolutely not +// go about doing it this way: the semantics are just not very well +// defined, and in any case you probably wanted to operate on trees, +// not token streams. + + + + + +/** + * Removes all unrecognized tags from the list of tokens. + * + * This strategy iterates through all the tokens and removes unrecognized + * tokens. If a token is not recognized but a TagTransform is defined for + * that element, the element will be transformed accordingly. + */ + +class HTMLPurifier_Strategy_RemoveForeignElements extends HTMLPurifier_Strategy +{ + + /** + * @param HTMLPurifier_Token[] $tokens + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array|HTMLPurifier_Token[] + */ + public function execute($tokens, $config, $context) + { + $definition = $config->getHTMLDefinition(); + $generator = new HTMLPurifier_Generator($config, $context); + $result = array(); + + $escape_invalid_tags = $config->get('Core.EscapeInvalidTags'); + $remove_invalid_img = $config->get('Core.RemoveInvalidImg'); + + // currently only used to determine if comments should be kept + $trusted = $config->get('HTML.Trusted'); + $comment_lookup = $config->get('HTML.AllowedComments'); + $comment_regexp = $config->get('HTML.AllowedCommentsRegexp'); + $check_comments = $comment_lookup !== array() || $comment_regexp !== null; + + $remove_script_contents = $config->get('Core.RemoveScriptContents'); + $hidden_elements = $config->get('Core.HiddenElements'); + + // remove script contents compatibility + if ($remove_script_contents === true) { + $hidden_elements['script'] = true; + } elseif ($remove_script_contents === false && isset($hidden_elements['script'])) { + unset($hidden_elements['script']); + } + + $attr_validator = new HTMLPurifier_AttrValidator(); + + // removes tokens until it reaches a closing tag with its value + $remove_until = false; + + // converts comments into text tokens when this is equal to a tag name + $textify_comments = false; + + $token = false; + $context->register('CurrentToken', $token); + + $e = false; + if ($config->get('Core.CollectErrors')) { + $e =& $context->get('ErrorCollector'); + } + + foreach ($tokens as $token) { + if ($remove_until) { + if (empty($token->is_tag) || $token->name !== $remove_until) { + continue; + } + } + if (!empty($token->is_tag)) { + // DEFINITION CALL + + // before any processing, try to transform the element + if (isset($definition->info_tag_transform[$token->name])) { + $original_name = $token->name; + // there is a transformation for this tag + // DEFINITION CALL + $token = $definition-> + info_tag_transform[$token->name]->transform($token, $config, $context); + if ($e) { + $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Tag transform', $original_name); + } + } + + if (isset($definition->info[$token->name])) { + // mostly everything's good, but + // we need to make sure required attributes are in order + if (($token instanceof HTMLPurifier_Token_Start || $token instanceof HTMLPurifier_Token_Empty) && + $definition->info[$token->name]->required_attr && + ($token->name != 'img' || $remove_invalid_img) // ensure config option still works + ) { + $attr_validator->validateToken($token, $config, $context); + $ok = true; + foreach ($definition->info[$token->name]->required_attr as $name) { + if (!isset($token->attr[$name])) { + $ok = false; + break; + } + } + if (!$ok) { + if ($e) { + $e->send( + E_ERROR, + 'Strategy_RemoveForeignElements: Missing required attribute', + $name + ); + } + continue; + } + $token->armor['ValidateAttributes'] = true; + } + + if (isset($hidden_elements[$token->name]) && $token instanceof HTMLPurifier_Token_Start) { + $textify_comments = $token->name; + } elseif ($token->name === $textify_comments && $token instanceof HTMLPurifier_Token_End) { + $textify_comments = false; + } + + } elseif ($escape_invalid_tags) { + // invalid tag, generate HTML representation and insert in + if ($e) { + $e->send(E_WARNING, 'Strategy_RemoveForeignElements: Foreign element to text'); + } + $token = new HTMLPurifier_Token_Text( + $generator->generateFromToken($token) + ); + } else { + // check if we need to destroy all of the tag's children + // CAN BE GENERICIZED + if (isset($hidden_elements[$token->name])) { + if ($token instanceof HTMLPurifier_Token_Start) { + $remove_until = $token->name; + } elseif ($token instanceof HTMLPurifier_Token_Empty) { + // do nothing: we're still looking + } else { + $remove_until = false; + } + if ($e) { + $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Foreign meta element removed'); + } + } else { + if ($e) { + $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Foreign element removed'); + } + } + continue; + } + } elseif ($token instanceof HTMLPurifier_Token_Comment) { + // textify comments in script tags when they are allowed + if ($textify_comments !== false) { + $data = $token->data; + $token = new HTMLPurifier_Token_Text($data); + } elseif ($trusted || $check_comments) { + // always cleanup comments + $trailing_hyphen = false; + if ($e) { + // perform check whether or not there's a trailing hyphen + if (substr($token->data, -1) == '-') { + $trailing_hyphen = true; + } + } + $token->data = rtrim($token->data, '-'); + $found_double_hyphen = false; + while (strpos($token->data, '--') !== false) { + $found_double_hyphen = true; + $token->data = str_replace('--', '-', $token->data); + } + if ($trusted || !empty($comment_lookup[trim($token->data)]) || + ($comment_regexp !== null && preg_match($comment_regexp, trim($token->data)))) { + // OK good + if ($e) { + if ($trailing_hyphen) { + $e->send( + E_NOTICE, + 'Strategy_RemoveForeignElements: Trailing hyphen in comment removed' + ); + } + if ($found_double_hyphen) { + $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Hyphens in comment collapsed'); + } + } + } else { + if ($e) { + $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Comment removed'); + } + continue; + } + } else { + // strip comments + if ($e) { + $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Comment removed'); + } + continue; + } + } elseif ($token instanceof HTMLPurifier_Token_Text) { + } else { + continue; + } + $result[] = $token; + } + if ($remove_until && $e) { + // we removed tokens until the end, throw error + $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Token removed to end', $remove_until); + } + $context->destroy('CurrentToken'); + return $result; + } +} + + + + + +/** + * Validate all attributes in the tokens. + */ + +class HTMLPurifier_Strategy_ValidateAttributes extends HTMLPurifier_Strategy +{ + + /** + * @param HTMLPurifier_Token[] $tokens + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return HTMLPurifier_Token[] + */ + public function execute($tokens, $config, $context) + { + // setup validator + $validator = new HTMLPurifier_AttrValidator(); + + $token = false; + $context->register('CurrentToken', $token); + + foreach ($tokens as $key => $token) { + + // only process tokens that have attributes, + // namely start and empty tags + if (!$token instanceof HTMLPurifier_Token_Start && !$token instanceof HTMLPurifier_Token_Empty) { + continue; + } + + // skip tokens that are armored + if (!empty($token->armor['ValidateAttributes'])) { + continue; + } + + // note that we have no facilities here for removing tokens + $validator->validateToken($token, $config, $context); + } + $context->destroy('CurrentToken'); + return $tokens; + } +} + + + + + +/** + * Transforms FONT tags to the proper form (SPAN with CSS styling) + * + * This transformation takes the three proprietary attributes of FONT and + * transforms them into their corresponding CSS attributes. These are color, + * face, and size. + * + * @note Size is an interesting case because it doesn't map cleanly to CSS. + * Thanks to + * http://style.cleverchimp.com/font_size_intervals/altintervals.html + * for reasonable mappings. + * @warning This doesn't work completely correctly; specifically, this + * TagTransform operates before well-formedness is enforced, so + * the "active formatting elements" algorithm doesn't get applied. + */ +class HTMLPurifier_TagTransform_Font extends HTMLPurifier_TagTransform +{ + /** + * @type string + */ + public $transform_to = 'span'; + + /** + * @type array + */ + protected $_size_lookup = array( + '0' => 'xx-small', + '1' => 'xx-small', + '2' => 'small', + '3' => 'medium', + '4' => 'large', + '5' => 'x-large', + '6' => 'xx-large', + '7' => '300%', + '-1' => 'smaller', + '-2' => '60%', + '+1' => 'larger', + '+2' => '150%', + '+3' => '200%', + '+4' => '300%' + ); + + /** + * @param HTMLPurifier_Token_Tag $tag + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return HTMLPurifier_Token_End|string + */ + public function transform($tag, $config, $context) + { + if ($tag instanceof HTMLPurifier_Token_End) { + $new_tag = clone $tag; + $new_tag->name = $this->transform_to; + return $new_tag; + } + + $attr = $tag->attr; + $prepend_style = ''; + + // handle color transform + if (isset($attr['color'])) { + $prepend_style .= 'color:' . $attr['color'] . ';'; + unset($attr['color']); + } + + // handle face transform + if (isset($attr['face'])) { + $prepend_style .= 'font-family:' . $attr['face'] . ';'; + unset($attr['face']); + } + + // handle size transform + if (isset($attr['size'])) { + // normalize large numbers + if ($attr['size'] !== '') { + if ($attr['size'][0] == '+' || $attr['size'][0] == '-') { + $size = (int)$attr['size']; + if ($size < -2) { + $attr['size'] = '-2'; + } + if ($size > 4) { + $attr['size'] = '+4'; + } + } else { + $size = (int)$attr['size']; + if ($size > 7) { + $attr['size'] = '7'; + } + } + } + if (isset($this->_size_lookup[$attr['size']])) { + $prepend_style .= 'font-size:' . + $this->_size_lookup[$attr['size']] . ';'; + } + unset($attr['size']); + } + + if ($prepend_style) { + $attr['style'] = isset($attr['style']) ? + $prepend_style . $attr['style'] : + $prepend_style; + } + + $new_tag = clone $tag; + $new_tag->name = $this->transform_to; + $new_tag->attr = $attr; + + return $new_tag; + } +} + + + + + +/** + * Simple transformation, just change tag name to something else, + * and possibly add some styling. This will cover most of the deprecated + * tag cases. + */ +class HTMLPurifier_TagTransform_Simple extends HTMLPurifier_TagTransform +{ + /** + * @type string + */ + protected $style; + + /** + * @param string $transform_to Tag name to transform to. + * @param string $style CSS style to add to the tag + */ + public function __construct($transform_to, $style = null) + { + $this->transform_to = $transform_to; + $this->style = $style; + } + + /** + * @param HTMLPurifier_Token_Tag $tag + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return string + */ + public function transform($tag, $config, $context) + { + $new_tag = clone $tag; + $new_tag->name = $this->transform_to; + if (!is_null($this->style) && + ($new_tag instanceof HTMLPurifier_Token_Start || $new_tag instanceof HTMLPurifier_Token_Empty) + ) { + $this->prependCSS($new_tag->attr, $this->style); + } + return $new_tag; + } +} + + + + + +/** + * Concrete comment token class. Generally will be ignored. + */ +class HTMLPurifier_Token_Comment extends HTMLPurifier_Token +{ + /** + * Character data within comment. + * @type string + */ + public $data; + + /** + * @type bool + */ + public $is_whitespace = true; + + /** + * Transparent constructor. + * + * @param string $data String comment data. + * @param int $line + * @param int $col + */ + public function __construct($data, $line = null, $col = null) + { + $this->data = $data; + $this->line = $line; + $this->col = $col; + } + + public function toNode() { + return new HTMLPurifier_Node_Comment($this->data, $this->line, $this->col); + } +} + + + + + +/** + * Abstract class of a tag token (start, end or empty), and its behavior. + */ +abstract class HTMLPurifier_Token_Tag extends HTMLPurifier_Token +{ + /** + * Static bool marker that indicates the class is a tag. + * + * This allows us to check objects with !empty($obj->is_tag) + * without having to use a function call is_a(). + * @type bool + */ + public $is_tag = true; + + /** + * The lower-case name of the tag, like 'a', 'b' or 'blockquote'. + * + * @note Strictly speaking, XML tags are case sensitive, so we shouldn't + * be lower-casing them, but these tokens cater to HTML tags, which are + * insensitive. + * @type string + */ + public $name; + + /** + * Associative array of the tag's attributes. + * @type array + */ + public $attr = array(); + + /** + * Non-overloaded constructor, which lower-cases passed tag name. + * + * @param string $name String name. + * @param array $attr Associative array of attributes. + * @param int $line + * @param int $col + * @param array $armor + */ + public function __construct($name, $attr = array(), $line = null, $col = null, $armor = array()) + { + $this->name = ctype_lower($name) ? $name : strtolower($name); + foreach ($attr as $key => $value) { + // normalization only necessary when key is not lowercase + if (!ctype_lower($key)) { + $new_key = strtolower($key); + if (!isset($attr[$new_key])) { + $attr[$new_key] = $attr[$key]; + } + if ($new_key !== $key) { + unset($attr[$key]); + } + } + } + $this->attr = $attr; + $this->line = $line; + $this->col = $col; + $this->armor = $armor; + } + + public function toNode() { + return new HTMLPurifier_Node_Element($this->name, $this->attr, $this->line, $this->col, $this->armor); + } +} + + + + + +/** + * Concrete empty token class. + */ +class HTMLPurifier_Token_Empty extends HTMLPurifier_Token_Tag +{ + public function toNode() { + $n = parent::toNode(); + $n->empty = true; + return $n; + } +} + + + + + +/** + * Concrete end token class. + * + * @warning This class accepts attributes even though end tags cannot. This + * is for optimization reasons, as under normal circumstances, the Lexers + * do not pass attributes. + */ +class HTMLPurifier_Token_End extends HTMLPurifier_Token_Tag +{ + /** + * Token that started this node. + * Added by MakeWellFormed. Please do not edit this! + * @type HTMLPurifier_Token + */ + public $start; + + public function toNode() { + throw new Exception("HTMLPurifier_Token_End->toNode not supported!"); + } +} + + + + + +/** + * Concrete start token class. + */ +class HTMLPurifier_Token_Start extends HTMLPurifier_Token_Tag +{ +} + + + + + +/** + * Concrete text token class. + * + * Text tokens comprise of regular parsed character data (PCDATA) and raw + * character data (from the CDATA sections). Internally, their + * data is parsed with all entities expanded. Surprisingly, the text token + * does have a "tag name" called #PCDATA, which is how the DTD represents it + * in permissible child nodes. + */ +class HTMLPurifier_Token_Text extends HTMLPurifier_Token +{ + + /** + * @type string + */ + public $name = '#PCDATA'; + /**< PCDATA tag name compatible with DTD. */ + + /** + * @type string + */ + public $data; + /**< Parsed character data of text. */ + + /** + * @type bool + */ + public $is_whitespace; + + /**< Bool indicating if node is whitespace. */ + + /** + * Constructor, accepts data and determines if it is whitespace. + * @param string $data String parsed character data. + * @param int $line + * @param int $col + */ + public function __construct($data, $line = null, $col = null) + { + $this->data = $data; + $this->is_whitespace = ctype_space($data); + $this->line = $line; + $this->col = $col; + } + + public function toNode() { + return new HTMLPurifier_Node_Text($this->data, $this->is_whitespace, $this->line, $this->col); + } +} + + + + + +class HTMLPurifier_URIFilter_DisableExternal extends HTMLPurifier_URIFilter +{ + /** + * @type string + */ + public $name = 'DisableExternal'; + + /** + * @type array + */ + protected $ourHostParts = false; + + /** + * @param HTMLPurifier_Config $config + * @return void + */ + public function prepare($config) + { + $our_host = $config->getDefinition('URI')->host; + if ($our_host !== null) { + $this->ourHostParts = array_reverse(explode('.', $our_host)); + } + } + + /** + * @param HTMLPurifier_URI $uri Reference + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function filter(&$uri, $config, $context) + { + if (is_null($uri->host)) { + return true; + } + if ($this->ourHostParts === false) { + return false; + } + $host_parts = array_reverse(explode('.', $uri->host)); + foreach ($this->ourHostParts as $i => $x) { + if (!isset($host_parts[$i])) { + return false; + } + if ($host_parts[$i] != $this->ourHostParts[$i]) { + return false; + } + } + return true; + } +} + + + + + +class HTMLPurifier_URIFilter_DisableExternalResources extends HTMLPurifier_URIFilter_DisableExternal +{ + /** + * @type string + */ + public $name = 'DisableExternalResources'; + + /** + * @param HTMLPurifier_URI $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function filter(&$uri, $config, $context) + { + if (!$context->get('EmbeddedURI', true)) { + return true; + } + return parent::filter($uri, $config, $context); + } +} + + + + + +class HTMLPurifier_URIFilter_DisableResources extends HTMLPurifier_URIFilter +{ + /** + * @type string + */ + public $name = 'DisableResources'; + + /** + * @param HTMLPurifier_URI $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function filter(&$uri, $config, $context) + { + return !$context->get('EmbeddedURI', true); + } +} + + + + + +// It's not clear to me whether or not Punycode means that hostnames +// do not have canonical forms anymore. As far as I can tell, it's +// not a problem (punycoding should be identity when no Unicode +// points are involved), but I'm not 100% sure +class HTMLPurifier_URIFilter_HostBlacklist extends HTMLPurifier_URIFilter +{ + /** + * @type string + */ + public $name = 'HostBlacklist'; + + /** + * @type array + */ + protected $blacklist = array(); + + /** + * @param HTMLPurifier_Config $config + * @return bool + */ + public function prepare($config) + { + $this->blacklist = $config->get('URI.HostBlacklist'); + return true; + } + + /** + * @param HTMLPurifier_URI $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function filter(&$uri, $config, $context) + { + foreach ($this->blacklist as $blacklisted_host_fragment) { + if (strpos($uri->host, $blacklisted_host_fragment) !== false) { + return false; + } + } + return true; + } +} + + + + + +// does not support network paths + +class HTMLPurifier_URIFilter_MakeAbsolute extends HTMLPurifier_URIFilter +{ + /** + * @type string + */ + public $name = 'MakeAbsolute'; + + /** + * @type + */ + protected $base; + + /** + * @type array + */ + protected $basePathStack = array(); + + /** + * @param HTMLPurifier_Config $config + * @return bool + */ + public function prepare($config) + { + $def = $config->getDefinition('URI'); + $this->base = $def->base; + if (is_null($this->base)) { + trigger_error( + 'URI.MakeAbsolute is being ignored due to lack of ' . + 'value for URI.Base configuration', + E_USER_WARNING + ); + return false; + } + $this->base->fragment = null; // fragment is invalid for base URI + $stack = explode('/', $this->base->path); + array_pop($stack); // discard last segment + $stack = $this->_collapseStack($stack); // do pre-parsing + $this->basePathStack = $stack; + return true; + } + + /** + * @param HTMLPurifier_URI $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function filter(&$uri, $config, $context) + { + if (is_null($this->base)) { + return true; + } // abort early + if ($uri->path === '' && is_null($uri->scheme) && + is_null($uri->host) && is_null($uri->query) && is_null($uri->fragment)) { + // reference to current document + $uri = clone $this->base; + return true; + } + if (!is_null($uri->scheme)) { + // absolute URI already: don't change + if (!is_null($uri->host)) { + return true; + } + $scheme_obj = $uri->getSchemeObj($config, $context); + if (!$scheme_obj) { + // scheme not recognized + return false; + } + if (!$scheme_obj->hierarchical) { + // non-hierarchal URI with explicit scheme, don't change + return true; + } + // special case: had a scheme but always is hierarchical and had no authority + } + if (!is_null($uri->host)) { + // network path, don't bother + return true; + } + if ($uri->path === '') { + $uri->path = $this->base->path; + } elseif ($uri->path[0] !== '/') { + // relative path, needs more complicated processing + $stack = explode('/', $uri->path); + $new_stack = array_merge($this->basePathStack, $stack); + if ($new_stack[0] !== '' && !is_null($this->base->host)) { + array_unshift($new_stack, ''); + } + $new_stack = $this->_collapseStack($new_stack); + $uri->path = implode('/', $new_stack); + } else { + // absolute path, but still we should collapse + $uri->path = implode('/', $this->_collapseStack(explode('/', $uri->path))); + } + // re-combine + $uri->scheme = $this->base->scheme; + if (is_null($uri->userinfo)) { + $uri->userinfo = $this->base->userinfo; + } + if (is_null($uri->host)) { + $uri->host = $this->base->host; + } + if (is_null($uri->port)) { + $uri->port = $this->base->port; + } + return true; + } + + /** + * Resolve dots and double-dots in a path stack + * @param array $stack + * @return array + */ + private function _collapseStack($stack) + { + $result = array(); + $is_folder = false; + for ($i = 0; isset($stack[$i]); $i++) { + $is_folder = false; + // absorb an internally duplicated slash + if ($stack[$i] == '' && $i && isset($stack[$i + 1])) { + continue; + } + if ($stack[$i] == '..') { + if (!empty($result)) { + $segment = array_pop($result); + if ($segment === '' && empty($result)) { + // error case: attempted to back out too far: + // restore the leading slash + $result[] = ''; + } elseif ($segment === '..') { + $result[] = '..'; // cannot remove .. with .. + } + } else { + // relative path, preserve the double-dots + $result[] = '..'; + } + $is_folder = true; + continue; + } + if ($stack[$i] == '.') { + // silently absorb + $is_folder = true; + continue; + } + $result[] = $stack[$i]; + } + if ($is_folder) { + $result[] = ''; + } + return $result; + } +} + + + + + +class HTMLPurifier_URIFilter_Munge extends HTMLPurifier_URIFilter +{ + /** + * @type string + */ + public $name = 'Munge'; + + /** + * @type bool + */ + public $post = true; + + /** + * @type string + */ + private $target; + + /** + * @type HTMLPurifier_URIParser + */ + private $parser; + + /** + * @type bool + */ + private $doEmbed; + + /** + * @type string + */ + private $secretKey; + + /** + * @type array + */ + protected $replace = array(); + + /** + * @param HTMLPurifier_Config $config + * @return bool + */ + public function prepare($config) + { + $this->target = $config->get('URI.' . $this->name); + $this->parser = new HTMLPurifier_URIParser(); + $this->doEmbed = $config->get('URI.MungeResources'); + $this->secretKey = $config->get('URI.MungeSecretKey'); + if ($this->secretKey && !function_exists('hash_hmac')) { + throw new Exception("Cannot use %URI.MungeSecretKey without hash_hmac support."); + } + return true; + } + + /** + * @param HTMLPurifier_URI $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function filter(&$uri, $config, $context) + { + if ($context->get('EmbeddedURI', true) && !$this->doEmbed) { + return true; + } + + $scheme_obj = $uri->getSchemeObj($config, $context); + if (!$scheme_obj) { + return true; + } // ignore unknown schemes, maybe another postfilter did it + if (!$scheme_obj->browsable) { + return true; + } // ignore non-browseable schemes, since we can't munge those in a reasonable way + if ($uri->isBenign($config, $context)) { + return true; + } // don't redirect if a benign URL + + $this->makeReplace($uri, $config, $context); + $this->replace = array_map('rawurlencode', $this->replace); + + $new_uri = strtr($this->target, $this->replace); + $new_uri = $this->parser->parse($new_uri); + // don't redirect if the target host is the same as the + // starting host + if ($uri->host === $new_uri->host) { + return true; + } + $uri = $new_uri; // overwrite + return true; + } + + /** + * @param HTMLPurifier_URI $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + */ + protected function makeReplace($uri, $config, $context) + { + $string = $uri->toString(); + // always available + $this->replace['%s'] = $string; + $this->replace['%r'] = $context->get('EmbeddedURI', true); + $token = $context->get('CurrentToken', true); + $this->replace['%n'] = $token ? $token->name : null; + $this->replace['%m'] = $context->get('CurrentAttr', true); + $this->replace['%p'] = $context->get('CurrentCSSProperty', true); + // not always available + if ($this->secretKey) { + $this->replace['%t'] = hash_hmac("sha256", $string, $this->secretKey); + } + } +} + + + + + +/** + * Implements safety checks for safe iframes. + * + * @warning This filter is *critical* for ensuring that %HTML.SafeIframe + * works safely. + */ +class HTMLPurifier_URIFilter_SafeIframe extends HTMLPurifier_URIFilter +{ + /** + * @type string + */ + public $name = 'SafeIframe'; + + /** + * @type bool + */ + public $always_load = true; + + /** + * @type string + */ + protected $regexp = null; + + // XXX: The not so good bit about how this is all set up now is we + // can't check HTML.SafeIframe in the 'prepare' step: we have to + // defer till the actual filtering. + /** + * @param HTMLPurifier_Config $config + * @return bool + */ + public function prepare($config) + { + $this->regexp = $config->get('URI.SafeIframeRegexp'); + return true; + } + + /** + * @param HTMLPurifier_URI $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function filter(&$uri, $config, $context) + { + // check if filter not applicable + if (!$config->get('HTML.SafeIframe')) { + return true; + } + // check if the filter should actually trigger + if (!$context->get('EmbeddedURI', true)) { + return true; + } + $token = $context->get('CurrentToken', true); + if (!($token && $token->name == 'iframe')) { + return true; + } + // check if we actually have some whitelists enabled + if ($this->regexp === null) { + return false; + } + // actually check the whitelists + return preg_match($this->regexp, $uri->toString()); + } +} + + + + + +/** + * Implements data: URI for base64 encoded images supported by GD. + */ +class HTMLPurifier_URIScheme_data extends HTMLPurifier_URIScheme +{ + /** + * @type bool + */ + public $browsable = true; + + /** + * @type array + */ + public $allowed_types = array( + // you better write validation code for other types if you + // decide to allow them + 'image/jpeg' => true, + 'image/gif' => true, + 'image/png' => true, + ); + // this is actually irrelevant since we only write out the path + // component + /** + * @type bool + */ + public $may_omit_host = true; + + /** + * @param HTMLPurifier_URI $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function doValidate(&$uri, $config, $context) + { + $result = explode(',', $uri->path, 2); + $is_base64 = false; + $charset = null; + $content_type = null; + if (count($result) == 2) { + list($metadata, $data) = $result; + // do some legwork on the metadata + $metas = explode(';', $metadata); + while (!empty($metas)) { + $cur = array_shift($metas); + if ($cur == 'base64') { + $is_base64 = true; + break; + } + if (substr($cur, 0, 8) == 'charset=') { + // doesn't match if there are arbitrary spaces, but + // whatever dude + if ($charset !== null) { + continue; + } // garbage + $charset = substr($cur, 8); // not used + } else { + if ($content_type !== null) { + continue; + } // garbage + $content_type = $cur; + } + } + } else { + $data = $result[0]; + } + if ($content_type !== null && empty($this->allowed_types[$content_type])) { + return false; + } + if ($charset !== null) { + // error; we don't allow plaintext stuff + $charset = null; + } + $data = rawurldecode($data); + if ($is_base64) { + $raw_data = base64_decode($data); + } else { + $raw_data = $data; + } + if ( strlen($raw_data) < 12 ) { + // error; exif_imagetype throws exception with small files, + // and this likely indicates a corrupt URI/failed parse anyway + return false; + } + // XXX probably want to refactor this into a general mechanism + // for filtering arbitrary content types + if (function_exists('sys_get_temp_dir')) { + $file = tempnam(sys_get_temp_dir(), ""); + } else { + $file = tempnam("/tmp", ""); + } + file_put_contents($file, $raw_data); + if (function_exists('exif_imagetype')) { + $image_code = exif_imagetype($file); + unlink($file); + } elseif (function_exists('getimagesize')) { + set_error_handler(array($this, 'muteErrorHandler')); + $info = getimagesize($file); + restore_error_handler(); + unlink($file); + if ($info == false) { + return false; + } + $image_code = $info[2]; + } else { + trigger_error("could not find exif_imagetype or getimagesize functions", E_USER_ERROR); + } + $real_content_type = image_type_to_mime_type($image_code); + if ($real_content_type != $content_type) { + // we're nice guys; if the content type is something else we + // support, change it over + if (empty($this->allowed_types[$real_content_type])) { + return false; + } + $content_type = $real_content_type; + } + // ok, it's kosher, rewrite what we need + $uri->userinfo = null; + $uri->host = null; + $uri->port = null; + $uri->fragment = null; + $uri->query = null; + $uri->path = "$content_type;base64," . base64_encode($raw_data); + return true; + } + + /** + * @param int $errno + * @param string $errstr + */ + public function muteErrorHandler($errno, $errstr) + { + } +} + + + +/** + * Validates file as defined by RFC 1630 and RFC 1738. + */ +class HTMLPurifier_URIScheme_file extends HTMLPurifier_URIScheme +{ + /** + * Generally file:// URLs are not accessible from most + * machines, so placing them as an img src is incorrect. + * @type bool + */ + public $browsable = false; + + /** + * Basically the *only* URI scheme for which this is true, since + * accessing files on the local machine is very common. In fact, + * browsers on some operating systems don't understand the + * authority, though I hear it is used on Windows to refer to + * network shares. + * @type bool + */ + public $may_omit_host = true; + + /** + * @param HTMLPurifier_URI $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function doValidate(&$uri, $config, $context) + { + // Authentication method is not supported + $uri->userinfo = null; + // file:// makes no provisions for accessing the resource + $uri->port = null; + // While it seems to work on Firefox, the querystring has + // no possible effect and is thus stripped. + $uri->query = null; + return true; + } +} + + + + + +/** + * Validates ftp (File Transfer Protocol) URIs as defined by generic RFC 1738. + */ +class HTMLPurifier_URIScheme_ftp extends HTMLPurifier_URIScheme +{ + /** + * @type int + */ + public $default_port = 21; + + /** + * @type bool + */ + public $browsable = true; // usually + + /** + * @type bool + */ + public $hierarchical = true; + + /** + * @param HTMLPurifier_URI $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function doValidate(&$uri, $config, $context) + { + $uri->query = null; + + // typecode check + $semicolon_pos = strrpos($uri->path, ';'); // reverse + if ($semicolon_pos !== false) { + $type = substr($uri->path, $semicolon_pos + 1); // no semicolon + $uri->path = substr($uri->path, 0, $semicolon_pos); + $type_ret = ''; + if (strpos($type, '=') !== false) { + // figure out whether or not the declaration is correct + list($key, $typecode) = explode('=', $type, 2); + if ($key !== 'type') { + // invalid key, tack it back on encoded + $uri->path .= '%3B' . $type; + } elseif ($typecode === 'a' || $typecode === 'i' || $typecode === 'd') { + $type_ret = ";type=$typecode"; + } + } else { + $uri->path .= '%3B' . $type; + } + $uri->path = str_replace(';', '%3B', $uri->path); + $uri->path .= $type_ret; + } + return true; + } +} + + + + + +/** + * Validates http (HyperText Transfer Protocol) as defined by RFC 2616 + */ +class HTMLPurifier_URIScheme_http extends HTMLPurifier_URIScheme +{ + /** + * @type int + */ + public $default_port = 80; + + /** + * @type bool + */ + public $browsable = true; + + /** + * @type bool + */ + public $hierarchical = true; + + /** + * @param HTMLPurifier_URI $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function doValidate(&$uri, $config, $context) + { + $uri->userinfo = null; + return true; + } +} + + + + + +/** + * Validates https (Secure HTTP) according to http scheme. + */ +class HTMLPurifier_URIScheme_https extends HTMLPurifier_URIScheme_http +{ + /** + * @type int + */ + public $default_port = 443; + /** + * @type bool + */ + public $secure = true; +} + + + + + +// VERY RELAXED! Shouldn't cause problems, not even Firefox checks if the +// email is valid, but be careful! + +/** + * Validates mailto (for E-mail) according to RFC 2368 + * @todo Validate the email address + * @todo Filter allowed query parameters + */ + +class HTMLPurifier_URIScheme_mailto extends HTMLPurifier_URIScheme +{ + /** + * @type bool + */ + public $browsable = false; + + /** + * @type bool + */ + public $may_omit_host = true; + + /** + * @param HTMLPurifier_URI $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function doValidate(&$uri, $config, $context) + { + $uri->userinfo = null; + $uri->host = null; + $uri->port = null; + // we need to validate path against RFC 2368's addr-spec + return true; + } +} + + + + + +/** + * Validates news (Usenet) as defined by generic RFC 1738 + */ +class HTMLPurifier_URIScheme_news extends HTMLPurifier_URIScheme +{ + /** + * @type bool + */ + public $browsable = false; + + /** + * @type bool + */ + public $may_omit_host = true; + + /** + * @param HTMLPurifier_URI $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function doValidate(&$uri, $config, $context) + { + $uri->userinfo = null; + $uri->host = null; + $uri->port = null; + $uri->query = null; + // typecode check needed on path + return true; + } +} + + + + + +/** + * Validates nntp (Network News Transfer Protocol) as defined by generic RFC 1738 + */ +class HTMLPurifier_URIScheme_nntp extends HTMLPurifier_URIScheme +{ + /** + * @type int + */ + public $default_port = 119; + + /** + * @type bool + */ + public $browsable = false; + + /** + * @param HTMLPurifier_URI $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function doValidate(&$uri, $config, $context) + { + $uri->userinfo = null; + $uri->query = null; + return true; + } +} + + + + + +/** + * Validates tel (for phone numbers). + * + * The relevant specifications for this protocol are RFC 3966 and RFC 5341, + * but this class takes a much simpler approach: we normalize phone + * numbers so that they only include (possibly) a leading plus, + * and then any number of digits and x'es. + */ + +class HTMLPurifier_URIScheme_tel extends HTMLPurifier_URIScheme +{ + /** + * @type bool + */ + public $browsable = false; + + /** + * @type bool + */ + public $may_omit_host = true; + + /** + * @param HTMLPurifier_URI $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function doValidate(&$uri, $config, $context) + { + $uri->userinfo = null; + $uri->host = null; + $uri->port = null; + + // Delete all non-numeric characters, non-x characters + // from phone number, EXCEPT for a leading plus sign. + $uri->path = preg_replace('/(?!^\+)[^\dx]/', '', + // Normalize e(x)tension to lower-case + str_replace('X', 'x', $uri->path)); + + return true; + } +} + + + + + +/** + * Performs safe variable parsing based on types which can be used by + * users. This may not be able to represent all possible data inputs, + * however. + */ +class HTMLPurifier_VarParser_Flexible extends HTMLPurifier_VarParser +{ + /** + * @param mixed $var + * @param int $type + * @param bool $allow_null + * @return array|bool|float|int|mixed|null|string + * @throws HTMLPurifier_VarParserException + */ + protected function parseImplementation($var, $type, $allow_null) + { + if ($allow_null && $var === null) { + return null; + } + switch ($type) { + // Note: if code "breaks" from the switch, it triggers a generic + // exception to be thrown. Specific errors can be specifically + // done here. + case self::C_MIXED: + case self::ISTRING: + case self::C_STRING: + case self::TEXT: + case self::ITEXT: + return $var; + case self::C_INT: + if (is_string($var) && ctype_digit($var)) { + $var = (int)$var; + } + return $var; + case self::C_FLOAT: + if ((is_string($var) && is_numeric($var)) || is_int($var)) { + $var = (float)$var; + } + return $var; + case self::C_BOOL: + if (is_int($var) && ($var === 0 || $var === 1)) { + $var = (bool)$var; + } elseif (is_string($var)) { + if ($var == 'on' || $var == 'true' || $var == '1') { + $var = true; + } elseif ($var == 'off' || $var == 'false' || $var == '0') { + $var = false; + } else { + throw new HTMLPurifier_VarParserException("Unrecognized value '$var' for $type"); + } + } + return $var; + case self::ALIST: + case self::HASH: + case self::LOOKUP: + if (is_string($var)) { + // special case: technically, this is an array with + // a single empty string item, but having an empty + // array is more intuitive + if ($var == '') { + return array(); + } + if (strpos($var, "\n") === false && strpos($var, "\r") === false) { + // simplistic string to array method that only works + // for simple lists of tag names or alphanumeric characters + $var = explode(',', $var); + } else { + $var = preg_split('/(,|[\n\r]+)/', $var); + } + // remove spaces + foreach ($var as $i => $j) { + $var[$i] = trim($j); + } + if ($type === self::HASH) { + // key:value,key2:value2 + $nvar = array(); + foreach ($var as $keypair) { + $c = explode(':', $keypair, 2); + if (!isset($c[1])) { + continue; + } + $nvar[trim($c[0])] = trim($c[1]); + } + $var = $nvar; + } + } + if (!is_array($var)) { + break; + } + $keys = array_keys($var); + if ($keys === array_keys($keys)) { + if ($type == self::ALIST) { + return $var; + } elseif ($type == self::LOOKUP) { + $new = array(); + foreach ($var as $key) { + $new[$key] = true; + } + return $new; + } else { + break; + } + } + if ($type === self::ALIST) { + trigger_error("Array list did not have consecutive integer indexes", E_USER_WARNING); + return array_values($var); + } + if ($type === self::LOOKUP) { + foreach ($var as $key => $value) { + if ($value !== true) { + trigger_error( + "Lookup array has non-true value at key '$key'; " . + "maybe your input array was not indexed numerically", + E_USER_WARNING + ); + } + $var[$key] = true; + } + } + return $var; + default: + $this->errorInconsistent(__CLASS__, $type); + } + $this->errorGeneric($var, $type); + } +} + + + + + +/** + * This variable parser uses PHP's internal code engine. Because it does + * this, it can represent all inputs; however, it is dangerous and cannot + * be used by users. + */ +class HTMLPurifier_VarParser_Native extends HTMLPurifier_VarParser +{ + + /** + * @param mixed $var + * @param int $type + * @param bool $allow_null + * @return null|string + */ + protected function parseImplementation($var, $type, $allow_null) + { + return $this->evalExpression($var); + } + + /** + * @param string $expr + * @return mixed + * @throws HTMLPurifier_VarParserException + */ + protected function evalExpression($expr) + { + $var = null; + $result = eval("\$var = $expr;"); + if ($result === false) { + throw new HTMLPurifier_VarParserException("Fatal error in evaluated code"); + } + return $var; + } +} + + + diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/Builder/ConfigSchema.php b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/Builder/ConfigSchema.php new file mode 100644 index 000000000..1174575ea --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/Builder/ConfigSchema.php @@ -0,0 +1,48 @@ +directives as $d) { + $schema->add( + $d->id->key, + $d->default, + $d->type, + $d->typeAllowsNull + ); + if ($d->allowed !== null) { + $schema->addAllowedValues( + $d->id->key, + $d->allowed + ); + } + foreach ($d->aliases as $alias) { + $schema->addAlias( + $alias->key, + $d->id->key + ); + } + if ($d->valueAliases !== null) { + $schema->addValueAliases( + $d->id->key, + $d->valueAliases + ); + } + } + $schema->postProcess(); + return $schema; + } +} + +// vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/Builder/Xml.php b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/Builder/Xml.php new file mode 100644 index 000000000..0d00bf1d1 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/Builder/Xml.php @@ -0,0 +1,144 @@ +startElement('div'); + + $purifier = HTMLPurifier::getInstance(); + $html = $purifier->purify($html); + $this->writeAttribute('xmlns', 'http://www.w3.org/1999/xhtml'); + $this->writeRaw($html); + + $this->endElement(); // div + } + + /** + * @param mixed $var + * @return string + */ + protected function export($var) + { + if ($var === array()) { + return 'array()'; + } + return var_export($var, true); + } + + /** + * @param HTMLPurifier_ConfigSchema_Interchange $interchange + */ + public function build($interchange) + { + // global access, only use as last resort + $this->interchange = $interchange; + + $this->setIndent(true); + $this->startDocument('1.0', 'UTF-8'); + $this->startElement('configdoc'); + $this->writeElement('title', $interchange->name); + + foreach ($interchange->directives as $directive) { + $this->buildDirective($directive); + } + + if ($this->namespace) { + $this->endElement(); + } // namespace + + $this->endElement(); // configdoc + $this->flush(); + } + + /** + * @param HTMLPurifier_ConfigSchema_Interchange_Directive $directive + */ + public function buildDirective($directive) + { + // Kludge, although I suppose having a notion of a "root namespace" + // certainly makes things look nicer when documentation is built. + // Depends on things being sorted. + if (!$this->namespace || $this->namespace !== $directive->id->getRootNamespace()) { + if ($this->namespace) { + $this->endElement(); + } // namespace + $this->namespace = $directive->id->getRootNamespace(); + $this->startElement('namespace'); + $this->writeAttribute('id', $this->namespace); + $this->writeElement('name', $this->namespace); + } + + $this->startElement('directive'); + $this->writeAttribute('id', $directive->id->toString()); + + $this->writeElement('name', $directive->id->getDirective()); + + $this->startElement('aliases'); + foreach ($directive->aliases as $alias) { + $this->writeElement('alias', $alias->toString()); + } + $this->endElement(); // aliases + + $this->startElement('constraints'); + if ($directive->version) { + $this->writeElement('version', $directive->version); + } + $this->startElement('type'); + if ($directive->typeAllowsNull) { + $this->writeAttribute('allow-null', 'yes'); + } + $this->text($directive->type); + $this->endElement(); // type + if ($directive->allowed) { + $this->startElement('allowed'); + foreach ($directive->allowed as $value => $x) { + $this->writeElement('value', $value); + } + $this->endElement(); // allowed + } + $this->writeElement('default', $this->export($directive->default)); + $this->writeAttribute('xml:space', 'preserve'); + if ($directive->external) { + $this->startElement('external'); + foreach ($directive->external as $project) { + $this->writeElement('project', $project); + } + $this->endElement(); + } + $this->endElement(); // constraints + + if ($directive->deprecatedVersion) { + $this->startElement('deprecated'); + $this->writeElement('version', $directive->deprecatedVersion); + $this->writeElement('use', $directive->deprecatedUse->toString()); + $this->endElement(); // deprecated + } + + $this->startElement('description'); + $this->writeHTMLDiv($directive->description); + $this->endElement(); // description + + $this->endElement(); // directive + } +} + +// vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/Exception.php b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/Exception.php new file mode 100644 index 000000000..1abdcfc06 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/Exception.php @@ -0,0 +1,11 @@ + array(directive info) + * @type HTMLPurifier_ConfigSchema_Interchange_Directive[] + */ + public $directives = array(); + + /** + * Adds a directive array to $directives + * @param HTMLPurifier_ConfigSchema_Interchange_Directive $directive + * @throws HTMLPurifier_ConfigSchema_Exception + */ + public function addDirective($directive) + { + if (isset($this->directives[$i = $directive->id->toString()])) { + throw new HTMLPurifier_ConfigSchema_Exception("Cannot redefine directive '$i'"); + } + $this->directives[$i] = $directive; + } + + /** + * Convenience function to perform standard validation. Throws exception + * on failed validation. + */ + public function validate() + { + $validator = new HTMLPurifier_ConfigSchema_Validator(); + return $validator->validate($this); + } +} + +// vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/Interchange/Directive.php b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/Interchange/Directive.php new file mode 100644 index 000000000..4c39c5c68 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/Interchange/Directive.php @@ -0,0 +1,89 @@ + true). + * Null if all values are allowed. + * @type array + */ + public $allowed; + + /** + * List of aliases for the directive. + * e.g. array(new HTMLPurifier_ConfigSchema_Interchange_Id('Ns', 'Dir'))). + * @type HTMLPurifier_ConfigSchema_Interchange_Id[] + */ + public $aliases = array(); + + /** + * Hash of value aliases, e.g. array('alt' => 'real'). Null if value + * aliasing is disabled (necessary for non-scalar types). + * @type array + */ + public $valueAliases; + + /** + * Version of HTML Purifier the directive was introduced, e.g. '1.3.1'. + * Null if the directive has always existed. + * @type string + */ + public $version; + + /** + * ID of directive that supercedes this old directive. + * Null if not deprecated. + * @type HTMLPurifier_ConfigSchema_Interchange_Id + */ + public $deprecatedUse; + + /** + * Version of HTML Purifier this directive was deprecated. Null if not + * deprecated. + * @type string + */ + public $deprecatedVersion; + + /** + * List of external projects this directive depends on, e.g. array('CSSTidy'). + * @type array + */ + public $external = array(); +} + +// vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/Interchange/Id.php b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/Interchange/Id.php new file mode 100644 index 000000000..3ee817114 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/Interchange/Id.php @@ -0,0 +1,58 @@ +key = $key; + } + + /** + * @return string + * @warning This is NOT magic, to ensure that people don't abuse SPL and + * cause problems for PHP 5.0 support. + */ + public function toString() + { + return $this->key; + } + + /** + * @return string + */ + public function getRootNamespace() + { + return substr($this->key, 0, strpos($this->key, ".")); + } + + /** + * @return string + */ + public function getDirective() + { + return substr($this->key, strpos($this->key, ".") + 1); + } + + /** + * @param string $id + * @return HTMLPurifier_ConfigSchema_Interchange_Id + */ + public static function make($id) + { + return new HTMLPurifier_ConfigSchema_Interchange_Id($id); + } +} + +// vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/InterchangeBuilder.php b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/InterchangeBuilder.php new file mode 100644 index 000000000..fe9b3268f --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/InterchangeBuilder.php @@ -0,0 +1,226 @@ +varParser = $varParser ? $varParser : new HTMLPurifier_VarParser_Native(); + } + + /** + * @param string $dir + * @return HTMLPurifier_ConfigSchema_Interchange + */ + public static function buildFromDirectory($dir = null) + { + $builder = new HTMLPurifier_ConfigSchema_InterchangeBuilder(); + $interchange = new HTMLPurifier_ConfigSchema_Interchange(); + return $builder->buildDir($interchange, $dir); + } + + /** + * @param HTMLPurifier_ConfigSchema_Interchange $interchange + * @param string $dir + * @return HTMLPurifier_ConfigSchema_Interchange + */ + public function buildDir($interchange, $dir = null) + { + if (!$dir) { + $dir = HTMLPURIFIER_PREFIX . '/HTMLPurifier/ConfigSchema/schema'; + } + if (file_exists($dir . '/info.ini')) { + $info = parse_ini_file($dir . '/info.ini'); + $interchange->name = $info['name']; + } + + $files = array(); + $dh = opendir($dir); + while (false !== ($file = readdir($dh))) { + if (!$file || $file[0] == '.' || strrchr($file, '.') !== '.txt') { + continue; + } + $files[] = $file; + } + closedir($dh); + + sort($files); + foreach ($files as $file) { + $this->buildFile($interchange, $dir . '/' . $file); + } + return $interchange; + } + + /** + * @param HTMLPurifier_ConfigSchema_Interchange $interchange + * @param string $file + */ + public function buildFile($interchange, $file) + { + $parser = new HTMLPurifier_StringHashParser(); + $this->build( + $interchange, + new HTMLPurifier_StringHash($parser->parseFile($file)) + ); + } + + /** + * Builds an interchange object based on a hash. + * @param HTMLPurifier_ConfigSchema_Interchange $interchange HTMLPurifier_ConfigSchema_Interchange object to build + * @param HTMLPurifier_StringHash $hash source data + * @throws HTMLPurifier_ConfigSchema_Exception + */ + public function build($interchange, $hash) + { + if (!$hash instanceof HTMLPurifier_StringHash) { + $hash = new HTMLPurifier_StringHash($hash); + } + if (!isset($hash['ID'])) { + throw new HTMLPurifier_ConfigSchema_Exception('Hash does not have any ID'); + } + if (strpos($hash['ID'], '.') === false) { + if (count($hash) == 2 && isset($hash['DESCRIPTION'])) { + $hash->offsetGet('DESCRIPTION'); // prevent complaining + } else { + throw new HTMLPurifier_ConfigSchema_Exception('All directives must have a namespace'); + } + } else { + $this->buildDirective($interchange, $hash); + } + $this->_findUnused($hash); + } + + /** + * @param HTMLPurifier_ConfigSchema_Interchange $interchange + * @param HTMLPurifier_StringHash $hash + * @throws HTMLPurifier_ConfigSchema_Exception + */ + public function buildDirective($interchange, $hash) + { + $directive = new HTMLPurifier_ConfigSchema_Interchange_Directive(); + + // These are required elements: + $directive->id = $this->id($hash->offsetGet('ID')); + $id = $directive->id->toString(); // convenience + + if (isset($hash['TYPE'])) { + $type = explode('/', $hash->offsetGet('TYPE')); + if (isset($type[1])) { + $directive->typeAllowsNull = true; + } + $directive->type = $type[0]; + } else { + throw new HTMLPurifier_ConfigSchema_Exception("TYPE in directive hash '$id' not defined"); + } + + if (isset($hash['DEFAULT'])) { + try { + $directive->default = $this->varParser->parse( + $hash->offsetGet('DEFAULT'), + $directive->type, + $directive->typeAllowsNull + ); + } catch (HTMLPurifier_VarParserException $e) { + throw new HTMLPurifier_ConfigSchema_Exception($e->getMessage() . " in DEFAULT in directive hash '$id'"); + } + } + + if (isset($hash['DESCRIPTION'])) { + $directive->description = $hash->offsetGet('DESCRIPTION'); + } + + if (isset($hash['ALLOWED'])) { + $directive->allowed = $this->lookup($this->evalArray($hash->offsetGet('ALLOWED'))); + } + + if (isset($hash['VALUE-ALIASES'])) { + $directive->valueAliases = $this->evalArray($hash->offsetGet('VALUE-ALIASES')); + } + + if (isset($hash['ALIASES'])) { + $raw_aliases = trim($hash->offsetGet('ALIASES')); + $aliases = preg_split('/\s*,\s*/', $raw_aliases); + foreach ($aliases as $alias) { + $directive->aliases[] = $this->id($alias); + } + } + + if (isset($hash['VERSION'])) { + $directive->version = $hash->offsetGet('VERSION'); + } + + if (isset($hash['DEPRECATED-USE'])) { + $directive->deprecatedUse = $this->id($hash->offsetGet('DEPRECATED-USE')); + } + + if (isset($hash['DEPRECATED-VERSION'])) { + $directive->deprecatedVersion = $hash->offsetGet('DEPRECATED-VERSION'); + } + + if (isset($hash['EXTERNAL'])) { + $directive->external = preg_split('/\s*,\s*/', trim($hash->offsetGet('EXTERNAL'))); + } + + $interchange->addDirective($directive); + } + + /** + * Evaluates an array PHP code string without array() wrapper + * @param string $contents + */ + protected function evalArray($contents) + { + return eval('return array(' . $contents . ');'); + } + + /** + * Converts an array list into a lookup array. + * @param array $array + * @return array + */ + protected function lookup($array) + { + $ret = array(); + foreach ($array as $val) { + $ret[$val] = true; + } + return $ret; + } + + /** + * Convenience function that creates an HTMLPurifier_ConfigSchema_Interchange_Id + * object based on a string Id. + * @param string $id + * @return HTMLPurifier_ConfigSchema_Interchange_Id + */ + protected function id($id) + { + return HTMLPurifier_ConfigSchema_Interchange_Id::make($id); + } + + /** + * Triggers errors for any unused keys passed in the hash; such keys + * may indicate typos, missing values, etc. + * @param HTMLPurifier_StringHash $hash Hash to check. + */ + protected function _findUnused($hash) + { + $accessed = $hash->getAccessed(); + foreach ($hash as $k => $v) { + if (!isset($accessed[$k])) { + trigger_error("String hash key '$k' not used by builder", E_USER_NOTICE); + } + } + } +} + +// vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/Validator.php b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/Validator.php new file mode 100644 index 000000000..9f14444f3 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/Validator.php @@ -0,0 +1,248 @@ +parser = new HTMLPurifier_VarParser(); + } + + /** + * Validates a fully-formed interchange object. + * @param HTMLPurifier_ConfigSchema_Interchange $interchange + * @return bool + */ + public function validate($interchange) + { + $this->interchange = $interchange; + $this->aliases = array(); + // PHP is a bit lax with integer <=> string conversions in + // arrays, so we don't use the identical !== comparison + foreach ($interchange->directives as $i => $directive) { + $id = $directive->id->toString(); + if ($i != $id) { + $this->error(false, "Integrity violation: key '$i' does not match internal id '$id'"); + } + $this->validateDirective($directive); + } + return true; + } + + /** + * Validates a HTMLPurifier_ConfigSchema_Interchange_Id object. + * @param HTMLPurifier_ConfigSchema_Interchange_Id $id + */ + public function validateId($id) + { + $id_string = $id->toString(); + $this->context[] = "id '$id_string'"; + if (!$id instanceof HTMLPurifier_ConfigSchema_Interchange_Id) { + // handled by InterchangeBuilder + $this->error(false, 'is not an instance of HTMLPurifier_ConfigSchema_Interchange_Id'); + } + // keys are now unconstrained (we might want to narrow down to A-Za-z0-9.) + // we probably should check that it has at least one namespace + $this->with($id, 'key') + ->assertNotEmpty() + ->assertIsString(); // implicit assertIsString handled by InterchangeBuilder + array_pop($this->context); + } + + /** + * Validates a HTMLPurifier_ConfigSchema_Interchange_Directive object. + * @param HTMLPurifier_ConfigSchema_Interchange_Directive $d + */ + public function validateDirective($d) + { + $id = $d->id->toString(); + $this->context[] = "directive '$id'"; + $this->validateId($d->id); + + $this->with($d, 'description') + ->assertNotEmpty(); + + // BEGIN - handled by InterchangeBuilder + $this->with($d, 'type') + ->assertNotEmpty(); + $this->with($d, 'typeAllowsNull') + ->assertIsBool(); + try { + // This also tests validity of $d->type + $this->parser->parse($d->default, $d->type, $d->typeAllowsNull); + } catch (HTMLPurifier_VarParserException $e) { + $this->error('default', 'had error: ' . $e->getMessage()); + } + // END - handled by InterchangeBuilder + + if (!is_null($d->allowed) || !empty($d->valueAliases)) { + // allowed and valueAliases require that we be dealing with + // strings, so check for that early. + $d_int = HTMLPurifier_VarParser::$types[$d->type]; + if (!isset(HTMLPurifier_VarParser::$stringTypes[$d_int])) { + $this->error('type', 'must be a string type when used with allowed or value aliases'); + } + } + + $this->validateDirectiveAllowed($d); + $this->validateDirectiveValueAliases($d); + $this->validateDirectiveAliases($d); + + array_pop($this->context); + } + + /** + * Extra validation if $allowed member variable of + * HTMLPurifier_ConfigSchema_Interchange_Directive is defined. + * @param HTMLPurifier_ConfigSchema_Interchange_Directive $d + */ + public function validateDirectiveAllowed($d) + { + if (is_null($d->allowed)) { + return; + } + $this->with($d, 'allowed') + ->assertNotEmpty() + ->assertIsLookup(); // handled by InterchangeBuilder + if (is_string($d->default) && !isset($d->allowed[$d->default])) { + $this->error('default', 'must be an allowed value'); + } + $this->context[] = 'allowed'; + foreach ($d->allowed as $val => $x) { + if (!is_string($val)) { + $this->error("value $val", 'must be a string'); + } + } + array_pop($this->context); + } + + /** + * Extra validation if $valueAliases member variable of + * HTMLPurifier_ConfigSchema_Interchange_Directive is defined. + * @param HTMLPurifier_ConfigSchema_Interchange_Directive $d + */ + public function validateDirectiveValueAliases($d) + { + if (is_null($d->valueAliases)) { + return; + } + $this->with($d, 'valueAliases') + ->assertIsArray(); // handled by InterchangeBuilder + $this->context[] = 'valueAliases'; + foreach ($d->valueAliases as $alias => $real) { + if (!is_string($alias)) { + $this->error("alias $alias", 'must be a string'); + } + if (!is_string($real)) { + $this->error("alias target $real from alias '$alias'", 'must be a string'); + } + if ($alias === $real) { + $this->error("alias '$alias'", "must not be an alias to itself"); + } + } + if (!is_null($d->allowed)) { + foreach ($d->valueAliases as $alias => $real) { + if (isset($d->allowed[$alias])) { + $this->error("alias '$alias'", 'must not be an allowed value'); + } elseif (!isset($d->allowed[$real])) { + $this->error("alias '$alias'", 'must be an alias to an allowed value'); + } + } + } + array_pop($this->context); + } + + /** + * Extra validation if $aliases member variable of + * HTMLPurifier_ConfigSchema_Interchange_Directive is defined. + * @param HTMLPurifier_ConfigSchema_Interchange_Directive $d + */ + public function validateDirectiveAliases($d) + { + $this->with($d, 'aliases') + ->assertIsArray(); // handled by InterchangeBuilder + $this->context[] = 'aliases'; + foreach ($d->aliases as $alias) { + $this->validateId($alias); + $s = $alias->toString(); + if (isset($this->interchange->directives[$s])) { + $this->error("alias '$s'", 'collides with another directive'); + } + if (isset($this->aliases[$s])) { + $other_directive = $this->aliases[$s]; + $this->error("alias '$s'", "collides with alias for directive '$other_directive'"); + } + $this->aliases[$s] = $d->id->toString(); + } + array_pop($this->context); + } + + // protected helper functions + + /** + * Convenience function for generating HTMLPurifier_ConfigSchema_ValidatorAtom + * for validating simple member variables of objects. + * @param $obj + * @param $member + * @return HTMLPurifier_ConfigSchema_ValidatorAtom + */ + protected function with($obj, $member) + { + return new HTMLPurifier_ConfigSchema_ValidatorAtom($this->getFormattedContext(), $obj, $member); + } + + /** + * Emits an error, providing helpful context. + * @throws HTMLPurifier_ConfigSchema_Exception + */ + protected function error($target, $msg) + { + if ($target !== false) { + $prefix = ucfirst($target) . ' in ' . $this->getFormattedContext(); + } else { + $prefix = ucfirst($this->getFormattedContext()); + } + throw new HTMLPurifier_ConfigSchema_Exception(trim($prefix . ' ' . $msg)); + } + + /** + * Returns a formatted context string. + * @return string + */ + protected function getFormattedContext() + { + return implode(' in ', array_reverse($this->context)); + } +} + +// vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/ValidatorAtom.php b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/ValidatorAtom.php new file mode 100644 index 000000000..a2e0b4a1b --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/ValidatorAtom.php @@ -0,0 +1,130 @@ +context = $context; + $this->obj = $obj; + $this->member = $member; + $this->contents =& $obj->$member; + } + + /** + * @return HTMLPurifier_ConfigSchema_ValidatorAtom + */ + public function assertIsString() + { + if (!is_string($this->contents)) { + $this->error('must be a string'); + } + return $this; + } + + /** + * @return HTMLPurifier_ConfigSchema_ValidatorAtom + */ + public function assertIsBool() + { + if (!is_bool($this->contents)) { + $this->error('must be a boolean'); + } + return $this; + } + + /** + * @return HTMLPurifier_ConfigSchema_ValidatorAtom + */ + public function assertIsArray() + { + if (!is_array($this->contents)) { + $this->error('must be an array'); + } + return $this; + } + + /** + * @return HTMLPurifier_ConfigSchema_ValidatorAtom + */ + public function assertNotNull() + { + if ($this->contents === null) { + $this->error('must not be null'); + } + return $this; + } + + /** + * @return HTMLPurifier_ConfigSchema_ValidatorAtom + */ + public function assertAlnum() + { + $this->assertIsString(); + if (!ctype_alnum($this->contents)) { + $this->error('must be alphanumeric'); + } + return $this; + } + + /** + * @return HTMLPurifier_ConfigSchema_ValidatorAtom + */ + public function assertNotEmpty() + { + if (empty($this->contents)) { + $this->error('must not be empty'); + } + return $this; + } + + /** + * @return HTMLPurifier_ConfigSchema_ValidatorAtom + */ + public function assertIsLookup() + { + $this->assertIsArray(); + foreach ($this->contents as $v) { + if ($v !== true) { + $this->error('must be a lookup array'); + } + } + return $this; + } + + /** + * @param string $msg + * @throws HTMLPurifier_ConfigSchema_Exception + */ + protected function error($msg) + { + throw new HTMLPurifier_ConfigSchema_Exception(ucfirst($this->member) . ' in ' . $this->context . ' ' . $msg); + } +} + +// vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema.ser b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema.ser new file mode 100644 index 000000000..47bd259b2 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema.ser @@ -0,0 +1 @@ +O:25:"HTMLPurifier_ConfigSchema":3:{s:8:"defaults";a:126:{s:19:"Attr.AllowedClasses";N;s:24:"Attr.AllowedFrameTargets";a:0:{}s:15:"Attr.AllowedRel";a:0:{}s:15:"Attr.AllowedRev";a:0:{}s:18:"Attr.ClassUseCDATA";N;s:20:"Attr.DefaultImageAlt";N;s:24:"Attr.DefaultInvalidImage";s:0:"";s:27:"Attr.DefaultInvalidImageAlt";s:13:"Invalid image";s:19:"Attr.DefaultTextDir";s:3:"ltr";s:13:"Attr.EnableID";b:0;s:21:"Attr.ForbiddenClasses";a:0:{}s:13:"Attr.ID.HTML5";N;s:16:"Attr.IDBlacklist";a:0:{}s:22:"Attr.IDBlacklistRegexp";N;s:13:"Attr.IDPrefix";s:0:"";s:18:"Attr.IDPrefixLocal";s:0:"";s:24:"AutoFormat.AutoParagraph";b:0;s:17:"AutoFormat.Custom";a:0:{}s:25:"AutoFormat.DisplayLinkURI";b:0;s:18:"AutoFormat.Linkify";b:0;s:33:"AutoFormat.PurifierLinkify.DocURL";s:3:"#%s";s:26:"AutoFormat.PurifierLinkify";b:0;s:32:"AutoFormat.RemoveEmpty.Predicate";a:4:{s:8:"colgroup";a:0:{}s:2:"th";a:0:{}s:2:"td";a:0:{}s:6:"iframe";a:1:{i:0;s:3:"src";}}s:44:"AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions";a:2:{s:2:"td";b:1;s:2:"th";b:1;}s:33:"AutoFormat.RemoveEmpty.RemoveNbsp";b:0;s:22:"AutoFormat.RemoveEmpty";b:0;s:39:"AutoFormat.RemoveSpansWithoutAttributes";b:0;s:19:"CSS.AllowDuplicates";b:0;s:18:"CSS.AllowImportant";b:0;s:15:"CSS.AllowTricky";b:0;s:16:"CSS.AllowedFonts";N;s:21:"CSS.AllowedProperties";N;s:17:"CSS.DefinitionRev";i:1;s:23:"CSS.ForbiddenProperties";a:0:{}s:16:"CSS.MaxImgLength";s:6:"1200px";s:15:"CSS.Proprietary";b:0;s:11:"CSS.Trusted";b:0;s:20:"Cache.DefinitionImpl";s:10:"Serializer";s:20:"Cache.SerializerPath";N;s:27:"Cache.SerializerPermissions";i:493;s:22:"Core.AggressivelyFixLt";b:1;s:29:"Core.AggressivelyRemoveScript";b:1;s:28:"Core.AllowHostnameUnderscore";b:0;s:23:"Core.AllowParseManyTags";b:0;s:18:"Core.CollectErrors";b:0;s:18:"Core.ColorKeywords";a:148:{s:9:"aliceblue";s:7:"#F0F8FF";s:12:"antiquewhite";s:7:"#FAEBD7";s:4:"aqua";s:7:"#00FFFF";s:10:"aquamarine";s:7:"#7FFFD4";s:5:"azure";s:7:"#F0FFFF";s:5:"beige";s:7:"#F5F5DC";s:6:"bisque";s:7:"#FFE4C4";s:5:"black";s:7:"#000000";s:14:"blanchedalmond";s:7:"#FFEBCD";s:4:"blue";s:7:"#0000FF";s:10:"blueviolet";s:7:"#8A2BE2";s:5:"brown";s:7:"#A52A2A";s:9:"burlywood";s:7:"#DEB887";s:9:"cadetblue";s:7:"#5F9EA0";s:10:"chartreuse";s:7:"#7FFF00";s:9:"chocolate";s:7:"#D2691E";s:5:"coral";s:7:"#FF7F50";s:14:"cornflowerblue";s:7:"#6495ED";s:8:"cornsilk";s:7:"#FFF8DC";s:7:"crimson";s:7:"#DC143C";s:4:"cyan";s:7:"#00FFFF";s:8:"darkblue";s:7:"#00008B";s:8:"darkcyan";s:7:"#008B8B";s:13:"darkgoldenrod";s:7:"#B8860B";s:8:"darkgray";s:7:"#A9A9A9";s:8:"darkgrey";s:7:"#A9A9A9";s:9:"darkgreen";s:7:"#006400";s:9:"darkkhaki";s:7:"#BDB76B";s:11:"darkmagenta";s:7:"#8B008B";s:14:"darkolivegreen";s:7:"#556B2F";s:10:"darkorange";s:7:"#FF8C00";s:10:"darkorchid";s:7:"#9932CC";s:7:"darkred";s:7:"#8B0000";s:10:"darksalmon";s:7:"#E9967A";s:12:"darkseagreen";s:7:"#8FBC8F";s:13:"darkslateblue";s:7:"#483D8B";s:13:"darkslategray";s:7:"#2F4F4F";s:13:"darkslategrey";s:7:"#2F4F4F";s:13:"darkturquoise";s:7:"#00CED1";s:10:"darkviolet";s:7:"#9400D3";s:8:"deeppink";s:7:"#FF1493";s:11:"deepskyblue";s:7:"#00BFFF";s:7:"dimgray";s:7:"#696969";s:7:"dimgrey";s:7:"#696969";s:10:"dodgerblue";s:7:"#1E90FF";s:9:"firebrick";s:7:"#B22222";s:11:"floralwhite";s:7:"#FFFAF0";s:11:"forestgreen";s:7:"#228B22";s:7:"fuchsia";s:7:"#FF00FF";s:9:"gainsboro";s:7:"#DCDCDC";s:10:"ghostwhite";s:7:"#F8F8FF";s:4:"gold";s:7:"#FFD700";s:9:"goldenrod";s:7:"#DAA520";s:4:"gray";s:7:"#808080";s:4:"grey";s:7:"#808080";s:5:"green";s:7:"#008000";s:11:"greenyellow";s:7:"#ADFF2F";s:8:"honeydew";s:7:"#F0FFF0";s:7:"hotpink";s:7:"#FF69B4";s:9:"indianred";s:7:"#CD5C5C";s:6:"indigo";s:7:"#4B0082";s:5:"ivory";s:7:"#FFFFF0";s:5:"khaki";s:7:"#F0E68C";s:8:"lavender";s:7:"#E6E6FA";s:13:"lavenderblush";s:7:"#FFF0F5";s:9:"lawngreen";s:7:"#7CFC00";s:12:"lemonchiffon";s:7:"#FFFACD";s:9:"lightblue";s:7:"#ADD8E6";s:10:"lightcoral";s:7:"#F08080";s:9:"lightcyan";s:7:"#E0FFFF";s:20:"lightgoldenrodyellow";s:7:"#FAFAD2";s:9:"lightgray";s:7:"#D3D3D3";s:9:"lightgrey";s:7:"#D3D3D3";s:10:"lightgreen";s:7:"#90EE90";s:9:"lightpink";s:7:"#FFB6C1";s:11:"lightsalmon";s:7:"#FFA07A";s:13:"lightseagreen";s:7:"#20B2AA";s:12:"lightskyblue";s:7:"#87CEFA";s:14:"lightslategray";s:7:"#778899";s:14:"lightslategrey";s:7:"#778899";s:14:"lightsteelblue";s:7:"#B0C4DE";s:11:"lightyellow";s:7:"#FFFFE0";s:4:"lime";s:7:"#00FF00";s:9:"limegreen";s:7:"#32CD32";s:5:"linen";s:7:"#FAF0E6";s:7:"magenta";s:7:"#FF00FF";s:6:"maroon";s:7:"#800000";s:16:"mediumaquamarine";s:7:"#66CDAA";s:10:"mediumblue";s:7:"#0000CD";s:12:"mediumorchid";s:7:"#BA55D3";s:12:"mediumpurple";s:7:"#9370DB";s:14:"mediumseagreen";s:7:"#3CB371";s:15:"mediumslateblue";s:7:"#7B68EE";s:17:"mediumspringgreen";s:7:"#00FA9A";s:15:"mediumturquoise";s:7:"#48D1CC";s:15:"mediumvioletred";s:7:"#C71585";s:12:"midnightblue";s:7:"#191970";s:9:"mintcream";s:7:"#F5FFFA";s:9:"mistyrose";s:7:"#FFE4E1";s:8:"moccasin";s:7:"#FFE4B5";s:11:"navajowhite";s:7:"#FFDEAD";s:4:"navy";s:7:"#000080";s:7:"oldlace";s:7:"#FDF5E6";s:5:"olive";s:7:"#808000";s:9:"olivedrab";s:7:"#6B8E23";s:6:"orange";s:7:"#FFA500";s:9:"orangered";s:7:"#FF4500";s:6:"orchid";s:7:"#DA70D6";s:13:"palegoldenrod";s:7:"#EEE8AA";s:9:"palegreen";s:7:"#98FB98";s:13:"paleturquoise";s:7:"#AFEEEE";s:13:"palevioletred";s:7:"#DB7093";s:10:"papayawhip";s:7:"#FFEFD5";s:9:"peachpuff";s:7:"#FFDAB9";s:4:"peru";s:7:"#CD853F";s:4:"pink";s:7:"#FFC0CB";s:4:"plum";s:7:"#DDA0DD";s:10:"powderblue";s:7:"#B0E0E6";s:6:"purple";s:7:"#800080";s:13:"rebeccapurple";s:7:"#663399";s:3:"red";s:7:"#FF0000";s:9:"rosybrown";s:7:"#BC8F8F";s:9:"royalblue";s:7:"#4169E1";s:11:"saddlebrown";s:7:"#8B4513";s:6:"salmon";s:7:"#FA8072";s:10:"sandybrown";s:7:"#F4A460";s:8:"seagreen";s:7:"#2E8B57";s:8:"seashell";s:7:"#FFF5EE";s:6:"sienna";s:7:"#A0522D";s:6:"silver";s:7:"#C0C0C0";s:7:"skyblue";s:7:"#87CEEB";s:9:"slateblue";s:7:"#6A5ACD";s:9:"slategray";s:7:"#708090";s:9:"slategrey";s:7:"#708090";s:4:"snow";s:7:"#FFFAFA";s:11:"springgreen";s:7:"#00FF7F";s:9:"steelblue";s:7:"#4682B4";s:3:"tan";s:7:"#D2B48C";s:4:"teal";s:7:"#008080";s:7:"thistle";s:7:"#D8BFD8";s:6:"tomato";s:7:"#FF6347";s:9:"turquoise";s:7:"#40E0D0";s:6:"violet";s:7:"#EE82EE";s:5:"wheat";s:7:"#F5DEB3";s:5:"white";s:7:"#FFFFFF";s:10:"whitesmoke";s:7:"#F5F5F5";s:6:"yellow";s:7:"#FFFF00";s:11:"yellowgreen";s:7:"#9ACD32";}s:30:"Core.ConvertDocumentToFragment";b:1;s:36:"Core.DirectLexLineNumberSyncInterval";i:0;s:20:"Core.DisableExcludes";b:0;s:15:"Core.EnableIDNA";b:0;s:13:"Core.Encoding";s:5:"utf-8";s:26:"Core.EscapeInvalidChildren";b:0;s:22:"Core.EscapeInvalidTags";b:0;s:29:"Core.EscapeNonASCIICharacters";b:0;s:19:"Core.HiddenElements";a:2:{s:6:"script";b:1;s:5:"style";b:1;}s:13:"Core.Language";s:2:"en";s:24:"Core.LegacyEntityDecoder";b:0;s:14:"Core.LexerImpl";N;s:24:"Core.MaintainLineNumbers";N;s:22:"Core.NormalizeNewlines";b:1;s:21:"Core.RemoveInvalidImg";b:1;s:33:"Core.RemoveProcessingInstructions";b:0;s:25:"Core.RemoveScriptContents";N;s:13:"Filter.Custom";a:0:{}s:34:"Filter.ExtractStyleBlocks.Escaping";b:1;s:31:"Filter.ExtractStyleBlocks.Scope";N;s:34:"Filter.ExtractStyleBlocks.TidyImpl";N;s:25:"Filter.ExtractStyleBlocks";b:0;s:14:"Filter.YouTube";b:0;s:12:"HTML.Allowed";N;s:22:"HTML.AllowedAttributes";N;s:20:"HTML.AllowedComments";a:0:{}s:26:"HTML.AllowedCommentsRegexp";N;s:20:"HTML.AllowedElements";N;s:19:"HTML.AllowedModules";N;s:23:"HTML.Attr.Name.UseCDATA";b:0;s:17:"HTML.BlockWrapper";s:1:"p";s:16:"HTML.CoreModules";a:7:{s:9:"Structure";b:1;s:4:"Text";b:1;s:9:"Hypertext";b:1;s:4:"List";b:1;s:22:"NonXMLCommonAttributes";b:1;s:19:"XMLCommonAttributes";b:1;s:16:"CommonAttributes";b:1;}s:18:"HTML.CustomDoctype";N;s:17:"HTML.DefinitionID";N;s:18:"HTML.DefinitionRev";i:1;s:12:"HTML.Doctype";N;s:25:"HTML.FlashAllowFullScreen";b:0;s:24:"HTML.ForbiddenAttributes";a:0:{}s:22:"HTML.ForbiddenElements";a:0:{}s:17:"HTML.MaxImgLength";i:1200;s:13:"HTML.Nofollow";b:0;s:11:"HTML.Parent";s:3:"div";s:16:"HTML.Proprietary";b:0;s:14:"HTML.SafeEmbed";b:0;s:15:"HTML.SafeIframe";b:0;s:15:"HTML.SafeObject";b:0;s:18:"HTML.SafeScripting";a:0:{}s:11:"HTML.Strict";b:0;s:16:"HTML.TargetBlank";b:0;s:19:"HTML.TargetNoopener";b:1;s:21:"HTML.TargetNoreferrer";b:1;s:12:"HTML.TidyAdd";a:0:{}s:14:"HTML.TidyLevel";s:6:"medium";s:15:"HTML.TidyRemove";a:0:{}s:12:"HTML.Trusted";b:0;s:10:"HTML.XHTML";b:1;s:28:"Output.CommentScriptContents";b:1;s:19:"Output.FixInnerHTML";b:1;s:18:"Output.FlashCompat";b:0;s:14:"Output.Newline";N;s:15:"Output.SortAttr";b:0;s:17:"Output.TidyFormat";b:0;s:17:"Test.ForceNoIconv";b:0;s:18:"URI.AllowedSchemes";a:7:{s:4:"http";b:1;s:5:"https";b:1;s:6:"mailto";b:1;s:3:"ftp";b:1;s:4:"nntp";b:1;s:4:"news";b:1;s:3:"tel";b:1;}s:8:"URI.Base";N;s:17:"URI.DefaultScheme";s:4:"http";s:16:"URI.DefinitionID";N;s:17:"URI.DefinitionRev";i:1;s:11:"URI.Disable";b:0;s:19:"URI.DisableExternal";b:0;s:28:"URI.DisableExternalResources";b:0;s:20:"URI.DisableResources";b:0;s:8:"URI.Host";N;s:17:"URI.HostBlacklist";a:0:{}s:16:"URI.MakeAbsolute";b:0;s:9:"URI.Munge";N;s:18:"URI.MungeResources";b:0;s:18:"URI.MungeSecretKey";N;s:26:"URI.OverrideAllowedSchemes";b:1;s:20:"URI.SafeIframeRegexp";N;}s:12:"defaultPlist";O:25:"HTMLPurifier_PropertyList":3:{s:7:"*data";a:126:{s:19:"Attr.AllowedClasses";N;s:24:"Attr.AllowedFrameTargets";a:0:{}s:15:"Attr.AllowedRel";a:0:{}s:15:"Attr.AllowedRev";a:0:{}s:18:"Attr.ClassUseCDATA";N;s:20:"Attr.DefaultImageAlt";N;s:24:"Attr.DefaultInvalidImage";s:0:"";s:27:"Attr.DefaultInvalidImageAlt";s:13:"Invalid image";s:19:"Attr.DefaultTextDir";s:3:"ltr";s:13:"Attr.EnableID";b:0;s:21:"Attr.ForbiddenClasses";a:0:{}s:13:"Attr.ID.HTML5";N;s:16:"Attr.IDBlacklist";a:0:{}s:22:"Attr.IDBlacklistRegexp";N;s:13:"Attr.IDPrefix";s:0:"";s:18:"Attr.IDPrefixLocal";s:0:"";s:24:"AutoFormat.AutoParagraph";b:0;s:17:"AutoFormat.Custom";a:0:{}s:25:"AutoFormat.DisplayLinkURI";b:0;s:18:"AutoFormat.Linkify";b:0;s:33:"AutoFormat.PurifierLinkify.DocURL";s:3:"#%s";s:26:"AutoFormat.PurifierLinkify";b:0;s:32:"AutoFormat.RemoveEmpty.Predicate";a:4:{s:8:"colgroup";a:0:{}s:2:"th";a:0:{}s:2:"td";a:0:{}s:6:"iframe";a:1:{i:0;s:3:"src";}}s:44:"AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions";a:2:{s:2:"td";b:1;s:2:"th";b:1;}s:33:"AutoFormat.RemoveEmpty.RemoveNbsp";b:0;s:22:"AutoFormat.RemoveEmpty";b:0;s:39:"AutoFormat.RemoveSpansWithoutAttributes";b:0;s:19:"CSS.AllowDuplicates";b:0;s:18:"CSS.AllowImportant";b:0;s:15:"CSS.AllowTricky";b:0;s:16:"CSS.AllowedFonts";N;s:21:"CSS.AllowedProperties";N;s:17:"CSS.DefinitionRev";i:1;s:23:"CSS.ForbiddenProperties";a:0:{}s:16:"CSS.MaxImgLength";s:6:"1200px";s:15:"CSS.Proprietary";b:0;s:11:"CSS.Trusted";b:0;s:20:"Cache.DefinitionImpl";s:10:"Serializer";s:20:"Cache.SerializerPath";N;s:27:"Cache.SerializerPermissions";i:493;s:22:"Core.AggressivelyFixLt";b:1;s:29:"Core.AggressivelyRemoveScript";b:1;s:28:"Core.AllowHostnameUnderscore";b:0;s:23:"Core.AllowParseManyTags";b:0;s:18:"Core.CollectErrors";b:0;s:18:"Core.ColorKeywords";a:148:{s:9:"aliceblue";s:7:"#F0F8FF";s:12:"antiquewhite";s:7:"#FAEBD7";s:4:"aqua";s:7:"#00FFFF";s:10:"aquamarine";s:7:"#7FFFD4";s:5:"azure";s:7:"#F0FFFF";s:5:"beige";s:7:"#F5F5DC";s:6:"bisque";s:7:"#FFE4C4";s:5:"black";s:7:"#000000";s:14:"blanchedalmond";s:7:"#FFEBCD";s:4:"blue";s:7:"#0000FF";s:10:"blueviolet";s:7:"#8A2BE2";s:5:"brown";s:7:"#A52A2A";s:9:"burlywood";s:7:"#DEB887";s:9:"cadetblue";s:7:"#5F9EA0";s:10:"chartreuse";s:7:"#7FFF00";s:9:"chocolate";s:7:"#D2691E";s:5:"coral";s:7:"#FF7F50";s:14:"cornflowerblue";s:7:"#6495ED";s:8:"cornsilk";s:7:"#FFF8DC";s:7:"crimson";s:7:"#DC143C";s:4:"cyan";s:7:"#00FFFF";s:8:"darkblue";s:7:"#00008B";s:8:"darkcyan";s:7:"#008B8B";s:13:"darkgoldenrod";s:7:"#B8860B";s:8:"darkgray";s:7:"#A9A9A9";s:8:"darkgrey";s:7:"#A9A9A9";s:9:"darkgreen";s:7:"#006400";s:9:"darkkhaki";s:7:"#BDB76B";s:11:"darkmagenta";s:7:"#8B008B";s:14:"darkolivegreen";s:7:"#556B2F";s:10:"darkorange";s:7:"#FF8C00";s:10:"darkorchid";s:7:"#9932CC";s:7:"darkred";s:7:"#8B0000";s:10:"darksalmon";s:7:"#E9967A";s:12:"darkseagreen";s:7:"#8FBC8F";s:13:"darkslateblue";s:7:"#483D8B";s:13:"darkslategray";s:7:"#2F4F4F";s:13:"darkslategrey";s:7:"#2F4F4F";s:13:"darkturquoise";s:7:"#00CED1";s:10:"darkviolet";s:7:"#9400D3";s:8:"deeppink";s:7:"#FF1493";s:11:"deepskyblue";s:7:"#00BFFF";s:7:"dimgray";s:7:"#696969";s:7:"dimgrey";s:7:"#696969";s:10:"dodgerblue";s:7:"#1E90FF";s:9:"firebrick";s:7:"#B22222";s:11:"floralwhite";s:7:"#FFFAF0";s:11:"forestgreen";s:7:"#228B22";s:7:"fuchsia";s:7:"#FF00FF";s:9:"gainsboro";s:7:"#DCDCDC";s:10:"ghostwhite";s:7:"#F8F8FF";s:4:"gold";s:7:"#FFD700";s:9:"goldenrod";s:7:"#DAA520";s:4:"gray";s:7:"#808080";s:4:"grey";s:7:"#808080";s:5:"green";s:7:"#008000";s:11:"greenyellow";s:7:"#ADFF2F";s:8:"honeydew";s:7:"#F0FFF0";s:7:"hotpink";s:7:"#FF69B4";s:9:"indianred";s:7:"#CD5C5C";s:6:"indigo";s:7:"#4B0082";s:5:"ivory";s:7:"#FFFFF0";s:5:"khaki";s:7:"#F0E68C";s:8:"lavender";s:7:"#E6E6FA";s:13:"lavenderblush";s:7:"#FFF0F5";s:9:"lawngreen";s:7:"#7CFC00";s:12:"lemonchiffon";s:7:"#FFFACD";s:9:"lightblue";s:7:"#ADD8E6";s:10:"lightcoral";s:7:"#F08080";s:9:"lightcyan";s:7:"#E0FFFF";s:20:"lightgoldenrodyellow";s:7:"#FAFAD2";s:9:"lightgray";s:7:"#D3D3D3";s:9:"lightgrey";s:7:"#D3D3D3";s:10:"lightgreen";s:7:"#90EE90";s:9:"lightpink";s:7:"#FFB6C1";s:11:"lightsalmon";s:7:"#FFA07A";s:13:"lightseagreen";s:7:"#20B2AA";s:12:"lightskyblue";s:7:"#87CEFA";s:14:"lightslategray";s:7:"#778899";s:14:"lightslategrey";s:7:"#778899";s:14:"lightsteelblue";s:7:"#B0C4DE";s:11:"lightyellow";s:7:"#FFFFE0";s:4:"lime";s:7:"#00FF00";s:9:"limegreen";s:7:"#32CD32";s:5:"linen";s:7:"#FAF0E6";s:7:"magenta";s:7:"#FF00FF";s:6:"maroon";s:7:"#800000";s:16:"mediumaquamarine";s:7:"#66CDAA";s:10:"mediumblue";s:7:"#0000CD";s:12:"mediumorchid";s:7:"#BA55D3";s:12:"mediumpurple";s:7:"#9370DB";s:14:"mediumseagreen";s:7:"#3CB371";s:15:"mediumslateblue";s:7:"#7B68EE";s:17:"mediumspringgreen";s:7:"#00FA9A";s:15:"mediumturquoise";s:7:"#48D1CC";s:15:"mediumvioletred";s:7:"#C71585";s:12:"midnightblue";s:7:"#191970";s:9:"mintcream";s:7:"#F5FFFA";s:9:"mistyrose";s:7:"#FFE4E1";s:8:"moccasin";s:7:"#FFE4B5";s:11:"navajowhite";s:7:"#FFDEAD";s:4:"navy";s:7:"#000080";s:7:"oldlace";s:7:"#FDF5E6";s:5:"olive";s:7:"#808000";s:9:"olivedrab";s:7:"#6B8E23";s:6:"orange";s:7:"#FFA500";s:9:"orangered";s:7:"#FF4500";s:6:"orchid";s:7:"#DA70D6";s:13:"palegoldenrod";s:7:"#EEE8AA";s:9:"palegreen";s:7:"#98FB98";s:13:"paleturquoise";s:7:"#AFEEEE";s:13:"palevioletred";s:7:"#DB7093";s:10:"papayawhip";s:7:"#FFEFD5";s:9:"peachpuff";s:7:"#FFDAB9";s:4:"peru";s:7:"#CD853F";s:4:"pink";s:7:"#FFC0CB";s:4:"plum";s:7:"#DDA0DD";s:10:"powderblue";s:7:"#B0E0E6";s:6:"purple";s:7:"#800080";s:13:"rebeccapurple";s:7:"#663399";s:3:"red";s:7:"#FF0000";s:9:"rosybrown";s:7:"#BC8F8F";s:9:"royalblue";s:7:"#4169E1";s:11:"saddlebrown";s:7:"#8B4513";s:6:"salmon";s:7:"#FA8072";s:10:"sandybrown";s:7:"#F4A460";s:8:"seagreen";s:7:"#2E8B57";s:8:"seashell";s:7:"#FFF5EE";s:6:"sienna";s:7:"#A0522D";s:6:"silver";s:7:"#C0C0C0";s:7:"skyblue";s:7:"#87CEEB";s:9:"slateblue";s:7:"#6A5ACD";s:9:"slategray";s:7:"#708090";s:9:"slategrey";s:7:"#708090";s:4:"snow";s:7:"#FFFAFA";s:11:"springgreen";s:7:"#00FF7F";s:9:"steelblue";s:7:"#4682B4";s:3:"tan";s:7:"#D2B48C";s:4:"teal";s:7:"#008080";s:7:"thistle";s:7:"#D8BFD8";s:6:"tomato";s:7:"#FF6347";s:9:"turquoise";s:7:"#40E0D0";s:6:"violet";s:7:"#EE82EE";s:5:"wheat";s:7:"#F5DEB3";s:5:"white";s:7:"#FFFFFF";s:10:"whitesmoke";s:7:"#F5F5F5";s:6:"yellow";s:7:"#FFFF00";s:11:"yellowgreen";s:7:"#9ACD32";}s:30:"Core.ConvertDocumentToFragment";b:1;s:36:"Core.DirectLexLineNumberSyncInterval";i:0;s:20:"Core.DisableExcludes";b:0;s:15:"Core.EnableIDNA";b:0;s:13:"Core.Encoding";s:5:"utf-8";s:26:"Core.EscapeInvalidChildren";b:0;s:22:"Core.EscapeInvalidTags";b:0;s:29:"Core.EscapeNonASCIICharacters";b:0;s:19:"Core.HiddenElements";a:2:{s:6:"script";b:1;s:5:"style";b:1;}s:13:"Core.Language";s:2:"en";s:24:"Core.LegacyEntityDecoder";b:0;s:14:"Core.LexerImpl";N;s:24:"Core.MaintainLineNumbers";N;s:22:"Core.NormalizeNewlines";b:1;s:21:"Core.RemoveInvalidImg";b:1;s:33:"Core.RemoveProcessingInstructions";b:0;s:25:"Core.RemoveScriptContents";N;s:13:"Filter.Custom";a:0:{}s:34:"Filter.ExtractStyleBlocks.Escaping";b:1;s:31:"Filter.ExtractStyleBlocks.Scope";N;s:34:"Filter.ExtractStyleBlocks.TidyImpl";N;s:25:"Filter.ExtractStyleBlocks";b:0;s:14:"Filter.YouTube";b:0;s:12:"HTML.Allowed";N;s:22:"HTML.AllowedAttributes";N;s:20:"HTML.AllowedComments";a:0:{}s:26:"HTML.AllowedCommentsRegexp";N;s:20:"HTML.AllowedElements";N;s:19:"HTML.AllowedModules";N;s:23:"HTML.Attr.Name.UseCDATA";b:0;s:17:"HTML.BlockWrapper";s:1:"p";s:16:"HTML.CoreModules";a:7:{s:9:"Structure";b:1;s:4:"Text";b:1;s:9:"Hypertext";b:1;s:4:"List";b:1;s:22:"NonXMLCommonAttributes";b:1;s:19:"XMLCommonAttributes";b:1;s:16:"CommonAttributes";b:1;}s:18:"HTML.CustomDoctype";N;s:17:"HTML.DefinitionID";N;s:18:"HTML.DefinitionRev";i:1;s:12:"HTML.Doctype";N;s:25:"HTML.FlashAllowFullScreen";b:0;s:24:"HTML.ForbiddenAttributes";a:0:{}s:22:"HTML.ForbiddenElements";a:0:{}s:17:"HTML.MaxImgLength";i:1200;s:13:"HTML.Nofollow";b:0;s:11:"HTML.Parent";s:3:"div";s:16:"HTML.Proprietary";b:0;s:14:"HTML.SafeEmbed";b:0;s:15:"HTML.SafeIframe";b:0;s:15:"HTML.SafeObject";b:0;s:18:"HTML.SafeScripting";a:0:{}s:11:"HTML.Strict";b:0;s:16:"HTML.TargetBlank";b:0;s:19:"HTML.TargetNoopener";b:1;s:21:"HTML.TargetNoreferrer";b:1;s:12:"HTML.TidyAdd";a:0:{}s:14:"HTML.TidyLevel";s:6:"medium";s:15:"HTML.TidyRemove";a:0:{}s:12:"HTML.Trusted";b:0;s:10:"HTML.XHTML";b:1;s:28:"Output.CommentScriptContents";b:1;s:19:"Output.FixInnerHTML";b:1;s:18:"Output.FlashCompat";b:0;s:14:"Output.Newline";N;s:15:"Output.SortAttr";b:0;s:17:"Output.TidyFormat";b:0;s:17:"Test.ForceNoIconv";b:0;s:18:"URI.AllowedSchemes";a:7:{s:4:"http";b:1;s:5:"https";b:1;s:6:"mailto";b:1;s:3:"ftp";b:1;s:4:"nntp";b:1;s:4:"news";b:1;s:3:"tel";b:1;}s:8:"URI.Base";N;s:17:"URI.DefaultScheme";s:4:"http";s:16:"URI.DefinitionID";N;s:17:"URI.DefinitionRev";i:1;s:11:"URI.Disable";b:0;s:19:"URI.DisableExternal";b:0;s:28:"URI.DisableExternalResources";b:0;s:20:"URI.DisableResources";b:0;s:8:"URI.Host";N;s:17:"URI.HostBlacklist";a:0:{}s:16:"URI.MakeAbsolute";b:0;s:9:"URI.Munge";N;s:18:"URI.MungeResources";b:0;s:18:"URI.MungeSecretKey";N;s:26:"URI.OverrideAllowedSchemes";b:1;s:20:"URI.SafeIframeRegexp";N;}s:9:"*parent";N;s:8:"*cache";N;}s:4:"info";a:139:{s:19:"Attr.AllowedClasses";i:-8;s:24:"Attr.AllowedFrameTargets";i:8;s:15:"Attr.AllowedRel";i:8;s:15:"Attr.AllowedRev";i:8;s:18:"Attr.ClassUseCDATA";i:-7;s:20:"Attr.DefaultImageAlt";i:-1;s:24:"Attr.DefaultInvalidImage";i:1;s:27:"Attr.DefaultInvalidImageAlt";i:1;s:19:"Attr.DefaultTextDir";O:8:"stdClass":2:{s:4:"type";i:1;s:7:"allowed";a:2:{s:3:"ltr";b:1;s:3:"rtl";b:1;}}s:13:"Attr.EnableID";i:7;s:17:"HTML.EnableAttrID";O:8:"stdClass":2:{s:3:"key";s:13:"Attr.EnableID";s:7:"isAlias";b:1;}s:21:"Attr.ForbiddenClasses";i:8;s:13:"Attr.ID.HTML5";i:-7;s:16:"Attr.IDBlacklist";i:9;s:22:"Attr.IDBlacklistRegexp";i:-1;s:13:"Attr.IDPrefix";i:1;s:18:"Attr.IDPrefixLocal";i:1;s:24:"AutoFormat.AutoParagraph";i:7;s:17:"AutoFormat.Custom";i:9;s:25:"AutoFormat.DisplayLinkURI";i:7;s:18:"AutoFormat.Linkify";i:7;s:33:"AutoFormat.PurifierLinkify.DocURL";i:1;s:37:"AutoFormatParam.PurifierLinkifyDocURL";O:8:"stdClass":2:{s:3:"key";s:33:"AutoFormat.PurifierLinkify.DocURL";s:7:"isAlias";b:1;}s:26:"AutoFormat.PurifierLinkify";i:7;s:32:"AutoFormat.RemoveEmpty.Predicate";i:10;s:44:"AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions";i:8;s:33:"AutoFormat.RemoveEmpty.RemoveNbsp";i:7;s:22:"AutoFormat.RemoveEmpty";i:7;s:39:"AutoFormat.RemoveSpansWithoutAttributes";i:7;s:19:"CSS.AllowDuplicates";i:7;s:18:"CSS.AllowImportant";i:7;s:15:"CSS.AllowTricky";i:7;s:16:"CSS.AllowedFonts";i:-8;s:21:"CSS.AllowedProperties";i:-8;s:17:"CSS.DefinitionRev";i:5;s:23:"CSS.ForbiddenProperties";i:8;s:16:"CSS.MaxImgLength";i:-1;s:15:"CSS.Proprietary";i:7;s:11:"CSS.Trusted";i:7;s:20:"Cache.DefinitionImpl";i:-1;s:20:"Core.DefinitionCache";O:8:"stdClass":2:{s:3:"key";s:20:"Cache.DefinitionImpl";s:7:"isAlias";b:1;}s:20:"Cache.SerializerPath";i:-1;s:27:"Cache.SerializerPermissions";i:-5;s:22:"Core.AggressivelyFixLt";i:7;s:29:"Core.AggressivelyRemoveScript";i:7;s:28:"Core.AllowHostnameUnderscore";i:7;s:23:"Core.AllowParseManyTags";i:7;s:18:"Core.CollectErrors";i:7;s:18:"Core.ColorKeywords";i:10;s:30:"Core.ConvertDocumentToFragment";i:7;s:24:"Core.AcceptFullDocuments";O:8:"stdClass":2:{s:3:"key";s:30:"Core.ConvertDocumentToFragment";s:7:"isAlias";b:1;}s:36:"Core.DirectLexLineNumberSyncInterval";i:5;s:20:"Core.DisableExcludes";i:7;s:15:"Core.EnableIDNA";i:7;s:13:"Core.Encoding";i:2;s:26:"Core.EscapeInvalidChildren";i:7;s:22:"Core.EscapeInvalidTags";i:7;s:29:"Core.EscapeNonASCIICharacters";i:7;s:19:"Core.HiddenElements";i:8;s:13:"Core.Language";i:1;s:24:"Core.LegacyEntityDecoder";i:7;s:14:"Core.LexerImpl";i:-11;s:24:"Core.MaintainLineNumbers";i:-7;s:22:"Core.NormalizeNewlines";i:7;s:21:"Core.RemoveInvalidImg";i:7;s:33:"Core.RemoveProcessingInstructions";i:7;s:25:"Core.RemoveScriptContents";i:-7;s:13:"Filter.Custom";i:9;s:34:"Filter.ExtractStyleBlocks.Escaping";i:7;s:33:"Filter.ExtractStyleBlocksEscaping";O:8:"stdClass":2:{s:3:"key";s:34:"Filter.ExtractStyleBlocks.Escaping";s:7:"isAlias";b:1;}s:38:"FilterParam.ExtractStyleBlocksEscaping";O:8:"stdClass":2:{s:3:"key";s:34:"Filter.ExtractStyleBlocks.Escaping";s:7:"isAlias";b:1;}s:31:"Filter.ExtractStyleBlocks.Scope";i:-1;s:30:"Filter.ExtractStyleBlocksScope";O:8:"stdClass":2:{s:3:"key";s:31:"Filter.ExtractStyleBlocks.Scope";s:7:"isAlias";b:1;}s:35:"FilterParam.ExtractStyleBlocksScope";O:8:"stdClass":2:{s:3:"key";s:31:"Filter.ExtractStyleBlocks.Scope";s:7:"isAlias";b:1;}s:34:"Filter.ExtractStyleBlocks.TidyImpl";i:-11;s:38:"FilterParam.ExtractStyleBlocksTidyImpl";O:8:"stdClass":2:{s:3:"key";s:34:"Filter.ExtractStyleBlocks.TidyImpl";s:7:"isAlias";b:1;}s:25:"Filter.ExtractStyleBlocks";i:7;s:14:"Filter.YouTube";i:7;s:12:"HTML.Allowed";i:-4;s:22:"HTML.AllowedAttributes";i:-8;s:20:"HTML.AllowedComments";i:8;s:26:"HTML.AllowedCommentsRegexp";i:-1;s:20:"HTML.AllowedElements";i:-8;s:19:"HTML.AllowedModules";i:-8;s:23:"HTML.Attr.Name.UseCDATA";i:7;s:17:"HTML.BlockWrapper";i:1;s:16:"HTML.CoreModules";i:8;s:18:"HTML.CustomDoctype";i:-1;s:17:"HTML.DefinitionID";i:-1;s:18:"HTML.DefinitionRev";i:5;s:12:"HTML.Doctype";O:8:"stdClass":3:{s:4:"type";i:1;s:10:"allow_null";b:1;s:7:"allowed";a:5:{s:22:"HTML 4.01 Transitional";b:1;s:16:"HTML 4.01 Strict";b:1;s:22:"XHTML 1.0 Transitional";b:1;s:16:"XHTML 1.0 Strict";b:1;s:9:"XHTML 1.1";b:1;}}s:25:"HTML.FlashAllowFullScreen";i:7;s:24:"HTML.ForbiddenAttributes";i:8;s:22:"HTML.ForbiddenElements";i:8;s:17:"HTML.MaxImgLength";i:-5;s:13:"HTML.Nofollow";i:7;s:11:"HTML.Parent";i:1;s:16:"HTML.Proprietary";i:7;s:14:"HTML.SafeEmbed";i:7;s:15:"HTML.SafeIframe";i:7;s:15:"HTML.SafeObject";i:7;s:18:"HTML.SafeScripting";i:8;s:11:"HTML.Strict";i:7;s:16:"HTML.TargetBlank";i:7;s:19:"HTML.TargetNoopener";i:7;s:21:"HTML.TargetNoreferrer";i:7;s:12:"HTML.TidyAdd";i:8;s:14:"HTML.TidyLevel";O:8:"stdClass":2:{s:4:"type";i:1;s:7:"allowed";a:4:{s:4:"none";b:1;s:5:"light";b:1;s:6:"medium";b:1;s:5:"heavy";b:1;}}s:15:"HTML.TidyRemove";i:8;s:12:"HTML.Trusted";i:7;s:10:"HTML.XHTML";i:7;s:10:"Core.XHTML";O:8:"stdClass":2:{s:3:"key";s:10:"HTML.XHTML";s:7:"isAlias";b:1;}s:28:"Output.CommentScriptContents";i:7;s:26:"Core.CommentScriptContents";O:8:"stdClass":2:{s:3:"key";s:28:"Output.CommentScriptContents";s:7:"isAlias";b:1;}s:19:"Output.FixInnerHTML";i:7;s:18:"Output.FlashCompat";i:7;s:14:"Output.Newline";i:-1;s:15:"Output.SortAttr";i:7;s:17:"Output.TidyFormat";i:7;s:15:"Core.TidyFormat";O:8:"stdClass":2:{s:3:"key";s:17:"Output.TidyFormat";s:7:"isAlias";b:1;}s:17:"Test.ForceNoIconv";i:7;s:18:"URI.AllowedSchemes";i:8;s:8:"URI.Base";i:-1;s:17:"URI.DefaultScheme";i:-1;s:16:"URI.DefinitionID";i:-1;s:17:"URI.DefinitionRev";i:5;s:11:"URI.Disable";i:7;s:15:"Attr.DisableURI";O:8:"stdClass":2:{s:3:"key";s:11:"URI.Disable";s:7:"isAlias";b:1;}s:19:"URI.DisableExternal";i:7;s:28:"URI.DisableExternalResources";i:7;s:20:"URI.DisableResources";i:7;s:8:"URI.Host";i:-1;s:17:"URI.HostBlacklist";i:9;s:16:"URI.MakeAbsolute";i:7;s:9:"URI.Munge";i:-1;s:18:"URI.MungeResources";i:7;s:18:"URI.MungeSecretKey";i:-1;s:26:"URI.OverrideAllowedSchemes";i:7;s:20:"URI.SafeIframeRegexp";i:-1;}} \ No newline at end of file diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.AllowedClasses.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.AllowedClasses.txt new file mode 100644 index 000000000..4a42382ec --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.AllowedClasses.txt @@ -0,0 +1,8 @@ +Attr.AllowedClasses +TYPE: lookup/null +VERSION: 4.0.0 +DEFAULT: null +--DESCRIPTION-- +List of allowed class values in the class attribute. By default, this is null, +which means all classes are allowed. +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.AllowedFrameTargets.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.AllowedFrameTargets.txt new file mode 100644 index 000000000..b033eb516 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.AllowedFrameTargets.txt @@ -0,0 +1,12 @@ +Attr.AllowedFrameTargets +TYPE: lookup +DEFAULT: array() +--DESCRIPTION-- +Lookup table of all allowed link frame targets. Some commonly used link +targets include _blank, _self, _parent and _top. Values should be +lowercase, as validation will be done in a case-sensitive manner despite +W3C's recommendation. XHTML 1.0 Strict does not permit the target attribute +so this directive will have no effect in that doctype. XHTML 1.1 does not +enable the Target module by default, you will have to manually enable it +(see the module documentation for more details.) +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRel.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRel.txt new file mode 100644 index 000000000..ed72a9d56 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRel.txt @@ -0,0 +1,9 @@ +Attr.AllowedRel +TYPE: lookup +VERSION: 1.6.0 +DEFAULT: array() +--DESCRIPTION-- +List of allowed forward document relationships in the rel attribute. Common +values may be nofollow or print. By default, this is empty, meaning that no +document relationships are allowed. +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRev.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRev.txt new file mode 100644 index 000000000..1ae672d01 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRev.txt @@ -0,0 +1,9 @@ +Attr.AllowedRev +TYPE: lookup +VERSION: 1.6.0 +DEFAULT: array() +--DESCRIPTION-- +List of allowed reverse document relationships in the rev attribute. This +attribute is a bit of an edge-case; if you don't know what it is for, stay +away. +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.ClassUseCDATA.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.ClassUseCDATA.txt new file mode 100644 index 000000000..119a9d2c6 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.ClassUseCDATA.txt @@ -0,0 +1,19 @@ +Attr.ClassUseCDATA +TYPE: bool/null +DEFAULT: null +VERSION: 4.0.0 +--DESCRIPTION-- +If null, class will auto-detect the doctype and, if matching XHTML 1.1 or +XHTML 2.0, will use the restrictive NMTOKENS specification of class. Otherwise, +it will use a relaxed CDATA definition. If true, the relaxed CDATA definition +is forced; if false, the NMTOKENS definition is forced. To get behavior +of HTML Purifier prior to 4.0.0, set this directive to false. + +Some rational behind the auto-detection: +in previous versions of HTML Purifier, it was assumed that the form of +class was NMTOKENS, as specified by the XHTML Modularization (representing +XHTML 1.1 and XHTML 2.0). The DTDs for HTML 4.01 and XHTML 1.0, however +specify class as CDATA. HTML 5 effectively defines it as CDATA, but +with the additional constraint that each name should be unique (this is not +explicitly outlined in previous specifications). +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.DefaultImageAlt.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.DefaultImageAlt.txt new file mode 100644 index 000000000..80b1431c3 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.DefaultImageAlt.txt @@ -0,0 +1,11 @@ +Attr.DefaultImageAlt +TYPE: string/null +DEFAULT: null +VERSION: 3.2.0 +--DESCRIPTION-- +This is the content of the alt tag of an image if the user had not +previously specified an alt attribute. This applies to all images without +a valid alt attribute, as opposed to %Attr.DefaultInvalidImageAlt, which +only applies to invalid images, and overrides in the case of an invalid image. +Default behavior with null is to use the basename of the src tag for the alt. +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImage.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImage.txt new file mode 100644 index 000000000..c51000d1d --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImage.txt @@ -0,0 +1,9 @@ +Attr.DefaultInvalidImage +TYPE: string +DEFAULT: '' +--DESCRIPTION-- +This is the default image an img tag will be pointed to if it does not have +a valid src attribute. In future versions, we may allow the image tag to +be removed completely, but due to design issues, this is not possible right +now. +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImageAlt.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImageAlt.txt new file mode 100644 index 000000000..c1ec4b038 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImageAlt.txt @@ -0,0 +1,8 @@ +Attr.DefaultInvalidImageAlt +TYPE: string +DEFAULT: 'Invalid image' +--DESCRIPTION-- +This is the content of the alt tag of an invalid image if the user had not +previously specified an alt attribute. It has no effect when the image is +valid but there was no alt attribute present. +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.DefaultTextDir.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.DefaultTextDir.txt new file mode 100644 index 000000000..f57dcc40f --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.DefaultTextDir.txt @@ -0,0 +1,10 @@ +Attr.DefaultTextDir +TYPE: string +DEFAULT: 'ltr' +--DESCRIPTION-- +Defines the default text direction (ltr or rtl) of the document being +parsed. This generally is the same as the value of the dir attribute in +HTML, or ltr if that is not specified. +--ALLOWED-- +'ltr', 'rtl' +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.EnableID.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.EnableID.txt new file mode 100644 index 000000000..9b93a5575 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.EnableID.txt @@ -0,0 +1,16 @@ +Attr.EnableID +TYPE: bool +DEFAULT: false +VERSION: 1.2.0 +--DESCRIPTION-- +Allows the ID attribute in HTML. This is disabled by default due to the +fact that without proper configuration user input can easily break the +validation of a webpage by specifying an ID that is already on the +surrounding HTML. If you don't mind throwing caution to the wind, enable +this directive, but I strongly recommend you also consider blacklisting IDs +you use (%Attr.IDBlacklist) or prefixing all user supplied IDs +(%Attr.IDPrefix). When set to true HTML Purifier reverts to the behavior of +pre-1.2.0 versions. +--ALIASES-- +HTML.EnableAttrID +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.ForbiddenClasses.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.ForbiddenClasses.txt new file mode 100644 index 000000000..fed8954cf --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.ForbiddenClasses.txt @@ -0,0 +1,8 @@ +Attr.ForbiddenClasses +TYPE: lookup +VERSION: 4.0.0 +DEFAULT: array() +--DESCRIPTION-- +List of forbidden class values in the class attribute. By default, this is +empty, which means that no classes are forbidden. See also %Attr.AllowedClasses. +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.ID.HTML5.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.ID.HTML5.txt new file mode 100644 index 000000000..c48e62fbe --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.ID.HTML5.txt @@ -0,0 +1,10 @@ +Attr.ID.HTML5 +TYPE: bool/null +DEFAULT: null +VERSION: 4.8.0 +--DESCRIPTION-- +In HTML5, restrictions on the format of the id attribute have been significantly +relaxed, such that any string is valid so long as it contains no spaces and +is at least one character. In lieu of a general HTML5 compatibility flag, +set this configuration directive to true to use the relaxed rules. +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklist.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklist.txt new file mode 100644 index 000000000..52168bb5e --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklist.txt @@ -0,0 +1,5 @@ +Attr.IDBlacklist +TYPE: list +DEFAULT: array() +DESCRIPTION: Array of IDs not allowed in the document. +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklistRegexp.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklistRegexp.txt new file mode 100644 index 000000000..7b8504307 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklistRegexp.txt @@ -0,0 +1,9 @@ +Attr.IDBlacklistRegexp +TYPE: string/null +VERSION: 1.6.0 +DEFAULT: NULL +--DESCRIPTION-- +PCRE regular expression to be matched against all IDs. If the expression is +matches, the ID is rejected. Use this with care: may cause significant +degradation. ID matching is done after all other validation. +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefix.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefix.txt new file mode 100644 index 000000000..578138277 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefix.txt @@ -0,0 +1,12 @@ +Attr.IDPrefix +TYPE: string +VERSION: 1.2.0 +DEFAULT: '' +--DESCRIPTION-- +String to prefix to IDs. If you have no idea what IDs your pages may use, +you may opt to simply add a prefix to all user-submitted ID attributes so +that they are still usable, but will not conflict with core page IDs. +Example: setting the directive to 'user_' will result in a user submitted +'foo' to become 'user_foo' Be sure to set %HTML.EnableAttrID to true +before using this. +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefixLocal.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefixLocal.txt new file mode 100644 index 000000000..f91fcd602 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefixLocal.txt @@ -0,0 +1,14 @@ +Attr.IDPrefixLocal +TYPE: string +VERSION: 1.2.0 +DEFAULT: '' +--DESCRIPTION-- +Temporary prefix for IDs used in conjunction with %Attr.IDPrefix. If you +need to allow multiple sets of user content on web page, you may need to +have a seperate prefix that changes with each iteration. This way, +seperately submitted user content displayed on the same page doesn't +clobber each other. Ideal values are unique identifiers for the content it +represents (i.e. the id of the row in the database). Be sure to add a +seperator (like an underscore) at the end. Warning: this directive will +not work unless %Attr.IDPrefix is set to a non-empty value! +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.AutoParagraph.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.AutoParagraph.txt new file mode 100644 index 000000000..2d7f94e02 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.AutoParagraph.txt @@ -0,0 +1,31 @@ +AutoFormat.AutoParagraph +TYPE: bool +VERSION: 2.0.1 +DEFAULT: false +--DESCRIPTION-- + +

      + This directive turns on auto-paragraphing, where double newlines are + converted in to paragraphs whenever possible. Auto-paragraphing: +

      +
        +
      • Always applies to inline elements or text in the root node,
      • +
      • Applies to inline elements or text with double newlines in nodes + that allow paragraph tags,
      • +
      • Applies to double newlines in paragraph tags
      • +
      +

      + p tags must be allowed for this directive to take effect. + We do not use br tags for paragraphing, as that is + semantically incorrect. +

      +

      + To prevent auto-paragraphing as a content-producer, refrain from using + double-newlines except to specify a new paragraph or in contexts where + it has special meaning (whitespace usually has no meaning except in + tags like pre, so this should not be difficult.) To prevent + the paragraphing of inline text adjacent to block elements, wrap them + in div tags (the behavior is slightly different outside of + the root node.) +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.Custom.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.Custom.txt new file mode 100644 index 000000000..2eb1974fd --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.Custom.txt @@ -0,0 +1,12 @@ +AutoFormat.Custom +TYPE: list +VERSION: 2.0.1 +DEFAULT: array() +--DESCRIPTION-- + +

      + This directive can be used to add custom auto-format injectors. + Specify an array of injector names (class name minus the prefix) + or concrete implementations. Injector class must exist. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.DisplayLinkURI.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.DisplayLinkURI.txt new file mode 100644 index 000000000..c955de7f6 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.DisplayLinkURI.txt @@ -0,0 +1,11 @@ +AutoFormat.DisplayLinkURI +TYPE: bool +VERSION: 3.2.0 +DEFAULT: false +--DESCRIPTION-- +

      + This directive turns on the in-text display of URIs in <a> tags, and disables + those links. For example, example becomes + example (http://example.com). +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.Linkify.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.Linkify.txt new file mode 100644 index 000000000..328b2b2bf --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.Linkify.txt @@ -0,0 +1,12 @@ +AutoFormat.Linkify +TYPE: bool +VERSION: 2.0.1 +DEFAULT: false +--DESCRIPTION-- + +

      + This directive turns on linkification, auto-linking http, ftp and + https URLs. a tags with the href attribute + must be allowed. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.DocURL.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.DocURL.txt new file mode 100644 index 000000000..d0532b6ba --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.DocURL.txt @@ -0,0 +1,12 @@ +AutoFormat.PurifierLinkify.DocURL +TYPE: string +VERSION: 2.0.1 +DEFAULT: '#%s' +ALIASES: AutoFormatParam.PurifierLinkifyDocURL +--DESCRIPTION-- +

      + Location of configuration documentation to link to, let %s substitute + into the configuration's namespace and directive names sans the percent + sign. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.txt new file mode 100644 index 000000000..f3ab259a1 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.txt @@ -0,0 +1,12 @@ +AutoFormat.PurifierLinkify +TYPE: bool +VERSION: 2.0.1 +DEFAULT: false +--DESCRIPTION-- + +

      + Internal auto-formatter that converts configuration directives in + syntax %Namespace.Directive to links. a tags + with the href attribute must be allowed. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.Predicate.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.Predicate.txt new file mode 100644 index 000000000..376f771ea --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.Predicate.txt @@ -0,0 +1,14 @@ +AutoFormat.RemoveEmpty.Predicate +TYPE: hash +VERSION: 4.7.0 +DEFAULT: array('colgroup' => array(), 'th' => array(), 'td' => array(), 'iframe' => array('src')) +--DESCRIPTION-- +

      + Given that an element has no contents, it will be removed by default, unless + this predicate dictates otherwise. The predicate can either be an associative + map from tag name to list of attributes that must be present for the element + to be considered preserved: thus, the default always preserves colgroup, + th and td, and also iframe if it + has a src. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions.txt new file mode 100644 index 000000000..219d04ac4 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions.txt @@ -0,0 +1,11 @@ +AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions +TYPE: lookup +VERSION: 4.0.0 +DEFAULT: array('td' => true, 'th' => true) +--DESCRIPTION-- +

      + When %AutoFormat.RemoveEmpty and %AutoFormat.RemoveEmpty.RemoveNbsp + are enabled, this directive defines what HTML elements should not be + removede if they have only a non-breaking space in them. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.txt new file mode 100644 index 000000000..e557ad216 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.txt @@ -0,0 +1,15 @@ +AutoFormat.RemoveEmpty.RemoveNbsp +TYPE: bool +VERSION: 4.0.0 +DEFAULT: false +--DESCRIPTION-- +

      + When enabled, HTML Purifier will treat any elements that contain only + non-breaking spaces as well as regular whitespace as empty, and remove + them when %AutoFormat.RemoveEmpty is enabled. +

      +

      + See %AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions for a list of elements + that don't have this behavior applied to them. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.txt new file mode 100644 index 000000000..6b5a7a5c9 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.txt @@ -0,0 +1,46 @@ +AutoFormat.RemoveEmpty +TYPE: bool +VERSION: 3.2.0 +DEFAULT: false +--DESCRIPTION-- +

      + When enabled, HTML Purifier will attempt to remove empty elements that + contribute no semantic information to the document. The following types + of nodes will be removed: +

      +
      • + Tags with no attributes and no content, and that are not empty + elements (remove <a></a> but not + <br />), and +
      • +
      • + Tags with no content, except for:
          +
        • The colgroup element, or
        • +
        • + Elements with the id or name attribute, + when those attributes are permitted on those elements. +
        • +
      • +
      +

      + Please be very careful when using this functionality; while it may not + seem that empty elements contain useful information, they can alter the + layout of a document given appropriate styling. This directive is most + useful when you are processing machine-generated HTML, please avoid using + it on regular user HTML. +

      +

      + Elements that contain only whitespace will be treated as empty. Non-breaking + spaces, however, do not count as whitespace. See + %AutoFormat.RemoveEmpty.RemoveNbsp for alternate behavior. +

      +

      + This algorithm is not perfect; you may still notice some empty tags, + particularly if a node had elements, but those elements were later removed + because they were not permitted in that context, or tags that, after + being auto-closed by another tag, where empty. This is for safety reasons + to prevent clever code from breaking validation. The general rule of thumb: + if a tag looked empty on the way in, it will get removed; if HTML Purifier + made it empty, it will stay. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveSpansWithoutAttributes.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveSpansWithoutAttributes.txt new file mode 100644 index 000000000..a448770e5 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveSpansWithoutAttributes.txt @@ -0,0 +1,11 @@ +AutoFormat.RemoveSpansWithoutAttributes +TYPE: bool +VERSION: 4.0.1 +DEFAULT: false +--DESCRIPTION-- +

      + This directive causes span tags without any attributes + to be removed. It will also remove spans that had all attributes + removed during processing. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/CSS.AllowDuplicates.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/CSS.AllowDuplicates.txt new file mode 100644 index 000000000..acfeab3c8 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/CSS.AllowDuplicates.txt @@ -0,0 +1,11 @@ +CSS.AllowDuplicates +TYPE: bool +DEFAULT: false +VERSION: 4.8.0 +--DESCRIPTION-- +

      + By default, HTML Purifier removes duplicate CSS properties, + like color:red; color:blue. If this is set to + true, duplicate properties are allowed. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/CSS.AllowImportant.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/CSS.AllowImportant.txt new file mode 100644 index 000000000..8096eb01a --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/CSS.AllowImportant.txt @@ -0,0 +1,8 @@ +CSS.AllowImportant +TYPE: bool +DEFAULT: false +VERSION: 3.1.0 +--DESCRIPTION-- +This parameter determines whether or not !important cascade modifiers should +be allowed in user CSS. If false, !important will stripped. +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/CSS.AllowTricky.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/CSS.AllowTricky.txt new file mode 100644 index 000000000..9d34debc4 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/CSS.AllowTricky.txt @@ -0,0 +1,11 @@ +CSS.AllowTricky +TYPE: bool +DEFAULT: false +VERSION: 3.1.0 +--DESCRIPTION-- +This parameter determines whether or not to allow "tricky" CSS properties and +values. Tricky CSS properties/values can drastically modify page layout or +be used for deceptive practices but do not directly constitute a security risk. +For example, display:none; is considered a tricky property that +will only be allowed if this directive is set to true. +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/CSS.AllowedFonts.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/CSS.AllowedFonts.txt new file mode 100644 index 000000000..7c2b54763 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/CSS.AllowedFonts.txt @@ -0,0 +1,12 @@ +CSS.AllowedFonts +TYPE: lookup/null +VERSION: 4.3.0 +DEFAULT: NULL +--DESCRIPTION-- +

      + Allows you to manually specify a set of allowed fonts. If + NULL, all fonts are allowed. This directive + affects generic names (serif, sans-serif, monospace, cursive, + fantasy) as well as specific font families. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/CSS.AllowedProperties.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/CSS.AllowedProperties.txt new file mode 100644 index 000000000..f1ba513c3 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/CSS.AllowedProperties.txt @@ -0,0 +1,18 @@ +CSS.AllowedProperties +TYPE: lookup/null +VERSION: 3.1.0 +DEFAULT: NULL +--DESCRIPTION-- + +

      + If HTML Purifier's style attributes set is unsatisfactory for your needs, + you can overload it with your own list of tags to allow. Note that this + method is subtractive: it does its job by taking away from HTML Purifier + usual feature set, so you cannot add an attribute that HTML Purifier never + supported in the first place. +

      +

      + Warning: If another directive conflicts with the + elements here, that directive will win and override. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/CSS.DefinitionRev.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/CSS.DefinitionRev.txt new file mode 100644 index 000000000..96b410829 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/CSS.DefinitionRev.txt @@ -0,0 +1,11 @@ +CSS.DefinitionRev +TYPE: int +VERSION: 2.0.0 +DEFAULT: 1 +--DESCRIPTION-- + +

      + Revision identifier for your custom definition. See + %HTML.DefinitionRev for details. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/CSS.ForbiddenProperties.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/CSS.ForbiddenProperties.txt new file mode 100644 index 000000000..923e8e995 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/CSS.ForbiddenProperties.txt @@ -0,0 +1,13 @@ +CSS.ForbiddenProperties +TYPE: lookup +VERSION: 4.2.0 +DEFAULT: array() +--DESCRIPTION-- +

      + This is the logical inverse of %CSS.AllowedProperties, and it will + override that directive or any other directive. If possible, + %CSS.AllowedProperties is recommended over this directive, + because it can sometimes be difficult to tell whether or not you've + forbidden all of the CSS properties you truly would like to disallow. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/CSS.MaxImgLength.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/CSS.MaxImgLength.txt new file mode 100644 index 000000000..3808581e2 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/CSS.MaxImgLength.txt @@ -0,0 +1,16 @@ +CSS.MaxImgLength +TYPE: string/null +DEFAULT: '1200px' +VERSION: 3.1.1 +--DESCRIPTION-- +

      + This parameter sets the maximum allowed length on img tags, + effectively the width and height properties. + Only absolute units of measurement (in, pt, pc, mm, cm) and pixels (px) are allowed. This is + in place to prevent imagecrash attacks, disable with null at your own risk. + This directive is similar to %HTML.MaxImgLength, and both should be + concurrently edited, although there are + subtle differences in the input format (the CSS max is a number with + a unit). +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/CSS.Proprietary.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/CSS.Proprietary.txt new file mode 100644 index 000000000..8a26f228d --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/CSS.Proprietary.txt @@ -0,0 +1,10 @@ +CSS.Proprietary +TYPE: bool +VERSION: 3.0.0 +DEFAULT: false +--DESCRIPTION-- + +

      + Whether or not to allow safe, proprietary CSS values. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/CSS.Trusted.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/CSS.Trusted.txt new file mode 100644 index 000000000..917ec42ba --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/CSS.Trusted.txt @@ -0,0 +1,9 @@ +CSS.Trusted +TYPE: bool +VERSION: 4.2.1 +DEFAULT: false +--DESCRIPTION-- +Indicates whether or not the user's CSS input is trusted or not. If the +input is trusted, a more expansive set of allowed properties. See +also %HTML.Trusted. +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Cache.DefinitionImpl.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Cache.DefinitionImpl.txt new file mode 100644 index 000000000..afc6a87a6 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Cache.DefinitionImpl.txt @@ -0,0 +1,14 @@ +Cache.DefinitionImpl +TYPE: string/null +VERSION: 2.0.0 +DEFAULT: 'Serializer' +--DESCRIPTION-- + +This directive defines which method to use when caching definitions, +the complex data-type that makes HTML Purifier tick. Set to null +to disable caching (not recommended, as you will see a definite +performance degradation). + +--ALIASES-- +Core.DefinitionCache +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPath.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPath.txt new file mode 100644 index 000000000..668f248af --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPath.txt @@ -0,0 +1,13 @@ +Cache.SerializerPath +TYPE: string/null +VERSION: 2.0.0 +DEFAULT: NULL +--DESCRIPTION-- + +

      + Absolute path with no trailing slash to store serialized definitions in. + Default is within the + HTML Purifier library inside DefinitionCache/Serializer. This + path must be writable by the webserver. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPermissions.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPermissions.txt new file mode 100644 index 000000000..f6059e672 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPermissions.txt @@ -0,0 +1,16 @@ +Cache.SerializerPermissions +TYPE: int/null +VERSION: 4.3.0 +DEFAULT: 0755 +--DESCRIPTION-- + +

      + Directory permissions of the files and directories created inside + the DefinitionCache/Serializer or other custom serializer path. +

      +

      + In HTML Purifier 4.8.0, this also supports NULL, + which means that no chmod'ing or directory creation shall + occur. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.AggressivelyFixLt.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.AggressivelyFixLt.txt new file mode 100644 index 000000000..e0fa378ea --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.AggressivelyFixLt.txt @@ -0,0 +1,18 @@ +Core.AggressivelyFixLt +TYPE: bool +VERSION: 2.1.0 +DEFAULT: true +--DESCRIPTION-- +

      + This directive enables aggressive pre-filter fixes HTML Purifier can + perform in order to ensure that open angled-brackets do not get killed + during parsing stage. Enabling this will result in two preg_replace_callback + calls and at least two preg_replace calls for every HTML document parsed; + if your users make very well-formed HTML, you can set this directive false. + This has no effect when DirectLex is used. +

      +

      + Notice: This directive's default turned from false to true + in HTML Purifier 3.2.0. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.AggressivelyRemoveScript.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.AggressivelyRemoveScript.txt new file mode 100644 index 000000000..fb140b69d --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.AggressivelyRemoveScript.txt @@ -0,0 +1,16 @@ +Core.AggressivelyRemoveScript +TYPE: bool +VERSION: 4.9.0 +DEFAULT: true +--DESCRIPTION-- +

      + This directive enables aggressive pre-filter removal of + script tags. This is not necessary for security, + but it can help work around a bug in libxml where embedded + HTML elements inside script sections cause the parser to + choke. To revert to pre-4.9.0 behavior, set this to false. + This directive has no effect if %Core.Trusted is true, + %Core.RemoveScriptContents is false, or %Core.HiddenElements + does not contain script. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.AllowHostnameUnderscore.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.AllowHostnameUnderscore.txt new file mode 100644 index 000000000..405d36f17 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.AllowHostnameUnderscore.txt @@ -0,0 +1,16 @@ +Core.AllowHostnameUnderscore +TYPE: bool +VERSION: 4.6.0 +DEFAULT: false +--DESCRIPTION-- +

      + By RFC 1123, underscores are not permitted in host names. + (This is in contrast to the specification for DNS, RFC + 2181, which allows underscores.) + However, most browsers do the right thing when faced with + an underscore in the host name, and so some poorly written + websites are written with the expectation this should work. + Setting this parameter to true relaxes our allowed character + check so that underscores are permitted. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.AllowParseManyTags.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.AllowParseManyTags.txt new file mode 100644 index 000000000..b4b9b102f --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.AllowParseManyTags.txt @@ -0,0 +1,12 @@ +Core.AllowParseManyTags +TYPE: bool +DEFAULT: false +VERSION: 4.10.1 +--DESCRIPTION-- +

      + This directive allows parsing of many nested tags. + If you set true, relaxes any hardcoded limit from the parser. + However, in that case it may cause a Dos attack. + Be careful when enabling it. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.CollectErrors.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.CollectErrors.txt new file mode 100644 index 000000000..c6ea06990 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.CollectErrors.txt @@ -0,0 +1,12 @@ +Core.CollectErrors +TYPE: bool +VERSION: 2.0.0 +DEFAULT: false +--DESCRIPTION-- + +Whether or not to collect errors found while filtering the document. This +is a useful way to give feedback to your users. Warning: +Currently this feature is very patchy and experimental, with lots of +possible error messages not yet implemented. It will not cause any +problems, but it may not help your users either. +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.ColorKeywords.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.ColorKeywords.txt new file mode 100644 index 000000000..fc100868e --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.ColorKeywords.txt @@ -0,0 +1,160 @@ +Core.ColorKeywords +TYPE: hash +VERSION: 2.0.0 +--DEFAULT-- +array ( + 'aliceblue' => '#F0F8FF', + 'antiquewhite' => '#FAEBD7', + 'aqua' => '#00FFFF', + 'aquamarine' => '#7FFFD4', + 'azure' => '#F0FFFF', + 'beige' => '#F5F5DC', + 'bisque' => '#FFE4C4', + 'black' => '#000000', + 'blanchedalmond' => '#FFEBCD', + 'blue' => '#0000FF', + 'blueviolet' => '#8A2BE2', + 'brown' => '#A52A2A', + 'burlywood' => '#DEB887', + 'cadetblue' => '#5F9EA0', + 'chartreuse' => '#7FFF00', + 'chocolate' => '#D2691E', + 'coral' => '#FF7F50', + 'cornflowerblue' => '#6495ED', + 'cornsilk' => '#FFF8DC', + 'crimson' => '#DC143C', + 'cyan' => '#00FFFF', + 'darkblue' => '#00008B', + 'darkcyan' => '#008B8B', + 'darkgoldenrod' => '#B8860B', + 'darkgray' => '#A9A9A9', + 'darkgrey' => '#A9A9A9', + 'darkgreen' => '#006400', + 'darkkhaki' => '#BDB76B', + 'darkmagenta' => '#8B008B', + 'darkolivegreen' => '#556B2F', + 'darkorange' => '#FF8C00', + 'darkorchid' => '#9932CC', + 'darkred' => '#8B0000', + 'darksalmon' => '#E9967A', + 'darkseagreen' => '#8FBC8F', + 'darkslateblue' => '#483D8B', + 'darkslategray' => '#2F4F4F', + 'darkslategrey' => '#2F4F4F', + 'darkturquoise' => '#00CED1', + 'darkviolet' => '#9400D3', + 'deeppink' => '#FF1493', + 'deepskyblue' => '#00BFFF', + 'dimgray' => '#696969', + 'dimgrey' => '#696969', + 'dodgerblue' => '#1E90FF', + 'firebrick' => '#B22222', + 'floralwhite' => '#FFFAF0', + 'forestgreen' => '#228B22', + 'fuchsia' => '#FF00FF', + 'gainsboro' => '#DCDCDC', + 'ghostwhite' => '#F8F8FF', + 'gold' => '#FFD700', + 'goldenrod' => '#DAA520', + 'gray' => '#808080', + 'grey' => '#808080', + 'green' => '#008000', + 'greenyellow' => '#ADFF2F', + 'honeydew' => '#F0FFF0', + 'hotpink' => '#FF69B4', + 'indianred' => '#CD5C5C', + 'indigo' => '#4B0082', + 'ivory' => '#FFFFF0', + 'khaki' => '#F0E68C', + 'lavender' => '#E6E6FA', + 'lavenderblush' => '#FFF0F5', + 'lawngreen' => '#7CFC00', + 'lemonchiffon' => '#FFFACD', + 'lightblue' => '#ADD8E6', + 'lightcoral' => '#F08080', + 'lightcyan' => '#E0FFFF', + 'lightgoldenrodyellow' => '#FAFAD2', + 'lightgray' => '#D3D3D3', + 'lightgrey' => '#D3D3D3', + 'lightgreen' => '#90EE90', + 'lightpink' => '#FFB6C1', + 'lightsalmon' => '#FFA07A', + 'lightseagreen' => '#20B2AA', + 'lightskyblue' => '#87CEFA', + 'lightslategray' => '#778899', + 'lightslategrey' => '#778899', + 'lightsteelblue' => '#B0C4DE', + 'lightyellow' => '#FFFFE0', + 'lime' => '#00FF00', + 'limegreen' => '#32CD32', + 'linen' => '#FAF0E6', + 'magenta' => '#FF00FF', + 'maroon' => '#800000', + 'mediumaquamarine' => '#66CDAA', + 'mediumblue' => '#0000CD', + 'mediumorchid' => '#BA55D3', + 'mediumpurple' => '#9370DB', + 'mediumseagreen' => '#3CB371', + 'mediumslateblue' => '#7B68EE', + 'mediumspringgreen' => '#00FA9A', + 'mediumturquoise' => '#48D1CC', + 'mediumvioletred' => '#C71585', + 'midnightblue' => '#191970', + 'mintcream' => '#F5FFFA', + 'mistyrose' => '#FFE4E1', + 'moccasin' => '#FFE4B5', + 'navajowhite' => '#FFDEAD', + 'navy' => '#000080', + 'oldlace' => '#FDF5E6', + 'olive' => '#808000', + 'olivedrab' => '#6B8E23', + 'orange' => '#FFA500', + 'orangered' => '#FF4500', + 'orchid' => '#DA70D6', + 'palegoldenrod' => '#EEE8AA', + 'palegreen' => '#98FB98', + 'paleturquoise' => '#AFEEEE', + 'palevioletred' => '#DB7093', + 'papayawhip' => '#FFEFD5', + 'peachpuff' => '#FFDAB9', + 'peru' => '#CD853F', + 'pink' => '#FFC0CB', + 'plum' => '#DDA0DD', + 'powderblue' => '#B0E0E6', + 'purple' => '#800080', + 'rebeccapurple' => '#663399', + 'red' => '#FF0000', + 'rosybrown' => '#BC8F8F', + 'royalblue' => '#4169E1', + 'saddlebrown' => '#8B4513', + 'salmon' => '#FA8072', + 'sandybrown' => '#F4A460', + 'seagreen' => '#2E8B57', + 'seashell' => '#FFF5EE', + 'sienna' => '#A0522D', + 'silver' => '#C0C0C0', + 'skyblue' => '#87CEEB', + 'slateblue' => '#6A5ACD', + 'slategray' => '#708090', + 'slategrey' => '#708090', + 'snow' => '#FFFAFA', + 'springgreen' => '#00FF7F', + 'steelblue' => '#4682B4', + 'tan' => '#D2B48C', + 'teal' => '#008080', + 'thistle' => '#D8BFD8', + 'tomato' => '#FF6347', + 'turquoise' => '#40E0D0', + 'violet' => '#EE82EE', + 'wheat' => '#F5DEB3', + 'white' => '#FFFFFF', + 'whitesmoke' => '#F5F5F5', + 'yellow' => '#FFFF00', + 'yellowgreen' => '#9ACD32' +) +--DESCRIPTION-- + +Lookup array of color names to six digit hexadecimal number corresponding +to color, with preceding hash mark. Used when parsing colors. The lookup +is done in a case-insensitive manner. +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.ConvertDocumentToFragment.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.ConvertDocumentToFragment.txt new file mode 100644 index 000000000..656d3783a --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.ConvertDocumentToFragment.txt @@ -0,0 +1,14 @@ +Core.ConvertDocumentToFragment +TYPE: bool +DEFAULT: true +--DESCRIPTION-- + +This parameter determines whether or not the filter should convert +input that is a full document with html and body tags to a fragment +of just the contents of a body tag. This parameter is simply something +HTML Purifier can do during an edge-case: for most inputs, this +processing is not necessary. + +--ALIASES-- +Core.AcceptFullDocuments +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.DirectLexLineNumberSyncInterval.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.DirectLexLineNumberSyncInterval.txt new file mode 100644 index 000000000..2f54e462a --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.DirectLexLineNumberSyncInterval.txt @@ -0,0 +1,17 @@ +Core.DirectLexLineNumberSyncInterval +TYPE: int +VERSION: 2.0.0 +DEFAULT: 0 +--DESCRIPTION-- + +

      + Specifies the number of tokens the DirectLex line number tracking + implementations should process before attempting to resyncronize the + current line count by manually counting all previous new-lines. When + at 0, this functionality is disabled. Lower values will decrease + performance, and this is only strictly necessary if the counting + algorithm is buggy (in which case you should report it as a bug). + This has no effect when %Core.MaintainLineNumbers is disabled or DirectLex is + not being used. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.DisableExcludes.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.DisableExcludes.txt new file mode 100644 index 000000000..3c63c923c --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.DisableExcludes.txt @@ -0,0 +1,14 @@ +Core.DisableExcludes +TYPE: bool +DEFAULT: false +VERSION: 4.5.0 +--DESCRIPTION-- +

      + This directive disables SGML-style exclusions, e.g. the exclusion of + <object> in any descendant of a + <pre> tag. Disabling excludes will allow some + invalid documents to pass through HTML Purifier, but HTML Purifier + will also be less likely to accidentally remove large documents during + processing. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.EnableIDNA.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.EnableIDNA.txt new file mode 100644 index 000000000..7f498e7e7 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.EnableIDNA.txt @@ -0,0 +1,9 @@ +Core.EnableIDNA +TYPE: bool +DEFAULT: false +VERSION: 4.4.0 +--DESCRIPTION-- +Allows international domain names in URLs. This configuration option +requires the PEAR Net_IDNA2 module to be installed. It operates by +punycoding any internationalized host names for maximum portability. +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.Encoding.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.Encoding.txt new file mode 100644 index 000000000..89e2ae34b --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.Encoding.txt @@ -0,0 +1,15 @@ +Core.Encoding +TYPE: istring +DEFAULT: 'utf-8' +--DESCRIPTION-- +If for some reason you are unable to convert all webpages to UTF-8, you can +use this directive as a stop-gap compatibility change to let HTML Purifier +deal with non UTF-8 input. This technique has notable deficiencies: +absolutely no characters outside of the selected character encoding will be +preserved, not even the ones that have been ampersand escaped (this is due +to a UTF-8 specific feature that automatically resolves all +entities), making it pretty useless for anything except the most I18N-blind +applications, although %Core.EscapeNonASCIICharacters offers fixes this +trouble with another tradeoff. This directive only accepts ISO-8859-1 if +iconv is not enabled. +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidChildren.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidChildren.txt new file mode 100644 index 000000000..1cc3fcda2 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidChildren.txt @@ -0,0 +1,12 @@ +Core.EscapeInvalidChildren +TYPE: bool +DEFAULT: false +--DESCRIPTION-- +

      Warning: this configuration option is no longer does anything as of 4.6.0.

      + +

      When true, a child is found that is not allowed in the context of the +parent element will be transformed into text as if it were ASCII. When +false, that element and all internal tags will be dropped, though text will +be preserved. There is no option for dropping the element but preserving +child nodes.

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidTags.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidTags.txt new file mode 100644 index 000000000..299775fab --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidTags.txt @@ -0,0 +1,7 @@ +Core.EscapeInvalidTags +TYPE: bool +DEFAULT: false +--DESCRIPTION-- +When true, invalid tags will be written back to the document as plain text. +Otherwise, they are silently dropped. +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.EscapeNonASCIICharacters.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.EscapeNonASCIICharacters.txt new file mode 100644 index 000000000..f50db2f92 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.EscapeNonASCIICharacters.txt @@ -0,0 +1,13 @@ +Core.EscapeNonASCIICharacters +TYPE: bool +VERSION: 1.4.0 +DEFAULT: false +--DESCRIPTION-- +This directive overcomes a deficiency in %Core.Encoding by blindly +converting all non-ASCII characters into decimal numeric entities before +converting it to its native encoding. This means that even characters that +can be expressed in the non-UTF-8 encoding will be entity-ized, which can +be a real downer for encodings like Big5. It also assumes that the ASCII +repetoire is available, although this is the case for almost all encodings. +Anyway, use UTF-8! +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.HiddenElements.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.HiddenElements.txt new file mode 100644 index 000000000..c337e47fc --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.HiddenElements.txt @@ -0,0 +1,19 @@ +Core.HiddenElements +TYPE: lookup +--DEFAULT-- +array ( + 'script' => true, + 'style' => true, +) +--DESCRIPTION-- + +

      + This directive is a lookup array of elements which should have their + contents removed when they are not allowed by the HTML definition. + For example, the contents of a script tag are not + normally shown in a document, so if script tags are to be removed, + their contents should be removed to. This is opposed to a b + tag, which defines some presentational changes but does not hide its + contents. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.Language.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.Language.txt new file mode 100644 index 000000000..ed1f39b5f --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.Language.txt @@ -0,0 +1,10 @@ +Core.Language +TYPE: string +VERSION: 2.0.0 +DEFAULT: 'en' +--DESCRIPTION-- + +ISO 639 language code for localizable things in HTML Purifier to use, +which is mainly error reporting. There is currently only an English (en) +translation, so this directive is currently useless. +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.LegacyEntityDecoder.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.LegacyEntityDecoder.txt new file mode 100644 index 000000000..81d9ae4dc --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.LegacyEntityDecoder.txt @@ -0,0 +1,36 @@ +Core.LegacyEntityDecoder +TYPE: bool +VERSION: 4.9.0 +DEFAULT: false +--DESCRIPTION-- +

      + Prior to HTML Purifier 4.9.0, entities were decoded by performing + a global search replace for all entities whose decoded versions + did not have special meanings under HTML, and replaced them with + their decoded versions. We would match all entities, even if they did + not have a trailing semicolon, but only if there weren't any trailing + alphanumeric characters. +

      + + + + + + +
      OriginalTextAttribute
      &yen;¥¥
      &yen¥¥
      &yena&yena&yena
      &yen=¥=¥=
      +

      + In HTML Purifier 4.9.0, we changed the behavior of entity parsing + to match entities that had missing trailing semicolons in less + cases, to more closely match HTML5 parsing behavior: +

      + + + + + + +
      OriginalTextAttribute
      &yen;¥¥
      &yen¥¥
      &yena¥a&yena
      &yen=¥=&yen=
      +

      + This flag reverts back to pre-HTML Purifier 4.9.0 behavior. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.LexerImpl.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.LexerImpl.txt new file mode 100644 index 000000000..e11c0152c --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.LexerImpl.txt @@ -0,0 +1,34 @@ +Core.LexerImpl +TYPE: mixed/null +VERSION: 2.0.0 +DEFAULT: NULL +--DESCRIPTION-- + +

      + This parameter determines what lexer implementation can be used. The + valid values are: +

      +
      +
      null
      +
      + Recommended, the lexer implementation will be auto-detected based on + your PHP-version and configuration. +
      +
      string lexer identifier
      +
      + This is a slim way of manually overridding the implementation. + Currently recognized values are: DOMLex (the default PHP5 +implementation) + and DirectLex (the default PHP4 implementation). Only use this if + you know what you are doing: usually, the auto-detection will + manage things for cases you aren't even aware of. +
      +
      object lexer instance
      +
      + Super-advanced: you can specify your own, custom, implementation that + implements the interface defined by HTMLPurifier_Lexer. + I may remove this option simply because I don't expect anyone + to use it. +
      +
      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.MaintainLineNumbers.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.MaintainLineNumbers.txt new file mode 100644 index 000000000..838f10f61 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.MaintainLineNumbers.txt @@ -0,0 +1,16 @@ +Core.MaintainLineNumbers +TYPE: bool/null +VERSION: 2.0.0 +DEFAULT: NULL +--DESCRIPTION-- + +

      + If true, HTML Purifier will add line number information to all tokens. + This is useful when error reporting is turned on, but can result in + significant performance degradation and should not be used when + unnecessary. This directive must be used with the DirectLex lexer, + as the DOMLex lexer does not (yet) support this functionality. + If the value is null, an appropriate value will be selected based + on other configuration. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.NormalizeNewlines.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.NormalizeNewlines.txt new file mode 100644 index 000000000..94a88600d --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.NormalizeNewlines.txt @@ -0,0 +1,11 @@ +Core.NormalizeNewlines +TYPE: bool +VERSION: 4.2.0 +DEFAULT: true +--DESCRIPTION-- +

      + Whether or not to normalize newlines to the operating + system default. When false, HTML Purifier + will attempt to preserve mixed newline files. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.RemoveInvalidImg.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.RemoveInvalidImg.txt new file mode 100644 index 000000000..704ac56c8 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.RemoveInvalidImg.txt @@ -0,0 +1,12 @@ +Core.RemoveInvalidImg +TYPE: bool +DEFAULT: true +VERSION: 1.3.0 +--DESCRIPTION-- + +

      + This directive enables pre-emptive URI checking in img + tags, as the attribute validation strategy is not authorized to + remove elements from the document. Revert to pre-1.3.0 behavior by setting to false. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.RemoveProcessingInstructions.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.RemoveProcessingInstructions.txt new file mode 100644 index 000000000..ed6f13425 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.RemoveProcessingInstructions.txt @@ -0,0 +1,11 @@ +Core.RemoveProcessingInstructions +TYPE: bool +VERSION: 4.2.0 +DEFAULT: false +--DESCRIPTION-- +Instead of escaping processing instructions in the form <? ... +?>, remove it out-right. This may be useful if the HTML +you are validating contains XML processing instruction gunk, however, +it can also be user-unfriendly for people attempting to post PHP +snippets. +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.RemoveScriptContents.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.RemoveScriptContents.txt new file mode 100644 index 000000000..efbe994c2 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.RemoveScriptContents.txt @@ -0,0 +1,12 @@ +Core.RemoveScriptContents +TYPE: bool/null +DEFAULT: NULL +VERSION: 2.0.0 +DEPRECATED-VERSION: 2.1.0 +DEPRECATED-USE: Core.HiddenElements +--DESCRIPTION-- +

      + This directive enables HTML Purifier to remove not only script tags + but all of their contents. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Filter.Custom.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Filter.Custom.txt new file mode 100644 index 000000000..861ae66c3 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Filter.Custom.txt @@ -0,0 +1,11 @@ +Filter.Custom +TYPE: list +VERSION: 3.1.0 +DEFAULT: array() +--DESCRIPTION-- +

      + This directive can be used to add custom filters; it is nearly the + equivalent of the now deprecated HTMLPurifier->addFilter() + method. Specify an array of concrete implementations. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Escaping.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Escaping.txt new file mode 100644 index 000000000..69602635e --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Escaping.txt @@ -0,0 +1,14 @@ +Filter.ExtractStyleBlocks.Escaping +TYPE: bool +VERSION: 3.0.0 +DEFAULT: true +ALIASES: Filter.ExtractStyleBlocksEscaping, FilterParam.ExtractStyleBlocksEscaping +--DESCRIPTION-- + +

      + Whether or not to escape the dangerous characters <, > and & + as \3C, \3E and \26, respectively. This is can be safely set to false + if the contents of StyleBlocks will be placed in an external stylesheet, + where there is no risk of it being interpreted as HTML. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Scope.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Scope.txt new file mode 100644 index 000000000..baa81ae06 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Scope.txt @@ -0,0 +1,29 @@ +Filter.ExtractStyleBlocks.Scope +TYPE: string/null +VERSION: 3.0.0 +DEFAULT: NULL +ALIASES: Filter.ExtractStyleBlocksScope, FilterParam.ExtractStyleBlocksScope +--DESCRIPTION-- + +

      + If you would like users to be able to define external stylesheets, but + only allow them to specify CSS declarations for a specific node and + prevent them from fiddling with other elements, use this directive. + It accepts any valid CSS selector, and will prepend this to any + CSS declaration extracted from the document. For example, if this + directive is set to #user-content and a user uses the + selector a:hover, the final selector will be + #user-content a:hover. +

      +

      + The comma shorthand may be used; consider the above example, with + #user-content, #user-content2, the final selector will + be #user-content a:hover, #user-content2 a:hover. +

      +

      + Warning: It is possible for users to bypass this measure + using a naughty + selector. This is a bug in CSS Tidy 1.3, not HTML + Purifier, and I am working to get it fixed. Until then, HTML Purifier + performs a basic check to prevent this. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.TidyImpl.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.TidyImpl.txt new file mode 100644 index 000000000..3b7018917 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.TidyImpl.txt @@ -0,0 +1,16 @@ +Filter.ExtractStyleBlocks.TidyImpl +TYPE: mixed/null +VERSION: 3.1.0 +DEFAULT: NULL +ALIASES: FilterParam.ExtractStyleBlocksTidyImpl +--DESCRIPTION-- +

      + If left NULL, HTML Purifier will attempt to instantiate a csstidy + class to use for internal cleaning. This will usually be good enough. +

      +

      + However, for trusted user input, you can set this to false to + disable cleaning. In addition, you can supply your own concrete implementation + of Tidy's interface to use, although I don't know why you'd want to do that. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.txt new file mode 100644 index 000000000..be0177d4e --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.txt @@ -0,0 +1,74 @@ +Filter.ExtractStyleBlocks +TYPE: bool +VERSION: 3.1.0 +DEFAULT: false +EXTERNAL: CSSTidy +--DESCRIPTION-- +

      + This directive turns on the style block extraction filter, which removes + style blocks from input HTML, cleans them up with CSSTidy, + and places them in the StyleBlocks context variable, for further + use by you, usually to be placed in an external stylesheet, or a + style block in the head of your document. +

      +

      + Sample usage: +

      +
      ';
      +?>
      +
      +
      +
      +  Filter.ExtractStyleBlocks
      +body {color:#F00;} Some text';
      +
      +    $config = HTMLPurifier_Config::createDefault();
      +    $config->set('Filter', 'ExtractStyleBlocks', true);
      +    $purifier = new HTMLPurifier($config);
      +
      +    $html = $purifier->purify($dirty);
      +
      +    // This implementation writes the stylesheets to the styles/ directory.
      +    // You can also echo the styles inside the document, but it's a bit
      +    // more difficult to make sure they get interpreted properly by
      +    // browsers; try the usual CSS armoring techniques.
      +    $styles = $purifier->context->get('StyleBlocks');
      +    $dir = 'styles/';
      +    if (!is_dir($dir)) mkdir($dir);
      +    $hash = sha1($_GET['html']);
      +    foreach ($styles as $i => $style) {
      +        file_put_contents($name = $dir . $hash . "_$i");
      +        echo '';
      +    }
      +?>
      +
      +
      +  
      + +
      + + +]]>
      +

      + Warning: It is possible for a user to mount an + imagecrash attack using this CSS. Counter-measures are difficult; + it is not simply enough to limit the range of CSS lengths (using + relative lengths with many nesting levels allows for large values + to be attained without actually specifying them in the stylesheet), + and the flexible nature of selectors makes it difficult to selectively + disable lengths on image tags (HTML Purifier, however, does disable + CSS width and height in inline styling). There are probably two effective + counter measures: an explicit width and height set to auto in all + images in your document (unlikely) or the disabling of width and + height (somewhat reasonable). Whether or not these measures should be + used is left to the reader. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Filter.YouTube.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Filter.YouTube.txt new file mode 100644 index 000000000..882218668 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Filter.YouTube.txt @@ -0,0 +1,16 @@ +Filter.YouTube +TYPE: bool +VERSION: 3.1.0 +DEFAULT: false +--DESCRIPTION-- +

      + Warning: Deprecated in favor of %HTML.SafeObject and + %Output.FlashCompat (turn both on to allow YouTube videos and other + Flash content). +

      +

      + This directive enables YouTube video embedding in HTML Purifier. Check + this document + on embedding videos for more information on what this filter does. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Allowed.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Allowed.txt new file mode 100644 index 000000000..afd48a0d4 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Allowed.txt @@ -0,0 +1,25 @@ +HTML.Allowed +TYPE: itext/null +VERSION: 2.0.0 +DEFAULT: NULL +--DESCRIPTION-- + +

      + This is a preferred convenience directive that combines + %HTML.AllowedElements and %HTML.AllowedAttributes. + Specify elements and attributes that are allowed using: + element1[attr1|attr2],element2.... For example, + if you would like to only allow paragraphs and links, specify + a[href],p. You can specify attributes that apply + to all elements using an asterisk, e.g. *[lang]. + You can also use newlines instead of commas to separate elements. +

      +

      + Warning: + All of the constraints on the component directives are still enforced. + The syntax is a subset of TinyMCE's valid_elements + whitelist: directly copy-pasting it here will probably result in + broken whitelists. If %HTML.AllowedElements or %HTML.AllowedAttributes + are set, this directive has no effect. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.AllowedAttributes.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.AllowedAttributes.txt new file mode 100644 index 000000000..0e6ec54f3 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.AllowedAttributes.txt @@ -0,0 +1,19 @@ +HTML.AllowedAttributes +TYPE: lookup/null +VERSION: 1.3.0 +DEFAULT: NULL +--DESCRIPTION-- + +

      + If HTML Purifier's attribute set is unsatisfactory, overload it! + The syntax is "tag.attr" or "*.attr" for the global attributes + (style, id, class, dir, lang, xml:lang). +

      +

      + Warning: If another directive conflicts with the + elements here, that directive will win and override. For + example, %HTML.EnableAttrID will take precedence over *.id in this + directive. You must set that directive to true before you can use + IDs at all. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.AllowedComments.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.AllowedComments.txt new file mode 100644 index 000000000..8440bc39d --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.AllowedComments.txt @@ -0,0 +1,10 @@ +HTML.AllowedComments +TYPE: lookup +VERSION: 4.4.0 +DEFAULT: array() +--DESCRIPTION-- +A whitelist which indicates what explicit comment bodies should be +allowed, modulo leading and trailing whitespace. See also %HTML.AllowedCommentsRegexp +(these directives are union'ed together, so a comment is considered +valid if any directive deems it valid.) +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.AllowedCommentsRegexp.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.AllowedCommentsRegexp.txt new file mode 100644 index 000000000..b1e65beb1 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.AllowedCommentsRegexp.txt @@ -0,0 +1,15 @@ +HTML.AllowedCommentsRegexp +TYPE: string/null +VERSION: 4.4.0 +DEFAULT: NULL +--DESCRIPTION-- +A regexp, which if it matches the body of a comment, indicates that +it should be allowed. Trailing and leading spaces are removed prior +to running this regular expression. +Warning: Make sure you specify +correct anchor metacharacters ^regex$, otherwise you may accept +comments that you did not mean to! In particular, the regex /foo|bar/ +is probably not sufficiently strict, since it also allows foobar. +See also %HTML.AllowedComments (these directives are union'ed together, +so a comment is considered valid if any directive deems it valid.) +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.AllowedElements.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.AllowedElements.txt new file mode 100644 index 000000000..ca3c13ddb --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.AllowedElements.txt @@ -0,0 +1,23 @@ +HTML.AllowedElements +TYPE: lookup/null +VERSION: 1.3.0 +DEFAULT: NULL +--DESCRIPTION-- +

      + If HTML Purifier's tag set is unsatisfactory for your needs, you can + overload it with your own list of tags to allow. If you change + this, you probably also want to change %HTML.AllowedAttributes; see + also %HTML.Allowed which lets you set allowed elements and + attributes at the same time. +

      +

      + If you attempt to allow an element that HTML Purifier does not know + about, HTML Purifier will raise an error. You will need to manually + tell HTML Purifier about this element by using the + advanced customization features. +

      +

      + Warning: If another directive conflicts with the + elements here, that directive will win and override. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.AllowedModules.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.AllowedModules.txt new file mode 100644 index 000000000..e373791a5 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.AllowedModules.txt @@ -0,0 +1,20 @@ +HTML.AllowedModules +TYPE: lookup/null +VERSION: 2.0.0 +DEFAULT: NULL +--DESCRIPTION-- + +

      + A doctype comes with a set of usual modules to use. Without having + to mucking about with the doctypes, you can quickly activate or + disable these modules by specifying which modules you wish to allow + with this directive. This is most useful for unit testing specific + modules, although end users may find it useful for their own ends. +

      +

      + If you specify a module that does not exist, the manager will silently + fail to use it, so be careful! User-defined modules are not affected + by this directive. Modules defined in %HTML.CoreModules are not + affected by this directive. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Attr.Name.UseCDATA.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Attr.Name.UseCDATA.txt new file mode 100644 index 000000000..75d680ee1 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Attr.Name.UseCDATA.txt @@ -0,0 +1,11 @@ +HTML.Attr.Name.UseCDATA +TYPE: bool +DEFAULT: false +VERSION: 4.0.0 +--DESCRIPTION-- +The W3C specification DTD defines the name attribute to be CDATA, not ID, due +to limitations of DTD. In certain documents, this relaxed behavior is desired, +whether it is to specify duplicate names, or to specify names that would be +illegal IDs (for example, names that begin with a digit.) Set this configuration +directive to true to use the relaxed parsing rules. +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.BlockWrapper.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.BlockWrapper.txt new file mode 100644 index 000000000..f32b802c6 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.BlockWrapper.txt @@ -0,0 +1,18 @@ +HTML.BlockWrapper +TYPE: string +VERSION: 1.3.0 +DEFAULT: 'p' +--DESCRIPTION-- + +

      + String name of element to wrap inline elements that are inside a block + context. This only occurs in the children of blockquote in strict mode. +

      +

      + Example: by default value, + <blockquote>Foo</blockquote> would become + <blockquote><p>Foo</p></blockquote>. + The <p> tags can be replaced with whatever you desire, + as long as it is a block level element. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.CoreModules.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.CoreModules.txt new file mode 100644 index 000000000..fc8e40205 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.CoreModules.txt @@ -0,0 +1,23 @@ +HTML.CoreModules +TYPE: lookup +VERSION: 2.0.0 +--DEFAULT-- +array ( + 'Structure' => true, + 'Text' => true, + 'Hypertext' => true, + 'List' => true, + 'NonXMLCommonAttributes' => true, + 'XMLCommonAttributes' => true, + 'CommonAttributes' => true, +) +--DESCRIPTION-- + +

      + Certain modularized doctypes (XHTML, namely), have certain modules + that must be included for the doctype to be an conforming document + type: put those modules here. By default, XHTML's core modules + are used. You can set this to a blank array to disable core module + protection, but this is not recommended. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.CustomDoctype.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.CustomDoctype.txt new file mode 100644 index 000000000..187c0a0d5 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.CustomDoctype.txt @@ -0,0 +1,9 @@ +HTML.CustomDoctype +TYPE: string/null +VERSION: 2.0.1 +DEFAULT: NULL +--DESCRIPTION-- + +A custom doctype for power-users who defined their own document +type. This directive only applies when %HTML.Doctype is blank. +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionID.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionID.txt new file mode 100644 index 000000000..f5433e3f1 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionID.txt @@ -0,0 +1,33 @@ +HTML.DefinitionID +TYPE: string/null +DEFAULT: NULL +VERSION: 2.0.0 +--DESCRIPTION-- + +

      + Unique identifier for a custom-built HTML definition. If you edit + the raw version of the HTMLDefinition, introducing changes that the + configuration object does not reflect, you must specify this variable. + If you change your custom edits, you should change this directive, or + clear your cache. Example: +

      +
      +$config = HTMLPurifier_Config::createDefault();
      +$config->set('HTML', 'DefinitionID', '1');
      +$def = $config->getHTMLDefinition();
      +$def->addAttribute('a', 'tabindex', 'Number');
      +
      +

      + In the above example, the configuration is still at the defaults, but + using the advanced API, an extra attribute has been added. The + configuration object normally has no way of knowing that this change + has taken place, so it needs an extra directive: %HTML.DefinitionID. + If someone else attempts to use the default configuration, these two + pieces of code will not clobber each other in the cache, since one has + an extra directive attached to it. +

      +

      + You must specify a value to this directive to use the + advanced API features. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionRev.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionRev.txt new file mode 100644 index 000000000..0bb5a718d --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionRev.txt @@ -0,0 +1,16 @@ +HTML.DefinitionRev +TYPE: int +VERSION: 2.0.0 +DEFAULT: 1 +--DESCRIPTION-- + +

      + Revision identifier for your custom definition specified in + %HTML.DefinitionID. This serves the same purpose: uniquely identifying + your custom definition, but this one does so in a chronological + context: revision 3 is more up-to-date then revision 2. Thus, when + this gets incremented, the cache handling is smart enough to clean + up any older revisions of your definition as well as flush the + cache. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Doctype.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Doctype.txt new file mode 100644 index 000000000..a6969b995 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Doctype.txt @@ -0,0 +1,11 @@ +HTML.Doctype +TYPE: string/null +DEFAULT: NULL +--DESCRIPTION-- +Doctype to use during filtering. Technically speaking this is not actually +a doctype (as it does not identify a corresponding DTD), but we are using +this name for sake of simplicity. When non-blank, this will override any +older directives like %HTML.XHTML or %HTML.Strict. +--ALLOWED-- +'HTML 4.01 Transitional', 'HTML 4.01 Strict', 'XHTML 1.0 Transitional', 'XHTML 1.0 Strict', 'XHTML 1.1' +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.FlashAllowFullScreen.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.FlashAllowFullScreen.txt new file mode 100644 index 000000000..08d641f95 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.FlashAllowFullScreen.txt @@ -0,0 +1,11 @@ +HTML.FlashAllowFullScreen +TYPE: bool +VERSION: 4.2.0 +DEFAULT: false +--DESCRIPTION-- +

      + Whether or not to permit embedded Flash content from + %HTML.SafeObject to expand to the full screen. Corresponds to + the allowFullScreen parameter. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenAttributes.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenAttributes.txt new file mode 100644 index 000000000..2b8df97cb --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenAttributes.txt @@ -0,0 +1,21 @@ +HTML.ForbiddenAttributes +TYPE: lookup +VERSION: 3.1.0 +DEFAULT: array() +--DESCRIPTION-- +

      + While this directive is similar to %HTML.AllowedAttributes, for + forwards-compatibility with XML, this attribute has a different syntax. Instead of + tag.attr, use tag@attr. To disallow href + attributes in a tags, set this directive to + a@href. You can also disallow an attribute globally with + attr or *@attr (either syntax is fine; the latter + is provided for consistency with %HTML.AllowedAttributes). +

      +

      + Warning: This directive complements %HTML.ForbiddenElements, + accordingly, check + out that directive for a discussion of why you + should think twice before using this directive. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenElements.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenElements.txt new file mode 100644 index 000000000..40466c463 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenElements.txt @@ -0,0 +1,20 @@ +HTML.ForbiddenElements +TYPE: lookup +VERSION: 3.1.0 +DEFAULT: array() +--DESCRIPTION-- +

      + This was, perhaps, the most requested feature ever in HTML + Purifier. Please don't abuse it! This is the logical inverse of + %HTML.AllowedElements, and it will override that directive, or any + other directive. +

      +

      + If possible, %HTML.Allowed is recommended over this directive, because it + can sometimes be difficult to tell whether or not you've forbidden all of + the behavior you would like to disallow. If you forbid img + with the expectation of preventing images on your site, you'll be in for + a nasty surprise when people start using the background-image + CSS property. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.MaxImgLength.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.MaxImgLength.txt new file mode 100644 index 000000000..319747954 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.MaxImgLength.txt @@ -0,0 +1,14 @@ +HTML.MaxImgLength +TYPE: int/null +DEFAULT: 1200 +VERSION: 3.1.1 +--DESCRIPTION-- +

      + This directive controls the maximum number of pixels in the width and + height attributes in img tags. This is + in place to prevent imagecrash attacks, disable with null at your own risk. + This directive is similar to %CSS.MaxImgLength, and both should be + concurrently edited, although there are + subtle differences in the input format (the HTML max is an integer). +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Nofollow.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Nofollow.txt new file mode 100644 index 000000000..7aa356353 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Nofollow.txt @@ -0,0 +1,7 @@ +HTML.Nofollow +TYPE: bool +VERSION: 4.3.0 +DEFAULT: FALSE +--DESCRIPTION-- +If enabled, nofollow rel attributes are added to all outgoing links. +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Parent.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Parent.txt new file mode 100644 index 000000000..2d2fbd117 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Parent.txt @@ -0,0 +1,12 @@ +HTML.Parent +TYPE: string +VERSION: 1.3.0 +DEFAULT: 'div' +--DESCRIPTION-- + +

      + String name of element that HTML fragment passed to library will be + inserted in. An interesting variation would be using span as the + parent element, meaning that only inline tags would be allowed. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Proprietary.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Proprietary.txt new file mode 100644 index 000000000..b3c45e190 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Proprietary.txt @@ -0,0 +1,12 @@ +HTML.Proprietary +TYPE: bool +VERSION: 3.1.0 +DEFAULT: false +--DESCRIPTION-- +

      + Whether or not to allow proprietary elements and attributes in your + documents, as per HTMLPurifier_HTMLModule_Proprietary. + Warning: This can cause your documents to stop + validating! +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.SafeEmbed.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.SafeEmbed.txt new file mode 100644 index 000000000..556fa674f --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.SafeEmbed.txt @@ -0,0 +1,13 @@ +HTML.SafeEmbed +TYPE: bool +VERSION: 3.1.1 +DEFAULT: false +--DESCRIPTION-- +

      + Whether or not to permit embed tags in documents, with a number of extra + security features added to prevent script execution. This is similar to + what websites like MySpace do to embed tags. Embed is a proprietary + element and will cause your website to stop validating; you should + see if you can use %Output.FlashCompat with %HTML.SafeObject instead + first.

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.SafeIframe.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.SafeIframe.txt new file mode 100644 index 000000000..295a8cf66 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.SafeIframe.txt @@ -0,0 +1,13 @@ +HTML.SafeIframe +TYPE: bool +VERSION: 4.4.0 +DEFAULT: false +--DESCRIPTION-- +

      + Whether or not to permit iframe tags in untrusted documents. This + directive must be accompanied by a whitelist of permitted iframes, + such as %URI.SafeIframeRegexp, otherwise it will fatally error. + This directive has no effect on strict doctypes, as iframes are not + valid. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.SafeObject.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.SafeObject.txt new file mode 100644 index 000000000..07f6e536e --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.SafeObject.txt @@ -0,0 +1,13 @@ +HTML.SafeObject +TYPE: bool +VERSION: 3.1.1 +DEFAULT: false +--DESCRIPTION-- +

      + Whether or not to permit object tags in documents, with a number of extra + security features added to prevent script execution. This is similar to + what websites like MySpace do to object tags. You should also enable + %Output.FlashCompat in order to generate Internet Explorer + compatibility code for your object tags. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.SafeScripting.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.SafeScripting.txt new file mode 100644 index 000000000..641b4a8d6 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.SafeScripting.txt @@ -0,0 +1,10 @@ +HTML.SafeScripting +TYPE: lookup +VERSION: 4.5.0 +DEFAULT: array() +--DESCRIPTION-- +

      + Whether or not to permit script tags to external scripts in documents. + Inline scripting is not allowed, and the script must match an explicit whitelist. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Strict.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Strict.txt new file mode 100644 index 000000000..d99663a5e --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Strict.txt @@ -0,0 +1,9 @@ +HTML.Strict +TYPE: bool +VERSION: 1.3.0 +DEFAULT: false +DEPRECATED-VERSION: 1.7.0 +DEPRECATED-USE: HTML.Doctype +--DESCRIPTION-- +Determines whether or not to use Transitional (loose) or Strict rulesets. +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.TargetBlank.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.TargetBlank.txt new file mode 100644 index 000000000..d65f0d041 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.TargetBlank.txt @@ -0,0 +1,8 @@ +HTML.TargetBlank +TYPE: bool +VERSION: 4.4.0 +DEFAULT: FALSE +--DESCRIPTION-- +If enabled, target=blank attributes are added to all outgoing links. +(This includes links from an HTTPS version of a page to an HTTP version.) +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.TargetNoopener.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.TargetNoopener.txt new file mode 100644 index 000000000..05cb3424f --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.TargetNoopener.txt @@ -0,0 +1,10 @@ +--# vim: et sw=4 sts=4 +HTML.TargetNoopener +TYPE: bool +VERSION: 4.8.0 +DEFAULT: TRUE +--DESCRIPTION-- +If enabled, noopener rel attributes are added to links which have +a target attribute associated with them. This prevents malicious +destinations from overwriting the original window. +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.TargetNoreferrer.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.TargetNoreferrer.txt new file mode 100644 index 000000000..993a81704 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.TargetNoreferrer.txt @@ -0,0 +1,9 @@ +HTML.TargetNoreferrer +TYPE: bool +VERSION: 4.8.0 +DEFAULT: TRUE +--DESCRIPTION-- +If enabled, noreferrer rel attributes are added to links which have +a target attribute associated with them. This prevents malicious +destinations from overwriting the original window. +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.TidyAdd.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.TidyAdd.txt new file mode 100644 index 000000000..602453f6e --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.TidyAdd.txt @@ -0,0 +1,8 @@ +HTML.TidyAdd +TYPE: lookup +VERSION: 2.0.0 +DEFAULT: array() +--DESCRIPTION-- + +Fixes to add to the default set of Tidy fixes as per your level. +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.TidyLevel.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.TidyLevel.txt new file mode 100644 index 000000000..bf943e8f0 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.TidyLevel.txt @@ -0,0 +1,24 @@ +HTML.TidyLevel +TYPE: string +VERSION: 2.0.0 +DEFAULT: 'medium' +--DESCRIPTION-- + +

      General level of cleanliness the Tidy module should enforce. +There are four allowed values:

      +
      +
      none
      +
      No extra tidying should be done
      +
      light
      +
      Only fix elements that would be discarded otherwise due to + lack of support in doctype
      +
      medium
      +
      Enforce best practices
      +
      heavy
      +
      Transform all deprecated elements and attributes to standards + compliant equivalents
      +
      + +--ALLOWED-- +'none', 'light', 'medium', 'heavy' +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.TidyRemove.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.TidyRemove.txt new file mode 100644 index 000000000..92cca2a43 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.TidyRemove.txt @@ -0,0 +1,8 @@ +HTML.TidyRemove +TYPE: lookup +VERSION: 2.0.0 +DEFAULT: array() +--DESCRIPTION-- + +Fixes to remove from the default set of Tidy fixes as per your level. +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Trusted.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Trusted.txt new file mode 100644 index 000000000..bc8e65499 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Trusted.txt @@ -0,0 +1,9 @@ +HTML.Trusted +TYPE: bool +VERSION: 2.0.0 +DEFAULT: false +--DESCRIPTION-- +Indicates whether or not the user input is trusted or not. If the input is +trusted, a more expansive set of allowed tags and attributes will be used. +See also %CSS.Trusted. +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.XHTML.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.XHTML.txt new file mode 100644 index 000000000..a3c2f42c3 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.XHTML.txt @@ -0,0 +1,11 @@ +HTML.XHTML +TYPE: bool +DEFAULT: true +VERSION: 1.1.0 +DEPRECATED-VERSION: 1.7.0 +DEPRECATED-USE: HTML.Doctype +--DESCRIPTION-- +Determines whether or not output is XHTML 1.0 or HTML 4.01 flavor. +--ALIASES-- +Core.XHTML +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Output.CommentScriptContents.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Output.CommentScriptContents.txt new file mode 100644 index 000000000..2a1370470 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Output.CommentScriptContents.txt @@ -0,0 +1,10 @@ +Output.CommentScriptContents +TYPE: bool +VERSION: 2.0.0 +DEFAULT: true +--DESCRIPTION-- +Determines whether or not HTML Purifier should attempt to fix up the +contents of script tags for legacy browsers with comments. +--ALIASES-- +Core.CommentScriptContents +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Output.FixInnerHTML.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Output.FixInnerHTML.txt new file mode 100644 index 000000000..d215ba2d3 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Output.FixInnerHTML.txt @@ -0,0 +1,15 @@ +Output.FixInnerHTML +TYPE: bool +VERSION: 4.3.0 +DEFAULT: true +--DESCRIPTION-- +

      + If true, HTML Purifier will protect against Internet Explorer's + mishandling of the innerHTML attribute by appending + a space to any attribute that does not contain angled brackets, spaces + or quotes, but contains a backtick. This slightly changes the + semantics of any given attribute, so if this is unacceptable and + you do not use innerHTML on any of your pages, you can + turn this directive off. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Output.FlashCompat.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Output.FlashCompat.txt new file mode 100644 index 000000000..e58f91aa8 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Output.FlashCompat.txt @@ -0,0 +1,11 @@ +Output.FlashCompat +TYPE: bool +VERSION: 4.1.0 +DEFAULT: false +--DESCRIPTION-- +

      + If true, HTML Purifier will generate Internet Explorer compatibility + code for all object code. This is highly recommended if you enable + %HTML.SafeObject. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Output.Newline.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Output.Newline.txt new file mode 100644 index 000000000..4bb902523 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Output.Newline.txt @@ -0,0 +1,13 @@ +Output.Newline +TYPE: string/null +VERSION: 2.0.1 +DEFAULT: NULL +--DESCRIPTION-- + +

      + Newline string to format final output with. If left null, HTML Purifier + will auto-detect the default newline type of the system and use that; + you can manually override it here. Remember, \r\n is Windows, \r + is Mac, and \n is Unix. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Output.SortAttr.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Output.SortAttr.txt new file mode 100644 index 000000000..322310651 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Output.SortAttr.txt @@ -0,0 +1,14 @@ +Output.SortAttr +TYPE: bool +VERSION: 3.2.0 +DEFAULT: false +--DESCRIPTION-- +

      + If true, HTML Purifier will sort attributes by name before writing them back + to the document, converting a tag like: <el b="" a="" c="" /> + to <el a="" b="" c="" />. This is a workaround for + a bug in FCKeditor which causes it to swap attributes order, adding noise + to text diffs. If you're not seeing this bug, chances are, you don't need + this directive. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Output.TidyFormat.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Output.TidyFormat.txt new file mode 100644 index 000000000..23dd4d3d5 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Output.TidyFormat.txt @@ -0,0 +1,25 @@ +Output.TidyFormat +TYPE: bool +VERSION: 1.1.1 +DEFAULT: false +--DESCRIPTION-- +

      + Determines whether or not to run Tidy on the final output for pretty + formatting reasons, such as indentation and wrap. +

      +

      + This can greatly improve readability for editors who are hand-editing + the HTML, but is by no means necessary as HTML Purifier has already + fixed all major errors the HTML may have had. Tidy is a non-default + extension, and this directive will silently fail if Tidy is not + available. +

      +

      + If you are looking to make the overall look of your page's source + better, I recommend running Tidy on the entire page rather than just + user-content (after all, the indentation relative to the containing + blocks will be incorrect). +

      +--ALIASES-- +Core.TidyFormat +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Test.ForceNoIconv.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Test.ForceNoIconv.txt new file mode 100644 index 000000000..d1820cdbd --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/Test.ForceNoIconv.txt @@ -0,0 +1,7 @@ +Test.ForceNoIconv +TYPE: bool +DEFAULT: false +--DESCRIPTION-- +When set to true, HTMLPurifier_Encoder will act as if iconv does not exist +and use only pure PHP implementations. +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.AllowedSchemes.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.AllowedSchemes.txt new file mode 100644 index 000000000..0b0533a77 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.AllowedSchemes.txt @@ -0,0 +1,18 @@ +URI.AllowedSchemes +TYPE: lookup +--DEFAULT-- +array ( + 'http' => true, + 'https' => true, + 'mailto' => true, + 'ftp' => true, + 'nntp' => true, + 'news' => true, + 'tel' => true, +) +--DESCRIPTION-- +Whitelist that defines the schemes that a URI is allowed to have. This +prevents XSS attacks from using pseudo-schemes like javascript or mocha. +There is also support for the data and file +URI schemes, but they are not enabled by default. +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.Base.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.Base.txt new file mode 100644 index 000000000..ba4730808 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.Base.txt @@ -0,0 +1,17 @@ +URI.Base +TYPE: string/null +VERSION: 2.1.0 +DEFAULT: NULL +--DESCRIPTION-- + +

      + The base URI is the URI of the document this purified HTML will be + inserted into. This information is important if HTML Purifier needs + to calculate absolute URIs from relative URIs, such as when %URI.MakeAbsolute + is on. You may use a non-absolute URI for this value, but behavior + may vary (%URI.MakeAbsolute deals nicely with both absolute and + relative paths, but forwards-compatibility is not guaranteed). + Warning: If set, the scheme on this URI + overrides the one specified by %URI.DefaultScheme. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.DefaultScheme.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.DefaultScheme.txt new file mode 100644 index 000000000..981e44325 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.DefaultScheme.txt @@ -0,0 +1,15 @@ +URI.DefaultScheme +TYPE: string/null +DEFAULT: 'http' +--DESCRIPTION-- + +

      + Defines through what scheme the output will be served, in order to + select the proper object validator when no scheme information is present. +

      + +

      + Starting with HTML Purifier 4.9.0, the default scheme can be null, in + which case we reject all URIs which do not have explicit schemes. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.DefinitionID.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.DefinitionID.txt new file mode 100644 index 000000000..523204c08 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.DefinitionID.txt @@ -0,0 +1,11 @@ +URI.DefinitionID +TYPE: string/null +VERSION: 2.1.0 +DEFAULT: NULL +--DESCRIPTION-- + +

      + Unique identifier for a custom-built URI definition. If you want + to add custom URIFilters, you must specify this value. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.DefinitionRev.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.DefinitionRev.txt new file mode 100644 index 000000000..a9c07b1a3 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.DefinitionRev.txt @@ -0,0 +1,11 @@ +URI.DefinitionRev +TYPE: int +VERSION: 2.1.0 +DEFAULT: 1 +--DESCRIPTION-- + +

      + Revision identifier for your custom definition. See + %HTML.DefinitionRev for details. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.Disable.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.Disable.txt new file mode 100644 index 000000000..b19ca1d5b --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.Disable.txt @@ -0,0 +1,14 @@ +URI.Disable +TYPE: bool +VERSION: 1.3.0 +DEFAULT: false +--DESCRIPTION-- + +

      + Disables all URIs in all forms. Not sure why you'd want to do that + (after all, the Internet's founded on the notion of a hyperlink). +

      + +--ALIASES-- +Attr.DisableURI +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.DisableExternal.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.DisableExternal.txt new file mode 100644 index 000000000..9132ea4f5 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.DisableExternal.txt @@ -0,0 +1,11 @@ +URI.DisableExternal +TYPE: bool +VERSION: 1.2.0 +DEFAULT: false +--DESCRIPTION-- +Disables links to external websites. This is a highly effective anti-spam +and anti-pagerank-leech measure, but comes at a hefty price: nolinks or +images outside of your domain will be allowed. Non-linkified URIs will +still be preserved. If you want to be able to link to subdomains or use +absolute URIs, specify %URI.Host for your website. +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.DisableExternalResources.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.DisableExternalResources.txt new file mode 100644 index 000000000..d74bc1e3d --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.DisableExternalResources.txt @@ -0,0 +1,13 @@ +URI.DisableExternalResources +TYPE: bool +VERSION: 1.3.0 +DEFAULT: false +--DESCRIPTION-- +Disables the embedding of external resources, preventing users from +embedding things like images from other hosts. This prevents access +tracking (good for email viewers), bandwidth leeching, cross-site request +forging, goatse.cx posting, and other nasties, but also results in a loss +of end-user functionality (they can't directly post a pic they posted from +Flickr anymore). Use it if you don't have a robust user-content moderation +team. +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.DisableResources.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.DisableResources.txt new file mode 100644 index 000000000..6c106144a --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.DisableResources.txt @@ -0,0 +1,15 @@ +URI.DisableResources +TYPE: bool +VERSION: 4.2.0 +DEFAULT: false +--DESCRIPTION-- +

      + Disables embedding resources, essentially meaning no pictures. You can + still link to them though. See %URI.DisableExternalResources for why + this might be a good idea. +

      +

      + Note: While this directive has been available since 1.3.0, + it didn't actually start doing anything until 4.2.0. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.Host.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.Host.txt new file mode 100644 index 000000000..ba0e6bce1 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.Host.txt @@ -0,0 +1,19 @@ +URI.Host +TYPE: string/null +VERSION: 1.2.0 +DEFAULT: NULL +--DESCRIPTION-- + +

      + Defines the domain name of the server, so we can determine whether or + an absolute URI is from your website or not. Not strictly necessary, + as users should be using relative URIs to reference resources on your + website. It will, however, let you use absolute URIs to link to + subdomains of the domain you post here: i.e. example.com will allow + sub.example.com. However, higher up domains will still be excluded: + if you set %URI.Host to sub.example.com, example.com will be blocked. + Note: This directive overrides %URI.Base because + a given page may be on a sub-domain, but you wish HTML Purifier to be + more relaxed and allow some of the parent domains too. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.HostBlacklist.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.HostBlacklist.txt new file mode 100644 index 000000000..825fef276 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.HostBlacklist.txt @@ -0,0 +1,9 @@ +URI.HostBlacklist +TYPE: list +VERSION: 1.3.0 +DEFAULT: array() +--DESCRIPTION-- +List of strings that are forbidden in the host of any URI. Use it to kill +domain names of spam, etc. Note that it will catch anything in the domain, +so moo.com will catch moo.com.example.com. +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.MakeAbsolute.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.MakeAbsolute.txt new file mode 100644 index 000000000..eb58c7f1a --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.MakeAbsolute.txt @@ -0,0 +1,13 @@ +URI.MakeAbsolute +TYPE: bool +VERSION: 2.1.0 +DEFAULT: false +--DESCRIPTION-- + +

      + Converts all URIs into absolute forms. This is useful when the HTML + being filtered assumes a specific base path, but will actually be + viewed in a different context (and setting an alternate base URI is + not possible). %URI.Base must be set for this directive to work. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.Munge.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.Munge.txt new file mode 100644 index 000000000..bedd610d6 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.Munge.txt @@ -0,0 +1,83 @@ +URI.Munge +TYPE: string/null +VERSION: 1.3.0 +DEFAULT: NULL +--DESCRIPTION-- + +

      + Munges all browsable (usually http, https and ftp) + absolute URIs into another URI, usually a URI redirection service. + This directive accepts a URI, formatted with a %s where + the url-encoded original URI should be inserted (sample: + http://www.google.com/url?q=%s). +

      +

      + Uses for this directive: +

      +
        +
      • + Prevent PageRank leaks, while being fairly transparent + to users (you may also want to add some client side JavaScript to + override the text in the statusbar). Notice: + Many security experts believe that this form of protection does not deter spam-bots. +
      • +
      • + Redirect users to a splash page telling them they are leaving your + website. While this is poor usability practice, it is often mandated + in corporate environments. +
      • +
      +

      + Prior to HTML Purifier 3.1.1, this directive also enabled the munging + of browsable external resources, which could break things if your redirection + script was a splash page or used meta tags. To revert to + previous behavior, please use %URI.MungeResources. +

      +

      + You may want to also use %URI.MungeSecretKey along with this directive + in order to enforce what URIs your redirector script allows. Open + redirector scripts can be a security risk and negatively affect the + reputation of your domain name. +

      +

      + Starting with HTML Purifier 3.1.1, there is also these substitutions: +

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      KeyDescriptionExample <a href="">
      %r1 - The URI embeds a resource
      (blank) - The URI is merely a link
      %nThe name of the tag this URI came froma
      %mThe name of the attribute this URI came fromhref
      %pThe name of the CSS property this URI came from, or blank if irrelevant
      +

      + Admittedly, these letters are somewhat arbitrary; the only stipulation + was that they couldn't be a through f. r is for resource (I would have preferred + e, but you take what you can get), n is for name, m + was picked because it came after n (and I couldn't use a), p is for + property. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.MungeResources.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.MungeResources.txt new file mode 100644 index 000000000..ed4b5b0d0 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.MungeResources.txt @@ -0,0 +1,17 @@ +URI.MungeResources +TYPE: bool +VERSION: 3.1.1 +DEFAULT: false +--DESCRIPTION-- +

      + If true, any URI munging directives like %URI.Munge + will also apply to embedded resources, such as <img src="">. + Be careful enabling this directive if you have a redirector script + that does not use the Location HTTP header; all of your images + and other embedded resources will break. +

      +

      + Warning: It is strongly advised you use this in conjunction + %URI.MungeSecretKey to mitigate the security risk of an open redirector. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.MungeSecretKey.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.MungeSecretKey.txt new file mode 100644 index 000000000..123b6e26b --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.MungeSecretKey.txt @@ -0,0 +1,30 @@ +URI.MungeSecretKey +TYPE: string/null +VERSION: 3.1.1 +DEFAULT: NULL +--DESCRIPTION-- +

      + This directive enables secure checksum generation along with %URI.Munge. + It should be set to a secure key that is not shared with anyone else. + The checksum can be placed in the URI using %t. Use of this checksum + affords an additional level of protection by allowing a redirector + to check if a URI has passed through HTML Purifier with this line: +

      + +
      $checksum === hash_hmac("sha256", $url, $secret_key)
      + +

      + If the output is TRUE, the redirector script should accept the URI. +

      + +

      + Please note that it would still be possible for an attacker to procure + secure hashes en-mass by abusing your website's Preview feature or the + like, but this service affords an additional level of protection + that should be combined with website blacklisting. +

      + +

      + Remember this has no effect if %URI.Munge is not on. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.OverrideAllowedSchemes.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.OverrideAllowedSchemes.txt new file mode 100644 index 000000000..8b387dea3 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.OverrideAllowedSchemes.txt @@ -0,0 +1,9 @@ +URI.OverrideAllowedSchemes +TYPE: bool +DEFAULT: true +--DESCRIPTION-- +If this is set to true (which it is by default), you can override +%URI.AllowedSchemes by simply registering a HTMLPurifier_URIScheme to the +registry. If false, you will also have to update that directive in order +to add more schemes. +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.SafeIframeRegexp.txt b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.SafeIframeRegexp.txt new file mode 100644 index 000000000..7e1f227f7 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.SafeIframeRegexp.txt @@ -0,0 +1,22 @@ +URI.SafeIframeRegexp +TYPE: string/null +VERSION: 4.4.0 +DEFAULT: NULL +--DESCRIPTION-- +

      + A PCRE regular expression that will be matched against an iframe URI. This is + a relatively inflexible scheme, but works well enough for the most common + use-case of iframes: embedded video. This directive only has an effect if + %HTML.SafeIframe is enabled. Here are some example values: +

      +
        +
      • %^http://www.youtube.com/embed/% - Allow YouTube videos
      • +
      • %^http://player.vimeo.com/video/% - Allow Vimeo videos
      • +
      • %^http://(www.youtube.com/embed/|player.vimeo.com/video/)% - Allow both
      • +
      +

      + Note that this directive does not give you enough granularity to, say, disable + all autoplay videos. Pipe up on the HTML Purifier forums if this + is a capability you want. +

      +--# vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/info.ini b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/info.ini new file mode 100644 index 000000000..58e0ce4a1 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/ConfigSchema/schema/info.ini @@ -0,0 +1,3 @@ +name = "HTML Purifier" + +; vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/DefinitionCache/Serializer/CSS/4.12.0,4b8f5cc147226128f20b34fd242f9a542da33f33,1.ser b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/DefinitionCache/Serializer/CSS/4.12.0,4b8f5cc147226128f20b34fd242f9a542da33f33,1.ser new file mode 100644 index 000000000..0836afd47 Binary files /dev/null and b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/DefinitionCache/Serializer/CSS/4.12.0,4b8f5cc147226128f20b34fd242f9a542da33f33,1.ser differ diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/DefinitionCache/Serializer/CSS/4.12.0,6ed3cddb0362fa608490c69ad17343eaa304f453,1.ser b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/DefinitionCache/Serializer/CSS/4.12.0,6ed3cddb0362fa608490c69ad17343eaa304f453,1.ser new file mode 100644 index 000000000..0836afd47 Binary files /dev/null and b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/DefinitionCache/Serializer/CSS/4.12.0,6ed3cddb0362fa608490c69ad17343eaa304f453,1.ser differ diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/DefinitionCache/Serializer/HTML/4.12.0,132a9da9acd828683e3c11d28bd6cd64933d3dbf,1.ser b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/DefinitionCache/Serializer/HTML/4.12.0,132a9da9acd828683e3c11d28bd6cd64933d3dbf,1.ser new file mode 100644 index 000000000..4a9d21668 Binary files /dev/null and b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/DefinitionCache/Serializer/HTML/4.12.0,132a9da9acd828683e3c11d28bd6cd64933d3dbf,1.ser differ diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/DefinitionCache/Serializer/HTML/4.12.0,42f82c1f7be4e237b50e9858ba91a70760101129,1.ser b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/DefinitionCache/Serializer/HTML/4.12.0,42f82c1f7be4e237b50e9858ba91a70760101129,1.ser new file mode 100644 index 000000000..cf86fd2b8 Binary files /dev/null and b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/DefinitionCache/Serializer/HTML/4.12.0,42f82c1f7be4e237b50e9858ba91a70760101129,1.ser differ diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/DefinitionCache/Serializer/HTML/4.12.0,ba93667159f850128d1932f0a5f765e2c28f98ae,1.ser b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/DefinitionCache/Serializer/HTML/4.12.0,ba93667159f850128d1932f0a5f765e2c28f98ae,1.ser new file mode 100644 index 000000000..e5684d332 Binary files /dev/null and b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/DefinitionCache/Serializer/HTML/4.12.0,ba93667159f850128d1932f0a5f765e2c28f98ae,1.ser differ diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/DefinitionCache/Serializer/HTML/4.12.0,eda857aeb62b2207fc39136e0afaf0748e4f216a,1.ser b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/DefinitionCache/Serializer/HTML/4.12.0,eda857aeb62b2207fc39136e0afaf0748e4f216a,1.ser new file mode 100644 index 000000000..4a9d21668 Binary files /dev/null and b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/DefinitionCache/Serializer/HTML/4.12.0,eda857aeb62b2207fc39136e0afaf0748e4f216a,1.ser differ diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/DefinitionCache/Serializer/URI/4.12.0,07a4c403f3e74330856e8db3dd118b76ec956423,1.ser b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/DefinitionCache/Serializer/URI/4.12.0,07a4c403f3e74330856e8db3dd118b76ec956423,1.ser new file mode 100644 index 000000000..80fd339ea Binary files /dev/null and b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/DefinitionCache/Serializer/URI/4.12.0,07a4c403f3e74330856e8db3dd118b76ec956423,1.ser differ diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/DefinitionCache/Serializer/URI/4.12.0,242e5b4ad9f5a3333a7afd41cf153f66abfeaa22,1.ser b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/DefinitionCache/Serializer/URI/4.12.0,242e5b4ad9f5a3333a7afd41cf153f66abfeaa22,1.ser new file mode 100644 index 000000000..9ba827b48 Binary files /dev/null and b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/DefinitionCache/Serializer/URI/4.12.0,242e5b4ad9f5a3333a7afd41cf153f66abfeaa22,1.ser differ diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/DefinitionCache/Serializer/URI/4.12.0,6d6b73aefd22cf9037df9ec5e34d57a99550cf76,1.ser b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/DefinitionCache/Serializer/URI/4.12.0,6d6b73aefd22cf9037df9ec5e34d57a99550cf76,1.ser new file mode 100644 index 000000000..fe0a13768 Binary files /dev/null and b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/DefinitionCache/Serializer/URI/4.12.0,6d6b73aefd22cf9037df9ec5e34d57a99550cf76,1.ser differ diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/DefinitionCache/Serializer/URI/4.12.0,8d5a26a338f4dfbe1d8343ddfba9deb0d5db1c06,1.ser b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/DefinitionCache/Serializer/URI/4.12.0,8d5a26a338f4dfbe1d8343ddfba9deb0d5db1c06,1.ser new file mode 100644 index 000000000..cf79fb662 Binary files /dev/null and b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/DefinitionCache/Serializer/URI/4.12.0,8d5a26a338f4dfbe1d8343ddfba9deb0d5db1c06,1.ser differ diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/DefinitionCache/Serializer/URI/4.12.0,9c975d9086b015823892fc44045f9525fcc18f23,1.ser b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/DefinitionCache/Serializer/URI/4.12.0,9c975d9086b015823892fc44045f9525fcc18f23,1.ser new file mode 100644 index 000000000..1c73f2cae Binary files /dev/null and b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/DefinitionCache/Serializer/URI/4.12.0,9c975d9086b015823892fc44045f9525fcc18f23,1.ser differ diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/DefinitionCache/Serializer/URI/4.12.0,aae5ce54208e28babe6b782e03fd81cab218908f,1.ser b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/DefinitionCache/Serializer/URI/4.12.0,aae5ce54208e28babe6b782e03fd81cab218908f,1.ser new file mode 100644 index 000000000..3fe6cff8e Binary files /dev/null and b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/DefinitionCache/Serializer/URI/4.12.0,aae5ce54208e28babe6b782e03fd81cab218908f,1.ser differ diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/DefinitionCache/Serializer/URI/4.12.0,c6a98b86066527170724e608b56306afd394a821,1.ser b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/DefinitionCache/Serializer/URI/4.12.0,c6a98b86066527170724e608b56306afd394a821,1.ser new file mode 100644 index 000000000..0adc6a6dd Binary files /dev/null and b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/DefinitionCache/Serializer/URI/4.12.0,c6a98b86066527170724e608b56306afd394a821,1.ser differ diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/EntityLookup/entities.ser b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/EntityLookup/entities.ser new file mode 100644 index 000000000..e8b08128b --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/EntityLookup/entities.ser @@ -0,0 +1 @@ +a:253:{s:4:"fnof";s:2:"ƒ";s:5:"Alpha";s:2:"Α";s:4:"Beta";s:2:"Β";s:5:"Gamma";s:2:"Γ";s:5:"Delta";s:2:"Δ";s:7:"Epsilon";s:2:"Ε";s:4:"Zeta";s:2:"Ζ";s:3:"Eta";s:2:"Η";s:5:"Theta";s:2:"Θ";s:4:"Iota";s:2:"Ι";s:5:"Kappa";s:2:"Κ";s:6:"Lambda";s:2:"Λ";s:2:"Mu";s:2:"Μ";s:2:"Nu";s:2:"Ν";s:2:"Xi";s:2:"Ξ";s:7:"Omicron";s:2:"Ο";s:2:"Pi";s:2:"Π";s:3:"Rho";s:2:"Ρ";s:5:"Sigma";s:2:"Σ";s:3:"Tau";s:2:"Τ";s:7:"Upsilon";s:2:"Υ";s:3:"Phi";s:2:"Φ";s:3:"Chi";s:2:"Χ";s:3:"Psi";s:2:"Ψ";s:5:"Omega";s:2:"Ω";s:5:"alpha";s:2:"α";s:4:"beta";s:2:"β";s:5:"gamma";s:2:"γ";s:5:"delta";s:2:"δ";s:7:"epsilon";s:2:"ε";s:4:"zeta";s:2:"ζ";s:3:"eta";s:2:"η";s:5:"theta";s:2:"θ";s:4:"iota";s:2:"ι";s:5:"kappa";s:2:"κ";s:6:"lambda";s:2:"λ";s:2:"mu";s:2:"μ";s:2:"nu";s:2:"ν";s:2:"xi";s:2:"ξ";s:7:"omicron";s:2:"ο";s:2:"pi";s:2:"π";s:3:"rho";s:2:"ρ";s:6:"sigmaf";s:2:"ς";s:5:"sigma";s:2:"σ";s:3:"tau";s:2:"τ";s:7:"upsilon";s:2:"υ";s:3:"phi";s:2:"φ";s:3:"chi";s:2:"χ";s:3:"psi";s:2:"ψ";s:5:"omega";s:2:"ω";s:8:"thetasym";s:2:"ϑ";s:5:"upsih";s:2:"ϒ";s:3:"piv";s:2:"ϖ";s:4:"bull";s:3:"•";s:6:"hellip";s:3:"…";s:5:"prime";s:3:"′";s:5:"Prime";s:3:"″";s:5:"oline";s:3:"‾";s:5:"frasl";s:3:"⁄";s:6:"weierp";s:3:"℘";s:5:"image";s:3:"ℑ";s:4:"real";s:3:"ℜ";s:5:"trade";s:3:"™";s:7:"alefsym";s:3:"ℵ";s:4:"larr";s:3:"←";s:4:"uarr";s:3:"↑";s:4:"rarr";s:3:"→";s:4:"darr";s:3:"↓";s:4:"harr";s:3:"↔";s:5:"crarr";s:3:"↵";s:4:"lArr";s:3:"⇐";s:4:"uArr";s:3:"⇑";s:4:"rArr";s:3:"⇒";s:4:"dArr";s:3:"⇓";s:4:"hArr";s:3:"⇔";s:6:"forall";s:3:"∀";s:4:"part";s:3:"∂";s:5:"exist";s:3:"∃";s:5:"empty";s:3:"∅";s:5:"nabla";s:3:"∇";s:4:"isin";s:3:"∈";s:5:"notin";s:3:"∉";s:2:"ni";s:3:"∋";s:4:"prod";s:3:"∏";s:3:"sum";s:3:"∑";s:5:"minus";s:3:"−";s:6:"lowast";s:3:"∗";s:5:"radic";s:3:"√";s:4:"prop";s:3:"∝";s:5:"infin";s:3:"∞";s:3:"ang";s:3:"∠";s:3:"and";s:3:"∧";s:2:"or";s:3:"∨";s:3:"cap";s:3:"∩";s:3:"cup";s:3:"∪";s:3:"int";s:3:"∫";s:6:"there4";s:3:"∴";s:3:"sim";s:3:"∼";s:4:"cong";s:3:"≅";s:5:"asymp";s:3:"≈";s:2:"ne";s:3:"≠";s:5:"equiv";s:3:"≡";s:2:"le";s:3:"≤";s:2:"ge";s:3:"≥";s:3:"sub";s:3:"⊂";s:3:"sup";s:3:"⊃";s:4:"nsub";s:3:"⊄";s:4:"sube";s:3:"⊆";s:4:"supe";s:3:"⊇";s:5:"oplus";s:3:"⊕";s:6:"otimes";s:3:"⊗";s:4:"perp";s:3:"⊥";s:4:"sdot";s:3:"⋅";s:5:"lceil";s:3:"⌈";s:5:"rceil";s:3:"⌉";s:6:"lfloor";s:3:"⌊";s:6:"rfloor";s:3:"⌋";s:4:"lang";s:3:"〈";s:4:"rang";s:3:"〉";s:3:"loz";s:3:"◊";s:6:"spades";s:3:"♠";s:5:"clubs";s:3:"♣";s:6:"hearts";s:3:"♥";s:5:"diams";s:3:"♦";s:4:"quot";s:1:""";s:3:"amp";s:1:"&";s:2:"lt";s:1:"<";s:2:"gt";s:1:">";s:4:"apos";s:1:"'";s:5:"OElig";s:2:"Œ";s:5:"oelig";s:2:"œ";s:6:"Scaron";s:2:"Š";s:6:"scaron";s:2:"š";s:4:"Yuml";s:2:"Ÿ";s:4:"circ";s:2:"ˆ";s:5:"tilde";s:2:"˜";s:4:"ensp";s:3:" ";s:4:"emsp";s:3:" ";s:6:"thinsp";s:3:" ";s:4:"zwnj";s:3:"‌";s:3:"zwj";s:3:"‍";s:3:"lrm";s:3:"‎";s:3:"rlm";s:3:"‏";s:5:"ndash";s:3:"–";s:5:"mdash";s:3:"—";s:5:"lsquo";s:3:"‘";s:5:"rsquo";s:3:"’";s:5:"sbquo";s:3:"‚";s:5:"ldquo";s:3:"“";s:5:"rdquo";s:3:"”";s:5:"bdquo";s:3:"„";s:6:"dagger";s:3:"†";s:6:"Dagger";s:3:"‡";s:6:"permil";s:3:"‰";s:6:"lsaquo";s:3:"‹";s:6:"rsaquo";s:3:"›";s:4:"euro";s:3:"€";s:4:"nbsp";s:2:" ";s:5:"iexcl";s:2:"¡";s:4:"cent";s:2:"¢";s:5:"pound";s:2:"£";s:6:"curren";s:2:"¤";s:3:"yen";s:2:"¥";s:6:"brvbar";s:2:"¦";s:4:"sect";s:2:"§";s:3:"uml";s:2:"¨";s:4:"copy";s:2:"©";s:4:"ordf";s:2:"ª";s:5:"laquo";s:2:"«";s:3:"not";s:2:"¬";s:3:"shy";s:2:"­";s:3:"reg";s:2:"®";s:4:"macr";s:2:"¯";s:3:"deg";s:2:"°";s:6:"plusmn";s:2:"±";s:4:"sup2";s:2:"²";s:4:"sup3";s:2:"³";s:5:"acute";s:2:"´";s:5:"micro";s:2:"µ";s:4:"para";s:2:"¶";s:6:"middot";s:2:"·";s:5:"cedil";s:2:"¸";s:4:"sup1";s:2:"¹";s:4:"ordm";s:2:"º";s:5:"raquo";s:2:"»";s:6:"frac14";s:2:"¼";s:6:"frac12";s:2:"½";s:6:"frac34";s:2:"¾";s:6:"iquest";s:2:"¿";s:6:"Agrave";s:2:"À";s:6:"Aacute";s:2:"Á";s:5:"Acirc";s:2:"Â";s:6:"Atilde";s:2:"Ã";s:4:"Auml";s:2:"Ä";s:5:"Aring";s:2:"Å";s:5:"AElig";s:2:"Æ";s:6:"Ccedil";s:2:"Ç";s:6:"Egrave";s:2:"È";s:6:"Eacute";s:2:"É";s:5:"Ecirc";s:2:"Ê";s:4:"Euml";s:2:"Ë";s:6:"Igrave";s:2:"Ì";s:6:"Iacute";s:2:"Í";s:5:"Icirc";s:2:"Î";s:4:"Iuml";s:2:"Ï";s:3:"ETH";s:2:"Ð";s:6:"Ntilde";s:2:"Ñ";s:6:"Ograve";s:2:"Ò";s:6:"Oacute";s:2:"Ó";s:5:"Ocirc";s:2:"Ô";s:6:"Otilde";s:2:"Õ";s:4:"Ouml";s:2:"Ö";s:5:"times";s:2:"×";s:6:"Oslash";s:2:"Ø";s:6:"Ugrave";s:2:"Ù";s:6:"Uacute";s:2:"Ú";s:5:"Ucirc";s:2:"Û";s:4:"Uuml";s:2:"Ü";s:6:"Yacute";s:2:"Ý";s:5:"THORN";s:2:"Þ";s:5:"szlig";s:2:"ß";s:6:"agrave";s:2:"à";s:6:"aacute";s:2:"á";s:5:"acirc";s:2:"â";s:6:"atilde";s:2:"ã";s:4:"auml";s:2:"ä";s:5:"aring";s:2:"å";s:5:"aelig";s:2:"æ";s:6:"ccedil";s:2:"ç";s:6:"egrave";s:2:"è";s:6:"eacute";s:2:"é";s:5:"ecirc";s:2:"ê";s:4:"euml";s:2:"ë";s:6:"igrave";s:2:"ì";s:6:"iacute";s:2:"í";s:5:"icirc";s:2:"î";s:4:"iuml";s:2:"ï";s:3:"eth";s:2:"ð";s:6:"ntilde";s:2:"ñ";s:6:"ograve";s:2:"ò";s:6:"oacute";s:2:"ó";s:5:"ocirc";s:2:"ô";s:6:"otilde";s:2:"õ";s:4:"ouml";s:2:"ö";s:6:"divide";s:2:"÷";s:6:"oslash";s:2:"ø";s:6:"ugrave";s:2:"ù";s:6:"uacute";s:2:"ú";s:5:"ucirc";s:2:"û";s:4:"uuml";s:2:"ü";s:6:"yacute";s:2:"ý";s:5:"thorn";s:2:"þ";s:4:"yuml";s:2:"ÿ";} \ No newline at end of file diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/Filter/ExtractStyleBlocks.php b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/Filter/ExtractStyleBlocks.php new file mode 100644 index 000000000..5af24c202 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/Filter/ExtractStyleBlocks.php @@ -0,0 +1,341 @@ + blocks from input HTML, cleans them up + * using CSSTidy, and then places them in $purifier->context->get('StyleBlocks') + * so they can be used elsewhere in the document. + * + * @note + * See tests/HTMLPurifier/Filter/ExtractStyleBlocksTest.php for + * sample usage. + * + * @note + * This filter can also be used on stylesheets not included in the + * document--something purists would probably prefer. Just directly + * call HTMLPurifier_Filter_ExtractStyleBlocks->cleanCSS() + */ +class HTMLPurifier_Filter_ExtractStyleBlocks extends HTMLPurifier_Filter +{ + /** + * @type string + */ + public $name = 'ExtractStyleBlocks'; + + /** + * @type array + */ + private $_styleMatches = array(); + + /** + * @type csstidy + */ + private $_tidy; + + /** + * @type HTMLPurifier_AttrDef_HTML_ID + */ + private $_id_attrdef; + + /** + * @type HTMLPurifier_AttrDef_CSS_Ident + */ + private $_class_attrdef; + + /** + * @type HTMLPurifier_AttrDef_Enum + */ + private $_enum_attrdef; + + public function __construct() + { + $this->_tidy = new csstidy(); + $this->_tidy->set_cfg('lowercase_s', false); + $this->_id_attrdef = new HTMLPurifier_AttrDef_HTML_ID(true); + $this->_class_attrdef = new HTMLPurifier_AttrDef_CSS_Ident(); + $this->_enum_attrdef = new HTMLPurifier_AttrDef_Enum( + array( + 'first-child', + 'link', + 'visited', + 'active', + 'hover', + 'focus' + ) + ); + } + + /** + * Save the contents of CSS blocks to style matches + * @param array $matches preg_replace style $matches array + */ + protected function styleCallback($matches) + { + $this->_styleMatches[] = $matches[1]; + } + + /** + * Removes inline + // we must not grab foo in a font-family prop). + if ($config->get('Filter.ExtractStyleBlocks.Escaping')) { + $css = str_replace( + array('<', '>', '&'), + array('\3C ', '\3E ', '\26 '), + $css + ); + } + return $css; + } +} + +// vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/Filter/YouTube.php b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/Filter/YouTube.php new file mode 100644 index 000000000..b90ddf751 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/Filter/YouTube.php @@ -0,0 +1,65 @@ +]+>.+?' . + '(?:http:)?//www.youtube.com/((?:v|cp)/[A-Za-z0-9\-_=]+).+?#s'; + $pre_replace = '\1'; + return preg_replace($pre_regex, $pre_replace, $html); + } + + /** + * @param string $html + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return string + */ + public function postFilter($html, $config, $context) + { + $post_regex = '#((?:v|cp)/[A-Za-z0-9\-_=]+)#'; + return preg_replace_callback($post_regex, array($this, 'postFilterCallback'), $html); + } + + /** + * @param $url + * @return string + */ + protected function armorUrl($url) + { + return str_replace('--', '--', $url); + } + + /** + * @param array $matches + * @return string + */ + protected function postFilterCallback($matches) + { + $url = $this->armorUrl($matches[1]); + return '' . + '' . + '' . + ''; + } +} + +// vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/Language/classes/en-x-test.php b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/Language/classes/en-x-test.php new file mode 100644 index 000000000..a6f8d1634 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/Language/classes/en-x-test.php @@ -0,0 +1,9 @@ + 'HTML Purifier X' +); + +$errorNames = array(); + +// vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/Language/messages/en-x-testmini.php b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/Language/messages/en-x-testmini.php new file mode 100644 index 000000000..b6d1c99ea --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/Language/messages/en-x-testmini.php @@ -0,0 +1,14 @@ + 'HTML Purifier XNone' +); + +$errorNames = array(); + +// vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/Language/messages/en.php b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/Language/messages/en.php new file mode 100644 index 000000000..1fa30bdfe --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/Language/messages/en.php @@ -0,0 +1,55 @@ + 'HTML Purifier', +// for unit testing purposes + 'LanguageFactoryTest: Pizza' => 'Pizza', + 'LanguageTest: List' => '$1', + 'LanguageTest: Hash' => '$1.Keys; $1.Values', + 'Item separator' => ', ', + 'Item separator last' => ' and ', // non-Harvard style + + 'ErrorCollector: No errors' => 'No errors detected. However, because error reporting is still incomplete, there may have been errors that the error collector was not notified of; please inspect the output HTML carefully.', + 'ErrorCollector: At line' => ' at line $line', + 'ErrorCollector: Incidental errors' => 'Incidental errors', + 'Lexer: Unclosed comment' => 'Unclosed comment', + 'Lexer: Unescaped lt' => 'Unescaped less-than sign (<) should be <', + 'Lexer: Missing gt' => 'Missing greater-than sign (>), previous less-than sign (<) should be escaped', + 'Lexer: Missing attribute key' => 'Attribute declaration has no key', + 'Lexer: Missing end quote' => 'Attribute declaration has no end quote', + 'Lexer: Extracted body' => 'Removed document metadata tags', + 'Strategy_RemoveForeignElements: Tag transform' => '<$1> element transformed into $CurrentToken.Serialized', + 'Strategy_RemoveForeignElements: Missing required attribute' => '$CurrentToken.Compact element missing required attribute $1', + 'Strategy_RemoveForeignElements: Foreign element to text' => 'Unrecognized $CurrentToken.Serialized tag converted to text', + 'Strategy_RemoveForeignElements: Foreign element removed' => 'Unrecognized $CurrentToken.Serialized tag removed', + 'Strategy_RemoveForeignElements: Comment removed' => 'Comment containing "$CurrentToken.Data" removed', + 'Strategy_RemoveForeignElements: Foreign meta element removed' => 'Unrecognized $CurrentToken.Serialized meta tag and all descendants removed', + 'Strategy_RemoveForeignElements: Token removed to end' => 'Tags and text starting from $1 element where removed to end', + 'Strategy_RemoveForeignElements: Trailing hyphen in comment removed' => 'Trailing hyphen(s) in comment removed', + 'Strategy_RemoveForeignElements: Hyphens in comment collapsed' => 'Double hyphens in comments are not allowed, and were collapsed into single hyphens', + 'Strategy_MakeWellFormed: Unnecessary end tag removed' => 'Unnecessary $CurrentToken.Serialized tag removed', + 'Strategy_MakeWellFormed: Unnecessary end tag to text' => 'Unnecessary $CurrentToken.Serialized tag converted to text', + 'Strategy_MakeWellFormed: Tag auto closed' => '$1.Compact started on line $1.Line auto-closed by $CurrentToken.Compact', + 'Strategy_MakeWellFormed: Tag carryover' => '$1.Compact started on line $1.Line auto-continued into $CurrentToken.Compact', + 'Strategy_MakeWellFormed: Stray end tag removed' => 'Stray $CurrentToken.Serialized tag removed', + 'Strategy_MakeWellFormed: Stray end tag to text' => 'Stray $CurrentToken.Serialized tag converted to text', + 'Strategy_MakeWellFormed: Tag closed by element end' => '$1.Compact tag started on line $1.Line closed by end of $CurrentToken.Serialized', + 'Strategy_MakeWellFormed: Tag closed by document end' => '$1.Compact tag started on line $1.Line closed by end of document', + 'Strategy_FixNesting: Node removed' => '$CurrentToken.Compact node removed', + 'Strategy_FixNesting: Node excluded' => '$CurrentToken.Compact node removed due to descendant exclusion by ancestor element', + 'Strategy_FixNesting: Node reorganized' => 'Contents of $CurrentToken.Compact node reorganized to enforce its content model', + 'Strategy_FixNesting: Node contents removed' => 'Contents of $CurrentToken.Compact node removed', + 'AttrValidator: Attributes transformed' => 'Attributes on $CurrentToken.Compact transformed from $1.Keys to $2.Keys', + 'AttrValidator: Attribute removed' => '$CurrentAttr.Name attribute on $CurrentToken.Compact removed', +); + +$errorNames = array( + E_ERROR => 'Error', + E_WARNING => 'Warning', + E_NOTICE => 'Notice' +); + +// vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/Lexer/PH5P.php b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/Lexer/PH5P.php new file mode 100644 index 000000000..6b281a542 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/Lexer/PH5P.php @@ -0,0 +1,4788 @@ +normalize($html, $config, $context); + $new_html = $this->wrapHTML($new_html, $config, $context, false /* no div */); + try { + $parser = new HTML5($new_html); + $doc = $parser->save(); + } catch (DOMException $e) { + // Uh oh, it failed. Punt to DirectLex. + $lexer = new HTMLPurifier_Lexer_DirectLex(); + $context->register('PH5PError', $e); // save the error, so we can detect it + return $lexer->tokenizeHTML($html, $config, $context); // use original HTML + } + $tokens = array(); + $this->tokenizeDOM( + $doc->getElementsByTagName('html')->item(0)-> // + getElementsByTagName('body')->item(0) // + , + $tokens, $config + ); + return $tokens; + } +} + +/* + +Copyright 2007 Jeroen van der Meer + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ + +class HTML5 +{ + private $data; + private $char; + private $EOF; + private $state; + private $tree; + private $token; + private $content_model; + private $escape = false; + private $entities = array( + 'AElig;', + 'AElig', + 'AMP;', + 'AMP', + 'Aacute;', + 'Aacute', + 'Acirc;', + 'Acirc', + 'Agrave;', + 'Agrave', + 'Alpha;', + 'Aring;', + 'Aring', + 'Atilde;', + 'Atilde', + 'Auml;', + 'Auml', + 'Beta;', + 'COPY;', + 'COPY', + 'Ccedil;', + 'Ccedil', + 'Chi;', + 'Dagger;', + 'Delta;', + 'ETH;', + 'ETH', + 'Eacute;', + 'Eacute', + 'Ecirc;', + 'Ecirc', + 'Egrave;', + 'Egrave', + 'Epsilon;', + 'Eta;', + 'Euml;', + 'Euml', + 'GT;', + 'GT', + 'Gamma;', + 'Iacute;', + 'Iacute', + 'Icirc;', + 'Icirc', + 'Igrave;', + 'Igrave', + 'Iota;', + 'Iuml;', + 'Iuml', + 'Kappa;', + 'LT;', + 'LT', + 'Lambda;', + 'Mu;', + 'Ntilde;', + 'Ntilde', + 'Nu;', + 'OElig;', + 'Oacute;', + 'Oacute', + 'Ocirc;', + 'Ocirc', + 'Ograve;', + 'Ograve', + 'Omega;', + 'Omicron;', + 'Oslash;', + 'Oslash', + 'Otilde;', + 'Otilde', + 'Ouml;', + 'Ouml', + 'Phi;', + 'Pi;', + 'Prime;', + 'Psi;', + 'QUOT;', + 'QUOT', + 'REG;', + 'REG', + 'Rho;', + 'Scaron;', + 'Sigma;', + 'THORN;', + 'THORN', + 'TRADE;', + 'Tau;', + 'Theta;', + 'Uacute;', + 'Uacute', + 'Ucirc;', + 'Ucirc', + 'Ugrave;', + 'Ugrave', + 'Upsilon;', + 'Uuml;', + 'Uuml', + 'Xi;', + 'Yacute;', + 'Yacute', + 'Yuml;', + 'Zeta;', + 'aacute;', + 'aacute', + 'acirc;', + 'acirc', + 'acute;', + 'acute', + 'aelig;', + 'aelig', + 'agrave;', + 'agrave', + 'alefsym;', + 'alpha;', + 'amp;', + 'amp', + 'and;', + 'ang;', + 'apos;', + 'aring;', + 'aring', + 'asymp;', + 'atilde;', + 'atilde', + 'auml;', + 'auml', + 'bdquo;', + 'beta;', + 'brvbar;', + 'brvbar', + 'bull;', + 'cap;', + 'ccedil;', + 'ccedil', + 'cedil;', + 'cedil', + 'cent;', + 'cent', + 'chi;', + 'circ;', + 'clubs;', + 'cong;', + 'copy;', + 'copy', + 'crarr;', + 'cup;', + 'curren;', + 'curren', + 'dArr;', + 'dagger;', + 'darr;', + 'deg;', + 'deg', + 'delta;', + 'diams;', + 'divide;', + 'divide', + 'eacute;', + 'eacute', + 'ecirc;', + 'ecirc', + 'egrave;', + 'egrave', + 'empty;', + 'emsp;', + 'ensp;', + 'epsilon;', + 'equiv;', + 'eta;', + 'eth;', + 'eth', + 'euml;', + 'euml', + 'euro;', + 'exist;', + 'fnof;', + 'forall;', + 'frac12;', + 'frac12', + 'frac14;', + 'frac14', + 'frac34;', + 'frac34', + 'frasl;', + 'gamma;', + 'ge;', + 'gt;', + 'gt', + 'hArr;', + 'harr;', + 'hearts;', + 'hellip;', + 'iacute;', + 'iacute', + 'icirc;', + 'icirc', + 'iexcl;', + 'iexcl', + 'igrave;', + 'igrave', + 'image;', + 'infin;', + 'int;', + 'iota;', + 'iquest;', + 'iquest', + 'isin;', + 'iuml;', + 'iuml', + 'kappa;', + 'lArr;', + 'lambda;', + 'lang;', + 'laquo;', + 'laquo', + 'larr;', + 'lceil;', + 'ldquo;', + 'le;', + 'lfloor;', + 'lowast;', + 'loz;', + 'lrm;', + 'lsaquo;', + 'lsquo;', + 'lt;', + 'lt', + 'macr;', + 'macr', + 'mdash;', + 'micro;', + 'micro', + 'middot;', + 'middot', + 'minus;', + 'mu;', + 'nabla;', + 'nbsp;', + 'nbsp', + 'ndash;', + 'ne;', + 'ni;', + 'not;', + 'not', + 'notin;', + 'nsub;', + 'ntilde;', + 'ntilde', + 'nu;', + 'oacute;', + 'oacute', + 'ocirc;', + 'ocirc', + 'oelig;', + 'ograve;', + 'ograve', + 'oline;', + 'omega;', + 'omicron;', + 'oplus;', + 'or;', + 'ordf;', + 'ordf', + 'ordm;', + 'ordm', + 'oslash;', + 'oslash', + 'otilde;', + 'otilde', + 'otimes;', + 'ouml;', + 'ouml', + 'para;', + 'para', + 'part;', + 'permil;', + 'perp;', + 'phi;', + 'pi;', + 'piv;', + 'plusmn;', + 'plusmn', + 'pound;', + 'pound', + 'prime;', + 'prod;', + 'prop;', + 'psi;', + 'quot;', + 'quot', + 'rArr;', + 'radic;', + 'rang;', + 'raquo;', + 'raquo', + 'rarr;', + 'rceil;', + 'rdquo;', + 'real;', + 'reg;', + 'reg', + 'rfloor;', + 'rho;', + 'rlm;', + 'rsaquo;', + 'rsquo;', + 'sbquo;', + 'scaron;', + 'sdot;', + 'sect;', + 'sect', + 'shy;', + 'shy', + 'sigma;', + 'sigmaf;', + 'sim;', + 'spades;', + 'sub;', + 'sube;', + 'sum;', + 'sup1;', + 'sup1', + 'sup2;', + 'sup2', + 'sup3;', + 'sup3', + 'sup;', + 'supe;', + 'szlig;', + 'szlig', + 'tau;', + 'there4;', + 'theta;', + 'thetasym;', + 'thinsp;', + 'thorn;', + 'thorn', + 'tilde;', + 'times;', + 'times', + 'trade;', + 'uArr;', + 'uacute;', + 'uacute', + 'uarr;', + 'ucirc;', + 'ucirc', + 'ugrave;', + 'ugrave', + 'uml;', + 'uml', + 'upsih;', + 'upsilon;', + 'uuml;', + 'uuml', + 'weierp;', + 'xi;', + 'yacute;', + 'yacute', + 'yen;', + 'yen', + 'yuml;', + 'yuml', + 'zeta;', + 'zwj;', + 'zwnj;' + ); + + const PCDATA = 0; + const RCDATA = 1; + const CDATA = 2; + const PLAINTEXT = 3; + + const DOCTYPE = 0; + const STARTTAG = 1; + const ENDTAG = 2; + const COMMENT = 3; + const CHARACTR = 4; + const EOF = 5; + + public function __construct($data) + { + $this->data = $data; + $this->char = -1; + $this->EOF = strlen($data); + $this->tree = new HTML5TreeConstructer; + $this->content_model = self::PCDATA; + + $this->state = 'data'; + + while ($this->state !== null) { + $this->{$this->state . 'State'}(); + } + } + + public function save() + { + return $this->tree->save(); + } + + private function char() + { + return ($this->char < $this->EOF) + ? $this->data[$this->char] + : false; + } + + private function character($s, $l = 0) + { + if ($s + $l < $this->EOF) { + if ($l === 0) { + return $this->data[$s]; + } else { + return substr($this->data, $s, $l); + } + } + } + + private function characters($char_class, $start) + { + return preg_replace('#^([' . $char_class . ']+).*#s', '\\1', substr($this->data, $start)); + } + + private function dataState() + { + // Consume the next input character + $this->char++; + $char = $this->char(); + + if ($char === '&' && ($this->content_model === self::PCDATA || $this->content_model === self::RCDATA)) { + /* U+0026 AMPERSAND (&) + When the content model flag is set to one of the PCDATA or RCDATA + states: switch to the entity data state. Otherwise: treat it as per + the "anything else" entry below. */ + $this->state = 'entityData'; + + } elseif ($char === '-') { + /* If the content model flag is set to either the RCDATA state or + the CDATA state, and the escape flag is false, and there are at + least three characters before this one in the input stream, and the + last four characters in the input stream, including this one, are + U+003C LESS-THAN SIGN, U+0021 EXCLAMATION MARK, U+002D HYPHEN-MINUS, + and U+002D HYPHEN-MINUS (""), + set the escape flag to false. */ + if (($this->content_model === self::RCDATA || + $this->content_model === self::CDATA) && $this->escape === true && + $this->character($this->char, 3) === '-->' + ) { + $this->escape = false; + } + + /* In any case, emit the input character as a character token. + Stay in the data state. */ + $this->emitToken( + array( + 'type' => self::CHARACTR, + 'data' => $char + ) + ); + + } elseif ($this->char === $this->EOF) { + /* EOF + Emit an end-of-file token. */ + $this->EOF(); + + } elseif ($this->content_model === self::PLAINTEXT) { + /* When the content model flag is set to the PLAINTEXT state + THIS DIFFERS GREATLY FROM THE SPEC: Get the remaining characters of + the text and emit it as a character token. */ + $this->emitToken( + array( + 'type' => self::CHARACTR, + 'data' => substr($this->data, $this->char) + ) + ); + + $this->EOF(); + + } else { + /* Anything else + THIS DIFFERS GREATLY FROM THE SPEC: Get as many character that + otherwise would also be treated as a character token and emit it + as a single character token. Stay in the data state. */ + $len = strcspn($this->data, '<&', $this->char); + $char = substr($this->data, $this->char, $len); + $this->char += $len - 1; + + $this->emitToken( + array( + 'type' => self::CHARACTR, + 'data' => $char + ) + ); + + $this->state = 'data'; + } + } + + private function entityDataState() + { + // Attempt to consume an entity. + $entity = $this->entity(); + + // If nothing is returned, emit a U+0026 AMPERSAND character token. + // Otherwise, emit the character token that was returned. + $char = (!$entity) ? '&' : $entity; + $this->emitToken( + array( + 'type' => self::CHARACTR, + 'data' => $char + ) + ); + + // Finally, switch to the data state. + $this->state = 'data'; + } + + private function tagOpenState() + { + switch ($this->content_model) { + case self::RCDATA: + case self::CDATA: + /* If the next input character is a U+002F SOLIDUS (/) character, + consume it and switch to the close tag open state. If the next + input character is not a U+002F SOLIDUS (/) character, emit a + U+003C LESS-THAN SIGN character token and switch to the data + state to process the next input character. */ + if ($this->character($this->char + 1) === '/') { + $this->char++; + $this->state = 'closeTagOpen'; + + } else { + $this->emitToken( + array( + 'type' => self::CHARACTR, + 'data' => '<' + ) + ); + + $this->state = 'data'; + } + break; + + case self::PCDATA: + // If the content model flag is set to the PCDATA state + // Consume the next input character: + $this->char++; + $char = $this->char(); + + if ($char === '!') { + /* U+0021 EXCLAMATION MARK (!) + Switch to the markup declaration open state. */ + $this->state = 'markupDeclarationOpen'; + + } elseif ($char === '/') { + /* U+002F SOLIDUS (/) + Switch to the close tag open state. */ + $this->state = 'closeTagOpen'; + + } elseif (preg_match('/^[A-Za-z]$/', $char)) { + /* U+0041 LATIN LETTER A through to U+005A LATIN LETTER Z + Create a new start tag token, set its tag name to the lowercase + version of the input character (add 0x0020 to the character's code + point), then switch to the tag name state. (Don't emit the token + yet; further details will be filled in before it is emitted.) */ + $this->token = array( + 'name' => strtolower($char), + 'type' => self::STARTTAG, + 'attr' => array() + ); + + $this->state = 'tagName'; + + } elseif ($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Parse error. Emit a U+003C LESS-THAN SIGN character token and a + U+003E GREATER-THAN SIGN character token. Switch to the data state. */ + $this->emitToken( + array( + 'type' => self::CHARACTR, + 'data' => '<>' + ) + ); + + $this->state = 'data'; + + } elseif ($char === '?') { + /* U+003F QUESTION MARK (?) + Parse error. Switch to the bogus comment state. */ + $this->state = 'bogusComment'; + + } else { + /* Anything else + Parse error. Emit a U+003C LESS-THAN SIGN character token and + reconsume the current input character in the data state. */ + $this->emitToken( + array( + 'type' => self::CHARACTR, + 'data' => '<' + ) + ); + + $this->char--; + $this->state = 'data'; + } + break; + } + } + + private function closeTagOpenState() + { + $next_node = strtolower($this->characters('A-Za-z', $this->char + 1)); + $the_same = count($this->tree->stack) > 0 && $next_node === end($this->tree->stack)->nodeName; + + if (($this->content_model === self::RCDATA || $this->content_model === self::CDATA) && + (!$the_same || ($the_same && (!preg_match( + '/[\t\n\x0b\x0c >\/]/', + $this->character($this->char + 1 + strlen($next_node)) + ) || $this->EOF === $this->char))) + ) { + /* If the content model flag is set to the RCDATA or CDATA states then + examine the next few characters. If they do not match the tag name of + the last start tag token emitted (case insensitively), or if they do but + they are not immediately followed by one of the following characters: + * U+0009 CHARACTER TABULATION + * U+000A LINE FEED (LF) + * U+000B LINE TABULATION + * U+000C FORM FEED (FF) + * U+0020 SPACE + * U+003E GREATER-THAN SIGN (>) + * U+002F SOLIDUS (/) + * EOF + ...then there is a parse error. Emit a U+003C LESS-THAN SIGN character + token, a U+002F SOLIDUS character token, and switch to the data state + to process the next input character. */ + $this->emitToken( + array( + 'type' => self::CHARACTR, + 'data' => 'state = 'data'; + + } else { + /* Otherwise, if the content model flag is set to the PCDATA state, + or if the next few characters do match that tag name, consume the + next input character: */ + $this->char++; + $char = $this->char(); + + if (preg_match('/^[A-Za-z]$/', $char)) { + /* U+0041 LATIN LETTER A through to U+005A LATIN LETTER Z + Create a new end tag token, set its tag name to the lowercase version + of the input character (add 0x0020 to the character's code point), then + switch to the tag name state. (Don't emit the token yet; further details + will be filled in before it is emitted.) */ + $this->token = array( + 'name' => strtolower($char), + 'type' => self::ENDTAG + ); + + $this->state = 'tagName'; + + } elseif ($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Parse error. Switch to the data state. */ + $this->state = 'data'; + + } elseif ($this->char === $this->EOF) { + /* EOF + Parse error. Emit a U+003C LESS-THAN SIGN character token and a U+002F + SOLIDUS character token. Reconsume the EOF character in the data state. */ + $this->emitToken( + array( + 'type' => self::CHARACTR, + 'data' => 'char--; + $this->state = 'data'; + + } else { + /* Parse error. Switch to the bogus comment state. */ + $this->state = 'bogusComment'; + } + } + } + + private function tagNameState() + { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + /* U+0009 CHARACTER TABULATION + U+000A LINE FEED (LF) + U+000B LINE TABULATION + U+000C FORM FEED (FF) + U+0020 SPACE + Switch to the before attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif ($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Emit the current tag token. Switch to the data state. */ + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif ($this->char === $this->EOF) { + /* EOF + Parse error. Emit the current tag token. Reconsume the EOF + character in the data state. */ + $this->emitToken($this->token); + + $this->char--; + $this->state = 'data'; + + } elseif ($char === '/') { + /* U+002F SOLIDUS (/) + Parse error unless this is a permitted slash. Switch to the before + attribute name state. */ + $this->state = 'beforeAttributeName'; + + } else { + /* Anything else + Append the current input character to the current tag token's tag name. + Stay in the tag name state. */ + $this->token['name'] .= strtolower($char); + $this->state = 'tagName'; + } + } + + private function beforeAttributeNameState() + { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + /* U+0009 CHARACTER TABULATION + U+000A LINE FEED (LF) + U+000B LINE TABULATION + U+000C FORM FEED (FF) + U+0020 SPACE + Stay in the before attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif ($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Emit the current tag token. Switch to the data state. */ + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif ($char === '/') { + /* U+002F SOLIDUS (/) + Parse error unless this is a permitted slash. Stay in the before + attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif ($this->char === $this->EOF) { + /* EOF + Parse error. Emit the current tag token. Reconsume the EOF + character in the data state. */ + $this->emitToken($this->token); + + $this->char--; + $this->state = 'data'; + + } else { + /* Anything else + Start a new attribute in the current tag token. Set that attribute's + name to the current input character, and its value to the empty string. + Switch to the attribute name state. */ + $this->token['attr'][] = array( + 'name' => strtolower($char), + 'value' => null + ); + + $this->state = 'attributeName'; + } + } + + private function attributeNameState() + { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + /* U+0009 CHARACTER TABULATION + U+000A LINE FEED (LF) + U+000B LINE TABULATION + U+000C FORM FEED (FF) + U+0020 SPACE + Stay in the before attribute name state. */ + $this->state = 'afterAttributeName'; + + } elseif ($char === '=') { + /* U+003D EQUALS SIGN (=) + Switch to the before attribute value state. */ + $this->state = 'beforeAttributeValue'; + + } elseif ($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Emit the current tag token. Switch to the data state. */ + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif ($char === '/' && $this->character($this->char + 1) !== '>') { + /* U+002F SOLIDUS (/) + Parse error unless this is a permitted slash. Switch to the before + attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif ($this->char === $this->EOF) { + /* EOF + Parse error. Emit the current tag token. Reconsume the EOF + character in the data state. */ + $this->emitToken($this->token); + + $this->char--; + $this->state = 'data'; + + } else { + /* Anything else + Append the current input character to the current attribute's name. + Stay in the attribute name state. */ + $last = count($this->token['attr']) - 1; + $this->token['attr'][$last]['name'] .= strtolower($char); + + $this->state = 'attributeName'; + } + } + + private function afterAttributeNameState() + { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + /* U+0009 CHARACTER TABULATION + U+000A LINE FEED (LF) + U+000B LINE TABULATION + U+000C FORM FEED (FF) + U+0020 SPACE + Stay in the after attribute name state. */ + $this->state = 'afterAttributeName'; + + } elseif ($char === '=') { + /* U+003D EQUALS SIGN (=) + Switch to the before attribute value state. */ + $this->state = 'beforeAttributeValue'; + + } elseif ($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Emit the current tag token. Switch to the data state. */ + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif ($char === '/' && $this->character($this->char + 1) !== '>') { + /* U+002F SOLIDUS (/) + Parse error unless this is a permitted slash. Switch to the + before attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif ($this->char === $this->EOF) { + /* EOF + Parse error. Emit the current tag token. Reconsume the EOF + character in the data state. */ + $this->emitToken($this->token); + + $this->char--; + $this->state = 'data'; + + } else { + /* Anything else + Start a new attribute in the current tag token. Set that attribute's + name to the current input character, and its value to the empty string. + Switch to the attribute name state. */ + $this->token['attr'][] = array( + 'name' => strtolower($char), + 'value' => null + ); + + $this->state = 'attributeName'; + } + } + + private function beforeAttributeValueState() + { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + /* U+0009 CHARACTER TABULATION + U+000A LINE FEED (LF) + U+000B LINE TABULATION + U+000C FORM FEED (FF) + U+0020 SPACE + Stay in the before attribute value state. */ + $this->state = 'beforeAttributeValue'; + + } elseif ($char === '"') { + /* U+0022 QUOTATION MARK (") + Switch to the attribute value (double-quoted) state. */ + $this->state = 'attributeValueDoubleQuoted'; + + } elseif ($char === '&') { + /* U+0026 AMPERSAND (&) + Switch to the attribute value (unquoted) state and reconsume + this input character. */ + $this->char--; + $this->state = 'attributeValueUnquoted'; + + } elseif ($char === '\'') { + /* U+0027 APOSTROPHE (') + Switch to the attribute value (single-quoted) state. */ + $this->state = 'attributeValueSingleQuoted'; + + } elseif ($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Emit the current tag token. Switch to the data state. */ + $this->emitToken($this->token); + $this->state = 'data'; + + } else { + /* Anything else + Append the current input character to the current attribute's value. + Switch to the attribute value (unquoted) state. */ + $last = count($this->token['attr']) - 1; + $this->token['attr'][$last]['value'] .= $char; + + $this->state = 'attributeValueUnquoted'; + } + } + + private function attributeValueDoubleQuotedState() + { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if ($char === '"') { + /* U+0022 QUOTATION MARK (") + Switch to the before attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif ($char === '&') { + /* U+0026 AMPERSAND (&) + Switch to the entity in attribute value state. */ + $this->entityInAttributeValueState('double'); + + } elseif ($this->char === $this->EOF) { + /* EOF + Parse error. Emit the current tag token. Reconsume the character + in the data state. */ + $this->emitToken($this->token); + + $this->char--; + $this->state = 'data'; + + } else { + /* Anything else + Append the current input character to the current attribute's value. + Stay in the attribute value (double-quoted) state. */ + $last = count($this->token['attr']) - 1; + $this->token['attr'][$last]['value'] .= $char; + + $this->state = 'attributeValueDoubleQuoted'; + } + } + + private function attributeValueSingleQuotedState() + { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if ($char === '\'') { + /* U+0022 QUOTATION MARK (') + Switch to the before attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif ($char === '&') { + /* U+0026 AMPERSAND (&) + Switch to the entity in attribute value state. */ + $this->entityInAttributeValueState('single'); + + } elseif ($this->char === $this->EOF) { + /* EOF + Parse error. Emit the current tag token. Reconsume the character + in the data state. */ + $this->emitToken($this->token); + + $this->char--; + $this->state = 'data'; + + } else { + /* Anything else + Append the current input character to the current attribute's value. + Stay in the attribute value (single-quoted) state. */ + $last = count($this->token['attr']) - 1; + $this->token['attr'][$last]['value'] .= $char; + + $this->state = 'attributeValueSingleQuoted'; + } + } + + private function attributeValueUnquotedState() + { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + /* U+0009 CHARACTER TABULATION + U+000A LINE FEED (LF) + U+000B LINE TABULATION + U+000C FORM FEED (FF) + U+0020 SPACE + Switch to the before attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif ($char === '&') { + /* U+0026 AMPERSAND (&) + Switch to the entity in attribute value state. */ + $this->entityInAttributeValueState(); + + } elseif ($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Emit the current tag token. Switch to the data state. */ + $this->emitToken($this->token); + $this->state = 'data'; + + } else { + /* Anything else + Append the current input character to the current attribute's value. + Stay in the attribute value (unquoted) state. */ + $last = count($this->token['attr']) - 1; + $this->token['attr'][$last]['value'] .= $char; + + $this->state = 'attributeValueUnquoted'; + } + } + + private function entityInAttributeValueState() + { + // Attempt to consume an entity. + $entity = $this->entity(); + + // If nothing is returned, append a U+0026 AMPERSAND character to the + // current attribute's value. Otherwise, emit the character token that + // was returned. + $char = (!$entity) + ? '&' + : $entity; + + $last = count($this->token['attr']) - 1; + $this->token['attr'][$last]['value'] .= $char; + } + + private function bogusCommentState() + { + /* Consume every character up to the first U+003E GREATER-THAN SIGN + character (>) or the end of the file (EOF), whichever comes first. Emit + a comment token whose data is the concatenation of all the characters + starting from and including the character that caused the state machine + to switch into the bogus comment state, up to and including the last + consumed character before the U+003E character, if any, or up to the + end of the file otherwise. (If the comment was started by the end of + the file (EOF), the token is empty.) */ + $data = $this->characters('^>', $this->char); + $this->emitToken( + array( + 'data' => $data, + 'type' => self::COMMENT + ) + ); + + $this->char += strlen($data); + + /* Switch to the data state. */ + $this->state = 'data'; + + /* If the end of the file was reached, reconsume the EOF character. */ + if ($this->char === $this->EOF) { + $this->char = $this->EOF - 1; + } + } + + private function markupDeclarationOpenState() + { + /* If the next two characters are both U+002D HYPHEN-MINUS (-) + characters, consume those two characters, create a comment token whose + data is the empty string, and switch to the comment state. */ + if ($this->character($this->char + 1, 2) === '--') { + $this->char += 2; + $this->state = 'comment'; + $this->token = array( + 'data' => null, + 'type' => self::COMMENT + ); + + /* Otherwise if the next seven chacacters are a case-insensitive match + for the word "DOCTYPE", then consume those characters and switch to the + DOCTYPE state. */ + } elseif (strtolower($this->character($this->char + 1, 7)) === 'doctype') { + $this->char += 7; + $this->state = 'doctype'; + + /* Otherwise, is is a parse error. Switch to the bogus comment state. + The next character that is consumed, if any, is the first character + that will be in the comment. */ + } else { + $this->char++; + $this->state = 'bogusComment'; + } + } + + private function commentState() + { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + /* U+002D HYPHEN-MINUS (-) */ + if ($char === '-') { + /* Switch to the comment dash state */ + $this->state = 'commentDash'; + + /* EOF */ + } elseif ($this->char === $this->EOF) { + /* Parse error. Emit the comment token. Reconsume the EOF character + in the data state. */ + $this->emitToken($this->token); + $this->char--; + $this->state = 'data'; + + /* Anything else */ + } else { + /* Append the input character to the comment token's data. Stay in + the comment state. */ + $this->token['data'] .= $char; + } + } + + private function commentDashState() + { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + /* U+002D HYPHEN-MINUS (-) */ + if ($char === '-') { + /* Switch to the comment end state */ + $this->state = 'commentEnd'; + + /* EOF */ + } elseif ($this->char === $this->EOF) { + /* Parse error. Emit the comment token. Reconsume the EOF character + in the data state. */ + $this->emitToken($this->token); + $this->char--; + $this->state = 'data'; + + /* Anything else */ + } else { + /* Append a U+002D HYPHEN-MINUS (-) character and the input + character to the comment token's data. Switch to the comment state. */ + $this->token['data'] .= '-' . $char; + $this->state = 'comment'; + } + } + + private function commentEndState() + { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + if ($char === '>') { + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif ($char === '-') { + $this->token['data'] .= '-'; + + } elseif ($this->char === $this->EOF) { + $this->emitToken($this->token); + $this->char--; + $this->state = 'data'; + + } else { + $this->token['data'] .= '--' . $char; + $this->state = 'comment'; + } + } + + private function doctypeState() + { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + $this->state = 'beforeDoctypeName'; + + } else { + $this->char--; + $this->state = 'beforeDoctypeName'; + } + } + + private function beforeDoctypeNameState() + { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + // Stay in the before DOCTYPE name state. + + } elseif (preg_match('/^[a-z]$/', $char)) { + $this->token = array( + 'name' => strtoupper($char), + 'type' => self::DOCTYPE, + 'error' => true + ); + + $this->state = 'doctypeName'; + + } elseif ($char === '>') { + $this->emitToken( + array( + 'name' => null, + 'type' => self::DOCTYPE, + 'error' => true + ) + ); + + $this->state = 'data'; + + } elseif ($this->char === $this->EOF) { + $this->emitToken( + array( + 'name' => null, + 'type' => self::DOCTYPE, + 'error' => true + ) + ); + + $this->char--; + $this->state = 'data'; + + } else { + $this->token = array( + 'name' => $char, + 'type' => self::DOCTYPE, + 'error' => true + ); + + $this->state = 'doctypeName'; + } + } + + private function doctypeNameState() + { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + $this->state = 'AfterDoctypeName'; + + } elseif ($char === '>') { + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif (preg_match('/^[a-z]$/', $char)) { + $this->token['name'] .= strtoupper($char); + + } elseif ($this->char === $this->EOF) { + $this->emitToken($this->token); + $this->char--; + $this->state = 'data'; + + } else { + $this->token['name'] .= $char; + } + + $this->token['error'] = ($this->token['name'] === 'HTML') + ? false + : true; + } + + private function afterDoctypeNameState() + { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + // Stay in the DOCTYPE name state. + + } elseif ($char === '>') { + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif ($this->char === $this->EOF) { + $this->emitToken($this->token); + $this->char--; + $this->state = 'data'; + + } else { + $this->token['error'] = true; + $this->state = 'bogusDoctype'; + } + } + + private function bogusDoctypeState() + { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + if ($char === '>') { + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif ($this->char === $this->EOF) { + $this->emitToken($this->token); + $this->char--; + $this->state = 'data'; + + } else { + // Stay in the bogus DOCTYPE state. + } + } + + private function entity() + { + $start = $this->char; + + // This section defines how to consume an entity. This definition is + // used when parsing entities in text and in attributes. + + // The behaviour depends on the identity of the next character (the + // one immediately after the U+0026 AMPERSAND character): + + switch ($this->character($this->char + 1)) { + // U+0023 NUMBER SIGN (#) + case '#': + + // The behaviour further depends on the character after the + // U+0023 NUMBER SIGN: + switch ($this->character($this->char + 1)) { + // U+0078 LATIN SMALL LETTER X + // U+0058 LATIN CAPITAL LETTER X + case 'x': + case 'X': + // Follow the steps below, but using the range of + // characters U+0030 DIGIT ZERO through to U+0039 DIGIT + // NINE, U+0061 LATIN SMALL LETTER A through to U+0066 + // LATIN SMALL LETTER F, and U+0041 LATIN CAPITAL LETTER + // A, through to U+0046 LATIN CAPITAL LETTER F (in other + // words, 0-9, A-F, a-f). + $char = 1; + $char_class = '0-9A-Fa-f'; + break; + + // Anything else + default: + // Follow the steps below, but using the range of + // characters U+0030 DIGIT ZERO through to U+0039 DIGIT + // NINE (i.e. just 0-9). + $char = 0; + $char_class = '0-9'; + break; + } + + // Consume as many characters as match the range of characters + // given above. + $this->char++; + $e_name = $this->characters($char_class, $this->char + $char + 1); + $entity = $this->character($start, $this->char); + $cond = strlen($e_name) > 0; + + // The rest of the parsing happens below. + break; + + // Anything else + default: + // Consume the maximum number of characters possible, with the + // consumed characters case-sensitively matching one of the + // identifiers in the first column of the entities table. + + $e_name = $this->characters('0-9A-Za-z;', $this->char + 1); + $len = strlen($e_name); + + for ($c = 1; $c <= $len; $c++) { + $id = substr($e_name, 0, $c); + $this->char++; + + if (in_array($id, $this->entities)) { + if ($e_name[$c - 1] !== ';') { + if ($c < $len && $e_name[$c] == ';') { + $this->char++; // consume extra semicolon + } + } + $entity = $id; + break; + } + } + + $cond = isset($entity); + // The rest of the parsing happens below. + break; + } + + if (!$cond) { + // If no match can be made, then this is a parse error. No + // characters are consumed, and nothing is returned. + $this->char = $start; + return false; + } + + // Return a character token for the character corresponding to the + // entity name (as given by the second column of the entities table). + return html_entity_decode('&' . rtrim($entity, ';') . ';', ENT_QUOTES, 'UTF-8'); + } + + private function emitToken($token) + { + $emit = $this->tree->emitToken($token); + + if (is_int($emit)) { + $this->content_model = $emit; + + } elseif ($token['type'] === self::ENDTAG) { + $this->content_model = self::PCDATA; + } + } + + private function EOF() + { + $this->state = null; + $this->tree->emitToken( + array( + 'type' => self::EOF + ) + ); + } +} + +class HTML5TreeConstructer +{ + public $stack = array(); + + private $phase; + private $mode; + private $dom; + private $foster_parent = null; + private $a_formatting = array(); + + private $head_pointer = null; + private $form_pointer = null; + + private $scoping = array('button', 'caption', 'html', 'marquee', 'object', 'table', 'td', 'th'); + private $formatting = array( + 'a', + 'b', + 'big', + 'em', + 'font', + 'i', + 'nobr', + 's', + 'small', + 'strike', + 'strong', + 'tt', + 'u' + ); + private $special = array( + 'address', + 'area', + 'base', + 'basefont', + 'bgsound', + 'blockquote', + 'body', + 'br', + 'center', + 'col', + 'colgroup', + 'dd', + 'dir', + 'div', + 'dl', + 'dt', + 'embed', + 'fieldset', + 'form', + 'frame', + 'frameset', + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'head', + 'hr', + 'iframe', + 'image', + 'img', + 'input', + 'isindex', + 'li', + 'link', + 'listing', + 'menu', + 'meta', + 'noembed', + 'noframes', + 'noscript', + 'ol', + 'optgroup', + 'option', + 'p', + 'param', + 'plaintext', + 'pre', + 'script', + 'select', + 'spacer', + 'style', + 'tbody', + 'textarea', + 'tfoot', + 'thead', + 'title', + 'tr', + 'ul', + 'wbr' + ); + + // The different phases. + const INIT_PHASE = 0; + const ROOT_PHASE = 1; + const MAIN_PHASE = 2; + const END_PHASE = 3; + + // The different insertion modes for the main phase. + const BEFOR_HEAD = 0; + const IN_HEAD = 1; + const AFTER_HEAD = 2; + const IN_BODY = 3; + const IN_TABLE = 4; + const IN_CAPTION = 5; + const IN_CGROUP = 6; + const IN_TBODY = 7; + const IN_ROW = 8; + const IN_CELL = 9; + const IN_SELECT = 10; + const AFTER_BODY = 11; + const IN_FRAME = 12; + const AFTR_FRAME = 13; + + // The different types of elements. + const SPECIAL = 0; + const SCOPING = 1; + const FORMATTING = 2; + const PHRASING = 3; + + const MARKER = 0; + + public function __construct() + { + $this->phase = self::INIT_PHASE; + $this->mode = self::BEFOR_HEAD; + $this->dom = new DOMDocument; + + $this->dom->encoding = 'UTF-8'; + $this->dom->preserveWhiteSpace = true; + $this->dom->substituteEntities = true; + $this->dom->strictErrorChecking = false; + } + + // Process tag tokens + public function emitToken($token) + { + switch ($this->phase) { + case self::INIT_PHASE: + return $this->initPhase($token); + break; + case self::ROOT_PHASE: + return $this->rootElementPhase($token); + break; + case self::MAIN_PHASE: + return $this->mainPhase($token); + break; + case self::END_PHASE : + return $this->trailingEndPhase($token); + break; + } + } + + private function initPhase($token) + { + /* Initially, the tree construction stage must handle each token + emitted from the tokenisation stage as follows: */ + + /* A DOCTYPE token that is marked as being in error + A comment token + A start tag token + An end tag token + A character token that is not one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE + An end-of-file token */ + if ((isset($token['error']) && $token['error']) || + $token['type'] === HTML5::COMMENT || + $token['type'] === HTML5::STARTTAG || + $token['type'] === HTML5::ENDTAG || + $token['type'] === HTML5::EOF || + ($token['type'] === HTML5::CHARACTR && isset($token['data']) && + !preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) + ) { + /* This specification does not define how to handle this case. In + particular, user agents may ignore the entirety of this specification + altogether for such documents, and instead invoke special parse modes + with a greater emphasis on backwards compatibility. */ + + $this->phase = self::ROOT_PHASE; + return $this->rootElementPhase($token); + + /* A DOCTYPE token marked as being correct */ + } elseif (isset($token['error']) && !$token['error']) { + /* Append a DocumentType node to the Document node, with the name + attribute set to the name given in the DOCTYPE token (which will be + "HTML"), and the other attributes specific to DocumentType objects + set to null, empty lists, or the empty string as appropriate. */ + $doctype = new DOMDocumentType(null, null, 'HTML'); + + /* Then, switch to the root element phase of the tree construction + stage. */ + $this->phase = self::ROOT_PHASE; + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + } elseif (isset($token['data']) && preg_match( + '/^[\t\n\x0b\x0c ]+$/', + $token['data'] + ) + ) { + /* Append that character to the Document node. */ + $text = $this->dom->createTextNode($token['data']); + $this->dom->appendChild($text); + } + } + + private function rootElementPhase($token) + { + /* After the initial phase, as each token is emitted from the tokenisation + stage, it must be processed as described in this section. */ + + /* A DOCTYPE token */ + if ($token['type'] === HTML5::DOCTYPE) { + // Parse error. Ignore the token. + + /* A comment token */ + } elseif ($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the Document object with the data + attribute set to the data given in the comment token. */ + $comment = $this->dom->createComment($token['data']); + $this->dom->appendChild($comment); + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + } elseif ($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data']) + ) { + /* Append that character to the Document node. */ + $text = $this->dom->createTextNode($token['data']); + $this->dom->appendChild($text); + + /* A character token that is not one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED + (FF), or U+0020 SPACE + A start tag token + An end tag token + An end-of-file token */ + } elseif (($token['type'] === HTML5::CHARACTR && + !preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) || + $token['type'] === HTML5::STARTTAG || + $token['type'] === HTML5::ENDTAG || + $token['type'] === HTML5::EOF + ) { + /* Create an HTMLElement node with the tag name html, in the HTML + namespace. Append it to the Document object. Switch to the main + phase and reprocess the current token. */ + $html = $this->dom->createElement('html'); + $this->dom->appendChild($html); + $this->stack[] = $html; + + $this->phase = self::MAIN_PHASE; + return $this->mainPhase($token); + } + } + + private function mainPhase($token) + { + /* Tokens in the main phase must be handled as follows: */ + + /* A DOCTYPE token */ + if ($token['type'] === HTML5::DOCTYPE) { + // Parse error. Ignore the token. + + /* A start tag token with the tag name "html" */ + } elseif ($token['type'] === HTML5::STARTTAG && $token['name'] === 'html') { + /* If this start tag token was not the first start tag token, then + it is a parse error. */ + + /* For each attribute on the token, check to see if the attribute + is already present on the top element of the stack of open elements. + If it is not, add the attribute and its corresponding value to that + element. */ + foreach ($token['attr'] as $attr) { + if (!$this->stack[0]->hasAttribute($attr['name'])) { + $this->stack[0]->setAttribute($attr['name'], $attr['value']); + } + } + + /* An end-of-file token */ + } elseif ($token['type'] === HTML5::EOF) { + /* Generate implied end tags. */ + $this->generateImpliedEndTags(); + + /* Anything else. */ + } else { + /* Depends on the insertion mode: */ + switch ($this->mode) { + case self::BEFOR_HEAD: + return $this->beforeHead($token); + break; + case self::IN_HEAD: + return $this->inHead($token); + break; + case self::AFTER_HEAD: + return $this->afterHead($token); + break; + case self::IN_BODY: + return $this->inBody($token); + break; + case self::IN_TABLE: + return $this->inTable($token); + break; + case self::IN_CAPTION: + return $this->inCaption($token); + break; + case self::IN_CGROUP: + return $this->inColumnGroup($token); + break; + case self::IN_TBODY: + return $this->inTableBody($token); + break; + case self::IN_ROW: + return $this->inRow($token); + break; + case self::IN_CELL: + return $this->inCell($token); + break; + case self::IN_SELECT: + return $this->inSelect($token); + break; + case self::AFTER_BODY: + return $this->afterBody($token); + break; + case self::IN_FRAME: + return $this->inFrameset($token); + break; + case self::AFTR_FRAME: + return $this->afterFrameset($token); + break; + case self::END_PHASE: + return $this->trailingEndPhase($token); + break; + } + } + } + + private function beforeHead($token) + { + /* Handle the token as follows: */ + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + if ($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data']) + ) { + /* Append the character to the current node. */ + $this->insertText($token['data']); + + /* A comment token */ + } elseif ($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data attribute + set to the data given in the comment token. */ + $this->insertComment($token['data']); + + /* A start tag token with the tag name "head" */ + } elseif ($token['type'] === HTML5::STARTTAG && $token['name'] === 'head') { + /* Create an element for the token, append the new element to the + current node and push it onto the stack of open elements. */ + $element = $this->insertElement($token); + + /* Set the head element pointer to this new element node. */ + $this->head_pointer = $element; + + /* Change the insertion mode to "in head". */ + $this->mode = self::IN_HEAD; + + /* A start tag token whose tag name is one of: "base", "link", "meta", + "script", "style", "title". Or an end tag with the tag name "html". + Or a character token that is not one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE. Or any other start tag token */ + } elseif ($token['type'] === HTML5::STARTTAG || + ($token['type'] === HTML5::ENDTAG && $token['name'] === 'html') || + ($token['type'] === HTML5::CHARACTR && !preg_match( + '/^[\t\n\x0b\x0c ]$/', + $token['data'] + )) + ) { + /* Act as if a start tag token with the tag name "head" and no + attributes had been seen, then reprocess the current token. */ + $this->beforeHead( + array( + 'name' => 'head', + 'type' => HTML5::STARTTAG, + 'attr' => array() + ) + ); + + return $this->inHead($token); + + /* Any other end tag */ + } elseif ($token['type'] === HTML5::ENDTAG) { + /* Parse error. Ignore the token. */ + } + } + + private function inHead($token) + { + /* Handle the token as follows: */ + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE. + + THIS DIFFERS FROM THE SPEC: If the current node is either a title, style + or script element, append the character to the current node regardless + of its content. */ + if (($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) || ( + $token['type'] === HTML5::CHARACTR && in_array( + end($this->stack)->nodeName, + array('title', 'style', 'script') + )) + ) { + /* Append the character to the current node. */ + $this->insertText($token['data']); + + /* A comment token */ + } elseif ($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data attribute + set to the data given in the comment token. */ + $this->insertComment($token['data']); + + } elseif ($token['type'] === HTML5::ENDTAG && + in_array($token['name'], array('title', 'style', 'script')) + ) { + array_pop($this->stack); + return HTML5::PCDATA; + + /* A start tag with the tag name "title" */ + } elseif ($token['type'] === HTML5::STARTTAG && $token['name'] === 'title') { + /* Create an element for the token and append the new element to the + node pointed to by the head element pointer, or, if that is null + (innerHTML case), to the current node. */ + if ($this->head_pointer !== null) { + $element = $this->insertElement($token, false); + $this->head_pointer->appendChild($element); + + } else { + $element = $this->insertElement($token); + } + + /* Switch the tokeniser's content model flag to the RCDATA state. */ + return HTML5::RCDATA; + + /* A start tag with the tag name "style" */ + } elseif ($token['type'] === HTML5::STARTTAG && $token['name'] === 'style') { + /* Create an element for the token and append the new element to the + node pointed to by the head element pointer, or, if that is null + (innerHTML case), to the current node. */ + if ($this->head_pointer !== null) { + $element = $this->insertElement($token, false); + $this->head_pointer->appendChild($element); + + } else { + $this->insertElement($token); + } + + /* Switch the tokeniser's content model flag to the CDATA state. */ + return HTML5::CDATA; + + /* A start tag with the tag name "script" */ + } elseif ($token['type'] === HTML5::STARTTAG && $token['name'] === 'script') { + /* Create an element for the token. */ + $element = $this->insertElement($token, false); + $this->head_pointer->appendChild($element); + + /* Switch the tokeniser's content model flag to the CDATA state. */ + return HTML5::CDATA; + + /* A start tag with the tag name "base", "link", or "meta" */ + } elseif ($token['type'] === HTML5::STARTTAG && in_array( + $token['name'], + array('base', 'link', 'meta') + ) + ) { + /* Create an element for the token and append the new element to the + node pointed to by the head element pointer, or, if that is null + (innerHTML case), to the current node. */ + if ($this->head_pointer !== null) { + $element = $this->insertElement($token, false); + $this->head_pointer->appendChild($element); + array_pop($this->stack); + + } else { + $this->insertElement($token); + } + + /* An end tag with the tag name "head" */ + } elseif ($token['type'] === HTML5::ENDTAG && $token['name'] === 'head') { + /* If the current node is a head element, pop the current node off + the stack of open elements. */ + if ($this->head_pointer->isSameNode(end($this->stack))) { + array_pop($this->stack); + + /* Otherwise, this is a parse error. */ + } else { + // k + } + + /* Change the insertion mode to "after head". */ + $this->mode = self::AFTER_HEAD; + + /* A start tag with the tag name "head" or an end tag except "html". */ + } elseif (($token['type'] === HTML5::STARTTAG && $token['name'] === 'head') || + ($token['type'] === HTML5::ENDTAG && $token['name'] !== 'html') + ) { + // Parse error. Ignore the token. + + /* Anything else */ + } else { + /* If the current node is a head element, act as if an end tag + token with the tag name "head" had been seen. */ + if ($this->head_pointer->isSameNode(end($this->stack))) { + $this->inHead( + array( + 'name' => 'head', + 'type' => HTML5::ENDTAG + ) + ); + + /* Otherwise, change the insertion mode to "after head". */ + } else { + $this->mode = self::AFTER_HEAD; + } + + /* Then, reprocess the current token. */ + return $this->afterHead($token); + } + } + + private function afterHead($token) + { + /* Handle the token as follows: */ + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + if ($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data']) + ) { + /* Append the character to the current node. */ + $this->insertText($token['data']); + + /* A comment token */ + } elseif ($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data attribute + set to the data given in the comment token. */ + $this->insertComment($token['data']); + + /* A start tag token with the tag name "body" */ + } elseif ($token['type'] === HTML5::STARTTAG && $token['name'] === 'body') { + /* Insert a body element for the token. */ + $this->insertElement($token); + + /* Change the insertion mode to "in body". */ + $this->mode = self::IN_BODY; + + /* A start tag token with the tag name "frameset" */ + } elseif ($token['type'] === HTML5::STARTTAG && $token['name'] === 'frameset') { + /* Insert a frameset element for the token. */ + $this->insertElement($token); + + /* Change the insertion mode to "in frameset". */ + $this->mode = self::IN_FRAME; + + /* A start tag token whose tag name is one of: "base", "link", "meta", + "script", "style", "title" */ + } elseif ($token['type'] === HTML5::STARTTAG && in_array( + $token['name'], + array('base', 'link', 'meta', 'script', 'style', 'title') + ) + ) { + /* Parse error. Switch the insertion mode back to "in head" and + reprocess the token. */ + $this->mode = self::IN_HEAD; + return $this->inHead($token); + + /* Anything else */ + } else { + /* Act as if a start tag token with the tag name "body" and no + attributes had been seen, and then reprocess the current token. */ + $this->afterHead( + array( + 'name' => 'body', + 'type' => HTML5::STARTTAG, + 'attr' => array() + ) + ); + + return $this->inBody($token); + } + } + + private function inBody($token) + { + /* Handle the token as follows: */ + + switch ($token['type']) { + /* A character token */ + case HTML5::CHARACTR: + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Append the token's character to the current node. */ + $this->insertText($token['data']); + break; + + /* A comment token */ + case HTML5::COMMENT: + /* Append a Comment node to the current node with the data + attribute set to the data given in the comment token. */ + $this->insertComment($token['data']); + break; + + case HTML5::STARTTAG: + switch ($token['name']) { + /* A start tag token whose tag name is one of: "script", + "style" */ + case 'script': + case 'style': + /* Process the token as if the insertion mode had been "in + head". */ + return $this->inHead($token); + break; + + /* A start tag token whose tag name is one of: "base", "link", + "meta", "title" */ + case 'base': + case 'link': + case 'meta': + case 'title': + /* Parse error. Process the token as if the insertion mode + had been "in head". */ + return $this->inHead($token); + break; + + /* A start tag token with the tag name "body" */ + case 'body': + /* Parse error. If the second element on the stack of open + elements is not a body element, or, if the stack of open + elements has only one node on it, then ignore the token. + (innerHTML case) */ + if (count($this->stack) === 1 || $this->stack[1]->nodeName !== 'body') { + // Ignore + + /* Otherwise, for each attribute on the token, check to see + if the attribute is already present on the body element (the + second element) on the stack of open elements. If it is not, + add the attribute and its corresponding value to that + element. */ + } else { + foreach ($token['attr'] as $attr) { + if (!$this->stack[1]->hasAttribute($attr['name'])) { + $this->stack[1]->setAttribute($attr['name'], $attr['value']); + } + } + } + break; + + /* A start tag whose tag name is one of: "address", + "blockquote", "center", "dir", "div", "dl", "fieldset", + "listing", "menu", "ol", "p", "ul" */ + case 'address': + case 'blockquote': + case 'center': + case 'dir': + case 'div': + case 'dl': + case 'fieldset': + case 'listing': + case 'menu': + case 'ol': + case 'p': + case 'ul': + /* If the stack of open elements has a p element in scope, + then act as if an end tag with the tag name p had been + seen. */ + if ($this->elementInScope('p')) { + $this->emitToken( + array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + ) + ); + } + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + break; + + /* A start tag whose tag name is "form" */ + case 'form': + /* If the form element pointer is not null, ignore the + token with a parse error. */ + if ($this->form_pointer !== null) { + // Ignore. + + /* Otherwise: */ + } else { + /* If the stack of open elements has a p element in + scope, then act as if an end tag with the tag name p + had been seen. */ + if ($this->elementInScope('p')) { + $this->emitToken( + array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + ) + ); + } + + /* Insert an HTML element for the token, and set the + form element pointer to point to the element created. */ + $element = $this->insertElement($token); + $this->form_pointer = $element; + } + break; + + /* A start tag whose tag name is "li", "dd" or "dt" */ + case 'li': + case 'dd': + case 'dt': + /* If the stack of open elements has a p element in scope, + then act as if an end tag with the tag name p had been + seen. */ + if ($this->elementInScope('p')) { + $this->emitToken( + array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + ) + ); + } + + $stack_length = count($this->stack) - 1; + + for ($n = $stack_length; 0 <= $n; $n--) { + /* 1. Initialise node to be the current node (the + bottommost node of the stack). */ + $stop = false; + $node = $this->stack[$n]; + $cat = $this->getElementCategory($node->tagName); + + /* 2. If node is an li, dd or dt element, then pop all + the nodes from the current node up to node, including + node, then stop this algorithm. */ + if ($token['name'] === $node->tagName || ($token['name'] !== 'li' + && ($node->tagName === 'dd' || $node->tagName === 'dt')) + ) { + for ($x = $stack_length; $x >= $n; $x--) { + array_pop($this->stack); + } + + break; + } + + /* 3. If node is not in the formatting category, and is + not in the phrasing category, and is not an address or + div element, then stop this algorithm. */ + if ($cat !== self::FORMATTING && $cat !== self::PHRASING && + $node->tagName !== 'address' && $node->tagName !== 'div' + ) { + break; + } + } + + /* Finally, insert an HTML element with the same tag + name as the token's. */ + $this->insertElement($token); + break; + + /* A start tag token whose tag name is "plaintext" */ + case 'plaintext': + /* If the stack of open elements has a p element in scope, + then act as if an end tag with the tag name p had been + seen. */ + if ($this->elementInScope('p')) { + $this->emitToken( + array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + ) + ); + } + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + return HTML5::PLAINTEXT; + break; + + /* A start tag whose tag name is one of: "h1", "h2", "h3", "h4", + "h5", "h6" */ + case 'h1': + case 'h2': + case 'h3': + case 'h4': + case 'h5': + case 'h6': + /* If the stack of open elements has a p element in scope, + then act as if an end tag with the tag name p had been seen. */ + if ($this->elementInScope('p')) { + $this->emitToken( + array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + ) + ); + } + + /* If the stack of open elements has in scope an element whose + tag name is one of "h1", "h2", "h3", "h4", "h5", or "h6", then + this is a parse error; pop elements from the stack until an + element with one of those tag names has been popped from the + stack. */ + while ($this->elementInScope(array('h1', 'h2', 'h3', 'h4', 'h5', 'h6'))) { + array_pop($this->stack); + } + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + break; + + /* A start tag whose tag name is "a" */ + case 'a': + /* If the list of active formatting elements contains + an element whose tag name is "a" between the end of the + list and the last marker on the list (or the start of + the list if there is no marker on the list), then this + is a parse error; act as if an end tag with the tag name + "a" had been seen, then remove that element from the list + of active formatting elements and the stack of open + elements if the end tag didn't already remove it (it + might not have if the element is not in table scope). */ + $leng = count($this->a_formatting); + + for ($n = $leng - 1; $n >= 0; $n--) { + if ($this->a_formatting[$n] === self::MARKER) { + break; + + } elseif ($this->a_formatting[$n]->nodeName === 'a') { + $this->emitToken( + array( + 'name' => 'a', + 'type' => HTML5::ENDTAG + ) + ); + break; + } + } + + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an HTML element for the token. */ + $el = $this->insertElement($token); + + /* Add that element to the list of active formatting + elements. */ + $this->a_formatting[] = $el; + break; + + /* A start tag whose tag name is one of: "b", "big", "em", "font", + "i", "nobr", "s", "small", "strike", "strong", "tt", "u" */ + case 'b': + case 'big': + case 'em': + case 'font': + case 'i': + case 'nobr': + case 's': + case 'small': + case 'strike': + case 'strong': + case 'tt': + case 'u': + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an HTML element for the token. */ + $el = $this->insertElement($token); + + /* Add that element to the list of active formatting + elements. */ + $this->a_formatting[] = $el; + break; + + /* A start tag token whose tag name is "button" */ + case 'button': + /* If the stack of open elements has a button element in scope, + then this is a parse error; act as if an end tag with the tag + name "button" had been seen, then reprocess the token. (We don't + do that. Unnecessary.) */ + if ($this->elementInScope('button')) { + $this->inBody( + array( + 'name' => 'button', + 'type' => HTML5::ENDTAG + ) + ); + } + + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Insert a marker at the end of the list of active + formatting elements. */ + $this->a_formatting[] = self::MARKER; + break; + + /* A start tag token whose tag name is one of: "marquee", "object" */ + case 'marquee': + case 'object': + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Insert a marker at the end of the list of active + formatting elements. */ + $this->a_formatting[] = self::MARKER; + break; + + /* A start tag token whose tag name is "xmp" */ + case 'xmp': + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Switch the content model flag to the CDATA state. */ + return HTML5::CDATA; + break; + + /* A start tag whose tag name is "table" */ + case 'table': + /* If the stack of open elements has a p element in scope, + then act as if an end tag with the tag name p had been seen. */ + if ($this->elementInScope('p')) { + $this->emitToken( + array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + ) + ); + } + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Change the insertion mode to "in table". */ + $this->mode = self::IN_TABLE; + break; + + /* A start tag whose tag name is one of: "area", "basefont", + "bgsound", "br", "embed", "img", "param", "spacer", "wbr" */ + case 'area': + case 'basefont': + case 'bgsound': + case 'br': + case 'embed': + case 'img': + case 'param': + case 'spacer': + case 'wbr': + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Immediately pop the current node off the stack of open elements. */ + array_pop($this->stack); + break; + + /* A start tag whose tag name is "hr" */ + case 'hr': + /* If the stack of open elements has a p element in scope, + then act as if an end tag with the tag name p had been seen. */ + if ($this->elementInScope('p')) { + $this->emitToken( + array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + ) + ); + } + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Immediately pop the current node off the stack of open elements. */ + array_pop($this->stack); + break; + + /* A start tag whose tag name is "image" */ + case 'image': + /* Parse error. Change the token's tag name to "img" and + reprocess it. (Don't ask.) */ + $token['name'] = 'img'; + return $this->inBody($token); + break; + + /* A start tag whose tag name is "input" */ + case 'input': + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an input element for the token. */ + $element = $this->insertElement($token, false); + + /* If the form element pointer is not null, then associate the + input element with the form element pointed to by the form + element pointer. */ + $this->form_pointer !== null + ? $this->form_pointer->appendChild($element) + : end($this->stack)->appendChild($element); + + /* Pop that input element off the stack of open elements. */ + array_pop($this->stack); + break; + + /* A start tag whose tag name is "isindex" */ + case 'isindex': + /* Parse error. */ + // w/e + + /* If the form element pointer is not null, + then ignore the token. */ + if ($this->form_pointer === null) { + /* Act as if a start tag token with the tag name "form" had + been seen. */ + $this->inBody( + array( + 'name' => 'body', + 'type' => HTML5::STARTTAG, + 'attr' => array() + ) + ); + + /* Act as if a start tag token with the tag name "hr" had + been seen. */ + $this->inBody( + array( + 'name' => 'hr', + 'type' => HTML5::STARTTAG, + 'attr' => array() + ) + ); + + /* Act as if a start tag token with the tag name "p" had + been seen. */ + $this->inBody( + array( + 'name' => 'p', + 'type' => HTML5::STARTTAG, + 'attr' => array() + ) + ); + + /* Act as if a start tag token with the tag name "label" + had been seen. */ + $this->inBody( + array( + 'name' => 'label', + 'type' => HTML5::STARTTAG, + 'attr' => array() + ) + ); + + /* Act as if a stream of character tokens had been seen. */ + $this->insertText( + 'This is a searchable index. ' . + 'Insert your search keywords here: ' + ); + + /* Act as if a start tag token with the tag name "input" + had been seen, with all the attributes from the "isindex" + token, except with the "name" attribute set to the value + "isindex" (ignoring any explicit "name" attribute). */ + $attr = $token['attr']; + $attr[] = array('name' => 'name', 'value' => 'isindex'); + + $this->inBody( + array( + 'name' => 'input', + 'type' => HTML5::STARTTAG, + 'attr' => $attr + ) + ); + + /* Act as if a stream of character tokens had been seen + (see below for what they should say). */ + $this->insertText( + 'This is a searchable index. ' . + 'Insert your search keywords here: ' + ); + + /* Act as if an end tag token with the tag name "label" + had been seen. */ + $this->inBody( + array( + 'name' => 'label', + 'type' => HTML5::ENDTAG + ) + ); + + /* Act as if an end tag token with the tag name "p" had + been seen. */ + $this->inBody( + array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + ) + ); + + /* Act as if a start tag token with the tag name "hr" had + been seen. */ + $this->inBody( + array( + 'name' => 'hr', + 'type' => HTML5::ENDTAG + ) + ); + + /* Act as if an end tag token with the tag name "form" had + been seen. */ + $this->inBody( + array( + 'name' => 'form', + 'type' => HTML5::ENDTAG + ) + ); + } + break; + + /* A start tag whose tag name is "textarea" */ + case 'textarea': + $this->insertElement($token); + + /* Switch the tokeniser's content model flag to the + RCDATA state. */ + return HTML5::RCDATA; + break; + + /* A start tag whose tag name is one of: "iframe", "noembed", + "noframes" */ + case 'iframe': + case 'noembed': + case 'noframes': + $this->insertElement($token); + + /* Switch the tokeniser's content model flag to the CDATA state. */ + return HTML5::CDATA; + break; + + /* A start tag whose tag name is "select" */ + case 'select': + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Change the insertion mode to "in select". */ + $this->mode = self::IN_SELECT; + break; + + /* A start or end tag whose tag name is one of: "caption", "col", + "colgroup", "frame", "frameset", "head", "option", "optgroup", + "tbody", "td", "tfoot", "th", "thead", "tr". */ + case 'caption': + case 'col': + case 'colgroup': + case 'frame': + case 'frameset': + case 'head': + case 'option': + case 'optgroup': + case 'tbody': + case 'td': + case 'tfoot': + case 'th': + case 'thead': + case 'tr': + // Parse error. Ignore the token. + break; + + /* A start or end tag whose tag name is one of: "event-source", + "section", "nav", "article", "aside", "header", "footer", + "datagrid", "command" */ + case 'event-source': + case 'section': + case 'nav': + case 'article': + case 'aside': + case 'header': + case 'footer': + case 'datagrid': + case 'command': + // Work in progress! + break; + + /* A start tag token not covered by the previous entries */ + default: + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + $this->insertElement($token, true, true); + break; + } + break; + + case HTML5::ENDTAG: + switch ($token['name']) { + /* An end tag with the tag name "body" */ + case 'body': + /* If the second element in the stack of open elements is + not a body element, this is a parse error. Ignore the token. + (innerHTML case) */ + if (count($this->stack) < 2 || $this->stack[1]->nodeName !== 'body') { + // Ignore. + + /* If the current node is not the body element, then this + is a parse error. */ + } elseif (end($this->stack)->nodeName !== 'body') { + // Parse error. + } + + /* Change the insertion mode to "after body". */ + $this->mode = self::AFTER_BODY; + break; + + /* An end tag with the tag name "html" */ + case 'html': + /* Act as if an end tag with tag name "body" had been seen, + then, if that token wasn't ignored, reprocess the current + token. */ + $this->inBody( + array( + 'name' => 'body', + 'type' => HTML5::ENDTAG + ) + ); + + return $this->afterBody($token); + break; + + /* An end tag whose tag name is one of: "address", "blockquote", + "center", "dir", "div", "dl", "fieldset", "listing", "menu", + "ol", "pre", "ul" */ + case 'address': + case 'blockquote': + case 'center': + case 'dir': + case 'div': + case 'dl': + case 'fieldset': + case 'listing': + case 'menu': + case 'ol': + case 'pre': + case 'ul': + /* If the stack of open elements has an element in scope + with the same tag name as that of the token, then generate + implied end tags. */ + if ($this->elementInScope($token['name'])) { + $this->generateImpliedEndTags(); + + /* Now, if the current node is not an element with + the same tag name as that of the token, then this + is a parse error. */ + // w/e + + /* If the stack of open elements has an element in + scope with the same tag name as that of the token, + then pop elements from this stack until an element + with that tag name has been popped from the stack. */ + for ($n = count($this->stack) - 1; $n >= 0; $n--) { + if ($this->stack[$n]->nodeName === $token['name']) { + $n = -1; + } + + array_pop($this->stack); + } + } + break; + + /* An end tag whose tag name is "form" */ + case 'form': + /* If the stack of open elements has an element in scope + with the same tag name as that of the token, then generate + implied end tags. */ + if ($this->elementInScope($token['name'])) { + $this->generateImpliedEndTags(); + + } + + if (end($this->stack)->nodeName !== $token['name']) { + /* Now, if the current node is not an element with the + same tag name as that of the token, then this is a parse + error. */ + // w/e + + } else { + /* Otherwise, if the current node is an element with + the same tag name as that of the token pop that element + from the stack. */ + array_pop($this->stack); + } + + /* In any case, set the form element pointer to null. */ + $this->form_pointer = null; + break; + + /* An end tag whose tag name is "p" */ + case 'p': + /* If the stack of open elements has a p element in scope, + then generate implied end tags, except for p elements. */ + if ($this->elementInScope('p')) { + $this->generateImpliedEndTags(array('p')); + + /* If the current node is not a p element, then this is + a parse error. */ + // k + + /* If the stack of open elements has a p element in + scope, then pop elements from this stack until the stack + no longer has a p element in scope. */ + for ($n = count($this->stack) - 1; $n >= 0; $n--) { + if ($this->elementInScope('p')) { + array_pop($this->stack); + + } else { + break; + } + } + } + break; + + /* An end tag whose tag name is "dd", "dt", or "li" */ + case 'dd': + case 'dt': + case 'li': + /* If the stack of open elements has an element in scope + whose tag name matches the tag name of the token, then + generate implied end tags, except for elements with the + same tag name as the token. */ + if ($this->elementInScope($token['name'])) { + $this->generateImpliedEndTags(array($token['name'])); + + /* If the current node is not an element with the same + tag name as the token, then this is a parse error. */ + // w/e + + /* If the stack of open elements has an element in scope + whose tag name matches the tag name of the token, then + pop elements from this stack until an element with that + tag name has been popped from the stack. */ + for ($n = count($this->stack) - 1; $n >= 0; $n--) { + if ($this->stack[$n]->nodeName === $token['name']) { + $n = -1; + } + + array_pop($this->stack); + } + } + break; + + /* An end tag whose tag name is one of: "h1", "h2", "h3", "h4", + "h5", "h6" */ + case 'h1': + case 'h2': + case 'h3': + case 'h4': + case 'h5': + case 'h6': + $elements = array('h1', 'h2', 'h3', 'h4', 'h5', 'h6'); + + /* If the stack of open elements has in scope an element whose + tag name is one of "h1", "h2", "h3", "h4", "h5", or "h6", then + generate implied end tags. */ + if ($this->elementInScope($elements)) { + $this->generateImpliedEndTags(); + + /* Now, if the current node is not an element with the same + tag name as that of the token, then this is a parse error. */ + // w/e + + /* If the stack of open elements has in scope an element + whose tag name is one of "h1", "h2", "h3", "h4", "h5", or + "h6", then pop elements from the stack until an element + with one of those tag names has been popped from the stack. */ + while ($this->elementInScope($elements)) { + array_pop($this->stack); + } + } + break; + + /* An end tag whose tag name is one of: "a", "b", "big", "em", + "font", "i", "nobr", "s", "small", "strike", "strong", "tt", "u" */ + case 'a': + case 'b': + case 'big': + case 'em': + case 'font': + case 'i': + case 'nobr': + case 's': + case 'small': + case 'strike': + case 'strong': + case 'tt': + case 'u': + /* 1. Let the formatting element be the last element in + the list of active formatting elements that: + * is between the end of the list and the last scope + marker in the list, if any, or the start of the list + otherwise, and + * has the same tag name as the token. + */ + while (true) { + for ($a = count($this->a_formatting) - 1; $a >= 0; $a--) { + if ($this->a_formatting[$a] === self::MARKER) { + break; + + } elseif ($this->a_formatting[$a]->tagName === $token['name']) { + $formatting_element = $this->a_formatting[$a]; + $in_stack = in_array($formatting_element, $this->stack, true); + $fe_af_pos = $a; + break; + } + } + + /* If there is no such node, or, if that node is + also in the stack of open elements but the element + is not in scope, then this is a parse error. Abort + these steps. The token is ignored. */ + if (!isset($formatting_element) || ($in_stack && + !$this->elementInScope($token['name'])) + ) { + break; + + /* Otherwise, if there is such a node, but that node + is not in the stack of open elements, then this is a + parse error; remove the element from the list, and + abort these steps. */ + } elseif (isset($formatting_element) && !$in_stack) { + unset($this->a_formatting[$fe_af_pos]); + $this->a_formatting = array_merge($this->a_formatting); + break; + } + + /* 2. Let the furthest block be the topmost node in the + stack of open elements that is lower in the stack + than the formatting element, and is not an element in + the phrasing or formatting categories. There might + not be one. */ + $fe_s_pos = array_search($formatting_element, $this->stack, true); + $length = count($this->stack); + + for ($s = $fe_s_pos + 1; $s < $length; $s++) { + $category = $this->getElementCategory($this->stack[$s]->nodeName); + + if ($category !== self::PHRASING && $category !== self::FORMATTING) { + $furthest_block = $this->stack[$s]; + } + } + + /* 3. If there is no furthest block, then the UA must + skip the subsequent steps and instead just pop all + the nodes from the bottom of the stack of open + elements, from the current node up to the formatting + element, and remove the formatting element from the + list of active formatting elements. */ + if (!isset($furthest_block)) { + for ($n = $length - 1; $n >= $fe_s_pos; $n--) { + array_pop($this->stack); + } + + unset($this->a_formatting[$fe_af_pos]); + $this->a_formatting = array_merge($this->a_formatting); + break; + } + + /* 4. Let the common ancestor be the element + immediately above the formatting element in the stack + of open elements. */ + $common_ancestor = $this->stack[$fe_s_pos - 1]; + + /* 5. If the furthest block has a parent node, then + remove the furthest block from its parent node. */ + if ($furthest_block->parentNode !== null) { + $furthest_block->parentNode->removeChild($furthest_block); + } + + /* 6. Let a bookmark note the position of the + formatting element in the list of active formatting + elements relative to the elements on either side + of it in the list. */ + $bookmark = $fe_af_pos; + + /* 7. Let node and last node be the furthest block. + Follow these steps: */ + $node = $furthest_block; + $last_node = $furthest_block; + + while (true) { + for ($n = array_search($node, $this->stack, true) - 1; $n >= 0; $n--) { + /* 7.1 Let node be the element immediately + prior to node in the stack of open elements. */ + $node = $this->stack[$n]; + + /* 7.2 If node is not in the list of active + formatting elements, then remove node from + the stack of open elements and then go back + to step 1. */ + if (!in_array($node, $this->a_formatting, true)) { + unset($this->stack[$n]); + $this->stack = array_merge($this->stack); + + } else { + break; + } + } + + /* 7.3 Otherwise, if node is the formatting + element, then go to the next step in the overall + algorithm. */ + if ($node === $formatting_element) { + break; + + /* 7.4 Otherwise, if last node is the furthest + block, then move the aforementioned bookmark to + be immediately after the node in the list of + active formatting elements. */ + } elseif ($last_node === $furthest_block) { + $bookmark = array_search($node, $this->a_formatting, true) + 1; + } + + /* 7.5 If node has any children, perform a + shallow clone of node, replace the entry for + node in the list of active formatting elements + with an entry for the clone, replace the entry + for node in the stack of open elements with an + entry for the clone, and let node be the clone. */ + if ($node->hasChildNodes()) { + $clone = $node->cloneNode(); + $s_pos = array_search($node, $this->stack, true); + $a_pos = array_search($node, $this->a_formatting, true); + + $this->stack[$s_pos] = $clone; + $this->a_formatting[$a_pos] = $clone; + $node = $clone; + } + + /* 7.6 Insert last node into node, first removing + it from its previous parent node if any. */ + if ($last_node->parentNode !== null) { + $last_node->parentNode->removeChild($last_node); + } + + $node->appendChild($last_node); + + /* 7.7 Let last node be node. */ + $last_node = $node; + } + + /* 8. Insert whatever last node ended up being in + the previous step into the common ancestor node, + first removing it from its previous parent node if + any. */ + if ($last_node->parentNode !== null) { + $last_node->parentNode->removeChild($last_node); + } + + $common_ancestor->appendChild($last_node); + + /* 9. Perform a shallow clone of the formatting + element. */ + $clone = $formatting_element->cloneNode(); + + /* 10. Take all of the child nodes of the furthest + block and append them to the clone created in the + last step. */ + while ($furthest_block->hasChildNodes()) { + $child = $furthest_block->firstChild; + $furthest_block->removeChild($child); + $clone->appendChild($child); + } + + /* 11. Append that clone to the furthest block. */ + $furthest_block->appendChild($clone); + + /* 12. Remove the formatting element from the list + of active formatting elements, and insert the clone + into the list of active formatting elements at the + position of the aforementioned bookmark. */ + $fe_af_pos = array_search($formatting_element, $this->a_formatting, true); + unset($this->a_formatting[$fe_af_pos]); + $this->a_formatting = array_merge($this->a_formatting); + + $af_part1 = array_slice($this->a_formatting, 0, $bookmark - 1); + $af_part2 = array_slice($this->a_formatting, $bookmark, count($this->a_formatting)); + $this->a_formatting = array_merge($af_part1, array($clone), $af_part2); + + /* 13. Remove the formatting element from the stack + of open elements, and insert the clone into the stack + of open elements immediately after (i.e. in a more + deeply nested position than) the position of the + furthest block in that stack. */ + $fe_s_pos = array_search($formatting_element, $this->stack, true); + $fb_s_pos = array_search($furthest_block, $this->stack, true); + unset($this->stack[$fe_s_pos]); + + $s_part1 = array_slice($this->stack, 0, $fb_s_pos); + $s_part2 = array_slice($this->stack, $fb_s_pos + 1, count($this->stack)); + $this->stack = array_merge($s_part1, array($clone), $s_part2); + + /* 14. Jump back to step 1 in this series of steps. */ + unset($formatting_element, $fe_af_pos, $fe_s_pos, $furthest_block); + } + break; + + /* An end tag token whose tag name is one of: "button", + "marquee", "object" */ + case 'button': + case 'marquee': + case 'object': + /* If the stack of open elements has an element in scope whose + tag name matches the tag name of the token, then generate implied + tags. */ + if ($this->elementInScope($token['name'])) { + $this->generateImpliedEndTags(); + + /* Now, if the current node is not an element with the same + tag name as the token, then this is a parse error. */ + // k + + /* Now, if the stack of open elements has an element in scope + whose tag name matches the tag name of the token, then pop + elements from the stack until that element has been popped from + the stack, and clear the list of active formatting elements up + to the last marker. */ + for ($n = count($this->stack) - 1; $n >= 0; $n--) { + if ($this->stack[$n]->nodeName === $token['name']) { + $n = -1; + } + + array_pop($this->stack); + } + + $marker = end(array_keys($this->a_formatting, self::MARKER, true)); + + for ($n = count($this->a_formatting) - 1; $n > $marker; $n--) { + array_pop($this->a_formatting); + } + } + break; + + /* Or an end tag whose tag name is one of: "area", "basefont", + "bgsound", "br", "embed", "hr", "iframe", "image", "img", + "input", "isindex", "noembed", "noframes", "param", "select", + "spacer", "table", "textarea", "wbr" */ + case 'area': + case 'basefont': + case 'bgsound': + case 'br': + case 'embed': + case 'hr': + case 'iframe': + case 'image': + case 'img': + case 'input': + case 'isindex': + case 'noembed': + case 'noframes': + case 'param': + case 'select': + case 'spacer': + case 'table': + case 'textarea': + case 'wbr': + // Parse error. Ignore the token. + break; + + /* An end tag token not covered by the previous entries */ + default: + for ($n = count($this->stack) - 1; $n >= 0; $n--) { + /* Initialise node to be the current node (the bottommost + node of the stack). */ + $node = end($this->stack); + + /* If node has the same tag name as the end tag token, + then: */ + if ($token['name'] === $node->nodeName) { + /* Generate implied end tags. */ + $this->generateImpliedEndTags(); + + /* If the tag name of the end tag token does not + match the tag name of the current node, this is a + parse error. */ + // k + + /* Pop all the nodes from the current node up to + node, including node, then stop this algorithm. */ + for ($x = count($this->stack) - $n; $x >= $n; $x--) { + array_pop($this->stack); + } + + } else { + $category = $this->getElementCategory($node); + + if ($category !== self::SPECIAL && $category !== self::SCOPING) { + /* Otherwise, if node is in neither the formatting + category nor the phrasing category, then this is a + parse error. Stop this algorithm. The end tag token + is ignored. */ + return false; + } + } + } + break; + } + break; + } + } + + private function inTable($token) + { + $clear = array('html', 'table'); + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + if ($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data']) + ) { + /* Append the character to the current node. */ + $text = $this->dom->createTextNode($token['data']); + end($this->stack)->appendChild($text); + + /* A comment token */ + } elseif ($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data + attribute set to the data given in the comment token. */ + $comment = $this->dom->createComment($token['data']); + end($this->stack)->appendChild($comment); + + /* A start tag whose tag name is "caption" */ + } elseif ($token['type'] === HTML5::STARTTAG && + $token['name'] === 'caption' + ) { + /* Clear the stack back to a table context. */ + $this->clearStackToTableContext($clear); + + /* Insert a marker at the end of the list of active + formatting elements. */ + $this->a_formatting[] = self::MARKER; + + /* Insert an HTML element for the token, then switch the + insertion mode to "in caption". */ + $this->insertElement($token); + $this->mode = self::IN_CAPTION; + + /* A start tag whose tag name is "colgroup" */ + } elseif ($token['type'] === HTML5::STARTTAG && + $token['name'] === 'colgroup' + ) { + /* Clear the stack back to a table context. */ + $this->clearStackToTableContext($clear); + + /* Insert an HTML element for the token, then switch the + insertion mode to "in column group". */ + $this->insertElement($token); + $this->mode = self::IN_CGROUP; + + /* A start tag whose tag name is "col" */ + } elseif ($token['type'] === HTML5::STARTTAG && + $token['name'] === 'col' + ) { + $this->inTable( + array( + 'name' => 'colgroup', + 'type' => HTML5::STARTTAG, + 'attr' => array() + ) + ); + + $this->inColumnGroup($token); + + /* A start tag whose tag name is one of: "tbody", "tfoot", "thead" */ + } elseif ($token['type'] === HTML5::STARTTAG && in_array( + $token['name'], + array('tbody', 'tfoot', 'thead') + ) + ) { + /* Clear the stack back to a table context. */ + $this->clearStackToTableContext($clear); + + /* Insert an HTML element for the token, then switch the insertion + mode to "in table body". */ + $this->insertElement($token); + $this->mode = self::IN_TBODY; + + /* A start tag whose tag name is one of: "td", "th", "tr" */ + } elseif ($token['type'] === HTML5::STARTTAG && + in_array($token['name'], array('td', 'th', 'tr')) + ) { + /* Act as if a start tag token with the tag name "tbody" had been + seen, then reprocess the current token. */ + $this->inTable( + array( + 'name' => 'tbody', + 'type' => HTML5::STARTTAG, + 'attr' => array() + ) + ); + + return $this->inTableBody($token); + + /* A start tag whose tag name is "table" */ + } elseif ($token['type'] === HTML5::STARTTAG && + $token['name'] === 'table' + ) { + /* Parse error. Act as if an end tag token with the tag name "table" + had been seen, then, if that token wasn't ignored, reprocess the + current token. */ + $this->inTable( + array( + 'name' => 'table', + 'type' => HTML5::ENDTAG + ) + ); + + return $this->mainPhase($token); + + /* An end tag whose tag name is "table" */ + } elseif ($token['type'] === HTML5::ENDTAG && + $token['name'] === 'table' + ) { + /* If the stack of open elements does not have an element in table + scope with the same tag name as the token, this is a parse error. + Ignore the token. (innerHTML case) */ + if (!$this->elementInScope($token['name'], true)) { + return false; + + /* Otherwise: */ + } else { + /* Generate implied end tags. */ + $this->generateImpliedEndTags(); + + /* Now, if the current node is not a table element, then this + is a parse error. */ + // w/e + + /* Pop elements from this stack until a table element has been + popped from the stack. */ + while (true) { + $current = end($this->stack)->nodeName; + array_pop($this->stack); + + if ($current === 'table') { + break; + } + } + + /* Reset the insertion mode appropriately. */ + $this->resetInsertionMode(); + } + + /* An end tag whose tag name is one of: "body", "caption", "col", + "colgroup", "html", "tbody", "td", "tfoot", "th", "thead", "tr" */ + } elseif ($token['type'] === HTML5::ENDTAG && in_array( + $token['name'], + array( + 'body', + 'caption', + 'col', + 'colgroup', + 'html', + 'tbody', + 'td', + 'tfoot', + 'th', + 'thead', + 'tr' + ) + ) + ) { + // Parse error. Ignore the token. + + /* Anything else */ + } else { + /* Parse error. Process the token as if the insertion mode was "in + body", with the following exception: */ + + /* If the current node is a table, tbody, tfoot, thead, or tr + element, then, whenever a node would be inserted into the current + node, it must instead be inserted into the foster parent element. */ + if (in_array( + end($this->stack)->nodeName, + array('table', 'tbody', 'tfoot', 'thead', 'tr') + ) + ) { + /* The foster parent element is the parent element of the last + table element in the stack of open elements, if there is a + table element and it has such a parent element. If there is no + table element in the stack of open elements (innerHTML case), + then the foster parent element is the first element in the + stack of open elements (the html element). Otherwise, if there + is a table element in the stack of open elements, but the last + table element in the stack of open elements has no parent, or + its parent node is not an element, then the foster parent + element is the element before the last table element in the + stack of open elements. */ + for ($n = count($this->stack) - 1; $n >= 0; $n--) { + if ($this->stack[$n]->nodeName === 'table') { + $table = $this->stack[$n]; + break; + } + } + + if (isset($table) && $table->parentNode !== null) { + $this->foster_parent = $table->parentNode; + + } elseif (!isset($table)) { + $this->foster_parent = $this->stack[0]; + + } elseif (isset($table) && ($table->parentNode === null || + $table->parentNode->nodeType !== XML_ELEMENT_NODE) + ) { + $this->foster_parent = $this->stack[$n - 1]; + } + } + + $this->inBody($token); + } + } + + private function inCaption($token) + { + /* An end tag whose tag name is "caption" */ + if ($token['type'] === HTML5::ENDTAG && $token['name'] === 'caption') { + /* If the stack of open elements does not have an element in table + scope with the same tag name as the token, this is a parse error. + Ignore the token. (innerHTML case) */ + if (!$this->elementInScope($token['name'], true)) { + // Ignore + + /* Otherwise: */ + } else { + /* Generate implied end tags. */ + $this->generateImpliedEndTags(); + + /* Now, if the current node is not a caption element, then this + is a parse error. */ + // w/e + + /* Pop elements from this stack until a caption element has + been popped from the stack. */ + while (true) { + $node = end($this->stack)->nodeName; + array_pop($this->stack); + + if ($node === 'caption') { + break; + } + } + + /* Clear the list of active formatting elements up to the last + marker. */ + $this->clearTheActiveFormattingElementsUpToTheLastMarker(); + + /* Switch the insertion mode to "in table". */ + $this->mode = self::IN_TABLE; + } + + /* A start tag whose tag name is one of: "caption", "col", "colgroup", + "tbody", "td", "tfoot", "th", "thead", "tr", or an end tag whose tag + name is "table" */ + } elseif (($token['type'] === HTML5::STARTTAG && in_array( + $token['name'], + array( + 'caption', + 'col', + 'colgroup', + 'tbody', + 'td', + 'tfoot', + 'th', + 'thead', + 'tr' + ) + )) || ($token['type'] === HTML5::ENDTAG && + $token['name'] === 'table') + ) { + /* Parse error. Act as if an end tag with the tag name "caption" + had been seen, then, if that token wasn't ignored, reprocess the + current token. */ + $this->inCaption( + array( + 'name' => 'caption', + 'type' => HTML5::ENDTAG + ) + ); + + return $this->inTable($token); + + /* An end tag whose tag name is one of: "body", "col", "colgroup", + "html", "tbody", "td", "tfoot", "th", "thead", "tr" */ + } elseif ($token['type'] === HTML5::ENDTAG && in_array( + $token['name'], + array( + 'body', + 'col', + 'colgroup', + 'html', + 'tbody', + 'tfoot', + 'th', + 'thead', + 'tr' + ) + ) + ) { + // Parse error. Ignore the token. + + /* Anything else */ + } else { + /* Process the token as if the insertion mode was "in body". */ + $this->inBody($token); + } + } + + private function inColumnGroup($token) + { + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + if ($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data']) + ) { + /* Append the character to the current node. */ + $text = $this->dom->createTextNode($token['data']); + end($this->stack)->appendChild($text); + + /* A comment token */ + } elseif ($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data + attribute set to the data given in the comment token. */ + $comment = $this->dom->createComment($token['data']); + end($this->stack)->appendChild($comment); + + /* A start tag whose tag name is "col" */ + } elseif ($token['type'] === HTML5::STARTTAG && $token['name'] === 'col') { + /* Insert a col element for the token. Immediately pop the current + node off the stack of open elements. */ + $this->insertElement($token); + array_pop($this->stack); + + /* An end tag whose tag name is "colgroup" */ + } elseif ($token['type'] === HTML5::ENDTAG && + $token['name'] === 'colgroup' + ) { + /* If the current node is the root html element, then this is a + parse error, ignore the token. (innerHTML case) */ + if (end($this->stack)->nodeName === 'html') { + // Ignore + + /* Otherwise, pop the current node (which will be a colgroup + element) from the stack of open elements. Switch the insertion + mode to "in table". */ + } else { + array_pop($this->stack); + $this->mode = self::IN_TABLE; + } + + /* An end tag whose tag name is "col" */ + } elseif ($token['type'] === HTML5::ENDTAG && $token['name'] === 'col') { + /* Parse error. Ignore the token. */ + + /* Anything else */ + } else { + /* Act as if an end tag with the tag name "colgroup" had been seen, + and then, if that token wasn't ignored, reprocess the current token. */ + $this->inColumnGroup( + array( + 'name' => 'colgroup', + 'type' => HTML5::ENDTAG + ) + ); + + return $this->inTable($token); + } + } + + private function inTableBody($token) + { + $clear = array('tbody', 'tfoot', 'thead', 'html'); + + /* A start tag whose tag name is "tr" */ + if ($token['type'] === HTML5::STARTTAG && $token['name'] === 'tr') { + /* Clear the stack back to a table body context. */ + $this->clearStackToTableContext($clear); + + /* Insert a tr element for the token, then switch the insertion + mode to "in row". */ + $this->insertElement($token); + $this->mode = self::IN_ROW; + + /* A start tag whose tag name is one of: "th", "td" */ + } elseif ($token['type'] === HTML5::STARTTAG && + ($token['name'] === 'th' || $token['name'] === 'td') + ) { + /* Parse error. Act as if a start tag with the tag name "tr" had + been seen, then reprocess the current token. */ + $this->inTableBody( + array( + 'name' => 'tr', + 'type' => HTML5::STARTTAG, + 'attr' => array() + ) + ); + + return $this->inRow($token); + + /* An end tag whose tag name is one of: "tbody", "tfoot", "thead" */ + } elseif ($token['type'] === HTML5::ENDTAG && + in_array($token['name'], array('tbody', 'tfoot', 'thead')) + ) { + /* If the stack of open elements does not have an element in table + scope with the same tag name as the token, this is a parse error. + Ignore the token. */ + if (!$this->elementInScope($token['name'], true)) { + // Ignore + + /* Otherwise: */ + } else { + /* Clear the stack back to a table body context. */ + $this->clearStackToTableContext($clear); + + /* Pop the current node from the stack of open elements. Switch + the insertion mode to "in table". */ + array_pop($this->stack); + $this->mode = self::IN_TABLE; + } + + /* A start tag whose tag name is one of: "caption", "col", "colgroup", + "tbody", "tfoot", "thead", or an end tag whose tag name is "table" */ + } elseif (($token['type'] === HTML5::STARTTAG && in_array( + $token['name'], + array('caption', 'col', 'colgroup', 'tbody', 'tfoor', 'thead') + )) || + ($token['type'] === HTML5::STARTTAG && $token['name'] === 'table') + ) { + /* If the stack of open elements does not have a tbody, thead, or + tfoot element in table scope, this is a parse error. Ignore the + token. (innerHTML case) */ + if (!$this->elementInScope(array('tbody', 'thead', 'tfoot'), true)) { + // Ignore. + + /* Otherwise: */ + } else { + /* Clear the stack back to a table body context. */ + $this->clearStackToTableContext($clear); + + /* Act as if an end tag with the same tag name as the current + node ("tbody", "tfoot", or "thead") had been seen, then + reprocess the current token. */ + $this->inTableBody( + array( + 'name' => end($this->stack)->nodeName, + 'type' => HTML5::ENDTAG + ) + ); + + return $this->mainPhase($token); + } + + /* An end tag whose tag name is one of: "body", "caption", "col", + "colgroup", "html", "td", "th", "tr" */ + } elseif ($token['type'] === HTML5::ENDTAG && in_array( + $token['name'], + array('body', 'caption', 'col', 'colgroup', 'html', 'td', 'th', 'tr') + ) + ) { + /* Parse error. Ignore the token. */ + + /* Anything else */ + } else { + /* Process the token as if the insertion mode was "in table". */ + $this->inTable($token); + } + } + + private function inRow($token) + { + $clear = array('tr', 'html'); + + /* A start tag whose tag name is one of: "th", "td" */ + if ($token['type'] === HTML5::STARTTAG && + ($token['name'] === 'th' || $token['name'] === 'td') + ) { + /* Clear the stack back to a table row context. */ + $this->clearStackToTableContext($clear); + + /* Insert an HTML element for the token, then switch the insertion + mode to "in cell". */ + $this->insertElement($token); + $this->mode = self::IN_CELL; + + /* Insert a marker at the end of the list of active formatting + elements. */ + $this->a_formatting[] = self::MARKER; + + /* An end tag whose tag name is "tr" */ + } elseif ($token['type'] === HTML5::ENDTAG && $token['name'] === 'tr') { + /* If the stack of open elements does not have an element in table + scope with the same tag name as the token, this is a parse error. + Ignore the token. (innerHTML case) */ + if (!$this->elementInScope($token['name'], true)) { + // Ignore. + + /* Otherwise: */ + } else { + /* Clear the stack back to a table row context. */ + $this->clearStackToTableContext($clear); + + /* Pop the current node (which will be a tr element) from the + stack of open elements. Switch the insertion mode to "in table + body". */ + array_pop($this->stack); + $this->mode = self::IN_TBODY; + } + + /* A start tag whose tag name is one of: "caption", "col", "colgroup", + "tbody", "tfoot", "thead", "tr" or an end tag whose tag name is "table" */ + } elseif ($token['type'] === HTML5::STARTTAG && in_array( + $token['name'], + array('caption', 'col', 'colgroup', 'tbody', 'tfoot', 'thead', 'tr') + ) + ) { + /* Act as if an end tag with the tag name "tr" had been seen, then, + if that token wasn't ignored, reprocess the current token. */ + $this->inRow( + array( + 'name' => 'tr', + 'type' => HTML5::ENDTAG + ) + ); + + return $this->inCell($token); + + /* An end tag whose tag name is one of: "tbody", "tfoot", "thead" */ + } elseif ($token['type'] === HTML5::ENDTAG && + in_array($token['name'], array('tbody', 'tfoot', 'thead')) + ) { + /* If the stack of open elements does not have an element in table + scope with the same tag name as the token, this is a parse error. + Ignore the token. */ + if (!$this->elementInScope($token['name'], true)) { + // Ignore. + + /* Otherwise: */ + } else { + /* Otherwise, act as if an end tag with the tag name "tr" had + been seen, then reprocess the current token. */ + $this->inRow( + array( + 'name' => 'tr', + 'type' => HTML5::ENDTAG + ) + ); + + return $this->inCell($token); + } + + /* An end tag whose tag name is one of: "body", "caption", "col", + "colgroup", "html", "td", "th" */ + } elseif ($token['type'] === HTML5::ENDTAG && in_array( + $token['name'], + array('body', 'caption', 'col', 'colgroup', 'html', 'td', 'th', 'tr') + ) + ) { + /* Parse error. Ignore the token. */ + + /* Anything else */ + } else { + /* Process the token as if the insertion mode was "in table". */ + $this->inTable($token); + } + } + + private function inCell($token) + { + /* An end tag whose tag name is one of: "td", "th" */ + if ($token['type'] === HTML5::ENDTAG && + ($token['name'] === 'td' || $token['name'] === 'th') + ) { + /* If the stack of open elements does not have an element in table + scope with the same tag name as that of the token, then this is a + parse error and the token must be ignored. */ + if (!$this->elementInScope($token['name'], true)) { + // Ignore. + + /* Otherwise: */ + } else { + /* Generate implied end tags, except for elements with the same + tag name as the token. */ + $this->generateImpliedEndTags(array($token['name'])); + + /* Now, if the current node is not an element with the same tag + name as the token, then this is a parse error. */ + // k + + /* Pop elements from this stack until an element with the same + tag name as the token has been popped from the stack. */ + while (true) { + $node = end($this->stack)->nodeName; + array_pop($this->stack); + + if ($node === $token['name']) { + break; + } + } + + /* Clear the list of active formatting elements up to the last + marker. */ + $this->clearTheActiveFormattingElementsUpToTheLastMarker(); + + /* Switch the insertion mode to "in row". (The current node + will be a tr element at this point.) */ + $this->mode = self::IN_ROW; + } + + /* A start tag whose tag name is one of: "caption", "col", "colgroup", + "tbody", "td", "tfoot", "th", "thead", "tr" */ + } elseif ($token['type'] === HTML5::STARTTAG && in_array( + $token['name'], + array( + 'caption', + 'col', + 'colgroup', + 'tbody', + 'td', + 'tfoot', + 'th', + 'thead', + 'tr' + ) + ) + ) { + /* If the stack of open elements does not have a td or th element + in table scope, then this is a parse error; ignore the token. + (innerHTML case) */ + if (!$this->elementInScope(array('td', 'th'), true)) { + // Ignore. + + /* Otherwise, close the cell (see below) and reprocess the current + token. */ + } else { + $this->closeCell(); + return $this->inRow($token); + } + + /* A start tag whose tag name is one of: "caption", "col", "colgroup", + "tbody", "td", "tfoot", "th", "thead", "tr" */ + } elseif ($token['type'] === HTML5::STARTTAG && in_array( + $token['name'], + array( + 'caption', + 'col', + 'colgroup', + 'tbody', + 'td', + 'tfoot', + 'th', + 'thead', + 'tr' + ) + ) + ) { + /* If the stack of open elements does not have a td or th element + in table scope, then this is a parse error; ignore the token. + (innerHTML case) */ + if (!$this->elementInScope(array('td', 'th'), true)) { + // Ignore. + + /* Otherwise, close the cell (see below) and reprocess the current + token. */ + } else { + $this->closeCell(); + return $this->inRow($token); + } + + /* An end tag whose tag name is one of: "body", "caption", "col", + "colgroup", "html" */ + } elseif ($token['type'] === HTML5::ENDTAG && in_array( + $token['name'], + array('body', 'caption', 'col', 'colgroup', 'html') + ) + ) { + /* Parse error. Ignore the token. */ + + /* An end tag whose tag name is one of: "table", "tbody", "tfoot", + "thead", "tr" */ + } elseif ($token['type'] === HTML5::ENDTAG && in_array( + $token['name'], + array('table', 'tbody', 'tfoot', 'thead', 'tr') + ) + ) { + /* If the stack of open elements does not have an element in table + scope with the same tag name as that of the token (which can only + happen for "tbody", "tfoot" and "thead", or, in the innerHTML case), + then this is a parse error and the token must be ignored. */ + if (!$this->elementInScope($token['name'], true)) { + // Ignore. + + /* Otherwise, close the cell (see below) and reprocess the current + token. */ + } else { + $this->closeCell(); + return $this->inRow($token); + } + + /* Anything else */ + } else { + /* Process the token as if the insertion mode was "in body". */ + $this->inBody($token); + } + } + + private function inSelect($token) + { + /* Handle the token as follows: */ + + /* A character token */ + if ($token['type'] === HTML5::CHARACTR) { + /* Append the token's character to the current node. */ + $this->insertText($token['data']); + + /* A comment token */ + } elseif ($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data + attribute set to the data given in the comment token. */ + $this->insertComment($token['data']); + + /* A start tag token whose tag name is "option" */ + } elseif ($token['type'] === HTML5::STARTTAG && + $token['name'] === 'option' + ) { + /* If the current node is an option element, act as if an end tag + with the tag name "option" had been seen. */ + if (end($this->stack)->nodeName === 'option') { + $this->inSelect( + array( + 'name' => 'option', + 'type' => HTML5::ENDTAG + ) + ); + } + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* A start tag token whose tag name is "optgroup" */ + } elseif ($token['type'] === HTML5::STARTTAG && + $token['name'] === 'optgroup' + ) { + /* If the current node is an option element, act as if an end tag + with the tag name "option" had been seen. */ + if (end($this->stack)->nodeName === 'option') { + $this->inSelect( + array( + 'name' => 'option', + 'type' => HTML5::ENDTAG + ) + ); + } + + /* If the current node is an optgroup element, act as if an end tag + with the tag name "optgroup" had been seen. */ + if (end($this->stack)->nodeName === 'optgroup') { + $this->inSelect( + array( + 'name' => 'optgroup', + 'type' => HTML5::ENDTAG + ) + ); + } + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* An end tag token whose tag name is "optgroup" */ + } elseif ($token['type'] === HTML5::ENDTAG && + $token['name'] === 'optgroup' + ) { + /* First, if the current node is an option element, and the node + immediately before it in the stack of open elements is an optgroup + element, then act as if an end tag with the tag name "option" had + been seen. */ + $elements_in_stack = count($this->stack); + + if ($this->stack[$elements_in_stack - 1]->nodeName === 'option' && + $this->stack[$elements_in_stack - 2]->nodeName === 'optgroup' + ) { + $this->inSelect( + array( + 'name' => 'option', + 'type' => HTML5::ENDTAG + ) + ); + } + + /* If the current node is an optgroup element, then pop that node + from the stack of open elements. Otherwise, this is a parse error, + ignore the token. */ + if ($this->stack[$elements_in_stack - 1] === 'optgroup') { + array_pop($this->stack); + } + + /* An end tag token whose tag name is "option" */ + } elseif ($token['type'] === HTML5::ENDTAG && + $token['name'] === 'option' + ) { + /* If the current node is an option element, then pop that node + from the stack of open elements. Otherwise, this is a parse error, + ignore the token. */ + if (end($this->stack)->nodeName === 'option') { + array_pop($this->stack); + } + + /* An end tag whose tag name is "select" */ + } elseif ($token['type'] === HTML5::ENDTAG && + $token['name'] === 'select' + ) { + /* If the stack of open elements does not have an element in table + scope with the same tag name as the token, this is a parse error. + Ignore the token. (innerHTML case) */ + if (!$this->elementInScope($token['name'], true)) { + // w/e + + /* Otherwise: */ + } else { + /* Pop elements from the stack of open elements until a select + element has been popped from the stack. */ + while (true) { + $current = end($this->stack)->nodeName; + array_pop($this->stack); + + if ($current === 'select') { + break; + } + } + + /* Reset the insertion mode appropriately. */ + $this->resetInsertionMode(); + } + + /* A start tag whose tag name is "select" */ + } elseif ($token['name'] === 'select' && + $token['type'] === HTML5::STARTTAG + ) { + /* Parse error. Act as if the token had been an end tag with the + tag name "select" instead. */ + $this->inSelect( + array( + 'name' => 'select', + 'type' => HTML5::ENDTAG + ) + ); + + /* An end tag whose tag name is one of: "caption", "table", "tbody", + "tfoot", "thead", "tr", "td", "th" */ + } elseif (in_array( + $token['name'], + array( + 'caption', + 'table', + 'tbody', + 'tfoot', + 'thead', + 'tr', + 'td', + 'th' + ) + ) && $token['type'] === HTML5::ENDTAG + ) { + /* Parse error. */ + // w/e + + /* If the stack of open elements has an element in table scope with + the same tag name as that of the token, then act as if an end tag + with the tag name "select" had been seen, and reprocess the token. + Otherwise, ignore the token. */ + if ($this->elementInScope($token['name'], true)) { + $this->inSelect( + array( + 'name' => 'select', + 'type' => HTML5::ENDTAG + ) + ); + + $this->mainPhase($token); + } + + /* Anything else */ + } else { + /* Parse error. Ignore the token. */ + } + } + + private function afterBody($token) + { + /* Handle the token as follows: */ + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + if ($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data']) + ) { + /* Process the token as it would be processed if the insertion mode + was "in body". */ + $this->inBody($token); + + /* A comment token */ + } elseif ($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the first element in the stack of open + elements (the html element), with the data attribute set to the + data given in the comment token. */ + $comment = $this->dom->createComment($token['data']); + $this->stack[0]->appendChild($comment); + + /* An end tag with the tag name "html" */ + } elseif ($token['type'] === HTML5::ENDTAG && $token['name'] === 'html') { + /* If the parser was originally created in order to handle the + setting of an element's innerHTML attribute, this is a parse error; + ignore the token. (The element will be an html element in this + case.) (innerHTML case) */ + + /* Otherwise, switch to the trailing end phase. */ + $this->phase = self::END_PHASE; + + /* Anything else */ + } else { + /* Parse error. Set the insertion mode to "in body" and reprocess + the token. */ + $this->mode = self::IN_BODY; + return $this->inBody($token); + } + } + + private function inFrameset($token) + { + /* Handle the token as follows: */ + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + U+000D CARRIAGE RETURN (CR), or U+0020 SPACE */ + if ($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data']) + ) { + /* Append the character to the current node. */ + $this->insertText($token['data']); + + /* A comment token */ + } elseif ($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data + attribute set to the data given in the comment token. */ + $this->insertComment($token['data']); + + /* A start tag with the tag name "frameset" */ + } elseif ($token['name'] === 'frameset' && + $token['type'] === HTML5::STARTTAG + ) { + $this->insertElement($token); + + /* An end tag with the tag name "frameset" */ + } elseif ($token['name'] === 'frameset' && + $token['type'] === HTML5::ENDTAG + ) { + /* If the current node is the root html element, then this is a + parse error; ignore the token. (innerHTML case) */ + if (end($this->stack)->nodeName === 'html') { + // Ignore + + } else { + /* Otherwise, pop the current node from the stack of open + elements. */ + array_pop($this->stack); + + /* If the parser was not originally created in order to handle + the setting of an element's innerHTML attribute (innerHTML case), + and the current node is no longer a frameset element, then change + the insertion mode to "after frameset". */ + $this->mode = self::AFTR_FRAME; + } + + /* A start tag with the tag name "frame" */ + } elseif ($token['name'] === 'frame' && + $token['type'] === HTML5::STARTTAG + ) { + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Immediately pop the current node off the stack of open elements. */ + array_pop($this->stack); + + /* A start tag with the tag name "noframes" */ + } elseif ($token['name'] === 'noframes' && + $token['type'] === HTML5::STARTTAG + ) { + /* Process the token as if the insertion mode had been "in body". */ + $this->inBody($token); + + /* Anything else */ + } else { + /* Parse error. Ignore the token. */ + } + } + + private function afterFrameset($token) + { + /* Handle the token as follows: */ + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + U+000D CARRIAGE RETURN (CR), or U+0020 SPACE */ + if ($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data']) + ) { + /* Append the character to the current node. */ + $this->insertText($token['data']); + + /* A comment token */ + } elseif ($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data + attribute set to the data given in the comment token. */ + $this->insertComment($token['data']); + + /* An end tag with the tag name "html" */ + } elseif ($token['name'] === 'html' && + $token['type'] === HTML5::ENDTAG + ) { + /* Switch to the trailing end phase. */ + $this->phase = self::END_PHASE; + + /* A start tag with the tag name "noframes" */ + } elseif ($token['name'] === 'noframes' && + $token['type'] === HTML5::STARTTAG + ) { + /* Process the token as if the insertion mode had been "in body". */ + $this->inBody($token); + + /* Anything else */ + } else { + /* Parse error. Ignore the token. */ + } + } + + private function trailingEndPhase($token) + { + /* After the main phase, as each token is emitted from the tokenisation + stage, it must be processed as described in this section. */ + + /* A DOCTYPE token */ + if ($token['type'] === HTML5::DOCTYPE) { + // Parse error. Ignore the token. + + /* A comment token */ + } elseif ($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the Document object with the data + attribute set to the data given in the comment token. */ + $comment = $this->dom->createComment($token['data']); + $this->dom->appendChild($comment); + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + } elseif ($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data']) + ) { + /* Process the token as it would be processed in the main phase. */ + $this->mainPhase($token); + + /* A character token that is not one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE. Or a start tag token. Or an end tag token. */ + } elseif (($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) || + $token['type'] === HTML5::STARTTAG || $token['type'] === HTML5::ENDTAG + ) { + /* Parse error. Switch back to the main phase and reprocess the + token. */ + $this->phase = self::MAIN_PHASE; + return $this->mainPhase($token); + + /* An end-of-file token */ + } elseif ($token['type'] === HTML5::EOF) { + /* OMG DONE!! */ + } + } + + private function insertElement($token, $append = true, $check = false) + { + // Proprietary workaround for libxml2's limitations with tag names + if ($check) { + // Slightly modified HTML5 tag-name modification, + // removing anything that's not an ASCII letter, digit, or hyphen + $token['name'] = preg_replace('/[^a-z0-9-]/i', '', $token['name']); + // Remove leading hyphens and numbers + $token['name'] = ltrim($token['name'], '-0..9'); + // In theory, this should ever be needed, but just in case + if ($token['name'] === '') { + $token['name'] = 'span'; + } // arbitrary generic choice + } + + $el = $this->dom->createElement($token['name']); + + foreach ($token['attr'] as $attr) { + if (!$el->hasAttribute($attr['name'])) { + $el->setAttribute($attr['name'], $attr['value']); + } + } + + $this->appendToRealParent($el); + $this->stack[] = $el; + + return $el; + } + + private function insertText($data) + { + $text = $this->dom->createTextNode($data); + $this->appendToRealParent($text); + } + + private function insertComment($data) + { + $comment = $this->dom->createComment($data); + $this->appendToRealParent($comment); + } + + private function appendToRealParent($node) + { + if ($this->foster_parent === null) { + end($this->stack)->appendChild($node); + + } elseif ($this->foster_parent !== null) { + /* If the foster parent element is the parent element of the + last table element in the stack of open elements, then the new + node must be inserted immediately before the last table element + in the stack of open elements in the foster parent element; + otherwise, the new node must be appended to the foster parent + element. */ + for ($n = count($this->stack) - 1; $n >= 0; $n--) { + if ($this->stack[$n]->nodeName === 'table' && + $this->stack[$n]->parentNode !== null + ) { + $table = $this->stack[$n]; + break; + } + } + + if (isset($table) && $this->foster_parent->isSameNode($table->parentNode)) { + $this->foster_parent->insertBefore($node, $table); + } else { + $this->foster_parent->appendChild($node); + } + + $this->foster_parent = null; + } + } + + private function elementInScope($el, $table = false) + { + if (is_array($el)) { + foreach ($el as $element) { + if ($this->elementInScope($element, $table)) { + return true; + } + } + + return false; + } + + $leng = count($this->stack); + + for ($n = 0; $n < $leng; $n++) { + /* 1. Initialise node to be the current node (the bottommost node of + the stack). */ + $node = $this->stack[$leng - 1 - $n]; + + if ($node->tagName === $el) { + /* 2. If node is the target node, terminate in a match state. */ + return true; + + } elseif ($node->tagName === 'table') { + /* 3. Otherwise, if node is a table element, terminate in a failure + state. */ + return false; + + } elseif ($table === true && in_array( + $node->tagName, + array( + 'caption', + 'td', + 'th', + 'button', + 'marquee', + 'object' + ) + ) + ) { + /* 4. Otherwise, if the algorithm is the "has an element in scope" + variant (rather than the "has an element in table scope" variant), + and node is one of the following, terminate in a failure state. */ + return false; + + } elseif ($node === $node->ownerDocument->documentElement) { + /* 5. Otherwise, if node is an html element (root element), terminate + in a failure state. (This can only happen if the node is the topmost + node of the stack of open elements, and prevents the next step from + being invoked if there are no more elements in the stack.) */ + return false; + } + + /* Otherwise, set node to the previous entry in the stack of open + elements and return to step 2. (This will never fail, since the loop + will always terminate in the previous step if the top of the stack + is reached.) */ + } + } + + private function reconstructActiveFormattingElements() + { + /* 1. If there are no entries in the list of active formatting elements, + then there is nothing to reconstruct; stop this algorithm. */ + $formatting_elements = count($this->a_formatting); + + if ($formatting_elements === 0) { + return false; + } + + /* 3. Let entry be the last (most recently added) element in the list + of active formatting elements. */ + $entry = end($this->a_formatting); + + /* 2. If the last (most recently added) entry in the list of active + formatting elements is a marker, or if it is an element that is in the + stack of open elements, then there is nothing to reconstruct; stop this + algorithm. */ + if ($entry === self::MARKER || in_array($entry, $this->stack, true)) { + return false; + } + + for ($a = $formatting_elements - 1; $a >= 0; true) { + /* 4. If there are no entries before entry in the list of active + formatting elements, then jump to step 8. */ + if ($a === 0) { + $step_seven = false; + break; + } + + /* 5. Let entry be the entry one earlier than entry in the list of + active formatting elements. */ + $a--; + $entry = $this->a_formatting[$a]; + + /* 6. If entry is neither a marker nor an element that is also in + thetack of open elements, go to step 4. */ + if ($entry === self::MARKER || in_array($entry, $this->stack, true)) { + break; + } + } + + while (true) { + /* 7. Let entry be the element one later than entry in the list of + active formatting elements. */ + if (isset($step_seven) && $step_seven === true) { + $a++; + $entry = $this->a_formatting[$a]; + } + + /* 8. Perform a shallow clone of the element entry to obtain clone. */ + $clone = $entry->cloneNode(); + + /* 9. Append clone to the current node and push it onto the stack + of open elements so that it is the new current node. */ + end($this->stack)->appendChild($clone); + $this->stack[] = $clone; + + /* 10. Replace the entry for entry in the list with an entry for + clone. */ + $this->a_formatting[$a] = $clone; + + /* 11. If the entry for clone in the list of active formatting + elements is not the last entry in the list, return to step 7. */ + if (end($this->a_formatting) !== $clone) { + $step_seven = true; + } else { + break; + } + } + } + + private function clearTheActiveFormattingElementsUpToTheLastMarker() + { + /* When the steps below require the UA to clear the list of active + formatting elements up to the last marker, the UA must perform the + following steps: */ + + while (true) { + /* 1. Let entry be the last (most recently added) entry in the list + of active formatting elements. */ + $entry = end($this->a_formatting); + + /* 2. Remove entry from the list of active formatting elements. */ + array_pop($this->a_formatting); + + /* 3. If entry was a marker, then stop the algorithm at this point. + The list has been cleared up to the last marker. */ + if ($entry === self::MARKER) { + break; + } + } + } + + private function generateImpliedEndTags($exclude = array()) + { + /* When the steps below require the UA to generate implied end tags, + then, if the current node is a dd element, a dt element, an li element, + a p element, a td element, a th element, or a tr element, the UA must + act as if an end tag with the respective tag name had been seen and + then generate implied end tags again. */ + $node = end($this->stack); + $elements = array_diff(array('dd', 'dt', 'li', 'p', 'td', 'th', 'tr'), $exclude); + + while (in_array(end($this->stack)->nodeName, $elements)) { + array_pop($this->stack); + } + } + + private function getElementCategory($node) + { + $name = $node->tagName; + if (in_array($name, $this->special)) { + return self::SPECIAL; + } elseif (in_array($name, $this->scoping)) { + return self::SCOPING; + } elseif (in_array($name, $this->formatting)) { + return self::FORMATTING; + } else { + return self::PHRASING; + } + } + + private function clearStackToTableContext($elements) + { + /* When the steps above require the UA to clear the stack back to a + table context, it means that the UA must, while the current node is not + a table element or an html element, pop elements from the stack of open + elements. If this causes any elements to be popped from the stack, then + this is a parse error. */ + while (true) { + $node = end($this->stack)->nodeName; + + if (in_array($node, $elements)) { + break; + } else { + array_pop($this->stack); + } + } + } + + private function resetInsertionMode() + { + /* 1. Let last be false. */ + $last = false; + $leng = count($this->stack); + + for ($n = $leng - 1; $n >= 0; $n--) { + /* 2. Let node be the last node in the stack of open elements. */ + $node = $this->stack[$n]; + + /* 3. If node is the first node in the stack of open elements, then + set last to true. If the element whose innerHTML attribute is being + set is neither a td element nor a th element, then set node to the + element whose innerHTML attribute is being set. (innerHTML case) */ + if ($this->stack[0]->isSameNode($node)) { + $last = true; + } + + /* 4. If node is a select element, then switch the insertion mode to + "in select" and abort these steps. (innerHTML case) */ + if ($node->nodeName === 'select') { + $this->mode = self::IN_SELECT; + break; + + /* 5. If node is a td or th element, then switch the insertion mode + to "in cell" and abort these steps. */ + } elseif ($node->nodeName === 'td' || $node->nodeName === 'th') { + $this->mode = self::IN_CELL; + break; + + /* 6. If node is a tr element, then switch the insertion mode to + "in row" and abort these steps. */ + } elseif ($node->nodeName === 'tr') { + $this->mode = self::IN_ROW; + break; + + /* 7. If node is a tbody, thead, or tfoot element, then switch the + insertion mode to "in table body" and abort these steps. */ + } elseif (in_array($node->nodeName, array('tbody', 'thead', 'tfoot'))) { + $this->mode = self::IN_TBODY; + break; + + /* 8. If node is a caption element, then switch the insertion mode + to "in caption" and abort these steps. */ + } elseif ($node->nodeName === 'caption') { + $this->mode = self::IN_CAPTION; + break; + + /* 9. If node is a colgroup element, then switch the insertion mode + to "in column group" and abort these steps. (innerHTML case) */ + } elseif ($node->nodeName === 'colgroup') { + $this->mode = self::IN_CGROUP; + break; + + /* 10. If node is a table element, then switch the insertion mode + to "in table" and abort these steps. */ + } elseif ($node->nodeName === 'table') { + $this->mode = self::IN_TABLE; + break; + + /* 11. If node is a head element, then switch the insertion mode + to "in body" ("in body"! not "in head"!) and abort these steps. + (innerHTML case) */ + } elseif ($node->nodeName === 'head') { + $this->mode = self::IN_BODY; + break; + + /* 12. If node is a body element, then switch the insertion mode to + "in body" and abort these steps. */ + } elseif ($node->nodeName === 'body') { + $this->mode = self::IN_BODY; + break; + + /* 13. If node is a frameset element, then switch the insertion + mode to "in frameset" and abort these steps. (innerHTML case) */ + } elseif ($node->nodeName === 'frameset') { + $this->mode = self::IN_FRAME; + break; + + /* 14. If node is an html element, then: if the head element + pointer is null, switch the insertion mode to "before head", + otherwise, switch the insertion mode to "after head". In either + case, abort these steps. (innerHTML case) */ + } elseif ($node->nodeName === 'html') { + $this->mode = ($this->head_pointer === null) + ? self::BEFOR_HEAD + : self::AFTER_HEAD; + + break; + + /* 15. If last is true, then set the insertion mode to "in body" + and abort these steps. (innerHTML case) */ + } elseif ($last) { + $this->mode = self::IN_BODY; + break; + } + } + } + + private function closeCell() + { + /* If the stack of open elements has a td or th element in table scope, + then act as if an end tag token with that tag name had been seen. */ + foreach (array('td', 'th') as $cell) { + if ($this->elementInScope($cell, true)) { + $this->inCell( + array( + 'name' => $cell, + 'type' => HTML5::ENDTAG + ) + ); + + break; + } + } + } + + public function save() + { + return $this->dom; + } +} diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/Printer.php b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/Printer.php new file mode 100644 index 000000000..16acd4157 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/Printer.php @@ -0,0 +1,218 @@ +getAll(); + $context = new HTMLPurifier_Context(); + $this->generator = new HTMLPurifier_Generator($config, $context); + } + + /** + * Main function that renders object or aspect of that object + * @note Parameters vary depending on printer + */ + // function render() {} + + /** + * Returns a start tag + * @param string $tag Tag name + * @param array $attr Attribute array + * @return string + */ + protected function start($tag, $attr = array()) + { + return $this->generator->generateFromToken( + new HTMLPurifier_Token_Start($tag, $attr ? $attr : array()) + ); + } + + /** + * Returns an end tag + * @param string $tag Tag name + * @return string + */ + protected function end($tag) + { + return $this->generator->generateFromToken( + new HTMLPurifier_Token_End($tag) + ); + } + + /** + * Prints a complete element with content inside + * @param string $tag Tag name + * @param string $contents Element contents + * @param array $attr Tag attributes + * @param bool $escape whether or not to escape contents + * @return string + */ + protected function element($tag, $contents, $attr = array(), $escape = true) + { + return $this->start($tag, $attr) . + ($escape ? $this->escape($contents) : $contents) . + $this->end($tag); + } + + /** + * @param string $tag + * @param array $attr + * @return string + */ + protected function elementEmpty($tag, $attr = array()) + { + return $this->generator->generateFromToken( + new HTMLPurifier_Token_Empty($tag, $attr) + ); + } + + /** + * @param string $text + * @return string + */ + protected function text($text) + { + return $this->generator->generateFromToken( + new HTMLPurifier_Token_Text($text) + ); + } + + /** + * Prints a simple key/value row in a table. + * @param string $name Key + * @param mixed $value Value + * @return string + */ + protected function row($name, $value) + { + if (is_bool($value)) { + $value = $value ? 'On' : 'Off'; + } + return + $this->start('tr') . "\n" . + $this->element('th', $name) . "\n" . + $this->element('td', $value) . "\n" . + $this->end('tr'); + } + + /** + * Escapes a string for HTML output. + * @param string $string String to escape + * @return string + */ + protected function escape($string) + { + $string = HTMLPurifier_Encoder::cleanUTF8($string); + $string = htmlspecialchars($string, ENT_COMPAT, 'UTF-8'); + return $string; + } + + /** + * Takes a list of strings and turns them into a single list + * @param string[] $array List of strings + * @param bool $polite Bool whether or not to add an end before the last + * @return string + */ + protected function listify($array, $polite = false) + { + if (empty($array)) { + return 'None'; + } + $ret = ''; + $i = count($array); + foreach ($array as $value) { + $i--; + $ret .= $value; + if ($i > 0 && !($polite && $i == 1)) { + $ret .= ', '; + } + if ($polite && $i == 1) { + $ret .= 'and '; + } + } + return $ret; + } + + /** + * Retrieves the class of an object without prefixes, as well as metadata + * @param object $obj Object to determine class of + * @param string $sec_prefix Further prefix to remove + * @return string + */ + protected function getClass($obj, $sec_prefix = '') + { + static $five = null; + if ($five === null) { + $five = version_compare(PHP_VERSION, '5', '>='); + } + $prefix = 'HTMLPurifier_' . $sec_prefix; + if (!$five) { + $prefix = strtolower($prefix); + } + $class = str_replace($prefix, '', get_class($obj)); + $lclass = strtolower($class); + $class .= '('; + switch ($lclass) { + case 'enum': + $values = array(); + foreach ($obj->valid_values as $value => $bool) { + $values[] = $value; + } + $class .= implode(', ', $values); + break; + case 'css_composite': + $values = array(); + foreach ($obj->defs as $def) { + $values[] = $this->getClass($def, $sec_prefix); + } + $class .= implode(', ', $values); + break; + case 'css_multiple': + $class .= $this->getClass($obj->single, $sec_prefix) . ', '; + $class .= $obj->max; + break; + case 'css_denyelementdecorator': + $class .= $this->getClass($obj->def, $sec_prefix) . ', '; + $class .= $obj->element; + break; + case 'css_importantdecorator': + $class .= $this->getClass($obj->def, $sec_prefix); + if ($obj->allow) { + $class .= ', !important'; + } + break; + } + $class .= ')'; + return $class; + } +} + +// vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/Printer/CSSDefinition.php b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/Printer/CSSDefinition.php new file mode 100644 index 000000000..afc8c18ab --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/Printer/CSSDefinition.php @@ -0,0 +1,44 @@ +def = $config->getCSSDefinition(); + $ret = ''; + + $ret .= $this->start('div', array('class' => 'HTMLPurifier_Printer')); + $ret .= $this->start('table'); + + $ret .= $this->element('caption', 'Properties ($info)'); + + $ret .= $this->start('thead'); + $ret .= $this->start('tr'); + $ret .= $this->element('th', 'Property', array('class' => 'heavy')); + $ret .= $this->element('th', 'Definition', array('class' => 'heavy', 'style' => 'width:auto;')); + $ret .= $this->end('tr'); + $ret .= $this->end('thead'); + + ksort($this->def->info); + foreach ($this->def->info as $property => $obj) { + $name = $this->getClass($obj, 'AttrDef_'); + $ret .= $this->row($property, $name); + } + + $ret .= $this->end('table'); + $ret .= $this->end('div'); + + return $ret; + } +} + +// vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/Printer/ConfigForm.css b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/Printer/ConfigForm.css new file mode 100644 index 000000000..7af30fc3a --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/Printer/ConfigForm.css @@ -0,0 +1,10 @@ + +.hp-config {} + +.hp-config tbody th {text-align:right; padding-right:0.5em;} +.hp-config thead, .hp-config .namespace {background:#3C578C; color:#FFF;} +.hp-config .namespace th {text-align:center;} +.hp-config .verbose {display:none;} +.hp-config .controls {text-align:center;} + +/* vim: et sw=4 sts=4 */ diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/Printer/ConfigForm.js b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/Printer/ConfigForm.js new file mode 100644 index 000000000..83e065531 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/Printer/ConfigForm.js @@ -0,0 +1,5 @@ +function toggleWriteability(id_of_patient, checked) { + document.getElementById(id_of_patient).disabled = checked; +} + +// vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/Printer/ConfigForm.php b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/Printer/ConfigForm.php new file mode 100644 index 000000000..8897002ea --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/Printer/ConfigForm.php @@ -0,0 +1,451 @@ +docURL = $doc_url; + $this->name = $name; + $this->compress = $compress; + // initialize sub-printers + $this->fields[0] = new HTMLPurifier_Printer_ConfigForm_default(); + $this->fields[HTMLPurifier_VarParser::C_BOOL] = new HTMLPurifier_Printer_ConfigForm_bool(); + } + + /** + * Sets default column and row size for textareas in sub-printers + * @param $cols Integer columns of textarea, null to use default + * @param $rows Integer rows of textarea, null to use default + */ + public function setTextareaDimensions($cols = null, $rows = null) + { + if ($cols) { + $this->fields['default']->cols = $cols; + } + if ($rows) { + $this->fields['default']->rows = $rows; + } + } + + /** + * Retrieves styling, in case it is not accessible by webserver + */ + public static function getCSS() + { + return file_get_contents(HTMLPURIFIER_PREFIX . '/HTMLPurifier/Printer/ConfigForm.css'); + } + + /** + * Retrieves JavaScript, in case it is not accessible by webserver + */ + public static function getJavaScript() + { + return file_get_contents(HTMLPURIFIER_PREFIX . '/HTMLPurifier/Printer/ConfigForm.js'); + } + + /** + * Returns HTML output for a configuration form + * @param HTMLPurifier_Config|array $config Configuration object of current form state, or an array + * where [0] has an HTML namespace and [1] is being rendered. + * @param array|bool $allowed Optional namespace(s) and directives to restrict form to. + * @param bool $render_controls + * @return string + */ + public function render($config, $allowed = true, $render_controls = true) + { + if (is_array($config) && isset($config[0])) { + $gen_config = $config[0]; + $config = $config[1]; + } else { + $gen_config = $config; + } + + $this->config = $config; + $this->genConfig = $gen_config; + $this->prepareGenerator($gen_config); + + $allowed = HTMLPurifier_Config::getAllowedDirectivesForForm($allowed, $config->def); + $all = array(); + foreach ($allowed as $key) { + list($ns, $directive) = $key; + $all[$ns][$directive] = $config->get($ns . '.' . $directive); + } + + $ret = ''; + $ret .= $this->start('table', array('class' => 'hp-config')); + $ret .= $this->start('thead'); + $ret .= $this->start('tr'); + $ret .= $this->element('th', 'Directive', array('class' => 'hp-directive')); + $ret .= $this->element('th', 'Value', array('class' => 'hp-value')); + $ret .= $this->end('tr'); + $ret .= $this->end('thead'); + foreach ($all as $ns => $directives) { + $ret .= $this->renderNamespace($ns, $directives); + } + if ($render_controls) { + $ret .= $this->start('tbody'); + $ret .= $this->start('tr'); + $ret .= $this->start('td', array('colspan' => 2, 'class' => 'controls')); + $ret .= $this->elementEmpty('input', array('type' => 'submit', 'value' => 'Submit')); + $ret .= '[Reset]'; + $ret .= $this->end('td'); + $ret .= $this->end('tr'); + $ret .= $this->end('tbody'); + } + $ret .= $this->end('table'); + return $ret; + } + + /** + * Renders a single namespace + * @param $ns String namespace name + * @param array $directives array of directives to values + * @return string + */ + protected function renderNamespace($ns, $directives) + { + $ret = ''; + $ret .= $this->start('tbody', array('class' => 'namespace')); + $ret .= $this->start('tr'); + $ret .= $this->element('th', $ns, array('colspan' => 2)); + $ret .= $this->end('tr'); + $ret .= $this->end('tbody'); + $ret .= $this->start('tbody'); + foreach ($directives as $directive => $value) { + $ret .= $this->start('tr'); + $ret .= $this->start('th'); + if ($this->docURL) { + $url = str_replace('%s', urlencode("$ns.$directive"), $this->docURL); + $ret .= $this->start('a', array('href' => $url)); + } + $attr = array('for' => "{$this->name}:$ns.$directive"); + + // crop directive name if it's too long + if (!$this->compress || (strlen($directive) < $this->compress)) { + $directive_disp = $directive; + } else { + $directive_disp = substr($directive, 0, $this->compress - 2) . '...'; + $attr['title'] = $directive; + } + + $ret .= $this->element( + 'label', + $directive_disp, + // component printers must create an element with this id + $attr + ); + if ($this->docURL) { + $ret .= $this->end('a'); + } + $ret .= $this->end('th'); + + $ret .= $this->start('td'); + $def = $this->config->def->info["$ns.$directive"]; + if (is_int($def)) { + $allow_null = $def < 0; + $type = abs($def); + } else { + $type = $def->type; + $allow_null = isset($def->allow_null); + } + if (!isset($this->fields[$type])) { + $type = 0; + } // default + $type_obj = $this->fields[$type]; + if ($allow_null) { + $type_obj = new HTMLPurifier_Printer_ConfigForm_NullDecorator($type_obj); + } + $ret .= $type_obj->render($ns, $directive, $value, $this->name, array($this->genConfig, $this->config)); + $ret .= $this->end('td'); + $ret .= $this->end('tr'); + } + $ret .= $this->end('tbody'); + return $ret; + } + +} + +/** + * Printer decorator for directives that accept null + */ +class HTMLPurifier_Printer_ConfigForm_NullDecorator extends HTMLPurifier_Printer +{ + /** + * Printer being decorated + * @type HTMLPurifier_Printer + */ + protected $obj; + + /** + * @param HTMLPurifier_Printer $obj Printer to decorate + */ + public function __construct($obj) + { + parent::__construct(); + $this->obj = $obj; + } + + /** + * @param string $ns + * @param string $directive + * @param string $value + * @param string $name + * @param HTMLPurifier_Config|array $config + * @return string + */ + public function render($ns, $directive, $value, $name, $config) + { + if (is_array($config) && isset($config[0])) { + $gen_config = $config[0]; + $config = $config[1]; + } else { + $gen_config = $config; + } + $this->prepareGenerator($gen_config); + + $ret = ''; + $ret .= $this->start('label', array('for' => "$name:Null_$ns.$directive")); + $ret .= $this->element('span', "$ns.$directive:", array('class' => 'verbose')); + $ret .= $this->text(' Null/Disabled'); + $ret .= $this->end('label'); + $attr = array( + 'type' => 'checkbox', + 'value' => '1', + 'class' => 'null-toggle', + 'name' => "$name" . "[Null_$ns.$directive]", + 'id' => "$name:Null_$ns.$directive", + 'onclick' => "toggleWriteability('$name:$ns.$directive',checked)" // INLINE JAVASCRIPT!!!! + ); + if ($this->obj instanceof HTMLPurifier_Printer_ConfigForm_bool) { + // modify inline javascript slightly + $attr['onclick'] = + "toggleWriteability('$name:Yes_$ns.$directive',checked);" . + "toggleWriteability('$name:No_$ns.$directive',checked)"; + } + if ($value === null) { + $attr['checked'] = 'checked'; + } + $ret .= $this->elementEmpty('input', $attr); + $ret .= $this->text(' or '); + $ret .= $this->elementEmpty('br'); + $ret .= $this->obj->render($ns, $directive, $value, $name, array($gen_config, $config)); + return $ret; + } +} + +/** + * Swiss-army knife configuration form field printer + */ +class HTMLPurifier_Printer_ConfigForm_default extends HTMLPurifier_Printer +{ + /** + * @type int + */ + public $cols = 18; + + /** + * @type int + */ + public $rows = 5; + + /** + * @param string $ns + * @param string $directive + * @param string $value + * @param string $name + * @param HTMLPurifier_Config|array $config + * @return string + */ + public function render($ns, $directive, $value, $name, $config) + { + if (is_array($config) && isset($config[0])) { + $gen_config = $config[0]; + $config = $config[1]; + } else { + $gen_config = $config; + } + $this->prepareGenerator($gen_config); + // this should probably be split up a little + $ret = ''; + $def = $config->def->info["$ns.$directive"]; + if (is_int($def)) { + $type = abs($def); + } else { + $type = $def->type; + } + if (is_array($value)) { + switch ($type) { + case HTMLPurifier_VarParser::LOOKUP: + $array = $value; + $value = array(); + foreach ($array as $val => $b) { + $value[] = $val; + } + //TODO does this need a break? + case HTMLPurifier_VarParser::ALIST: + $value = implode(PHP_EOL, $value); + break; + case HTMLPurifier_VarParser::HASH: + $nvalue = ''; + foreach ($value as $i => $v) { + if (is_array($v)) { + // HACK + $v = implode(";", $v); + } + $nvalue .= "$i:$v" . PHP_EOL; + } + $value = $nvalue; + break; + default: + $value = ''; + } + } + if ($type === HTMLPurifier_VarParser::C_MIXED) { + return 'Not supported'; + $value = serialize($value); + } + $attr = array( + 'name' => "$name" . "[$ns.$directive]", + 'id' => "$name:$ns.$directive" + ); + if ($value === null) { + $attr['disabled'] = 'disabled'; + } + if (isset($def->allowed)) { + $ret .= $this->start('select', $attr); + foreach ($def->allowed as $val => $b) { + $attr = array(); + if ($value == $val) { + $attr['selected'] = 'selected'; + } + $ret .= $this->element('option', $val, $attr); + } + $ret .= $this->end('select'); + } elseif ($type === HTMLPurifier_VarParser::TEXT || + $type === HTMLPurifier_VarParser::ITEXT || + $type === HTMLPurifier_VarParser::ALIST || + $type === HTMLPurifier_VarParser::HASH || + $type === HTMLPurifier_VarParser::LOOKUP) { + $attr['cols'] = $this->cols; + $attr['rows'] = $this->rows; + $ret .= $this->start('textarea', $attr); + $ret .= $this->text($value); + $ret .= $this->end('textarea'); + } else { + $attr['value'] = $value; + $attr['type'] = 'text'; + $ret .= $this->elementEmpty('input', $attr); + } + return $ret; + } +} + +/** + * Bool form field printer + */ +class HTMLPurifier_Printer_ConfigForm_bool extends HTMLPurifier_Printer +{ + /** + * @param string $ns + * @param string $directive + * @param string $value + * @param string $name + * @param HTMLPurifier_Config|array $config + * @return string + */ + public function render($ns, $directive, $value, $name, $config) + { + if (is_array($config) && isset($config[0])) { + $gen_config = $config[0]; + $config = $config[1]; + } else { + $gen_config = $config; + } + $this->prepareGenerator($gen_config); + $ret = ''; + $ret .= $this->start('div', array('id' => "$name:$ns.$directive")); + + $ret .= $this->start('label', array('for' => "$name:Yes_$ns.$directive")); + $ret .= $this->element('span', "$ns.$directive:", array('class' => 'verbose')); + $ret .= $this->text(' Yes'); + $ret .= $this->end('label'); + + $attr = array( + 'type' => 'radio', + 'name' => "$name" . "[$ns.$directive]", + 'id' => "$name:Yes_$ns.$directive", + 'value' => '1' + ); + if ($value === true) { + $attr['checked'] = 'checked'; + } + if ($value === null) { + $attr['disabled'] = 'disabled'; + } + $ret .= $this->elementEmpty('input', $attr); + + $ret .= $this->start('label', array('for' => "$name:No_$ns.$directive")); + $ret .= $this->element('span', "$ns.$directive:", array('class' => 'verbose')); + $ret .= $this->text(' No'); + $ret .= $this->end('label'); + + $attr = array( + 'type' => 'radio', + 'name' => "$name" . "[$ns.$directive]", + 'id' => "$name:No_$ns.$directive", + 'value' => '0' + ); + if ($value === false) { + $attr['checked'] = 'checked'; + } + if ($value === null) { + $attr['disabled'] = 'disabled'; + } + $ret .= $this->elementEmpty('input', $attr); + + $ret .= $this->end('div'); + + return $ret; + } +} + +// vim: et sw=4 sts=4 diff --git a/3rdparty/HTMLPurifier/standalone/HTMLPurifier/Printer/HTMLDefinition.php b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/Printer/HTMLDefinition.php new file mode 100644 index 000000000..679d19ba3 --- /dev/null +++ b/3rdparty/HTMLPurifier/standalone/HTMLPurifier/Printer/HTMLDefinition.php @@ -0,0 +1,324 @@ +config =& $config; + + $this->def = $config->getHTMLDefinition(); + + $ret .= $this->start('div', array('class' => 'HTMLPurifier_Printer')); + + $ret .= $this->renderDoctype(); + $ret .= $this->renderEnvironment(); + $ret .= $this->renderContentSets(); + $ret .= $this->renderInfo(); + + $ret .= $this->end('div'); + + return $ret; + } + + /** + * Renders the Doctype table + * @return string + */ + protected function renderDoctype() + { + $doctype = $this->def->doctype; + $ret = ''; + $ret .= $this->start('table'); + $ret .= $this->element('caption', 'Doctype'); + $ret .= $this->row('Name', $doctype->name); + $ret .= $this->row('XML', $doctype->xml ? 'Yes' : 'No'); + $ret .= $this->row('Default Modules', implode($doctype->modules, ', ')); + $ret .= $this->row('Default Tidy Modules', implode($doctype->tidyModules, ', ')); + $ret .= $this->end('table'); + return $ret; + } + + + /** + * Renders environment table, which is miscellaneous info + * @return string + */ + protected function renderEnvironment() + { + $def = $this->def; + + $ret = ''; + + $ret .= $this->start('table'); + $ret .= $this->element('caption', 'Environment'); + + $ret .= $this->row('Parent of fragment', $def->info_parent); + $ret .= $this->renderChildren($def->info_parent_def->child); + $ret .= $this->row('Block wrap name', $def->info_block_wrapper); + + $ret .= $this->start('tr'); + $ret .= $this->element('th', 'Global attributes'); + $ret .= $this->element('td', $this->listifyAttr($def->info_global_attr), null, 0); + $ret .= $this->end('tr'); + + $ret .= $this->start('tr'); + $ret .= $this->element('th', 'Tag transforms'); + $list = array(); + foreach ($def->info_tag_transform as $old => $new) { + $new = $this->getClass($new, 'TagTransform_'); + $list[] = "<$old> with $new"; + } + $ret .= $this->element('td', $this->listify($list)); + $ret .= $this->end('tr'); + + $ret .= $this->start('tr'); + $ret .= $this->element('th', 'Pre-AttrTransform'); + $ret .= $this->element('td', $this->listifyObjectList($def->info_attr_transform_pre)); + $ret .= $this->end('tr'); + + $ret .= $this->start('tr'); + $ret .= $this->element('th', 'Post-AttrTransform'); + $ret .= $this->element('td', $this->listifyObjectList($def->info_attr_transform_post)); + $ret .= $this->end('tr'); + + $ret .= $this->end('table'); + return $ret; + } + + /** + * Renders the Content Sets table + * @return string + */ + protected function renderContentSets() + { + $ret = ''; + $ret .= $this->start('table'); + $ret .= $this->element('caption', 'Content Sets'); + foreach ($this->def->info_content_sets as $name => $lookup) { + $ret .= $this->heavyHeader($name); + $ret .= $this->start('tr'); + $ret .= $this->element('td', $this->listifyTagLookup($lookup)); + $ret .= $this->end('tr'); + } + $ret .= $this->end('table'); + return $ret; + } + + /** + * Renders the Elements ($info) table + * @return string + */ + protected function renderInfo() + { + $ret = ''; + $ret .= $this->start('table'); + $ret .= $this->element('caption', 'Elements ($info)'); + ksort($this->def->info); + $ret .= $this->heavyHeader('Allowed tags', 2); + $ret .= $this->start('tr'); + $ret .= $this->element('td', $this->listifyTagLookup($this->def->info), array('colspan' => 2)); + $ret .= $this->end('tr'); + foreach ($this->def->info as $name => $def) { + $ret .= $this->start('tr'); + $ret .= $this->element('th', "<$name>", array('class' => 'heavy', 'colspan' => 2)); + $ret .= $this->end('tr'); + $ret .= $this->start('tr'); + $ret .= $this->element('th', 'Inline content'); + $ret .= $this->element('td', $def->descendants_are_inline ? 'Yes' : 'No'); + $ret .= $this->end('tr'); + if (!empty($def->excludes)) { + $ret .= $this->start('tr'); + $ret .= $this->element('th', 'Excludes'); + $ret .= $this->element('td', $this->listifyTagLookup($def->excludes)); + $ret .= $this->end('tr'); + } + if (!empty($def->attr_transform_pre)) { + $ret .= $this->start('tr'); + $ret .= $this->element('th', 'Pre-AttrTransform'); + $ret .= $this->element('td', $this->listifyObjectList($def->attr_transform_pre)); + $ret .= $this->end('tr'); + } + if (!empty($def->attr_transform_post)) { + $ret .= $this->start('tr'); + $ret .= $this->element('th', 'Post-AttrTransform'); + $ret .= $this->element('td', $this->listifyObjectList($def->attr_transform_post)); + $ret .= $this->end('tr'); + } + if (!empty($def->auto_close)) { + $ret .= $this->start('tr'); + $ret .= $this->element('th', 'Auto closed by'); + $ret .= $this->element('td', $this->listifyTagLookup($def->auto_close)); + $ret .= $this->end('tr'); + } + $ret .= $this->start('tr'); + $ret .= $this->element('th', 'Allowed attributes'); + $ret .= $this->element('td', $this->listifyAttr($def->attr), array(), 0); + $ret .= $this->end('tr'); + + if (!empty($def->required_attr)) { + $ret .= $this->row('Required attributes', $this->listify($def->required_attr)); + } + + $ret .= $this->renderChildren($def->child); + } + $ret .= $this->end('table'); + return $ret; + } + + /** + * Renders a row describing the allowed children of an element + * @param HTMLPurifier_ChildDef $def HTMLPurifier_ChildDef of pertinent element + * @return string + */ + protected function renderChildren($def) + { + $context = new HTMLPurifier_Context(); + $ret = ''; + $ret .= $this->start('tr'); + $elements = array(); + $attr = array(); + if (isset($def->elements)) { + if ($def->type == 'strictblockquote') { + $def->validateChildren(array(), $this->config, $context); + } + $elements = $def->elements; + } + if ($def->type == 'chameleon') { + $attr['rowspan'] = 2; + } elseif ($def->type == 'empty') { + $elements = array(); + } elseif ($def->type == 'table') { + $elements = array_flip( + array( + 'col', + 'caption', + 'colgroup', + 'thead', + 'tfoot', + 'tbody', + 'tr' + ) + ); + } + $ret .= $this->element('th', 'Allowed children', $attr); + + if ($def->type == 'chameleon') { + + $ret .= $this->element( + 'td', + 'Block: ' . + $this->escape($this->listifyTagLookup($def->block->elements)), + null, + 0 + ); + $ret .= $this->end('tr'); + $ret .= $this->start('tr'); + $ret .= $this->element( + 'td', + 'Inline: ' . + $this->escape($this->listifyTagLookup($def->inline->elements)), + null, + 0 + ); + + } elseif ($def->type == 'custom') { + + $ret .= $this->element( + 'td', + '' . ucfirst($def->type) . ': ' . + $def->dtd_regex + ); + + } else { + $ret .= $this->element( + 'td', + '' . ucfirst($def->type) . ': ' . + $this->escape($this->listifyTagLookup($elements)), + null, + 0 + ); + } + $ret .= $this->end('tr'); + return $ret; + } + + /** + * Listifies a tag lookup table. + * @param array $array Tag lookup array in form of array('tagname' => true) + * @return string + */ + protected function listifyTagLookup($array) + { + ksort($array); + $list = array(); + foreach ($array as $name => $discard) { + if ($name !== '#PCDATA' && !isset($this->def->info[$name])) { + continue; + } + $list[] = $name; + } + return $this->listify($list); + } + + /** + * Listifies a list of objects by retrieving class names and internal state + * @param array $array List of objects + * @return string + * @todo Also add information about internal state + */ + protected function listifyObjectList($array) + { + ksort($array); + $list = array(); + foreach ($array as $obj) { + $list[] = $this->getClass($obj, 'AttrTransform_'); + } + return $this->listify($list); + } + + /** + * Listifies a hash of attributes to AttrDef classes + * @param array $array Array hash in form of array('attrname' => HTMLPurifier_AttrDef) + * @return string + */ + protected function listifyAttr($array) + { + ksort($array); + $list = array(); + foreach ($array as $name => $obj) { + if ($obj === false) { + continue; + } + $list[] = "$name = " . $this->getClass($obj, 'AttrDef_') . ''; + } + return $this->listify($list); + } + + /** + * Creates a heavy header row + * @param string $text + * @param int $num + * @return string + */ + protected function heavyHeader($text, $num = 1) + { + $ret = ''; + $ret .= $this->start('tr'); + $ret .= $this->element('th', $text, array('colspan' => $num, 'class' => 'heavy')); + $ret .= $this->end('tr'); + return $ret; + } +} + +// vim: et sw=4 sts=4 diff --git a/COPYING b/COPYING new file mode 100644 index 000000000..bae94e189 --- /dev/null +++ b/COPYING @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. \ No newline at end of file diff --git a/Email/assets/css/foundation.css b/Email/assets/css/foundation.css new file mode 100644 index 000000000..acd1b9827 --- /dev/null +++ b/Email/assets/css/foundation.css @@ -0,0 +1,1348 @@ +.wrapper { + width: 100%; } + +#outlook a { + padding: 0; } + +body { + width: 100% !important; + min-width: 100%; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; + margin: 0; + Margin: 0; + padding: 0; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; } + +.ExternalClass { + width: 100%; } + .ExternalClass, + .ExternalClass p, + .ExternalClass span, + .ExternalClass font, + .ExternalClass td, + .ExternalClass div { + line-height: 100%; } + +#backgroundTable { + margin: 0; + Margin: 0; + padding: 0; + width: 100% !important; + line-height: 100% !important; } + +img { + outline: none; + text-decoration: none; + -ms-interpolation-mode: bicubic; + width: auto; + max-width: 100%; + clear: both; + display: block; } + +center { + width: 100%; + min-width: 580px; } + +a img { + border: none; } + +p { + margin: 0 0 0 10px; + Margin: 0 0 0 10px; } + +table { + border-spacing: 0; + border-collapse: collapse; } + +td { + word-wrap: break-word; + -webkit-hyphens: auto; + -moz-hyphens: auto; + hyphens: auto; + border-collapse: collapse !important; } + +table, tr, td { + padding: 0; + vertical-align: top; + text-align: left; } + +@media only screen { + html { + min-height: 100%; + background: #f3f3f3; } } + +table.body { + background: #f3f3f3; + height: 100%; + width: 100%; } + +table.container { + background: #fefefe; + width: 580px; + margin: 0 auto; + Margin: 0 auto; + text-align: inherit; } + +table.row { + padding: 0; + width: 100%; + position: relative; } + +table.spacer { + width: 100%; } + table.spacer td { + mso-line-height-rule: exactly; } + +table.container table.row { + display: table; } + +td.columns, +td.column, +th.columns, +th.column { + margin: 0 auto; + Margin: 0 auto; + padding-left: 16px; + padding-bottom: 16px; } + td.columns .column, + td.columns .columns, + td.column .column, + td.column .columns, + th.columns .column, + th.columns .columns, + th.column .column, + th.column .columns { + padding-left: 0 !important; + padding-right: 0 !important; } + td.columns .column center, + td.columns .columns center, + td.column .column center, + td.column .columns center, + th.columns .column center, + th.columns .columns center, + th.column .column center, + th.column .columns center { + min-width: none !important; } + +td.columns.last, +td.column.last, +th.columns.last, +th.column.last { + padding-right: 16px; } + +td.columns table:not(.button), +td.column table:not(.button), +th.columns table:not(.button), +th.column table:not(.button) { + width: 100%; } + +td.large-1, +th.large-1 { + width: 32.33333px; + padding-left: 8px; + padding-right: 8px; } + +td.large-1.first, +th.large-1.first { + padding-left: 16px; } + +td.large-1.last, +th.large-1.last { + padding-right: 16px; } + +.collapse > tbody > tr > td.large-1, +.collapse > tbody > tr > th.large-1 { + padding-right: 0; + padding-left: 0; + width: 48.33333px; } + +.collapse td.large-1.first, +.collapse th.large-1.first, +.collapse td.large-1.last, +.collapse th.large-1.last { + width: 56.33333px; } + +td.large-1 center, +th.large-1 center { + min-width: 0.33333px; } + +.body .columns td.large-1, +.body .column td.large-1, +.body .columns th.large-1, +.body .column th.large-1 { + width: 8.33333%; } + +td.large-2, +th.large-2 { + width: 80.66667px; + padding-left: 8px; + padding-right: 8px; } + +td.large-2.first, +th.large-2.first { + padding-left: 16px; } + +td.large-2.last, +th.large-2.last { + padding-right: 16px; } + +.collapse > tbody > tr > td.large-2, +.collapse > tbody > tr > th.large-2 { + padding-right: 0; + padding-left: 0; + width: 96.66667px; } + +.collapse td.large-2.first, +.collapse th.large-2.first, +.collapse td.large-2.last, +.collapse th.large-2.last { + width: 104.66667px; } + +td.large-2 center, +th.large-2 center { + min-width: 48.66667px; } + +.body .columns td.large-2, +.body .column td.large-2, +.body .columns th.large-2, +.body .column th.large-2 { + width: 16.66667%; } + +td.large-3, +th.large-3 { + width: 129px; + padding-left: 8px; + padding-right: 8px; } + +td.large-3.first, +th.large-3.first { + padding-left: 16px; } + +td.large-3.last, +th.large-3.last { + padding-right: 16px; } + +.collapse > tbody > tr > td.large-3, +.collapse > tbody > tr > th.large-3 { + padding-right: 0; + padding-left: 0; + width: 145px; } + +.collapse td.large-3.first, +.collapse th.large-3.first, +.collapse td.large-3.last, +.collapse th.large-3.last { + width: 153px; } + +td.large-3 center, +th.large-3 center { + min-width: 97px; } + +.body .columns td.large-3, +.body .column td.large-3, +.body .columns th.large-3, +.body .column th.large-3 { + width: 25%; } + +td.large-4, +th.large-4 { + width: 177.33333px; + padding-left: 8px; + padding-right: 8px; } + +td.large-4.first, +th.large-4.first { + padding-left: 16px; } + +td.large-4.last, +th.large-4.last { + padding-right: 16px; } + +.collapse > tbody > tr > td.large-4, +.collapse > tbody > tr > th.large-4 { + padding-right: 0; + padding-left: 0; + width: 193.33333px; } + +.collapse td.large-4.first, +.collapse th.large-4.first, +.collapse td.large-4.last, +.collapse th.large-4.last { + width: 201.33333px; } + +td.large-4 center, +th.large-4 center { + min-width: 145.33333px; } + +.body .columns td.large-4, +.body .column td.large-4, +.body .columns th.large-4, +.body .column th.large-4 { + width: 33.33333%; } + +td.large-5, +th.large-5 { + width: 225.66667px; + padding-left: 8px; + padding-right: 8px; } + +td.large-5.first, +th.large-5.first { + padding-left: 16px; } + +td.large-5.last, +th.large-5.last { + padding-right: 16px; } + +.collapse > tbody > tr > td.large-5, +.collapse > tbody > tr > th.large-5 { + padding-right: 0; + padding-left: 0; + width: 241.66667px; } + +.collapse td.large-5.first, +.collapse th.large-5.first, +.collapse td.large-5.last, +.collapse th.large-5.last { + width: 249.66667px; } + +td.large-5 center, +th.large-5 center { + min-width: 193.66667px; } + +.body .columns td.large-5, +.body .column td.large-5, +.body .columns th.large-5, +.body .column th.large-5 { + width: 41.66667%; } + +td.large-6, +th.large-6 { + width: 274px; + padding-left: 8px; + padding-right: 8px; } + +td.large-6.first, +th.large-6.first { + padding-left: 16px; } + +td.large-6.last, +th.large-6.last { + padding-right: 16px; } + +.collapse > tbody > tr > td.large-6, +.collapse > tbody > tr > th.large-6 { + padding-right: 0; + padding-left: 0; + width: 290px; } + +.collapse td.large-6.first, +.collapse th.large-6.first, +.collapse td.large-6.last, +.collapse th.large-6.last { + width: 298px; } + +td.large-6 center, +th.large-6 center { + min-width: 242px; } + +.body .columns td.large-6, +.body .column td.large-6, +.body .columns th.large-6, +.body .column th.large-6 { + width: 50%; } + +td.large-7, +th.large-7 { + width: 322.33333px; + padding-left: 8px; + padding-right: 8px; } + +td.large-7.first, +th.large-7.first { + padding-left: 16px; } + +td.large-7.last, +th.large-7.last { + padding-right: 16px; } + +.collapse > tbody > tr > td.large-7, +.collapse > tbody > tr > th.large-7 { + padding-right: 0; + padding-left: 0; + width: 338.33333px; } + +.collapse td.large-7.first, +.collapse th.large-7.first, +.collapse td.large-7.last, +.collapse th.large-7.last { + width: 346.33333px; } + +td.large-7 center, +th.large-7 center { + min-width: 290.33333px; } + +.body .columns td.large-7, +.body .column td.large-7, +.body .columns th.large-7, +.body .column th.large-7 { + width: 58.33333%; } + +td.large-8, +th.large-8 { + width: 370.66667px; + padding-left: 8px; + padding-right: 8px; } + +td.large-8.first, +th.large-8.first { + padding-left: 16px; } + +td.large-8.last, +th.large-8.last { + padding-right: 16px; } + +.collapse > tbody > tr > td.large-8, +.collapse > tbody > tr > th.large-8 { + padding-right: 0; + padding-left: 0; + width: 386.66667px; } + +.collapse td.large-8.first, +.collapse th.large-8.first, +.collapse td.large-8.last, +.collapse th.large-8.last { + width: 394.66667px; } + +td.large-8 center, +th.large-8 center { + min-width: 338.66667px; } + +.body .columns td.large-8, +.body .column td.large-8, +.body .columns th.large-8, +.body .column th.large-8 { + width: 66.66667%; } + +td.large-9, +th.large-9 { + width: 419px; + padding-left: 8px; + padding-right: 8px; } + +td.large-9.first, +th.large-9.first { + padding-left: 16px; } + +td.large-9.last, +th.large-9.last { + padding-right: 16px; } + +.collapse > tbody > tr > td.large-9, +.collapse > tbody > tr > th.large-9 { + padding-right: 0; + padding-left: 0; + width: 435px; } + +.collapse td.large-9.first, +.collapse th.large-9.first, +.collapse td.large-9.last, +.collapse th.large-9.last { + width: 443px; } + +td.large-9 center, +th.large-9 center { + min-width: 387px; } + +.body .columns td.large-9, +.body .column td.large-9, +.body .columns th.large-9, +.body .column th.large-9 { + width: 75%; } + +td.large-10, +th.large-10 { + width: 467.33333px; + padding-left: 8px; + padding-right: 8px; } + +td.large-10.first, +th.large-10.first { + padding-left: 16px; } + +td.large-10.last, +th.large-10.last { + padding-right: 16px; } + +.collapse > tbody > tr > td.large-10, +.collapse > tbody > tr > th.large-10 { + padding-right: 0; + padding-left: 0; + width: 483.33333px; } + +.collapse td.large-10.first, +.collapse th.large-10.first, +.collapse td.large-10.last, +.collapse th.large-10.last { + width: 491.33333px; } + +td.large-10 center, +th.large-10 center { + min-width: 435.33333px; } + +.body .columns td.large-10, +.body .column td.large-10, +.body .columns th.large-10, +.body .column th.large-10 { + width: 83.33333%; } + +td.large-11, +th.large-11 { + width: 515.66667px; + padding-left: 8px; + padding-right: 8px; } + +td.large-11.first, +th.large-11.first { + padding-left: 16px; } + +td.large-11.last, +th.large-11.last { + padding-right: 16px; } + +.collapse > tbody > tr > td.large-11, +.collapse > tbody > tr > th.large-11 { + padding-right: 0; + padding-left: 0; + width: 531.66667px; } + +.collapse td.large-11.first, +.collapse th.large-11.first, +.collapse td.large-11.last, +.collapse th.large-11.last { + width: 539.66667px; } + +td.large-11 center, +th.large-11 center { + min-width: 483.66667px; } + +.body .columns td.large-11, +.body .column td.large-11, +.body .columns th.large-11, +.body .column th.large-11 { + width: 91.66667%; } + +td.large-12, +th.large-12 { + width: 564px; + padding-left: 8px; + padding-right: 8px; } + +td.large-12.first, +th.large-12.first { + padding-left: 16px; } + +td.large-12.last, +th.large-12.last { + padding-right: 16px; } + +.collapse > tbody > tr > td.large-12, +.collapse > tbody > tr > th.large-12 { + padding-right: 0; + padding-left: 0; + width: 580px; } + +.collapse td.large-12.first, +.collapse th.large-12.first, +.collapse td.large-12.last, +.collapse th.large-12.last { + width: 588px; } + +td.large-12 center, +th.large-12 center { + min-width: 532px; } + +.body .columns td.large-12, +.body .column td.large-12, +.body .columns th.large-12, +.body .column th.large-12 { + width: 100%; } + +td.large-offset-1, +td.large-offset-1.first, +td.large-offset-1.last, +th.large-offset-1, +th.large-offset-1.first, +th.large-offset-1.last { + padding-left: 64.33333px; } + +td.large-offset-2, +td.large-offset-2.first, +td.large-offset-2.last, +th.large-offset-2, +th.large-offset-2.first, +th.large-offset-2.last { + padding-left: 112.66667px; } + +td.large-offset-3, +td.large-offset-3.first, +td.large-offset-3.last, +th.large-offset-3, +th.large-offset-3.first, +th.large-offset-3.last { + padding-left: 161px; } + +td.large-offset-4, +td.large-offset-4.first, +td.large-offset-4.last, +th.large-offset-4, +th.large-offset-4.first, +th.large-offset-4.last { + padding-left: 209.33333px; } + +td.large-offset-5, +td.large-offset-5.first, +td.large-offset-5.last, +th.large-offset-5, +th.large-offset-5.first, +th.large-offset-5.last { + padding-left: 257.66667px; } + +td.large-offset-6, +td.large-offset-6.first, +td.large-offset-6.last, +th.large-offset-6, +th.large-offset-6.first, +th.large-offset-6.last { + padding-left: 306px; } + +td.large-offset-7, +td.large-offset-7.first, +td.large-offset-7.last, +th.large-offset-7, +th.large-offset-7.first, +th.large-offset-7.last { + padding-left: 354.33333px; } + +td.large-offset-8, +td.large-offset-8.first, +td.large-offset-8.last, +th.large-offset-8, +th.large-offset-8.first, +th.large-offset-8.last { + padding-left: 402.66667px; } + +td.large-offset-9, +td.large-offset-9.first, +td.large-offset-9.last, +th.large-offset-9, +th.large-offset-9.first, +th.large-offset-9.last { + padding-left: 451px; } + +td.large-offset-10, +td.large-offset-10.first, +td.large-offset-10.last, +th.large-offset-10, +th.large-offset-10.first, +th.large-offset-10.last { + padding-left: 499.33333px; } + +td.large-offset-11, +td.large-offset-11.first, +td.large-offset-11.last, +th.large-offset-11, +th.large-offset-11.first, +th.large-offset-11.last { + padding-left: 547.66667px; } + +td.expander, +th.expander { + visibility: hidden; + width: 0; + padding: 0 !important; } + +table.container.radius { + border-radius: 0; + border-collapse: separate; } + +.block-grid { + width: 100%; + max-width: 580px; } + .block-grid td { + display: inline-block; + padding: 8px; } + +.up-2 td { + width: 274px !important; } + +.up-3 td { + width: 177px !important; } + +.up-4 td { + width: 129px !important; } + +.up-5 td { + width: 100px !important; } + +.up-6 td { + width: 80px !important; } + +.up-7 td { + width: 66px !important; } + +.up-8 td { + width: 56px !important; } + +table.text-center, +th.text-center, +td.text-center, +h1.text-center, +h2.text-center, +h3.text-center, +h4.text-center, +h5.text-center, +h6.text-center, +p.text-center, +span.text-center { + text-align: center; } + +table.text-left, +th.text-left, +td.text-left, +h1.text-left, +h2.text-left, +h3.text-left, +h4.text-left, +h5.text-left, +h6.text-left, +p.text-left, +span.text-left { + text-align: left; } + +table.text-right, +th.text-right, +td.text-right, +h1.text-right, +h2.text-right, +h3.text-right, +h4.text-right, +h5.text-right, +h6.text-right, +p.text-right, +span.text-right { + text-align: right; } + +span.text-center { + display: block; + width: 100%; + text-align: center; } + +@media only screen and (max-width: 596px) { + .small-float-center { + margin: 0 auto !important; + float: none !important; + text-align: center !important; } + .small-text-center { + text-align: center !important; } + .small-text-left { + text-align: left !important; } + .small-text-right { + text-align: right !important; } } + +img.float-left { + float: left; + text-align: left; } + +img.float-right { + float: right; + text-align: right; } + +img.float-center, +img.text-center { + margin: 0 auto; + Margin: 0 auto; + float: none; + text-align: center; } + +table.float-center, +td.float-center, +th.float-center { + margin: 0 auto; + Margin: 0 auto; + float: none; + text-align: center; } + +.hide-for-large { + display: none !important; + mso-hide: all; + overflow: hidden; + max-height: 0; + font-size: 0; + width: 0; + line-height: 0; } + @media only screen and (max-width: 596px) { + .hide-for-large { + display: block !important; + width: auto !important; + overflow: visible !important; + max-height: none !important; + font-size: inherit !important; + line-height: inherit !important; } } + +table.body table.container .hide-for-large * { + mso-hide: all; } + +@media only screen and (max-width: 596px) { + table.body table.container .hide-for-large, + table.body table.container .row.hide-for-large { + display: table !important; + width: 100% !important; } } + +@media only screen and (max-width: 596px) { + table.body table.container .callout-inner.hide-for-large { + display: table-cell !important; + width: 100% !important; } } + +@media only screen and (max-width: 596px) { + table.body table.container .show-for-large { + display: none !important; + width: 0; + mso-hide: all; + overflow: hidden; } } + +body, +table.body, +h1, +h2, +h3, +h4, +h5, +h6, +p, +td, +th, +a { + color: #0a0a0a; + font-family: Helvetica, Arial, sans-serif; + font-weight: normal; + padding: 0; + margin: 0; + Margin: 0; + text-align: left; + line-height: 1.3; } + +h1, +h2, +h3, +h4, +h5, +h6 { + color: inherit; + word-wrap: normal; + font-family: Helvetica, Arial, sans-serif; + font-weight: normal; + margin-bottom: 10px; + Margin-bottom: 10px; } + +h1 { + font-size: 34px; } + +h2 { + font-size: 30px; } + +h3 { + font-size: 28px; } + +h4 { + font-size: 24px; } + +h5 { + font-size: 20px; } + +h6 { + font-size: 18px; } + +body, +table.body, +p, +td, +th { + font-size: 16px; + line-height: 1.3; } + +p { + margin-bottom: 10px; + Margin-bottom: 10px; } + p.lead { + font-size: 20px; + line-height: 1.6; } + p.subheader { + margin-top: 4px; + margin-bottom: 8px; + Margin-top: 4px; + Margin-bottom: 8px; + font-weight: normal; + line-height: 1.4; + color: #8a8a8a; } + +small { + font-size: 80%; + color: #cacaca; } + +a { + color: #2199e8; + text-decoration: none; } + a:hover { + color: #147dc2; } + a:active { + color: #147dc2; } + a:visited { + color: #2199e8; } + +h1 a, +h1 a:visited, +h2 a, +h2 a:visited, +h3 a, +h3 a:visited, +h4 a, +h4 a:visited, +h5 a, +h5 a:visited, +h6 a, +h6 a:visited { + color: #2199e8; } + +pre { + background: #f3f3f3; + margin: 30px 0; + Margin: 30px 0; } + pre code { + color: #cacaca; } + pre code span.callout { + color: #8a8a8a; + font-weight: bold; } + pre code span.callout-strong { + color: #ff6908; + font-weight: bold; } + +table.hr { + width: 100%; } + table.hr th { + height: 0; + max-width: 580px; + border-top: 0; + border-right: 0; + border-bottom: 1px solid #0a0a0a; + border-left: 0; + margin: 20px auto; + Margin: 20px auto; + clear: both; } + +.stat { + font-size: 40px; + line-height: 1; } + p + .stat { + margin-top: -16px; + Margin-top: -16px; } + +span.preheader { + display: none !important; + visibility: hidden; + mso-hide: all !important; + font-size: 1px; + color: #f3f3f3; + line-height: 1px; + max-height: 0px; + max-width: 0px; + opacity: 0; + overflow: hidden; } + +table.button { + width: auto; + margin: 0 0 16px 0; + Margin: 0 0 16px 0; } + table.button table td { + text-align: left; + color: #fefefe; + background: #2199e8; + border: 2px solid #2199e8; } + table.button table td a { + font-family: Helvetica, Arial, sans-serif; + font-size: 16px; + font-weight: bold; + color: #fefefe; + text-decoration: none; + display: inline-block; + padding: 8px 16px 8px 16px; + border: 0 solid #2199e8; + border-radius: 3px; } + table.button.radius table td { + border-radius: 3px; + border: none; } + table.button.rounded table td { + border-radius: 500px; + border: none; } + +table.button:hover table tr td a, +table.button:active table tr td a, +table.button table tr td a:visited, +table.button.tiny:hover table tr td a, +table.button.tiny:active table tr td a, +table.button.tiny table tr td a:visited, +table.button.small:hover table tr td a, +table.button.small:active table tr td a, +table.button.small table tr td a:visited, +table.button.large:hover table tr td a, +table.button.large:active table tr td a, +table.button.large table tr td a:visited { + color: #fefefe; } + +table.button.tiny table td, +table.button.tiny table a { + padding: 4px 8px 4px 8px; } + +table.button.tiny table a { + font-size: 10px; + font-weight: normal; } + +table.button.small table td, +table.button.small table a { + padding: 5px 10px 5px 10px; + font-size: 12px; } + +table.button.large table a { + padding: 10px 20px 10px 20px; + font-size: 20px; } + +table.button.expand, +table.button.expanded { + width: 100% !important; } + table.button.expand table, + table.button.expanded table { + width: 100%; } + table.button.expand table a, + table.button.expanded table a { + text-align: center; + width: 100%; + padding-left: 0; + padding-right: 0; } + table.button.expand center, + table.button.expanded center { + min-width: 0; } + +table.button:hover table td, +table.button:visited table td, +table.button:active table td { + background: #147dc2; + color: #fefefe; } + +table.button:hover table a, +table.button:visited table a, +table.button:active table a { + border: 0 solid #147dc2; } + +table.button.secondary table td { + background: #777777; + color: #fefefe; + border: 0px solid #777777; } + +table.button.secondary table a { + color: #fefefe; + border: 0 solid #777777; } + +table.button.secondary:hover table td { + background: #919191; + color: #fefefe; } + +table.button.secondary:hover table a { + border: 0 solid #919191; } + +table.button.secondary:hover table td a { + color: #fefefe; } + +table.button.secondary:active table td a { + color: #fefefe; } + +table.button.secondary table td a:visited { + color: #fefefe; } + +table.button.success table td { + background: #3adb76; + border: 0px solid #3adb76; } + +table.button.success table a { + border: 0 solid #3adb76; } + +table.button.success:hover table td { + background: #23bf5d; } + +table.button.success:hover table a { + border: 0 solid #23bf5d; } + +table.button.alert table td { + background: #ec5840; + border: 0px solid #ec5840; } + +table.button.alert table a { + border: 0 solid #ec5840; } + +table.button.alert:hover table td { + background: #e23317; } + +table.button.alert:hover table a { + border: 0 solid #e23317; } + +table.button.warning table td { + background: #ffae00; + border: 0px solid #ffae00; } + +table.button.warning table a { + border: 0px solid #ffae00; } + +table.button.warning:hover table td { + background: #cc8b00; } + +table.button.warning:hover table a { + border: 0px solid #cc8b00; } + +table.callout { + margin-bottom: 16px; + Margin-bottom: 16px; } + +th.callout-inner { + width: 100%; + border: 1px solid #cbcbcb; + padding: 10px; + background: #fefefe; } + th.callout-inner.primary { + background: #def0fc; + border: 1px solid #444444; + color: #0a0a0a; } + th.callout-inner.secondary { + background: #ebebeb; + border: 1px solid #444444; + color: #0a0a0a; } + th.callout-inner.success { + background: #e1faea; + border: 1px solid #1b9448; + color: #fefefe; } + th.callout-inner.warning { + background: #fff3d9; + border: 1px solid #996800; + color: #fefefe; } + th.callout-inner.alert { + background: #fce6e2; + border: 1px solid #b42912; + color: #fefefe; } + +.thumbnail { + border: solid 4px #fefefe; + box-shadow: 0 0 0 1px rgba(10, 10, 10, 0.2); + display: inline-block; + line-height: 0; + max-width: 100%; + transition: box-shadow 200ms ease-out; + border-radius: 3px; + margin-bottom: 16px; } + .thumbnail:hover, .thumbnail:focus { + box-shadow: 0 0 6px 1px rgba(33, 153, 232, 0.5); } + +table.menu { + width: 580px; } + table.menu td.menu-item, + table.menu th.menu-item { + padding: 10px; + padding-right: 10px; } + table.menu td.menu-item a, + table.menu th.menu-item a { + color: #2199e8; } + +table.menu.vertical td.menu-item, +table.menu.vertical th.menu-item { + padding: 10px; + padding-right: 0; + display: block; } + table.menu.vertical td.menu-item a, + table.menu.vertical th.menu-item a { + width: 100%; } + +table.menu.vertical td.menu-item table.menu.vertical td.menu-item, +table.menu.vertical td.menu-item table.menu.vertical th.menu-item, +table.menu.vertical th.menu-item table.menu.vertical td.menu-item, +table.menu.vertical th.menu-item table.menu.vertical th.menu-item { + padding-left: 10px; } + +table.menu.text-center a { + text-align: center; } + +.menu[align="center"] { + width: auto !important; } + +body.outlook p { + display: inline !important; } + +@media only screen and (max-width: 596px) { + table.body img { + width: auto; + height: auto; } + table.body center { + min-width: 0 !important; } + table.body .container { + width: 95% !important; } + table.body .columns, + table.body .column { + height: auto !important; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + padding-left: 16px !important; + padding-right: 16px !important; } + table.body .columns .column, + table.body .columns .columns, + table.body .column .column, + table.body .column .columns { + padding-left: 0 !important; + padding-right: 0 !important; } + table.body .collapse .columns, + table.body .collapse .column { + padding-left: 0 !important; + padding-right: 0 !important; } + td.small-1, + th.small-1 { + display: inline-block !important; + width: 8.33333% !important; } + td.small-2, + th.small-2 { + display: inline-block !important; + width: 16.66667% !important; } + td.small-3, + th.small-3 { + display: inline-block !important; + width: 25% !important; } + td.small-4, + th.small-4 { + display: inline-block !important; + width: 33.33333% !important; } + td.small-5, + th.small-5 { + display: inline-block !important; + width: 41.66667% !important; } + td.small-6, + th.small-6 { + display: inline-block !important; + width: 50% !important; } + td.small-7, + th.small-7 { + display: inline-block !important; + width: 58.33333% !important; } + td.small-8, + th.small-8 { + display: inline-block !important; + width: 66.66667% !important; } + td.small-9, + th.small-9 { + display: inline-block !important; + width: 75% !important; } + td.small-10, + th.small-10 { + display: inline-block !important; + width: 83.33333% !important; } + td.small-11, + th.small-11 { + display: inline-block !important; + width: 91.66667% !important; } + td.small-12, + th.small-12 { + display: inline-block !important; + width: 100% !important; } + .columns td.small-12, + .column td.small-12, + .columns th.small-12, + .column th.small-12 { + display: block !important; + width: 100% !important; } + table.body td.small-offset-1, + table.body th.small-offset-1 { + margin-left: 8.33333% !important; + Margin-left: 8.33333% !important; } + table.body td.small-offset-2, + table.body th.small-offset-2 { + margin-left: 16.66667% !important; + Margin-left: 16.66667% !important; } + table.body td.small-offset-3, + table.body th.small-offset-3 { + margin-left: 25% !important; + Margin-left: 25% !important; } + table.body td.small-offset-4, + table.body th.small-offset-4 { + margin-left: 33.33333% !important; + Margin-left: 33.33333% !important; } + table.body td.small-offset-5, + table.body th.small-offset-5 { + margin-left: 41.66667% !important; + Margin-left: 41.66667% !important; } + table.body td.small-offset-6, + table.body th.small-offset-6 { + margin-left: 50% !important; + Margin-left: 50% !important; } + table.body td.small-offset-7, + table.body th.small-offset-7 { + margin-left: 58.33333% !important; + Margin-left: 58.33333% !important; } + table.body td.small-offset-8, + table.body th.small-offset-8 { + margin-left: 66.66667% !important; + Margin-left: 66.66667% !important; } + table.body td.small-offset-9, + table.body th.small-offset-9 { + margin-left: 75% !important; + Margin-left: 75% !important; } + table.body td.small-offset-10, + table.body th.small-offset-10 { + margin-left: 83.33333% !important; + Margin-left: 83.33333% !important; } + table.body td.small-offset-11, + table.body th.small-offset-11 { + margin-left: 91.66667% !important; + Margin-left: 91.66667% !important; } + table.body table.columns td.expander, + table.body table.columns th.expander { + display: none !important; } + table.body .right-text-pad, + table.body .text-pad-right { + padding-left: 10px !important; } + table.body .left-text-pad, + table.body .text-pad-left { + padding-right: 10px !important; } + table.menu { + width: 100% !important; } + table.menu td, + table.menu th { + width: auto !important; + display: inline-block !important; } + table.menu.vertical td, + table.menu.vertical th, table.menu.small-vertical td, + table.menu.small-vertical th { + display: block !important; } + table.menu[align="center"] { + width: auto !important; } + table.button.small-expand, + table.button.small-expanded { + width: 100% !important; } + table.button.small-expand table, + table.button.small-expanded table { + width: 100%; } + table.button.small-expand table a, + table.button.small-expanded table a { + text-align: center !important; + width: 100% !important; + padding-left: 0 !important; + padding-right: 0 !important; } + table.button.small-expand center, + table.button.small-expanded center { + min-width: 0; } } diff --git a/Email/assets/res/pictures/lock.jpeg b/Email/assets/res/pictures/lock.jpeg new file mode 100644 index 000000000..9afc6e0b1 Binary files /dev/null and b/Email/assets/res/pictures/lock.jpeg differ diff --git a/Email/hello.eml.latte b/Email/hello.eml.latte new file mode 100644 index 000000000..cdd27bee4 --- /dev/null +++ b/Email/hello.eml.latte @@ -0,0 +1,21 @@ + + + + + + + Hello World! + + + + + + + +
      +
      + Блять +
      +
      + + \ No newline at end of file diff --git a/Email/password-reset.eml.latte b/Email/password-reset.eml.latte new file mode 100644 index 000000000..6e482814d --- /dev/null +++ b/Email/password-reset.eml.latte @@ -0,0 +1,204 @@ + + + + + + Восстановление пароля + + + + + + + + +
      +
      + + + + +
      +   +
      + + + + +
      + + + + +
      + + + + +
      + + + + +
      +   +
      +

      Забыли пароль?

      +
      +
      + + + + + +
      + + + + +
      +
      + +
      + + + + + +
      +   +
      + +
      + + + + + +
      +   +
      + +

      + Здравствуйте, {$name}! Вы вероятно забыли пароль от аккаунта OpenVK? Мы идём к Вам на помощь! +

      + + + + + +
      +   +
      + + + + + +
      + + + + +
      +
      + Сбросить пароль! +
      +
      +
      + + + + + +
      +   +
      + +

      + Если кнопка не работает, вы можете попробовать скопировать и вставить эту ссылку в адресную строку вашего веб-обозревателя: +

      + + + + + +
      + + http://{$_SERVER['HTTP_HOST']}/restore.pl?act=finish&key={$key} + +
      + +

      + Обратите внимание на то, что эту ссылку нельзя: +

      + +
        +
      • Передавать другим людям (даже друзьям, питомцам, соседам, любимым девушкам)
      • +
      • Использовать, если прошло более двух дней с её генерации
      • +
      + + + + + +
      +

      + Ещё раз обратите внимание на то, что данную ссылку или письмо ни в коем случае нельзя передавать другим людям! Даже если они представляются службой поддержки.
      + Это письмо предназначено исключительно для одноразового, непосредственного использования владельцем аккаунта. +

      +
      + + + + + +
      +   +
      + +

      + С уважением, овк-тян. +

      + + + + + +
      +   +
      + +
      + + + + + +
      +   +
      + +

      + + Вы получили это письмо так как кто-то или вы отправили запрос на восстановлние пароля. Это не рассылка и от неё нельзя отписаться. Если вы всё равно хотите перестать получать подобные письма, деактивируйте ваш аккаунт. + +

      +
      +
      +
      + + + + +
      +   +
      +
      +
      + + diff --git a/INSTALL.txt b/INSTALL.txt new file mode 100644 index 000000000..e2486500f --- /dev/null +++ b/INSTALL.txt @@ -0,0 +1,9 @@ +OpenVK Install Instructions +----------- +1) Import OVK DB +2) Import OVK EventDB to MySQL (you can also use Ya.Clickhouse lul) +3) Set up dependencies with composer +4) Set up dependencies with yarn (Web/static/js) +5) Create new user and give him admin permissions +6) ??? +7) Profit! \ No newline at end of file diff --git a/ServiceAPI/Handler.php b/ServiceAPI/Handler.php new file mode 100644 index 000000000..7b19d6b6b --- /dev/null +++ b/ServiceAPI/Handler.php @@ -0,0 +1,8 @@ +user = $user; + } + + function getTime(callable $resolve, callable $reject): void + { + $resolve((new DateTime)->format("%e %B %G" . tr("time_at_sp") . "%X")); + } + + function getServerVersion(callable $resolve, callable $reject): void + { + $resolve("OVK " . OPENVK_VERSION); + } +} diff --git a/Web/Events/ILPEmitable.php b/Web/Events/ILPEmitable.php new file mode 100644 index 000000000..3da2843fa --- /dev/null +++ b/Web/Events/ILPEmitable.php @@ -0,0 +1,7 @@ +payload = $message->simplify(); + } + + function getLongPoolSummary(): object + { + return (object) [ + "type" => "newMessage", + "message" => $this->payload, + ]; + } +} \ No newline at end of file diff --git a/Web/Models/Entities/Album.php b/Web/Models/Entities/Album.php new file mode 100644 index 000000000..2ee363028 --- /dev/null +++ b/Web/Models/Entities/Album.php @@ -0,0 +1,130 @@ +getRecord()->owner; + if($oid > 0) + return (new Users)->get($oid); + else + return (new Clubs)->get($oid * -1); + } + + function getPrettyId(): string + { + return $this->getRecord()->owner . "_" . $this->getRecord()->id; + } + + function getName(): string + { + switch($this->getRecord()->special_type) { + case Album::SPECIAL_AVATARS: + return "Изображения со страницы"; + case Album::SPECIAL_WALL: + return "Изображения со стены"; + default: + return $this->getRecord()->name; + } + } + + function getDescription(): ?string + { + return $this->getRecord()->description; + } + + function getCoverPhoto(): ?Photo + { + $cover = $this->getRecord()->cover_photo; + if(!$cover) { + $photos = iterator_to_array($this->getPhotos(1, 1)); + $photo = $photos[0] ?? NULL; + if(!$photo || $photo->isDeleted()) + return NULL; + else + return $photo; + } + + return (new Photos)->get($cover); + } + + function getPhotos(int $page = 1, ?int $perPage = NULL): \Traversable + { + $perPage = $perPage ?? OPENVK_DEFAULT_PER_PAGE; + + foreach($this->getRecord()->related("album_relations.album")->page($page, $perPage)->order("photo ASC") as $rel) { + $photo = $rel->ref("photos", "photo"); + if(!$photo) continue; + + yield new Photo($photo); + } + } + + function getPhotosCount(): int + { + return sizeof($this->getRecord()->related("album_relations.album")); + } + + function getCreationTime(): DateTime + { + return new DateTime($this->getRecord()->created); + } + + function getPublicationTime(): DateTime + { + return $this->getCreationTime(); + } + + function getEditTime(): ?DateTime + { + $edited = $this->getRecord()->edited; + if(is_null($edited)) return NULL; + + return new DateTime($edited); + } + + function isCreatedBySystem(): bool + { + return $this->getRecord()->special_type !== 0; + } + + function addPhoto(Photo $photo): void + { + DatabaseConnection::i()->getContext()->table("album_relations")->insert([ + "album" => $this->getRecord()->id, + "photo" => $photo->getId(), + ]); + } + + function removePhoto(Photo $photo): void + { + DatabaseConnection::i()->getContext()->table("album_relations")->where([ + "album" => $this->getRecord()->id, + "photo" => $photo->getId(), + ])->delete(); + } + + function hasPhoto(Photo $photo): bool + { + $rel = DatabaseConnection::i()->getContext()->table("album_relations")->where([ + "album" => $this->getRecord()->id, + "photo" => $photo->getId(), + ])->fetch(); + + return !is_null($rel); + } + + use Traits\TOwnable; +} diff --git a/Web/Models/Entities/Attachable.php b/Web/Models/Entities/Attachable.php new file mode 100644 index 000000000..a83c73838 --- /dev/null +++ b/Web/Models/Entities/Attachable.php @@ -0,0 +1,38 @@ +getRecord()->id; + } + + function getParents(): \Traversable + { + $sel = $this->getRecord() + ->related("attachments.attachable_id") + ->where("attachments.attachable_type", get_class($this)); + foreach($sel as $rel) { + $repoName = $rel->target_type . "s"; + $repoName = str_replace("Entities", "Repositories", $repoName); + $repo = new $repoName; + + yield $repo->get($rel->target_id); + } + } + + /** + * Deletes together with all references. + */ + function delete(bool $softly = true): void + { + $this->getRecord() + ->related("attachments.attachable_id") + ->where("attachments.attachable_type", get_class($this)) + ->delete(); + + parent::delete(); + } +} diff --git a/Web/Models/Entities/Audio.php b/Web/Models/Entities/Audio.php new file mode 100644 index 000000000..f27e7fc26 --- /dev/null +++ b/Web/Models/Entities/Audio.php @@ -0,0 +1,65 @@ +analyze($filename); + if(isset($meta["error"])) + throw new ISE(implode(", ", $meta["error"])); + + $this->setPerformer("Неизвестно"); + $this->setName("Без названия"); + } catch(\Exception $ex) { + exit("Хакеры? Интересно..."); + } + + return rename($filename, OPENVK_ROOT . "/storage/" . substr($hash, 0, 2) . "/$hash.mpeg3"); + } + + function getName(): string + { + return $this->getRecord()->name; + } + + function getPerformer(): string + { + return $this->getRecord()->performer; + } + + function getGenre(): string + { + return $this->getRecord()->genre; + } + + function getLyrics(): ?string + { + return $this->getRecord()->lyrics; + } + + function getCanonicalName(): string + { + return $this->getRecord()->performer . " — " . $this->getRecord()->name; + } + + function wire(): void + { + \Chandler\Database\DatabaseConnection::i()->getContext()->table("audio_relations")->insert([ + "user" => $this->getRecord()->owner, + "audio" => $this->getId(), + ]); + } +} diff --git a/Web/Models/Entities/Club.php b/Web/Models/Entities/Club.php new file mode 100644 index 000000000..7b5dbdfd5 --- /dev/null +++ b/Web/Models/Entities/Club.php @@ -0,0 +1,297 @@ +getRecord()->id; + } + + function getAvatarPhoto(): ?Photo + { + $avAlbum = (new Albums)->getClubAvatarAlbum($this); + $avCount = $avAlbum->getPhotosCount(); + $avPhotos = $avAlbum->getPhotos($avCount, 1); + + return iterator_to_array($avPhotos)[0] ?? NULL; + } + + function getAvatarUrl(): string + { + $avPhoto = $this->getAvatarPhoto(); + + return is_null($avPhoto) ? "/assets/packages/static/openvk/img/camera_200.png" : $avPhoto->getURL(); + } + + function getAvatarLink(): string + { + $avPhoto = $this->getAvatarPhoto(); + if(!$avPhoto) return "javascript:void(0)"; + + $pid = $avPhoto->getPrettyId(); + $aid = (new Albums)->getClubAvatarAlbum($this)->getId(); + + return "/photo$pid?from=album$aid"; + } + + function getURL(): string + { + if(!is_null($this->getShortCode())) + return "/" . $this->getShortCode(); + else + return "/club" . $this->getId(); + } + /* + function getAvatarUrl(): string + { + $avAlbum = (new Albums)->getUserAvatarAlbum($this); + $avCount = $avAlbum->getPhotosCount(); + $avPhotos = $avAlbum->getPhotos($avCount, 1); + $avPhoto = iterator_to_array($avPhotos)[0] ?? NULL; + + return is_null($avPhoto) ? "/assets/packages/static/openvk/img/camera_200.png" : $avPhoto->getURL(); + } */ + + function getName(): string + { + return ovk_proc_strtr($this->getRecord()->name, 32); + } + + function getCanonicalName(): string + { + return $this->getName(); + } + + function getOwner(): ?User + { + return (new Users)->get($this->getRecord()->owner); + } + + function getDescription(): ?string + { + return $this->getRecord()->about; + } + + function getShortCode(): ?string + { + return $this->getRecord()->shortcode; + } + + function getBanReason(): ?string + { + return $this->getRecord()->block_reason; + } + + function getOpennesStatus(): int + { + return $this->getRecord()->closed; + } + + function getType(): int + { + return $this->getRecord()->type; + } + + function isVerified(): bool + { + return (bool) $this->getRecord()->verified; + } + + function isBanned(): bool + { + return !is_null($this->getBanReason()); + } + + function canPost(): bool + { + return (bool) $this->getRecord()->wall; + } + + + function setShortCode(?string $code = NULL): ?bool + { + if(!is_null($code)) { + if(!preg_match("%^[a-z][a-z0-9\\.\\_]{0,30}[a-z0-9]$%", $code)) + return false; + if(in_array($code, OPENVK_ROOT_CONF["openvk"]["preferences"]["shortcodes"]["forbiddenNames"])) + return false; + } + + $this->stateChanges("shortcode", $code); + return true; + } + + function isSubscriptionAccepted(User $user): bool + { + return !is_null($this->getRecord()->related("subscriptions.follower")->where([ + "follower" => $this->getId(), + "target" => $user->getId(), + ])->fetch());; + } + + function getPostViewStats(bool $unique = false): ?array + { + $edb = eventdb(); + if(!$edb) + return NULL; + + $subs = []; + $viral = []; + $total = []; + for($i = 1; $i < 8; $i++) { + $begin = strtotime("-" . $i . "day midnight"); + $end = $i === 1 ? time() + 10 : strtotime("-" . ($i - 1) . "day midnight"); + + $query = "SELECT COUNT(" . ($unique ? "DISTINCT profile" : "*") . ") AS cnt FROM postViews"; + $query .= " WHERE `group`=1 AND owner=" . $this->getId(); + $query .= " AND timestamp > $begin AND timestamp < $end"; + + $sub = $edb->getConnection()->query("$query AND NOT subscribed=0")->fetch()->cnt; + $vir = $edb->getConnection()->query("$query AND subscribed=0")->fetch()->cnt; + $subs[] = $sub; + $viral[] = $vir; + $total[] = $sub + $vir; + } + + return [ + "total" => [ + "x" => array_reverse(range(1, 7)), + "y" => $total, + "type" => "scatter", + "line" => [ + "shape" => "spline", + "color" => "#597da3", + ], + "name" => $unique ? "Полный охват" : "Все просмотры", + ], + "subs" => [ + "x" => array_reverse(range(1, 7)), + "y" => $subs, + "type" => "scatter", + "line" => [ + "shape" => "spline", + "color" => "#b05c91", + ], + "fill" => "tozeroy", + "name" => $unique ? "Охват подписчиков" : "Просмотры подписчиков", + ], + "viral" => [ + "x" => array_reverse(range(1, 7)), + "y" => $viral, + "type" => "scatter", + "line" => [ + "shape" => "spline", + "color" => "#4d9fab", + ], + "fill" => "tozeroy", + "name" => $unique ? "Виральный охват" : "Виральные просмотры", + ], + ]; + } + + function getSubscriptionStatus(User $user): bool + { + $subbed = !is_null($this->getRecord()->related("subscriptions.target")->where([ + "target" => $this->getId(), + "model" => static::class, + "follower" => $user->getId(), + ])->fetch()); + + return $subbed && ($this->getOpennesStatus() === static::CLOSED ? $this->isSubscriptionAccepted($user) : true); + } + + function getFollowersQuery(): GroupedSelection + { + $query = $this->getRecord()->related("subscriptions.target"); + + if($this->getOpennesStatus() === static::OPEN) { + $query = $query->where("model", "openvk\\Web\\Models\\Entities\\Club"); + } else { + return false; + } + + return $query; + } + + function getFollowersCount(): int + { + return sizeof($this->getFollowersQuery()); + } + + function getFollowers(int $page = 1): \Traversable + { + $rels = $this->getFollowersQuery()->page($page, 6); + + foreach($rels as $rel) { + $rel = (new Users)->get($rel->follower); + if(!$rel) continue; + + yield $rel; + } + } + + function getManagers(int $page = 1): \Traversable + { + $rels = $this->getRecord()->related("group_coadmins.club")->page($page, 6); + + foreach($rels as $rel) { + $rel = (new Users)->get($rel->user); + if(!$rel) continue; + } + } + + function getManagersCount(): int + { + return sizeof($this->getRecord()->related("group_coadmins.club")) + 1; + } + + function addManager(User $user, ?string $comment = NULL): void + { + DB::i()->getContext()->table("group_coadmins")->insert([ + "club" => $this->getId(), + "user" => $user->getId(), + "comment" => $comment, + ]); + } + + function removeManager(User $user): void + { + DB::i()->getContext()->table("group_coadmins")->where([ + "club" => $this->getId(), + "user" => $user->getId(), + ])->delete(); + } + + function canBeModifiedBy(User $user): bool + { + $id = $user->getId(); + if($this->getRecord()->owner === $id) + return true; + + return !is_null($this->getRecord()->related("group_coadmins.club")->where("user", $id)->fetch()); + } + + use Traits\TSubscribable; +} diff --git a/Web/Models/Entities/Comment.php b/Web/Models/Entities/Comment.php new file mode 100644 index 000000000..ebbfccc30 --- /dev/null +++ b/Web/Models/Entities/Comment.php @@ -0,0 +1,27 @@ +getRecord()->id; + } + + function getVirtualId(): int + { + return 0; + } + + function getTarget(): ?Postable + { + $entityClassName = $this->getRecord()->model; + $repoClassName = str_replace("Entities", "Repositories", $entityClassName) . "s"; + $entity = (new $repoClassName)->get($this->getRecord()->target); + + return $entity; + } +} diff --git a/Web/Models/Entities/Correspondence.php b/Web/Models/Entities/Correspondence.php new file mode 100644 index 000000000..216df6a85 --- /dev/null +++ b/Web/Models/Entities/Correspondence.php @@ -0,0 +1,146 @@ +correspondents = [$correspondent, $anotherCorrespondent]; + $this->messages = DatabaseConnection::i()->getContext()->table("messages"); + } + + /** + * Get /im?sel url. + * + * @returns string - URL + */ + function getURL(): string + { + $id = $this->correspondents[1]->getId(); + $id = get_class($this->correspondents[1]) === 'openvk\Web\Models\Entities\Club' ? $id * -1 : $id; + + return "/im?sel=$id"; + } + + /** + * Get correspondents as array. + * + * @returns RowModel[] Array of correspondents (usually two) + */ + function getCorrespondents(): array + { + return $this->correspondents; + } + + /** + * Fetch messages. + * + * Fetch messages on per page basis. + * + * @param $page - page (defaults to first) + * @param $perPage - messages per page (defaults to default per page count) + * @returns \Traversable - iterable messages cursor + */ + function getMessages(?float $offset = NULL, ?int $perPage = NULL): array + { + $query = file_get_contents(__DIR__ . "/../sql/get-messages.tsql"); + $params = [ + [get_class($this->correspondents[0]), get_class($this->correspondents[1])], + [$this->correspondents[0]->getId(), $this->correspondents[1]->getId()], + [$perPage ?? OPENVK_DEFAULT_PER_PAGE] + ]; + $params = array_merge($params[0], $params[1], array_reverse($params[0]), array_reverse($params[1]), $params[2]); + + if(is_null($offset)) + $query = str_replace("\n AND (`id` < ?)", "", $query); + else + array_unshift($params, $offset); + + $msgs = DatabaseConnection::i()->getConnection()->query($query, ...$params); + $msgs = array_map(function($message) { + $message = new ActiveRow((array) $message, $this->messages); #Directly creating ActiveRow is faster than making query + + return new Message($message); + }, iterator_to_array($msgs)); + + return $msgs; + } + + /** + * Get last message from correspondence. + * + * @returns Message|null - message, if any + */ + function getPreviewMessage(): ?Message + { + $messages = $this->getMessages(null, 1); + return $messages[0] ?? NULL; + } + + /** + * Send message as user, who is currently logged in. + * + * @deprecated + * @returns Message|false - resulting message, or false in case of non-successful transaction + */ + function sendMessage(Message $message, bool $dontReverse = false) + { + $user = (new Users)->getByChandlerUser(Authenticator::i()->getUser()); + if(!$user) return false; + + $ids = [$this->correspondents[0]->getId(), $this->correspondents[1]->getId()]; + $classes = [get_class($this->correspondents[0]), get_class($this->correspondents[1])]; + if(!in_array($user->getId(), $ids)) return false; + if($ids[1] === $user->getId() && !$dontReverse) { + $ids = array_reverse($ids); + $classes = array_reverse($classes); + } + + $message->setSender_Id($ids[0]); + $message->setRecipient_Id($ids[1]); + $message->setSender_Type($classes[0]); + $message->setRecipient_Type($classes[1]); + $message->setCreated(time()); + $message->save(); + + # да + if($ids[0] !== $ids[1]) { + $event = new NewMessageEvent($message); + (SignalManager::i())->triggerEvent($event, $ids[1]); + } + + return $message; + } +} diff --git a/Web/Models/Entities/Manager.php b/Web/Models/Entities/Manager.php new file mode 100644 index 000000000..feefb69bf --- /dev/null +++ b/Web/Models/Entities/Manager.php @@ -0,0 +1,46 @@ +getRecord()->id; + } + + function getUserId(): string + { + return $this->getRecord()->user; + } + + function getUser(): ?User + { + return (new Users)->get($this->getRecord()->user); + } + + function getClubId(): string + { + return $this->getRecord()->club; + } + + function getClub(): ?Club + { + return (new Clubs)->get($this->getRecord()->club); + } + + function getComment(): string + { + return is_null($this->getRecord()->comment) ? "" : $this->getRecord()->comment; + } + + use Traits\TSubscribable; +} diff --git a/Web/Models/Entities/Media.php b/Web/Models/Entities/Media.php new file mode 100644 index 000000000..2d91246bd --- /dev/null +++ b/Web/Models/Entities/Media.php @@ -0,0 +1,90 @@ +changes["hash"])) + unlink($this->pathFromHash($this->changes["hash"])); + } + + private function getBaseDir(): string + { + $uploadSettings = OPENVK_ROOT_CONF["openvk"]["preferences"]["uploads"]; + if($uploadSettings["mode"] === "server" && $uploadSettings["server"]["kind"] === "cdn") + return $uploadSettings["server"]["directory"]; + else + return OPENVK_ROOT . "/storage/"; + } + + abstract protected function saveFile(string $filename, string $hash): bool; + + protected function pathFromHash(string $hash): string + { + $dir = $this->getBaseDir() . substr($hash, 0, 2); + if(!is_dir($dir)) + mkdir($dir); + + return "$dir/$hash." . $this->fileExtension; + } + + function getFileName(): string + { + return $this->pathFromHash($this->getRecord()->hash); + } + + function getURL(): string + { + $hash = $this->getRecord()->hash; + + switch(OPENVK_ROOT_CONF["openvk"]["preferences"]["uploads"]["mode"]) { + case "default": + return "http://" . $_SERVER['HTTP_HOST'] . "/blob_" . substr($hash, 0, 2) . "/$hash.$this->fileExtension"; + break; + case "accelerated": + return "http://" . $_SERVER['HTTP_HOST'] . "/openvk-datastore/$hash.$this->fileExtension"; + break; + case "server": + $settings = (object) OPENVK_ROOT_CONF["openvk"]["preferences"]["uploads"]["server"]; + return ( + $settings->protocol . + "://" . $settings->host . + $settings->path . + substr($hash, 0, 2) . "/$hash.$this->fileExtension" + ); + break; + } + } + + function getDescription(): ?string + { + return $this->getRecord()->description; + } + + function isDeleted(): bool + { + return (bool) $this->getRecord()->deleted; + } + + function setHash(string $hash): void + { + throw new ISE("Setting file hash manually is forbidden"); + } + + function setFile(array $file): void + { + if($file["error"] !== UPLOAD_ERR_OK) + throw new ISE("File uploaded is corrupted"); + + $hash = hash_file("whirlpool", $file["tmp_name"]); + $this->saveFile($file["tmp_name"], $hash); + + $this->stateChanges("hash", $hash); + } +} diff --git a/Web/Models/Entities/Message.php b/Web/Models/Entities/Message.php new file mode 100644 index 000000000..e9e0fb7af --- /dev/null +++ b/Web/Models/Entities/Message.php @@ -0,0 +1,106 @@ +getRecord()->sender_type === 'openvk\Web\Models\Entities\User') + return (new Users)->get($this->getRecord()->sender_id); + else if($this->getRecord()->sender_type === 'openvk\Web\Models\Entities\Club') + return (new Clubs)->get($this->getRecord()->sender_id); + } + + /** + * Get the destination of the message. + * + * Returns either user or club. + * + * @returns User|Club + */ + function getRecipient(): ?RowModel + { + if($this->getRecord()->recipient_type === 'openvk\Web\Models\Entities\User') + return (new Users)->get($this->getRecord()->recipient_id); + else if($this->getRecord()->recipient_type === 'openvk\Web\Models\Entities\Club') + return (new Clubs)->get($this->getRecord()->recipient_id); + } + + /** + * Get date of initial publication. + * + * @returns DateTime + */ + function getSendTime(): DateTime + { + return new DateTime($this->getRecord()->created); + } + + /** + * Get date of last edit, if any edits were made, otherwise null. + * + * @returns DateTime|null + */ + function getEditTime(): ?DateTime + { + $edited = $this->getRecord()->edited; + if(is_null($edited)) return NULL; + + return new DateTime($edited); + } + + /** + * Is this message an ad? + * + * Messages can never be ads. + * + * @returns false + */ + function isAd(): bool + { + return false; + } + + /** + * Simplify to array + * + * @returns array + */ + function simplify(): array + { + $author = $this->getSender(); + + return [ + "uuid" => $this->getId(), + "sender" => [ + "id" => $author->getId(), + "link" => $_SERVER['REQUEST_SCHEME'] . "://" . $_SERVER['HTTP_HOST'] . $author->getURL(), + "avatar" => $author->getAvatarUrl(), + "name" => $author->getFullName(), + ], + "timing" => [ + "sent" => (string) $this->getSendTime()->format("%e %B %G" . tr("time_at_sp") . "%X"), + "edited" => is_null($this->getEditTime()) ? null : (string) $this->getEditTime(), + ], + "text" => $this->getText(), + ]; + } + + use Traits\TRichText; +} diff --git a/Web/Models/Entities/Note.php b/Web/Models/Entities/Note.php new file mode 100644 index 000000000..172041a25 --- /dev/null +++ b/Web/Models/Entities/Note.php @@ -0,0 +1,99 @@ +set("Attr.AllowedClasses", []); + $config->set("Attr.DefaultInvalidImageAlt", "Unknown image"); + $config->set("AutoFormat.AutoParagraph", true); + $config->set("AutoFormat.Linkify", true); + $config->set("URI.Base", "//$_SERVER[SERVER_NAME]/"); + $config->set("URI.Munge", "/away.php?xinf=%n.%m:%r&css=%p&to=%s"); + $config->set("URI.MakeAbsolute", true); + $config->set("HTML.Doctype", "XHTML 1.1"); + $config->set("HTML.TidyLevel", "heavy"); + $config->set("HTML.TidyLevel", "heavy"); + $config->set("HTML.AllowedElements", [ + "div", + "h3", + "h4", + "h5", + "h6", + "p", + "i", + "b", + "a", + "del", + "ins", + "sup", + "sub", + "table", + "thead", + "tbody", + "tr", + "td", + "th", + "img", + "ul", + "ol", + "li", + "hr", + "br", + "acronym", + "blockquote", + "cite", + ]); + $config->set("HTML.AllowedAttributes", [ + "table.summary", + "td.abbr", + "th.abbr", + "a.href", + "img.src", + "img.alt", + "img.style", + "div.style", + "div.title", + ]); + $config->set("CSS.AllowedProperties", [ + "float", + "height", + "width", + "max-height", + "max-width", + "font-weight", + ]); + + $purifier = new HTMLPurifier($config); + return $purifier->purify($this->getRecord()->source); + } + + function getName(): string + { + return $this->getRecord()->name; + } + + function getPreview(int $length = 25): string + { + return ovk_proc_strtr(strip_tags($this->getRecord()->source), $length); + } + + function getText(): string + { + $cached = $this->getRecord()->cached_content; + if(!$cached) { + $cached = $this->renderHTML(); + $this->setCached_Content($cached); + $this->save(); + } + + return $cached; + } +} diff --git a/Web/Models/Entities/Notification.php b/Web/Models/Entities/Notification.php new file mode 100644 index 000000000..e69de29bb diff --git a/Web/Models/Entities/Notifications/ClubModeratorNotification.php b/Web/Models/Entities/Notifications/ClubModeratorNotification.php new file mode 100644 index 000000000..cb89deea3 --- /dev/null +++ b/Web/Models/Entities/Notifications/ClubModeratorNotification.php @@ -0,0 +1,13 @@ +getText(), 10)); + } +} diff --git a/Web/Models/Entities/Notifications/FriendRemovalNotification.php b/Web/Models/Entities/Notifications/FriendRemovalNotification.php new file mode 100644 index 000000000..17d68e0a3 --- /dev/null +++ b/Web/Models/Entities/Notifications/FriendRemovalNotification.php @@ -0,0 +1,13 @@ +recipient = $recipient; + $this->originModel = $originModel; + $this->targetModel = $targetModel; + $this->time = $time ?? time(); + $this->data = $data; + } + + private function encodeType(object $model): int + { + return (int) json_decode(file_get_contents(__DIR__ . "/../../../../data/modelCodes.json"), true)[get_class($model)]; + } + + function getActionCode(): int + { + return $this->actionCode; + } + + function setActionCode(int $code): void + { + $this->actionCode = $this->actionCode ?? $code; + } + + function getTemplatePath(): string + { + return implode("_", [ + "./../components/notifications/$this->actionCode/", + $this->encodeType($this->originModel), + $this->encodeType($this->targetModel), + ".xml" + ]); + } + + function getRecipient(): User + { + return $this->recipient; + } + + function getModel(int $index): RowModel + { + switch($index) { + case 0: + return $this->originModel; + case 1: + return $this->targetModel; + } + } + + function getData(): string + { + return $this->data; + } + + function getDateTime(): DateTime + { + return new DateTime($this->time); + } + + function emit(): bool + { + if(!($e = eventdb())) + return false; + + $edb = $e->getConnection(); + $edb->query("INSERT INTO notifications VALUES (0, ?, ?, ?, ?, ?, ?, ?, ?)", ...[ + $this->recipient->getId(), + $this->encodeType($this->originModel), + $this->originModel->getId(), + $this->encodeType($this->targetModel), + $this->targetModel->getId(), + $this->actionCode, + $this->data, + $this->time, + ]); + + return true; + } +} diff --git a/Web/Models/Entities/Notifications/RepostNotification.php b/Web/Models/Entities/Notifications/RepostNotification.php new file mode 100644 index 000000000..573a44a97 --- /dev/null +++ b/Web/Models/Entities/Notifications/RepostNotification.php @@ -0,0 +1,13 @@ +getText(), 10)); + } +} diff --git a/Web/Models/Entities/PasswordReset.php b/Web/Models/Entities/PasswordReset.php new file mode 100644 index 000000000..f54428bec --- /dev/null +++ b/Web/Models/Entities/PasswordReset.php @@ -0,0 +1,61 @@ +get($this->getRecord()->profile); + } + + function getKey(): string + { + return $this->getRecord()->key; + } + + function getToken(): string + { + return $this->getKey(); + } + + function getCreationTime(): DateTime + { + return new DateTime($this->getRecord()->timestamp); + } + + /** + * User can request password reset only if he does not have any "new" password resets. + * Password reset becomes "old" after 5 minutes and one second. + */ + function isNew(): bool + { + return $this->getRecord()->timestamp > (time() - 301); + } + + function isStillValid(): bool + { + return $this->getRecord()->timestamp > (time() - 172801); + } + + function verify(string $token): bool + { + try { + return $this->isStillValid() ? sodium_memcmp($this->getKey(), $token) : false; + } catch(\SodiumException $ex) { + return false; + } + } + + function save(): void + { + $this->stateChanges("key", base64_encode(openssl_random_pseudo_bytes(46))); + $this->stateChanges("timestamp", time()); + + parent::save(); + } +} diff --git a/Web/Models/Entities/Photo.php b/Web/Models/Entities/Photo.php new file mode 100644 index 000000000..372be11cc --- /dev/null +++ b/Web/Models/Entities/Photo.php @@ -0,0 +1,44 @@ +height >= ($image->width * pi())) || ($image->width >= ($image->height * pi()))) + throw new ISE("Invalid layout: expected layout that matches (x, ?!>3x)"); + + $image->save($this->pathFromHash($hash), 92, Image::JPEG); + + return true; + } + + function crop(real $left, real $top, real $width, real $height): bool + { + if(isset($this->changes["hash"])) + $hash = $this->changes["hash"]; + else if(!is_null($this->getRecord())) + $hash = $this->getRecord()->hash; + else + throw new ISE("Cannot crop uninitialized image. Please call setFile(\$_FILES[...]) first."); + + $image = Image::fromFile($this->pathFromHash($hash)); + $image->crop($left, $top, $width, $height); + return $image->save($this->pathFromHash($hash)); + } + + function isolate(): void + { + if(is_null($this->getRecord())) + throw new ISE("Cannot isolate unpresisted image. Please save() it first."); + + DB::i()->getContext()->table("album_relations")->where("photo", $this->getRecord()->id)->delete(); + } +} diff --git a/Web/Models/Entities/Post.php b/Web/Models/Entities/Post.php new file mode 100644 index 000000000..81374a353 --- /dev/null +++ b/Web/Models/Entities/Post.php @@ -0,0 +1,91 @@ +getRecord()->flags & 0b10000000) > 0 )) { + if($this->getRecord()->wall < 0) + return (new Clubs)->get(abs($this->getRecord()->wall)); + } + + return parent::getOwner(); + } + + function getPrettyId(): string + { + return $this->getRecord()->wall . "_" . $this->getVirtualId(); + } + + function getTargetWall(): int + { + return $this->getRecord()->wall; + } + + function getRepostCount(): int + { + return sizeof( + $this->getRecord() + ->related("attachments.attachable_id") + ->where("attachable_type", get_class($this)) + ); + } + + function isAd(): bool + { + return (bool) $this->getRecord()->ad; + } + + function isPostedOnBehalfOfGroup(): bool + { + return ($this->getRecord()->flags & 0b10000000) > 0; + } + + function isSigned(): bool + { + return ($this->getRecord()->flags & 0b01000000) > 0; + } + + function isExplicit(): bool + { + return ($this->getRecord()->flags & 0b00100000) > 0; + } + + function isDeleted(): bool + { + return (bool) $this->getRecord()->deleted; + } + + function getOwnerPost(): int + { + return $this->getRecord()->owner; + } + + function setContent(string $content): void + { + if(ctype_space($content)) + throw new \LengthException("Content length must be at least 1 character (not counting whitespaces)."); + + $this->stateChanges("content", $content); + } + + function deletePost(): void + { + $this->setDeleted(1); + $this->unwire(); + $this->save(); + } + + use Traits\TRichText; +} diff --git a/Web/Models/Entities/Postable.php b/Web/Models/Entities/Postable.php new file mode 100644 index 000000000..00b132b18 --- /dev/null +++ b/Web/Models/Entities/Postable.php @@ -0,0 +1,135 @@ +getContext()->table($this->tableName); + } + + function getOwner(): RowModel + { + $oid = (int) $this->getRecord()->owner; + if($oid > 0) + return (new Users)->get($oid); + else + return (new Clubs)->get($oid * -1); + } + + function getVirtualId(): int + { + return $this->getRecord()->virtual_id; + } + + function getPrettyId(): string + { + return $this->getRecord()->owner . "_" . $this->getVirtualId(); + } + + function getPublicationTime(): DateTime + { + return new DateTime($this->getRecord()->created); + } + + function getEditTime(): ?DateTime + { + $edited = $this->getRecord()->edited; + if(is_null($edited)) return NULL; + + return new DateTime($edited); + } + + function getComments(int $page, ?int $perPage = NULL): \Traversable + { + return (new Comments)->getCommentsByTarget($this, $page, $perPage); + } + + function getCommentsCount(): int + { + return (new Comments)->getCommentsCountByTarget($this); + } + + function getLikesCount(): int + { + return sizeof(DB::i()->getContext()->table("likes")->where([ + "model" => static::class, + "target" => $this->getRecord()->id, + ])); + } + + function toggleLike(User $user): void + { + $searchData = [ + "origin" => $user->getId(), + "model" => static::class, + "target" => $this->getRecord()->id, + ]; + if(sizeof(DB::i()->getContext()->table("likes")->where($searchData)) > 0) + DB::i()->getContext()->table("likes")->where($searchData)->delete(); + else + DB::i()->getContext()->table("likes")->insert($searchData); + } + + function hasLikeFrom(User $user): bool + { + $searchData = [ + "origin" => $user->getId(), + "model" => static::class, + "target" => $this->getRecord()->id, + ]; + + return sizeof(DB::i()->getContext()->table("likes")->where($searchData)) > 0; + } + + function setVirtual_Id(int $id): void + { + throw new ISE("Setting virtual id manually is forbidden"); + } + + function save(): void + { + $vref = $this->upperNodeReferenceColumnName; + + $vid = $this->getRecord()->{$vref} ?? $this->changes[$vref]; + if(!$vid) + throw new ISE("Can't presist post due to inability to calculate it's $vref post count. Have you set it?"); + + $pCount = sizeof($this->getTable()->where($vref, $vid)); + if(is_null($this->getRecord())) { + # lol allow ppl to taint created value + if(!isset($this->changes["created"])) + $this->stateChanges("created", time()); + + $this->stateChanges("virtual_id", $pCount + 1); + } else { + $this->stateChanges("edited", time()); + } + + parent::save(); + } + + use Traits\TAttachmentHost; + use Traits\TOwnable; +} diff --git a/Web/Models/Entities/Ticket.php b/Web/Models/Entities/Ticket.php new file mode 100644 index 000000000..d66e08b12 --- /dev/null +++ b/Web/Models/Entities/Ticket.php @@ -0,0 +1,73 @@ +getRecord()->id; + } + + function getStatus(): string + { + if ($this->getRecord()->type === 0) + { + return 'Вопрос находится на рассмотрении.'; + } elseif ($this->getRecord()->type === 1) { + return 'Есть ответ.'; + } elseif ($this->getRecord()->type === 2) { + return 'Закрыто.'; + } + } + + function getType(): int + { + return $this->getRecord()->type; + } + + function getName(): string + { + return ovk_proc_strtr($this->getRecord()->name, 100); + } + + function getContext(): string + { + return $this->getRecord()->text; + } + + function getTime(): DateTime + { + return new DateTime($this->getRecord()->created); + } + + function isDeleted(): bool + { + if ($this->getRecord()->deleted === 0) + { + return false; + } elseif ($this->getRecord()->deleted === 1) { + return true; + } + } + + function authorId(): int + { + return $this->getRecord()->user_id; + } + + function getUser(): user + { + return (new Users)->get($this->getRecord()->user_id); + } +} diff --git a/Web/Models/Entities/TicketComment.php b/Web/Models/Entities/TicketComment.php new file mode 100644 index 000000000..02e370021 --- /dev/null +++ b/Web/Models/Entities/TicketComment.php @@ -0,0 +1,41 @@ +getRecord()->id; + } + function getUType(): int + { + return $this->getRecord()->user_type; + } + + function getUser(): User + { + return (new Users)->get($this->getRecord()->user_id); + } + + function getContext(): string + { + return $this->getRecord()->text; + } + + function getTime(): DateTime + { + return new DateTime($this->getRecord()->created); + } + +} diff --git a/Web/Models/Entities/Traits/TAttachmentHost.php b/Web/Models/Entities/Traits/TAttachmentHost.php new file mode 100644 index 000000000..61bf487c1 --- /dev/null +++ b/Web/Models/Entities/Traits/TAttachmentHost.php @@ -0,0 +1,56 @@ + get_class($this), + "target_id" => $this->getId(), + "attachable_type" => get_class($attachment), + "attachable_id" => $attachment->getId(), + ]; + } + + function getChildren(): \Traversable + { + $sel = $this->getRecord() + ->related("attachments.target_id") + ->where("attachments.target_type", get_class($this)); + foreach($sel as $rel) { + $repoName = $rel->attachable_type . "s"; + $repoName = str_replace("Entities", "Repositories", $repoName); + $repo = new $repoName; + + yield $repo->get($rel->attachable_id); + } + } + + function attach(Attachable $attachment): void + { + DatabaseConnection::i()->getContext() + ->table("attachments") + ->insert($this->composeAttachmentRequestData($attachment)); + } + + function detach(Attachable $attachment): bool + { + $res = DatabaseConnection::i()->getContext() + ->table("attachments") + ->where($this->composeAttachmentRequestData($attachment)) + ->delete(); + + return $res > 0; + } + + function unwire(): void + { + $this->getRecord() + ->related("attachments.target_id") + ->where("attachments.target_type", get_class($this)) + ->delete(); + } +} diff --git a/Web/Models/Entities/Traits/TOwnable.php b/Web/Models/Entities/Traits/TOwnable.php new file mode 100644 index 000000000..4c6c9c947 --- /dev/null +++ b/Web/Models/Entities/Traits/TOwnable.php @@ -0,0 +1,18 @@ +isCreatedBySystem()) + return false; + + if($this->getRecord()->owner > 0) + return $this->getRecord()->owner === $user->getId(); + else + return $this->getOwner()->canBeModifiedBy($user); + } +} diff --git a/Web/Models/Entities/Traits/TRichText.php b/Web/Models/Entities/Traits/TRichText.php new file mode 100644 index 000000000..4d101afa0 --- /dev/null +++ b/Web/Models/Entities/Traits/TRichText.php @@ -0,0 +1,47 @@ +"; + + $text = str_replace($emoji["emoji"], $image, $text); + } + + return $text; + } + + private function removeZalgo(string $text): string + { + return preg_replace("%[\x{0300}-\x{036F}]{3,}%Xu", "�", $text); + } + + function getText(bool $html = true): string + { + $text = htmlentities($this->getRecord()->content, ENT_DISALLOWED | ENT_XHTML); + if($html) { + $rel = $this->isAd() ? "sponsored" : "ugc"; + $text = preg_replace( + "%((https?|ftp):\/\/(\S*?\.\S*?))([\s)\[\]{},;\"\':<]|\.\s|$)%", + "$3$4", + $text + ); + $text = preg_replace("%@(id|club)([0-9]++) \(([\p{L} 0-9]+)\)%Xu", "[$1$2|$3]", $text); + $text = preg_replace("%@(id|club)([0-9]++)%Xu", "[$1$2|@$1$2]", $text); + $text = preg_replace("%\[(id|club)([0-9]++)\|([\p{L} 0-9@]+)\]%Xu", "$3", $text); + $text = preg_replace("%(#([\p{L}_-]++[0-9]*[\p{L}_-]*))%Xu", "$1", $text); + $text = $this->formatEmojis($text); + $text = $this->removeZalgo($text); + $text = nl2br($text); + } + + return $text; + } +} diff --git a/Web/Models/Entities/Traits/TSubscribable.php b/Web/Models/Entities/Traits/TSubscribable.php new file mode 100644 index 000000000..802bc4272 --- /dev/null +++ b/Web/Models/Entities/Traits/TSubscribable.php @@ -0,0 +1,42 @@ +getContext()->table("subscriptions")->where([ + "model" => static::class, + "target" => $this->getId(), + ]); + + foreach($subs as $sub) { + $sub = (new Users)->get($sub->follower); + if(!$sub) continue; + + yield $sub; + } + }*/ + + function toggleSubscription(User $user): bool + { + $ctx = DatabaseConnection::i()->getContext(); + $data = [ + "follower" => $user->getId(), + "model" => static::class, + "target" => $this->getId(), + ]; + $sub = $ctx->table("subscriptions")->where($data); + + if(!($sub->fetch())) { + $ctx->table("subscriptions")->insert($data); + return true; + } + + $sub->delete(); + return false; + } +} diff --git a/Web/Models/Entities/User.php b/Web/Models/Entities/User.php new file mode 100644 index 000000000..1833fec4b --- /dev/null +++ b/Web/Models/Entities/User.php @@ -0,0 +1,641 @@ +getId(); + $query = "SELECT id FROM\n" . file_get_contents(__DIR__ . "/../sql/$filename.tsql"); + $query .= "\n LIMIT 6 OFFSET " . ( ($page - 1) * 6 ); + + $rels = DatabaseConnection::i()->getConnection()->query($query, $id, $id); + foreach($rels as $rel) { + $rel = (new Users)->get($rel->id); + if(!$rel) continue; + + yield $rel; + } + } + + protected function _abstractRelationCount(string $filename): int + { + $id = $this->getId(); + $query = "SELECT COUNT(*) AS cnt FROM\n" . file_get_contents(__DIR__ . "/../sql/$filename.tsql"); + + return (int) DatabaseConnection::i()->getConnection()->query($query, $id, $id)->fetch()->cnt; + } + + function getId(): int + { + return $this->getRecord()->id; + } + + function getStyle(): int + { + return $this->getRecord()->style; + } + + function getStyleAvatar(): int + { + return $this->getRecord()->style_avatar; + } + + function hasMilkshakeEnabled(): bool + { + return (bool) $this->getRecord()->milkshake; + } + + function getChandlerGUID(): string + { + return $this->getRecord()->user; + } + + function getChandlerUser(): ChandlerUser + { + return new ChandlerUser($this->getRecord()->ref("ChandlerUsers", "user")); + } + + function getURL(): string + { + if(!is_null($this->getShortCode())) + return "/" . $this->getShortCode(); + else + return "/id" . $this->getId(); + } + + function getAvatarUrl(): string + { + if($this->getRecord()->deleted) + return "/assets/packages/static/openvk/img/camera_200.png"; + else if($this->isBanned()) + return "/assets/packages/static/openvk/img/banned.jpg"; + + $avPhoto = $this->getAvatarPhoto(); + if(is_null($avPhoto)) + return "/assets/packages/static/openvk/img/camera_200.png"; + else + return $avPhoto->getURL(); + } + + function getAvatarLink(): string + { + $avPhoto = $this->getAvatarPhoto(); + if(!$avPhoto) return "javascript:void(0)"; + + $pid = $avPhoto->getPrettyId(); + $aid = (new Albums)->getUserAvatarAlbum($this)->getId(); + + return "/photo$pid?from=album$aid"; + } + + function getAvatarPhoto(): ?Photo + { + $avAlbum = (new Albums)->getUserAvatarAlbum($this); + $avCount = $avAlbum->getPhotosCount(); + $avPhotos = $avAlbum->getPhotos($avCount, 1); + + return iterator_to_array($avPhotos)[0] ?? NULL; + } + + function getFirstName(): string + { + return $this->getRecord()->deleted ? "DELETED" : mb_convert_case($this->getRecord()->first_name, MB_CASE_TITLE); + } + + function getLastName(): string + { + return $this->getRecord()->deleted ? "DELETED" : mb_convert_case($this->getRecord()->last_name, MB_CASE_TITLE); + } + + function getPseudo(): ?string + { + return $this->getRecord()->deleted ? "DELETED" : $this->getRecord()->pseudo; + } + + function getFullName(): string + { + if($this->getRecord()->deleted) + return "DELETED"; + + $pseudo = $this->getPseudo(); + if(!$pseudo) + $pseudo = " "; + else + $pseudo = " ($pseudo) "; + + return $this->getFirstName() . $pseudo . $this->getLastName(); + } + + function getCanonicalName(): string + { + if($this->getRecord()->deleted) + return "DELETED"; + else + return $this->getFirstName() . ' ' . $this->getLastName(); + } + + function getPhone(): ?string + { + return $this->getRecord()->phone; + } + + function getEmail(): ?string + { + return $this->getRecord()->email; + } + + function getOnline(): DateTime + { + return new DateTime($this->getRecord()->online); + } + + function getDescription(): ?string + { + return $this->getRecord()->about; + } + + function getStatus(): ?string + { + return $this->getRecord()->status; + } + + function getShortCode(): ?string + { + return $this->getRecord()->shortcode; + } + + function getBanReason(): ?string + { + return $this->getRecord()->block_reason; + } + + function getType(): int + { + return $this->getRecord()->type; + } + + function getCoins(): int + { + return $this->getRecord()->coins; + } + + function getReputation(): int + { + return $this->getRecord()->reputation; + } + + function getRegistrationTime(): DateTime + { + return new DateTime($this->getRecord()->since->getTimestamp()); + } + + function getRegistrationIP(): string + { + return $this->getRecord()->registering_ip; + } + + function getHometown(): ?string + { + return $this->getRecord()->hometown; + } + + function getPoliticalViews(): int + { + return $this->getRecord()->polit_views; + } + + function getMaritalStatus(): int + { + return $this->getRecord()->marital_status; + } + + function getContactEmail(): ?string + { + return $this->getRecord()->email_contact; + } + + function getTelegram(): ?string + { + return $this->getRecord()->telegram; + } + + function getInterests(): ?string + { + return $this->getRecord()->interests; + } + + function getFavoriteMusic(): ?string + { + return $this->getRecord()->fav_music; + } + + function getFavoriteFilms(): ?string + { + return $this->getRecord()->fav_films; + } + + function getFavoriteShows(): ?string + { + return $this->getRecord()->fav_shows; + } + + function getFavoriteBooks(): ?string + { + return $this->getRecord()->fav_books; + } + + function getFavoriteQuote(): ?string + { + return $this->getRecord()->fav_quote; + } + + function getCity(): ?string + { + return $this->getRecord()->city; + } + + function getPhysicalAddress(): ?string + { + return $this->getRecord()->address; + } + + function getNotificationOffset(): int + { + return $this->getRecord()->notification_offset; + } + + function updateNotificationOffset(): void + { + $this->stateChanges("notification_offset", time()); + } + + function getLeftMenuItemStatus(string $id): bool + { + return (bool) bmask($this->getRecord()->left_menu, [ + "length" => 1, + "mappings" => [ + "photos", + "videos", + "messages", + "notes", + "groups", + "news", + ], + ])->get($id); + } + + function getPrivacySetting(string $id): int + { + return (int) bmask($this->getRecord()->privacy, [ + "length" => 2, + "mappings" => [ + "page.read", + "page.info.read", + "groups.read", + "photos.read", + "videos.read", + "notes.read", + "friends.read", + "friends.add", + "wall.write", + ], + ])->get($id); + } + + function getPrivacyPermission(string $permission, ?User $user = NULL): bool + { + $permStatus = $this->getPrivacySetting($permission); + if(!$user) + return $permStatus === User::PRIVACY_EVERYONE; + else if($user->getId() === $this->getId()) + return true; + + switch($permStatus) { + case User::PRIVACY_ONLY_FRIENDS: + return $this->getSubscriptionStatus($user) === User::SUBSCRIPTION_MUTUAL; + case User::PRIVACY_ONLY_REGISTERED: + case User::PRIVACY_EVERYONE: + return true; + default: + return false; + } + } + + function getProfileCompletenessReport(): object + { + $incompleteness = 0; + $unfilled = []; + + if(!$this->getRecord()->status) { + $unfilled[] = "status"; + $incompleteness += 10; + } + if(!$this->getRecord()->telegram) { + $unfilled[] = "telegram"; + $incompleteness += 10; + } + if(!$this->getRecord()->email) { + $unfilled[] = "email"; + $incompleteness += 20; + } + if(!$this->getRecord()->phone) { + $unfilled[] = "phone"; + $incompleteness += 20; + } + if(!$this->getRecord()->city) { + $unfilled[] = "city"; + $incompleteness += 20; + } + if(!$this->getRecord()->interests) { + $unfilled[] = "interests"; + $incompleteness += 20; + } + + return (object) [ + "total" => 100 - $incompleteness, + "unfilled" => $unfilled, + ]; + } + + function getFriends(int $page = 1): \Traversable + { + return $this->_abstractRelationGenerator("get-friends", $page); + } + + function getFriendsCount(): int + { + return $this->_abstractRelationCount("get-friends"); + } + + function getFollowers(int $page = 1): \Traversable + { + return $this->_abstractRelationGenerator("get-followers", $page); + } + + function getFollowersCount(): int + { + return $this->_abstractRelationCount("get-followers"); + } + + function getSubscriptions(int $page = 1): \Traversable + { + return $this->_abstractRelationGenerator("get-subscriptions-user", $page); + } + + function getSubscriptionsCount(): int + { + return $this->_abstractRelationCount("get-subscriptions-user"); + } + + function getClubs(int $page = 1): \Traversable + { + $sel = $this->getRecord()->related("subscriptions.follower")->page($page, OPENVK_DEFAULT_PER_PAGE); + foreach($sel->where("model", "openvk\\Web\\Models\\Entities\\Club") as $target) { + $target = (new Clubs)->get($target->target); + if(!$target) continue; + + yield $target; + } + } + + function getClubCount(): int + { + $sel = $this->getRecord()->related("subscriptions.follower"); + $sel = $sel->where("model", "openvk\\Web\\Models\\Entities\\Club"); + + return sizeof($sel); + } + + function getMeetings(int $page = 1): \Traversable + { + $sel = $this->getRecord()->related("event_turnouts.user")->page($page, OPENVK_DEFAULT_PER_PAGE); + foreach($sel as $target) { + $target = (new Clubs)->get($target->event); + if(!$target) continue; + + yield $target; + } + } + + function getMeetingCount(): int + { + return sizeof($this->getRecord()->related("event_turnouts.user")); + } + + function getSubscriptionStatus(User $user): int + { + $subbed = !is_null($this->getRecord()->related("subscriptions.follower")->where([ + "model" => static::class, + "target" => $user->getId(), + ])->fetch()); + $followed = !is_null($this->getRecord()->related("subscriptions.target")->where([ + "model" => static::class, + "follower" => $user->getId(), + ])->fetch()); + + if($subbed && $followed) return User::SUBSCRIPTION_MUTUAL; + if($subbed) return User::SUBSCRIPTION_INCOMING; + if($followed) return User::SUBSCRIPTION_OUTGOING; + + return User::SUBSCRIPTION_ABSENT; + } + + function getNotificationsCount(bool $archived = false): int + { + return (new Notifications)->getNotificationCountByUser($this, $this->getNotificationOffset(), $archived); + } + + function getNotifications(int $page, bool $archived = false): \Traversable + { + return (new Notifications)->getNotificationsByUser($this, $this->getNotificationOffset(), $archived, $page); + } + + function getPendingPhoneVerification(): ?ActiveRow + { + return $this->getRecord()->ref("number_verification", "id"); + } + + function getRefLinkId(): string + { + $rand = openssl_random_pseudo_bytes(SODIUM_CRYPTO_STREAM_NONCEBYTES); # anime :) + $key = substr(hex2bin(CHANDLER_ROOT_CONF["security"]["secret"]), 0, SODIUM_CRYPTO_STREAM_KEYBYTES); + + return bin2hex($rand) . bin2hex(sodium_crypto_stream_xor((string) $this->getId(), $rand, $key)); + } + + function isFemale(): bool + { + return (bool) $this->getRecord()->sex; + } + + function isVerified(): bool + { + return (bool) $this->getRecord()->verified; + } + + function isBanned(): bool + { + return !is_null($this->getBanReason()); + } + + function prefersNotToSeeRating(): bool + { + return !((bool) $this->getRecord()->show_rating); + } + + function hasPendingNumberChange(): bool + { + return !is_null($this->getPendingPhoneVerification()); + } + + function verifyNumber(string $code): bool + { + $ver = $this->getPendingPhoneVerification(); + if(!$ver) return false; + + try { + if(sodium_memcmp((string) $ver->code, $code) === -1) return false; + } catch(\SodiumException $ex) { + return false; + } + + $this->setPhone($ver->number); + $this->save(); + + DatabaseConnection::i()->getContext() + ->table("number_verification") + ->where("user", $this->getId()) + ->delete(); + + return true; + } + + function setPrivacySetting(string $id, int $status): void + { + $this->stateChanges("privacy", bmask($this->changes["privacy"] ?? $this->getRecord()->privacy, [ + "length" => 2, + "mappings" => [ + "page.read", + "page.info.read", + "groups.read", + "photos.read", + "videos.read", + "notes.read", + "friends.read", + "friends.add", + "wall.write", + ], + ])->set($id, $status)->toInteger()); + } + + function setLeftMenuItemStatus(string $id, bool $status): void + { + $mask = bmask($this->changes["left_menu"] ?? $this->getRecord()->left_menu, [ + "length" => 1, + "mappings" => [ + "photos", + "videos", + "messages", + "notes", + "groups", + "news", + ], + ])->set($id, (int) $status)->toInteger(); + + $this->stateChanges("left_menu", $mask); + } + + function setShortCode(?string $code = NULL): ?bool + { + if(!is_null($code)) { + if(!preg_match("%^[a-z][a-z0-9\\.\\_]{0,30}[a-z0-9]$%", $code)) + return false; + if(in_array($code, OPENVK_ROOT_CONF["openvk"]["preferences"]["shortcodes"]["forbiddenNames"])) + return false; + if(\Chandler\MVC\Routing\Router::i()->getMatchingRoute("/$code")[0]->presenter !== "UnknownTextRouteStrategy") + return false; + } + + $this->stateChanges("shortcode", $code); + return true; + } + + function setPhoneWithVerification(string $phone): string + { + $code = unpack("S", openssl_random_pseudo_bytes(2))[1]; + + if($this->hasPendingNumberChange()) { + DatabaseConnection::i()->getContext() + ->table("number_verification") + ->where("user", $this->getId()) + ->update(["number" => $phone, "code" => $code]); + } else { + DatabaseConnection::i()->getContext() + ->table("number_verification") + ->insert(["user" => $this->getId(), "number" => $phone, "code" => $code]); + } + + return (string) $code; + } + + # KABOBSQL temporary fix + # Tuesday, the 7th of January 2020 @ 22:43 : implementing quick fix to this problem and monitoring + # NOTICE: this is an ongoing conversation, add your comments just above this line. Thanks! + function setOnline(int $time): bool + { + $this->stateChanges("shortcode", $this->getRecord()->shortcode); #fix KABOBSQL + $this->stateChanges("online", $time); + + return true; + } + + function adminNotify(string $message): bool + { + $admId = OPENVK_ROOT_CONF["openvk"]["preferences"]["support"]["adminAccount"]; + if(!$admId) + return false; + else if(is_null($admin = (new Users)->get($admId))) + return false; + + $cor = new Correspondence($admin, $this); + $msg = new Message; + $msg->setContent($message); + $cor->sendMessage($msg, true); + + return true; + } + + function isDeleted(): bool + { + if ($this->getRecord()->deleted == 1) + return TRUE; + else + return FALSE; + } + + + use Traits\TSubscribable; +} diff --git a/Web/Models/Entities/Video.php b/Web/Models/Entities/Video.php new file mode 100644 index 000000000..397979669 --- /dev/null +++ b/Web/Models/Entities/Video.php @@ -0,0 +1,87 @@ +pathFromHash($hash))) + mkdir($dirId); + + Shell::bash(__DIR__ . "/../shell/processVideo.sh", OPENVK_ROOT, $filename, $hash)->start(); #async :DDD + } catch(ShellUnavailableException $suex) { + exit(OPENVK_ROOT_CONF["openvk"]["debug"] ? "Shell is unavailable" : VIDEOS_FRIENDLY_ERROR); + } catch(UnknownCommandException $ucex) { + exit(OPENVK_ROOT_CONF["openvk"]["debug"] ? "bash is not installed" : VIDEOS_FRIENDLY_ERROR); + } + + usleep(200100); + return true; + } + + function getName(): string + { + return $this->getRecord()->name; + } + + function getType(): int + { + if(!is_null($this->getRecord()->hash)) + return Video::TYPE_DIRECT; + else if(!is_null($this->getRecord()->link)) + return Video::TYPE_EMBED; + } + + function getVideoDriver(): ?VideoDriver + { + if($this->getType() !== Video::TYPE_EMBED) + return NULL; + + [$videoDriver, $pointer] = explode(":", $this->getRecord()->link); + $videoDriver = "openvk\\Web\\Models\\VideoDrivers\\$videoDriver" . "VideoDriver"; + if(!class_exists($videoDriver)) + return NULL; + + return new $videoDriver($pointer); + } + + function getThumbnailURL(): string + { + if($this->getType() === Video::TYPE_DIRECT) { + return preg_replace("%\.[A-z]++$%", ".gif", $this->getURL()); + } else { + return $this->getVideoDriver()->getThumbnailURL(); + } + } + + function setLink(string $link): string + { + if(preg_match(file_get_contents(__DIR__ . "/../VideoDrivers/regex/youtube.txt"), $link, $matches)) { + $pointer = "YouTube:$matches[1]"; + } else if(preg_match(file_get_contents(__DIR__ . "/../VideoDrivers/regex/vimeo.txt"), $link, $matches)) { + $pointer = "Vimeo:$matches[1]"; + } else { + throw new ISE("Invalid link"); + } + + $this->stateChanges("link", $pointer); + + return $pointer; + } +} diff --git a/Web/Models/Repositories/Albums.php b/Web/Models/Repositories/Albums.php new file mode 100644 index 000000000..36af116b9 --- /dev/null +++ b/Web/Models/Repositories/Albums.php @@ -0,0 +1,116 @@ +context = DatabaseConnection::i()->getContext(); + $this->albums = $this->context->table("albums"); + } + + private function toAlbum(?ActiveRow $ar): ?Album + { + return is_null($ar) ? NULL : new Album($ar); + } + + private function getSpecialConditions(int $id, int $type): array + { + return [ + "name" => "[/!\\ DO NOT EDIT: INTERNAL NAME ASSIGNMENT IS ACTIVE]", + "owner" => $id, + "special_type" => $type, + ]; + } + + function get(int $id): ?Album + { + return $this->toAlbum($this->albums->get($id)); + } + + function getUserAlbums(User $user, int $page = 1, ?int $perPage = NULL): \Traversable + { + $perPage = $perPage ?? OPENVK_DEFAULT_PER_PAGE; + $albums = $this->albums->where("owner", $user->getId())->where("special_type", 0)->where("deleted", false); + foreach($albums->page($page, $perPage) as $album) + yield new Album($album); + } + + function getUserAlbumsCount(User $user): int + { + $albums = $this->albums->where("owner", $user->getId())->where("special_type", 0)->where("deleted", false); + return sizeof($albums); + } + + function getClubAlbums(Club $club, int $page = 1, ?int $perPage = NULL): \Traversable + { + $perPage = $perPage ?? OPENVK_DEFAULT_PER_PAGE; + foreach($this->albums->where("owner", $club->getId() * -1)->where("deleted", false)->page($page, $perPage) as $album) + yield new Album($album); + } + + function getClubAlbumsCount(Club $club): int + { + return sizeof($this->albums->where("owner", $club->getId() * -1)->where("deleted", false)); + } + + function getAvatarAlbumById(int $id, int $regTime): Album + { + $data = $this->getSpecialConditions($id, 16); + $album = $this->albums->where([ + "owner" => $id, + "special_type" => 16, + ])->fetch(); + if(!$album) { + $album = new Album; + $album->setName("[!!! internal album]"); + $album->setOwner($id); + $album->setSpecial_Type(16); + $album->setCreated($regTime); + $album->save(); + + return $album; + } + + return new Album($album); + } + + function getUserAvatarAlbum(User $user): Album + { + return $this->getAvatarAlbumById($user->getId(), $user->getRegistrationTime()->timestamp()); + } + + function getClubAvatarAlbum(Club $club): Album + { + return $this->getAvatarAlbumById($club->getId() * -1, 0); + } + + function getUserWallAlbum(User $user): Album + { + $data = $this->getSpecialConditions($user->getId(), 32); + $album = $this->albums->where([ + "owner" => $user->getId(), + "special_type" => 32, + ])->fetch(); + if(!$album) { + $album = new Album; + $album->setName("[!!! internal album]"); + $album->setOwner($user->getId()); + $album->setSpecial_Type(32); + $album->setCreated($user->getRegistrationTime()->timestamp()); + $album->save(); + + return $album; + } + + return new Album($album); + } +} diff --git a/Web/Models/Repositories/Audios.php b/Web/Models/Repositories/Audios.php new file mode 100644 index 000000000..8cf305b0d --- /dev/null +++ b/Web/Models/Repositories/Audios.php @@ -0,0 +1,38 @@ +context = DatabaseConnection::i()->getContext(); + $this->audios = $this->context->table("audios"); + $this->rels = $this->context->table("audio_relations"); + } + + function get(int $id): ?Video + { + $videos = $this->videos->get($id); + if(!$videos) return NULL; + + return new Audio($videos); + } + + function getByUser(User $user, int $page = 1, ?int $perPage = NULL): \Traversable + { + $perPage = $perPage ?? OPENVK_DEFAULT_PER_PAGE; + foreach($this->rels->where("user", $user->getId())->page($page, $perPage) as $rel) + yield $this->get($rel->audio); + } + + function getUserAudiosCount(User $user): int + { + return sizeof($this->rels->where("user", $user->getId())); + } +} diff --git a/Web/Models/Repositories/Clubs.php b/Web/Models/Repositories/Clubs.php new file mode 100644 index 000000000..7e45760fc --- /dev/null +++ b/Web/Models/Repositories/Clubs.php @@ -0,0 +1,48 @@ +context = DatabaseConnection::i()->getContext(); + $this->clubs = $this->context->table("groups"); + } + + private function toClub(?ActiveRow $ar): ?Club + { + return is_null($ar) ? NULL : new Club($ar); + } + + function getByShortURL(string $url): ?Club + { + return $this->toClub($this->clubs->where("shortcode", $url)->fetch()); + } + + function get(int $id): ?Club + { + return $this->toClub($this->clubs->get($id)); + } + + function find(string $query, int $page = 1, ?int $perPage = NULL): \Traversable + { + $query = '%'.$query.'%'; + $perPage = $perPage ?? OPENVK_DEFAULT_PER_PAGE; + foreach($this->clubs->where("name LIKE ? OR about LIKE ?", $query, $query)->page($page, $perPage) as $result) + yield new Club($result); + } + + function getFoundCount(string $query): int + { + $query = '%'.$query.'%'; + return sizeof($this->clubs->where("name LIKE ? OR about LIKE ?", $query, $query)); + } + + use \Nette\SmartObject; +} diff --git a/Web/Models/Repositories/Comments.php b/Web/Models/Repositories/Comments.php new file mode 100644 index 000000000..ea956a6fc --- /dev/null +++ b/Web/Models/Repositories/Comments.php @@ -0,0 +1,49 @@ +context = DatabaseConnection::i()->getContext(); + $this->comments = $this->context->table("comments"); + } + + private function toComment(?ActiveRow $ar): ?Comment + { + return is_null($ar) ? NULL : new Comment($ar); + } + + function get(int $id): ?Comment + { + return $this->toComment($this->comments->get($id)); + } + + function getCommentsByTarget(Postable $target, int $page, ?int $perPage = NULL): \Traversable + { + $comments = $this->comments->where([ + "model" => get_class($target), + "target" => $target->getId(), + "deleted" => false, + ])->page($page, $perPage ?? OPENVK_DEFAULT_PER_PAGE); + + foreach($comments as $comment) + yield $this->toComment($comment); + } + + function getCommentsCountByTarget(Postable $target): int + { + return sizeof($this->comments->where([ + "model" => get_class($target), + "target" => $target->getId(), + "deleted" => false, + ])); + } +} diff --git a/Web/Models/Repositories/ContentSearchRepository.php b/Web/Models/Repositories/ContentSearchRepository.php new file mode 100644 index 000000000..65f03d937 --- /dev/null +++ b/Web/Models/Repositories/ContentSearchRepository.php @@ -0,0 +1,32 @@ +ctx = DatabaseConnection::i()->getContext(); + $this->builder = $this->ctx; + } + + private function markParameterAsPassed(string $param): void + { + if(!in_array($param, $this->passedParams)) + $this->passedParams[] = $param; + } + + function setContentType() + { + + } +} diff --git a/Web/Models/Repositories/Conversations.php b/Web/Models/Repositories/Conversations.php new file mode 100644 index 000000000..191795214 --- /dev/null +++ b/Web/Models/Repositories/Conversations.php @@ -0,0 +1,47 @@ +context = DB::i()->getContext(); + $this->convos = $this->context->table("conversations"); + } + + private function toConversation(?ActiveRow $ar): ?M\AbstractConversation + { + if(is_null($ar)) + return NULL; + else if($ar->is_pm) + return new M\PrivateConversation($ar); + else + return new M\Conversation($ar); + } + + function get(int $id): ?M\AbstractConversation + { + return $this->toConversation($this->convos->get($id)); + } + + function getConversationsByUser(User $user, int $page = 1, ?int $perPage = NULL) : \Traversable + { + $rels = $this->context->table("conversation_members")->where([ + "deleted" => false, + "user" => $user->getId(), + ])->page($page, $perPage ?? OPENVK_DEFAULT_PER_PAGE); + foreach($rels as $rel) + yield $this->get($rel->conversation); + } + + function getPrivateConversation(User $user, int $peer): M\PrivateConversation + { + ; + } +} diff --git a/Web/Models/Repositories/Managers.php b/Web/Models/Repositories/Managers.php new file mode 100644 index 000000000..9f65cb189 --- /dev/null +++ b/Web/Models/Repositories/Managers.php @@ -0,0 +1,36 @@ +context = DatabaseConnection::i()->getContext(); + $this->managers= $this->context->table("group_coadmins"); + } + + private function toManager(?ActiveRow $ar): ?Manager + { + return is_null($ar) ? NULL : new Manager($ar); + } + + function get(int $id): ?Manager + { + return $this->toManager($this->managers->where("id", $id)->fetch()); + } + + function getByUserAndClub(int $user, int $club): ?Manager + { + return $this->toManager($this->managers->where("user", $user)->where("club", $club)->fetch()); + } + + use \Nette\SmartObject; +} diff --git a/Web/Models/Repositories/Messages.php b/Web/Models/Repositories/Messages.php new file mode 100644 index 000000000..c58331572 --- /dev/null +++ b/Web/Models/Repositories/Messages.php @@ -0,0 +1,38 @@ +context = DatabaseConnection::i()->getContext(); + $this->messages = $this->context->table("messages"); + } + + function getCorrespondencies(RowModel $correspondent, int $page = 1, ?int $perPage = NULL): \Traversable + { + $id = $correspondent->getId(); + $class = get_class($correspondent); + $limit = $perPage ?? OPENVK_DEFAULT_PER_PAGE; + $offset = ($page - 1) * $limit; + $query = file_get_contents(__DIR__ . "/../sql/get-correspondencies.tsql"); + DatabaseConnection::i()->getConnection()->query(file_get_contents(__DIR__ . "/../sql/mysql-msg-fix.tsql")); + $coresps = DatabaseConnection::i()->getConnection()->query($query, $id, $class, $id, $class, $limit, $offset); + foreach($coresps as $c) { + if($c->class === 'openvk\Web\Models\Entities\User') + $anotherCorrespondent = (new Users)->get($c->id); + else if($c->class === 'openvk\Web\Models\Entities\Club') + $anotherCorrespondent = (new Clubs)->get($c->id); + + yield new Correspondence($correspondent, $anotherCorrespondent); + } + } +} \ No newline at end of file diff --git a/Web/Models/Repositories/Notes.php b/Web/Models/Repositories/Notes.php new file mode 100644 index 000000000..576e3c2b0 --- /dev/null +++ b/Web/Models/Repositories/Notes.php @@ -0,0 +1,40 @@ +context = DatabaseConnection::i()->getContext(); + $this->notes = $this->context->table("notes"); + } + + private function toNote(?ActiveRow $ar): ?Note + { + return is_null($ar) ? NULL : new Note($ar); + } + + function get(int $id): ?Note + { + return $this->toNote($this->notes->get($id)); + } + + function getUserNotes(User $user, int $page = 1, ?int $perPage = NULL): \Traversable + { + $perPage = $perPage ?? OPENVK_DEFAULT_PER_PAGE; + foreach($this->notes->where("owner", $user->getId())->page($page, $perPage) as $album) + yield new Note($album); + } + + function getUserNotesCount(User $user): int + { + return sizeof($this->notes->where("owner", $user->getId())); + } +} diff --git a/Web/Models/Repositories/Notifications.php b/Web/Models/Repositories/Notifications.php new file mode 100644 index 000000000..53a4f1fc8 --- /dev/null +++ b/Web/Models/Repositories/Notifications.php @@ -0,0 +1,70 @@ +modelCodes = array_flip(json_decode(file_get_contents(__DIR__ . "/../../../data/modelCodes.json"), true)); + } + + private function getEDB(bool $throw = true): ?object + { + $eax = $this->edbc ?? eventdb(); + if(!$eax && $throw) + throw new \RuntimeException("Event database err!"); + + return is_null($eax) ? NULL : $eax->getConnection(); + } + + private function getModel(int $code, int $id): object + { + $repoClassName = str_replace("Entities", "Repositories", "\\" . $this->modelCodes[$code]) . "s"; + + return (new $repoClassName)->get($id); + } + + private function getQuery(User $user, bool $count = false, int $offset, bool $archived = false, int $page = 1, ?int $perPage = NULL): string + { + $query = "SELECT " . ($count ? "COUNT(*) AS cnt" : "*") . " FROM notifications WHERE recipientType=0 "; + $query .= "AND timestamp " . ($archived ? "<" : ">") . "$offset AND recipientId=" . $user->getId(); + if(!$count) { + $query .= " ORDER BY timestamp DESC"; + $query .= " LIMIT " . ($perPage ?? OPENVK_DEFAULT_PER_PAGE); + $query .= " OFFSET " . ((($page - 1) * $perPage) ?? OPENVK_DEFAULT_PER_PAGE); + } + + return $query; + } + + function getNotificationCountByUser(User $user, int $offset, bool $archived = false): int + { + $db = $this->getEDB(false); + if(!$db) + return 1; + + $results = $db->query($this->getQuery($user, true, $offset, $archived)); + + return $results->fetch()->cnt; + } + + function getNotificationsByUser(User $user, int $offset, bool $archived = false, int $page = 1, ?int $perPage = NULL): \Traversable + { + $results = $this->getEDB()->query($this->getQuery($user, false, $offset, $archived, $page, $perPage)); + foreach($results->fetchAll() as $notif) { + $originModel = $this->getModel($notif->originModelType, $notif->originModelId); + $targetModel = $this->getModel($notif->targetModelType, $notif->targetModelId); + $recipient = (new Users)->get($notif->recipientId); + + $notification = new Notification($recipient, $originModel, $targetModel, $notif->timestamp, $notif->additionalData); + $notification->setActionCode($notif->modelAction); + yield $notification; + } + } +} diff --git a/Web/Models/Repositories/Photos.php b/Web/Models/Repositories/Photos.php new file mode 100644 index 000000000..4ff8a1b95 --- /dev/null +++ b/Web/Models/Repositories/Photos.php @@ -0,0 +1,35 @@ +context = DatabaseConnection::i()->getContext(); + $this->photos = $this->context->table("photos"); + } + + function get(int $id): ?Photo + { + $photo = $this->photos->get($id); + if(!$photo) return NULL; + + return new Photo($photo); + } + + function getByOwnerAndVID(int $owner, int $vId): ?Photo + { + $photo = $this->photos->where([ + "owner" => $owner, + "virtual_id" => $vId, + ])->fetch(); + if(!$photo) return NULL; + + return new Photo($photo); + } +} diff --git a/Web/Models/Repositories/Posts.php b/Web/Models/Repositories/Posts.php new file mode 100644 index 000000000..12dc24eee --- /dev/null +++ b/Web/Models/Repositories/Posts.php @@ -0,0 +1,75 @@ +context = DatabaseConnection::i()->getContext(); + $this->posts = $this->context->table("posts"); + } + + private function toPost(?ActiveRow $ar): ?Post + { + return is_null($ar) ? NULL : new Post($ar); + } + + function get(int $id): ?Post + { + return $this->toPost($this->posts->get($id)); + } + + function getPostsFromUsersWall(int $user, int $page = 1, ?int $perPage = NULL): \Traversable + { + $sel = $this->posts->where(["wall" => $user, "deleted" => 0])->order("created DESC")->page($page, $perPage ?? OPENVK_DEFAULT_PER_PAGE); + + foreach($sel as $post) + yield new Post($post); + } + + function getPostsByHashtag(string $hashtag, int $page = 1, ?int $perPage = NULL): \Traversable + { + $hashtag = "#$hashtag"; + $sel = $this->posts + ->where("content LIKE ?", "%$hashtag%") + ->where("deleted", 0) + ->order("created DESC") + ->page($page, $perPage ?? OPENVK_DEFAULT_PER_PAGE); + + foreach($sel as $post) + yield new Post($post); + } + + function getPostCountByHashtag(string $hashtag): int + { + $hashtag = "#$hashtag"; + $sel = $this->posts + ->where("content LIKE ?", "%$hashtag%") + ->where("deleted", 0); + + return sizeof($sel); + } + + function getPostById(int $wall, int $post): ?Post + { + $post = $this->posts->where(['wall' => $wall, 'virtual_id' => $post])->fetch(); + if(!is_null($post)) + + return new Post($post); + else + return null; + + } + + function getPostCountOnUserWall(int $user): int + { + return sizeof($this->posts->where(["wall" => $user, "deleted" => 0])); + } +} diff --git a/Web/Models/Repositories/Repository.php b/Web/Models/Repositories/Repository.php new file mode 100644 index 000000000..3d8e15e4a --- /dev/null +++ b/Web/Models/Repositories/Repository.php @@ -0,0 +1,33 @@ +context = DatabaseConnection::i()->getContext(); + $this->table = $this->context->table($this->tableName); + } + + function toEntity(?ActiveRow $ar) + { + $entityName = "openvk\\Web\\Models\\Entities\\$this->modelName"; + return is_null($ar) ? NULL : new $entityName($ar); + } + + function get(int $id) + { + return $this->toEntity($this->table->get($id)); + } + + use \Nette\SmartObject; +} diff --git a/Web/Models/Repositories/Restores.php b/Web/Models/Repositories/Restores.php new file mode 100644 index 000000000..df9105a8c --- /dev/null +++ b/Web/Models/Repositories/Restores.php @@ -0,0 +1,33 @@ +context = DatabaseConnection::i()->getContext(); + $this->restores = $this->context->table("password_resets"); + } + + function toPasswordReset(?ActiveRow $ar): ?PasswordReset + { + return is_null($ar) ? NULL : new PasswordReset($ar); + } + + function getByToken(string $token): ?PasswordReset + { + return $this->toPasswordReset($this->restores->where("key", $token)->fetch()); + } + + function getLatestByUser(User $user): ?PasswordReset + { + return $this->toPasswordReset($this->restores->where("profile", $user->getId())->order("timestamp DESC")->fetch()); + } +} diff --git a/Web/Models/Repositories/TicketComments.php b/Web/Models/Repositories/TicketComments.php new file mode 100644 index 000000000..d5d1f07e2 --- /dev/null +++ b/Web/Models/Repositories/TicketComments.php @@ -0,0 +1,53 @@ +context = DatabaseConnection::i()->getContext(); + $this->comments = $this->context->table("tickets_comments"); + } + + function getCommentsById(int $ticket_id): \Traversable + { + foreach($this->comments->where(['ticket_id' => $ticket_id]) as $comment) yield new TicketComment($comment); + } + + // private function toTicket(?ActiveRow $ar): ?Ticket + // { + // return is_null($ar) ? NULL : new Ticket($ar); + // } + + // function getTicketsByuId(int $user_id): \Traversable + // { + // foreach($this->tickets->where(['user_id' => $user_id, 'deleted' => 0]) as $ticket) yield new Ticket($ticket); + // } + + // function getRequestById(int $req_id): ?Ticket + // { + // $requests = $this->tickets->where(['id' => $req_id])->fetch(); + // if(!is_null($requests)) + + // return new Req($requests); + // else + // return null; + + // } + + // function get(int $id): ?Ticket + // { + // return $this->toTicket($this->tickets->get($id)); + // } + + use \Nette\SmartObject; +} diff --git a/Web/Models/Repositories/Tickets.php b/Web/Models/Repositories/Tickets.php new file mode 100644 index 000000000..dd250f68a --- /dev/null +++ b/Web/Models/Repositories/Tickets.php @@ -0,0 +1,56 @@ +context = DatabaseConnection::i()->getContext(); + $this->tickets = $this->context->table("tickets"); + } + + private function toTicket(?ActiveRow $ar): ?Ticket + { + return is_null($ar) ? NULL : new Ticket($ar); + } + + function getTickets(int $state = 0, int $page = 1): \Traversable + { + foreach($this->tickets->where(["deleted" => 0, "type" => $state])->page($page, OPENVK_DEFAULT_PER_PAGE) as $t) + yield new Ticket($t); + } + + function getTicketCount(int $state = 0): int + { + return sizeof($this->tickets->where(["deleted" => 0, "type" => $state])); + } + + function getTicketsByuId(int $user_id): \Traversable + { + foreach($this->tickets->where(['user_id' => $user_id, 'deleted' => 0]) as $ticket) yield new Ticket($ticket); + } + + function getRequestById(int $req_id): ?Ticket + { + $requests = $this->tickets->where(['id' => $req_id])->fetch(); + if(!is_null($requests)) + + return new Req($requests); + else + return null; + + } + + function get(int $id): ?Ticket + { + return $this->toTicket($this->tickets->get($id)); + } + + use \Nette\SmartObject; +} diff --git a/Web/Models/Repositories/Users.php b/Web/Models/Repositories/Users.php new file mode 100644 index 000000000..33d503f63 --- /dev/null +++ b/Web/Models/Repositories/Users.php @@ -0,0 +1,63 @@ +context = DatabaseConnection::i()->getContext(); + $this->users = $this->context->table("profiles"); + } + + private function toUser(?ActiveRow $ar): ?User + { + return is_null($ar) ? NULL : new User($ar); + } + + function get(int $id): ?User + { + return $this->toUser($this->users->get($id)); + } + + function getByShortURL(string $url): ?User + { + return $this->toUser($this->users->where("shortcode", $url)->fetch()); + } + + function getByChandlerUser(ChandlerUser $user): ?User + { + return $this->toUser($this->users->where("user", $user->getId())->fetch()); + } + + function find(string $query, int $page = 1, ?int $perPage = NULL): \Traversable + { + $query = "$query%"; + $perPage = $perPage ?? OPENVK_DEFAULT_PER_PAGE; + foreach($this->users->where("first_name LIKE ? OR last_name LIKE ?", $query,$query)->page($page, $perPage) as $result) + yield new User($result); + } + + function getFoundCount(string $query): int + { + $query = "$query%"; + return sizeof($this->users->where("first_name LIKE ? OR last_name LIKE ?", $query, $query)); + } + + function getStatistics(): object + { + return (object) [ + "all" => sizeof(clone $this->users), + "active" => sizeof((clone $this->users)->where("online > 0")), + "online" => sizeof((clone $this->users)->where("online >= ?", time() - 900)), + ]; + } + + use \Nette\SmartObject; +} diff --git a/Web/Models/Repositories/Videos.php b/Web/Models/Repositories/Videos.php new file mode 100644 index 000000000..ef560136e --- /dev/null +++ b/Web/Models/Repositories/Videos.php @@ -0,0 +1,49 @@ +context = DatabaseConnection::i()->getContext(); + $this->videos = $this->context->table("videos"); + } + + function get(int $id): ?Video + { + $videos = $this->videos->get($id); + if(!$videos) return NULL; + + return new Video($videos); + } + + function getByOwnerAndVID(int $owner, int $vId): ?Video + { + $videos = $this->videos->where([ + "owner" => $owner, + "virtual_id" => $vId, + ])->fetch(); + if(!$videos) return NULL; + + return new Video($videos); + } + + function getByUser(User $user, int $page = 1, ?int $perPage = NULL): \Traversable + { + $perPage = $perPage ?? OPENVK_DEFAULT_PER_PAGE; + foreach($this->videos->where("owner", $user->getId())->page($page, $perPage) as $video) + if(!$video->deleted) + yield new Video($video); + } + + function getUserVideosCount(User $user): int + { + return sizeof($this->videos->where("owner", $user->getId())); + } +} diff --git a/Web/Models/RowModel.php b/Web/Models/RowModel.php new file mode 100644 index 000000000..92baeaa6a --- /dev/null +++ b/Web/Models/RowModel.php @@ -0,0 +1,7 @@ +id = $id; + } + + abstract function getThumbnailURL(): string; + + abstract function getURL(): string; + + abstract function getEmbed(): string; +} diff --git a/Web/Models/VideoDrivers/YouTubeVideoDriver.php b/Web/Models/VideoDrivers/YouTubeVideoDriver.php new file mode 100644 index 000000000..1c5b31608 --- /dev/null +++ b/Web/Models/VideoDrivers/YouTubeVideoDriver.php @@ -0,0 +1,29 @@ +id/mq3.jpg"; + } + + function getURL(): string + { + return "https://youtu.be/$this->id"; + } + + function getEmbed(): string + { + return << +CODE; + } +} diff --git a/Web/Models/VideoDrivers/regex/youtube.txt b/Web/Models/VideoDrivers/regex/youtube.txt new file mode 100644 index 000000000..e0a6eba1a --- /dev/null +++ b/Web/Models/VideoDrivers/regex/youtube.txt @@ -0,0 +1 @@ +%^(?:(?:http[s]?:\/\/)?)(?:w{3}\.)?(?:yt\.be\/watch\?v=|youtu\.be\/(?:watch\?v=)?|youtube\.com\/watch\?v=)([A-z0-9\-_]{11})$% \ No newline at end of file diff --git a/Web/Models/files/video/rendering.apng b/Web/Models/files/video/rendering.apng new file mode 100644 index 000000000..417aa4268 Binary files /dev/null and b/Web/Models/files/video/rendering.apng differ diff --git a/Web/Models/files/video/rendering.ogv b/Web/Models/files/video/rendering.ogv new file mode 100644 index 000000000..938dc1544 Binary files /dev/null and b/Web/Models/files/video/rendering.ogv differ diff --git a/Web/Models/shell/processVideo.sh b/Web/Models/shell/processVideo.sh new file mode 100644 index 000000000..94d7c5296 --- /dev/null +++ b/Web/Models/shell/processVideo.sh @@ -0,0 +1,12 @@ +tmpfile="$RANDOM-$(date +%s%N)" + +cp $2 "/tmp/vid_$tmpfile.bin" +cp ../files/video/rendering.apng $1/storage/${3:0:2}/$3.gif +cp ../files/video/rendering.ogv $1/storage/${3:0:2}/$3.ogv + +nice ffmpeg -i "/tmp/vid_$tmpfile.bin" -ss 00:00:01.000 -vframes 1 $1/storage/${3:0:2}/$3.gif +nice -n 20 ffmpeg -i "/tmp/vid_$tmpfile.bin" -c:v libtheora -q:v 7 -c:a libvorbis -q:a 4 -vf scale=640x360,setsar=1:1 -y "/tmp/ffmOi$tmpfile.ogv" + +cp "/tmp/ffmOi$tmpfile.ogv" $1/storage/${3:0:2}/$3.ogv +rm -f "/tmp/ffmOi$tmpfile.ogv" +rm -f "/tmp/vid_$tmpfile.bin" \ No newline at end of file diff --git a/Web/Models/sql/get-correspondencies.tsql b/Web/Models/sql/get-correspondencies.tsql new file mode 100644 index 000000000..1a2475761 --- /dev/null +++ b/Web/Models/sql/get-correspondencies.tsql @@ -0,0 +1,31 @@ +SELECT DISTINCT id, class FROM +( + ( + SELECT + recipient_id AS id, + recipient_type AS class, + created AS time + FROM messages + WHERE + sender_id = ? + AND + sender_type = ? + ) UNION ( + SELECT + sender_id AS id, + sender_type AS class, + created AS time + FROM messages + WHERE + recipient_id = ? + AND + recipient_type = ? + ) + ORDER BY + time + DESC +) dt +LIMIT + ? +OFFSET + ? \ No newline at end of file diff --git a/Web/Models/sql/get-followers.tsql b/Web/Models/sql/get-followers.tsql new file mode 100644 index 000000000..ae23d63a3 --- /dev/null +++ b/Web/Models/sql/get-followers.tsql @@ -0,0 +1,6 @@ + (SELECT follower AS __id FROM + (SELECT follower FROM subscriptions WHERE target=? AND model="openvk\\Web\\Models\\Entities\\User") u0 + LEFT JOIN + (SELECT target FROM subscriptions WHERE follower=? AND model="openvk\\Web\\Models\\Entities\\User") u1 + ON u0.follower = u1.target WHERE u1.target IS NULL) u2 +INNER JOIN profiles ON profiles.id = u2.__id \ No newline at end of file diff --git a/Web/Models/sql/get-friends.tsql b/Web/Models/sql/get-friends.tsql new file mode 100644 index 000000000..dc7b07306 --- /dev/null +++ b/Web/Models/sql/get-friends.tsql @@ -0,0 +1,6 @@ + (SELECT follower AS __id FROM + (SELECT follower FROM subscriptions WHERE target=? AND model="openvk\\Web\\Models\\Entities\\User") u0 + INNER JOIN + (SELECT target FROM subscriptions WHERE follower=? AND model="openvk\\Web\\Models\\Entities\\User") u1 + ON u0.follower = u1.target) u2 +INNER JOIN profiles ON profiles.id = u2.__id \ No newline at end of file diff --git a/Web/Models/sql/get-messages.tsql b/Web/Models/sql/get-messages.tsql new file mode 100644 index 000000000..83cdf7e01 --- /dev/null +++ b/Web/Models/sql/get-messages.tsql @@ -0,0 +1,25 @@ +SELECT + * +FROM + `messages` +WHERE + (`deleted` = 0) + AND (`id` < ?) + AND ( + ( + (`sender_type` = ?) + AND (`recipient_type` = ?) + AND (`sender_id` = ?) + AND (`recipient_id` = ?) + ) + OR ( + (`sender_type` = ?) + AND (`recipient_type` = ?) + AND (`sender_id` = ?) + AND (`recipient_id` = ?) + ) + ) +ORDER BY + `created` DESC +LIMIT +? \ No newline at end of file diff --git a/Web/Models/sql/get-subscriptions-user.tsql b/Web/Models/sql/get-subscriptions-user.tsql new file mode 100644 index 000000000..722db194a --- /dev/null +++ b/Web/Models/sql/get-subscriptions-user.tsql @@ -0,0 +1,6 @@ + (SELECT target AS __id FROM + (SELECT follower FROM subscriptions WHERE target=? AND model="openvk\\Web\\Models\\Entities\\User") u0 + RIGHT JOIN + (SELECT target FROM subscriptions WHERE follower=? AND model="openvk\\Web\\Models\\Entities\\User") u1 + ON u0.follower = u1.target WHERE u0.follower IS NULL) u2 +INNER JOIN profiles ON profiles.id = u2.__id \ No newline at end of file diff --git a/Web/Models/sql/mysql-msg-fix.tsql b/Web/Models/sql/mysql-msg-fix.tsql new file mode 100644 index 000000000..194ff0686 --- /dev/null +++ b/Web/Models/sql/mysql-msg-fix.tsql @@ -0,0 +1 @@ +SET sql_mode=(SELECT REPLACE(@@sql_mode,'ONLY_FULL_GROUP_BY','')); \ No newline at end of file diff --git a/Web/Models/sql/set-subscriptions-club.tsql b/Web/Models/sql/set-subscriptions-club.tsql new file mode 100644 index 000000000..6dc214348 --- /dev/null +++ b/Web/Models/sql/set-subscriptions-club.tsql @@ -0,0 +1,6 @@ + (SELECT target AS __id FROM + (SELECT follower FROM subscriptions WHERE target=? AND model="openvk\\Web\\Models\\Entities\\Club") u0 + RIGHT JOIN + (SELECT target FROM subscriptions WHERE follower=? AND model="openvk\\Web\\Models\\Entities\\Club") u1 + ON u0.follower = u1.target WHERE u0.follower IS NULL) u2 +INNER JOIN profiles ON profiles.id = u2.__id \ No newline at end of file diff --git a/Web/Presenters/AboutPresenter.php b/Web/Presenters/AboutPresenter.php new file mode 100644 index 000000000..00d035ed3 --- /dev/null +++ b/Web/Presenters/AboutPresenter.php @@ -0,0 +1,60 @@ +user)) { + header("HTTP/1.1 302 Found"); + header("Location: /id" . $this->user->id); + exit; + } + + $this->template->stats = (new Users)->getStatistics(); + } + + function renderRules(): void + { + $this->template->rules = file_get_contents(__DIR__ . "/../../data/rules.xhtml"); + } + + function renderHelp(): void + {} + + function renderBB(): void + {} + + function renderInvite(): void + {} + + function renderDonate(): void + {} + + function renderPrivacy(): void + {} + + function renderVersion(): void + { + //$composerFactory = new Factory(); + //$composer = $composerFactory->createComposer(new NullIO(), OPENVK_ROOT . "/composer.json", false); + } + + function renderLanguage(): void + { + if(!is_null($_GET['lg'])){ + Session::i()->set("lang", $_GET['lg']); + } + } + + function renderSandbox(): void + { + $this->template->manager = (new Managers)->get(4); + } +} diff --git a/Web/Presenters/AdminPresenter.php b/Web/Presenters/AdminPresenter.php new file mode 100644 index 000000000..d5f8f43cb --- /dev/null +++ b/Web/Presenters/AdminPresenter.php @@ -0,0 +1,80 @@ +users = $users; + $this->clubs = $clubs; + + parent::__construct(); + } + + private function searchResults(object $repo, &$count) + { + $query = $this->queryParam("q") ?? ""; + $page = (int) ($this->queryParam("p") ?? 1); + + $count = $repo->getFoundCount($query); + return $repo->find($query, $page); + } + + function renderIndex(): void + { + + } + + function renderUsers(): void + { + $this->template->users = $this->searchResults($this->users, $this->template->count); + } + + function renderUser(int $id): void + { + $user = $this->users->get($id); + if(!$user) + $this->notFound(); + + $this->template->user = $user; + + if($_SERVER["REQUEST_METHOD"] !== "POST") + return; + + switch($_POST["act"] ?? "info") { + default: + case "info": + break; + + + } + } + + function renderClubs(): void + { + $this->template->clubs = $this->searchResults($this->clubs, $this->template->count); + } + + function renderClub(int $id): void + { + $club = $this->clubs->get($id); + if(!$club) + $this->notFound(); + + $this->template->club = $club; + + if($_SERVER["REQUEST_METHOD"] === "POST") { + + } + } + + function renderFiles(): void + { + + } +} diff --git a/Web/Presenters/AudiosPresenter.php b/Web/Presenters/AudiosPresenter.php new file mode 100644 index 000000000..202b0173d --- /dev/null +++ b/Web/Presenters/AudiosPresenter.php @@ -0,0 +1,117 @@ +music = $music; + + parent::__construct(); + } + + function renderApp(int $user = 0): void + { + $this->assertUserLoggedIn(); + + $user = (new Users)->get($user === 0 ? $this->user->id : $user); + if(!$user) + $this->notFound(); + + $this->template->user = $user; + } + + function renderUpload(): void + { + $this->assertUserLoggedIn(); + + if($_SERVER["REQUEST_METHOD"] === "POST") { + if(!isset($_FILES["blob"])) + $this->flashFail("err", "Нету файла", "Выберите файл."); + + try { + $audio = new Audio; + $audio->setFile($_FILES["blob"]); + $audio->setOwner($this->user->id); + $audio->setCreated(time()); + if(!empty($this->postParam("name"))) + $audio->setName($this->postParam("name")); + if(!empty($this->postParam("performer"))) + $audio->setPerformer($this->postParam("performer")); + if(!empty($this->postParam("lyrics"))) + $audio->setLyrics($this->postParam("lyrics")); + if(!empty($this->postParam("genre"))) + $audio->setGenre($this->postParam("genre")); + } catch(ISE $ex) { + $this->flashFaile("err", "Произшла ошибка", "Файл повреждён или имеет неверный формат."); + } + + $audio->save(); + $audio->wire(); + + $this->redirect("/audios" . $this->user->id, static::REDIRECT_TEMPORARY); + } + } + + function renderApiList(int $user, int $page = 1): void + { + $this->assertUserLoggedIn(); + + header("Content-Type: application/json"); + + $owner = (new Users)->get($user); + if(!$owner) { + header("HTTP/1.1 404 Not Found"); + exit(json_encode([ + "result" => "error", + "response" => [ + "error" => [ + "code" => 2 << 4, + "desc" => "No user with id = $user", + ], + ], + ])); + } + + $music = []; + foreach($this->music->getByUser($owner, $page) as $audio) { + $music[] = [ + "id" => $audio->getId(), + "name" => [ + "actual" => $audio->getName(), + "full" => $audio->getCanonicalName(), + ], + "performer" => $audio->getPerformer(), + "genre" => $audio->getGenre(), + "lyrics" => $audio->getLyrics(), + "meta" => [ + "available_formats" => ["mp3"], + "user_unique_id" => $audio->getVirtualId(), + "created" => (string) $audio->getPublicationTime(), + ], + "files" => [ + [ + "format" => "mp3", + "url" => $audio->getURL(), + ], + ], + ]; + } + + exit(json_encode([ + "result" => "success", + "method" => "list", + "response" => [ + "count" => $this->music->getUserAudiosCount($owner), + "music" => $music, + "page" => $page, + ], + ])); + } +} diff --git a/Web/Presenters/AuthPresenter.php b/Web/Presenters/AuthPresenter.php new file mode 100644 index 000000000..ad39c93cc --- /dev/null +++ b/Web/Presenters/AuthPresenter.php @@ -0,0 +1,180 @@ +authenticator = Authenticator::i(); + $this->db = DatabaseConnection::i()->getContext(); + + $this->users = $users; + $this->restores = $restores; + + parent::__construct(); + } + + private function emailValid(string $email): bool + { + if(empty($email)) return false; + + $email = trim($email); + [$user, $domain] = explode("@", $email); + $domain = idn_to_ascii($domain) . "."; + + return checkdnsrr($domain, "MX"); + } + + function renderRegister(): void + { + if(!is_null($this->user)) + $this->redirect("/id" . $this->user->id, static::REDIRECT_TEMPORARY); + + if(!$this->hasPermission("user", "register", -1)) exit("Вас забанили"); + + if($_SERVER["REQUEST_METHOD"] === "POST") { + $this->assertCaptchaCheckPassed(); + + if(!$this->emailValid($this->postParam("email"))) + $this->flashFail("err", "Неверный email адрес", "Email, который вы ввели, не является корректным."); + + $chUser = ChandlerUser::create($this->postParam("email"), $this->postParam("password")); + if(!$chUser) + $this->flashFail("err", "Не удалось зарегистрироваться", "Пользователь с таким email уже существует."); + + $user = new User; + $user->setUser($chUser->getId()); + $user->setFirst_Name($this->postParam("first_name")); + $user->setLast_Name($this->postParam("last_name")); + $user->setSex((int) ($this->postParam("sex") === "female")); + $user->setEmail($this->postParam("email")); + $user->setSince(date("Y-m-d H:i:s")); + $user->save(); + + $this->authenticator->authenticate($chUser->getId()); + $this->redirect("/id" . $user->getId(), static::REDIRECT_TEMPORARY); + } + } + + function renderLogin(): void + { + if(!is_null($this->user)) + $this->redirect("/id" . $this->user->id, static::REDIRECT_TEMPORARY); + + if(!$this->hasPermission("user", "login", -1)) exit("Вас забанили"); + + if($_SERVER["REQUEST_METHOD"] === "POST") { + + $user = $this->db->table("ChandlerUsers")->where("login", $this->postParam("login"))->fetch(); + if(!$user) + $this->flashFail("err", "Не удалось войти", "Неверное имя пользователя или пароль. Забыли пароль?"); + + if(!$this->authenticator->login($user->id, $this->postParam("password"))) + $this->flashFail("err", "Не удалось войти", "Неверное имя пользователя или пароль. Забыли пароль?"); + + $redirUrl = $_GET["jReturnTo"] ?? "/id" . $user->related("profiles.user")->fetch()->id; + $this->redirect($redirUrl, static::REDIRECT_TEMPORARY); + exit; + } + } + + function renderSu(string $uuid): void + { + $this->assertNoCSRF(); + $this->assertUserLoggedIn(); + + if($uuid === "unset") { + Session::i()->set("_su", NULL); + $this->redirect("/", static::REDIRECT_TEMPORARY); + } + + if(!$this->db->table("ChandlerUsers")->where("id", $uuid)) + $this->flashFail("err", "Ошибка манипуляции токенами", "Пользователь не найден."); + + $this->assertPermission('openvk\Web\Models\Entities\User', 'substitute', 0); + Session::i()->set("_su", $uuid); + $this->flash("succ", "Профиль изменён", "Ваш активный профиль был изменён."); + $this->redirect("/", static::REDIRECT_TEMPORARY); + exit; + } + + function renderLogout(): void + { + $this->assertUserLoggedIn(); + $this->authenticator->logout(); + Session::i()->set("_su", NULL); + + $this->redirect("/", static::REDIRECT_TEMPORARY_PRESISTENT); + } + + function renderFinishRestoringPassword(): void + { + $request = $this->restores->getByToken(str_replace(" ", "+", $this->queryParam("key"))); + if(!$request || !$request->isStillValid()) { + $this->flash("err", "Ошибка манипулирования токеном", "Токен недействителен или истёк"); + $this->redirect("/"); + } + + if($_SERVER["REQUEST_METHOD"] === "POST") { + $user = $request->getUser()->getChandlerUser(); + $this->db->table("ChandlerTokens")->where("user", $user->getId())->delete(); #Logout from everywhere + + $user->updatePassword($this->postParam("password")); + $this->authenticator->authenticate($user->getId()); + + $request->delete(false); + $this->flash("succ", "Успешно", "Ваш пароль был успешно сброшен."); + $this->redirect("/settings"); + } + } + + function renderRestore(): void + { + if(($this->queryParam("act") ?? "default") === "finish") + $this->pass("openvk!Auth->finishRestoringPassword"); + + if($_SERVER["REQUEST_METHOD"] === "POST") { + $uRow = $this->db->table("ChandlerUsers")->where("login", $this->postParam("login"))->fetch(); + if(!$uRow) { + #Privacy of users must be protected. We will not tell if email is bound to a user or not. + $this->flashFail("succ", "Успешно", "Если вы зарегистрированы, вы получите инструкции на email."); + } + + $user = $this->users->getByChandlerUser(new ChandlerUser($uRow)); + if(!$user) + $this->flashFail("err", "Ошибка", "Непредвиденная ошибка при сбросе пароля."); + + $request = $this->restores->getLatestByUser($user); + if(!is_null($request) && $request->isNew()) + $this->flashFail("err", "Ошибка доступа", "Нельзя делать это так часто, извините."); + + $resetObj = new PasswordReset; + $resetObj->setProfile($user->getId()); + $resetObj->save(); + + $params = [ + "key" => $resetObj->getKey(), + "name" => $user->getCanonicalName(), + ]; + $this->sendmail($uRow->login, "password-reset", $params); #Vulnerability possible + + + $this->flashFail("succ", "Успешно", "Если вы зарегистрированы, вы получите инструкции на email."); + } + } +} diff --git a/Web/Presenters/AwayPresenter.php b/Web/Presenters/AwayPresenter.php new file mode 100644 index 000000000..a65ba7af9 --- /dev/null +++ b/Web/Presenters/AwayPresenter.php @@ -0,0 +1,13 @@ +queryParam("to")); + exit; + } +} diff --git a/Web/Presenters/BlobPresenter.php b/Web/Presenters/BlobPresenter.php new file mode 100644 index 000000000..99e6d87be --- /dev/null +++ b/Web/Presenters/BlobPresenter.php @@ -0,0 +1,35 @@ +getDirName($dir); + $path = OPENVK_ROOT . "/storage/$dir/$name.$format"; + if(!file_exists($path)) { + $this->notFound(); + } else { + if(isset($_SERVER["HTTP_IF_NONE_MATCH"])) + exit(header("HTTP/1.1 304 Not Modified")); + + header("Content-Type: " . mime_content_type($path)); + header("Content-Size: " . filesize($path)); + header("ETag: W/\"" . hash_file("snefru", $path) . "\""); + + readfile($path); + exit; + } + } +} diff --git a/Web/Presenters/CommentPresenter.php b/Web/Presenters/CommentPresenter.php new file mode 100644 index 000000000..664070e03 --- /dev/null +++ b/Web/Presenters/CommentPresenter.php @@ -0,0 +1,75 @@ + "openvk\\Web\\Models\\Repositories\\Posts", + "photos" => "openvk\\Web\\Models\\Repositories\\Photos", + "videos" => "openvk\\Web\\Models\\Repositories\\Videos", + "notes" => "openvk\\Web\\Models\\Repositories\\Notes", + ]; + + function renderLike(int $id): void + { + $this->assertUserLoggedIn(); + + $comment = (new Comments)->get($id); + if(!$comment || $comment->isDeleted()) $this->notFound(); + + if(!is_null($this->user)) $comment->toggleLike($this->user->identity); + + $this->redirect($_SERVER["HTTP_REFERER"], static::REDIRECT_TEMPORARY); + } + + function renderMakeComment(string $repo, int $eId): void + { + $this->assertUserLoggedIn(); + + $repoClass = $this->models[$repo] ?? NULL; + if(!$repoClass) chandler_http_panic(400, "Bad Request", "Unexpected $repo."); + + $repo = new $repoClass; + $entity = $repo->get($eId); + if(!$entity) $this->notFound(); + + try { + $comment = new Comment; + $comment->setOwner($this->user->id); + $comment->setModel(get_class($entity)); + $comment->setTarget($entity->getId()); + $comment->setContent($this->postParam("text")); + $comment->setCreated(time()); + $comment->save(); + } catch(\LogicException $ex) { + $this->flashFail("err", "Не удалось опубликовать комментарий", "Нельзя опубликовать пустой комментарий."); + } + + if($entity->getOwner()->getId() !== $this->user->identity->getId()) + if(($owner = $entity->getOwner()) instanceof User) + (new CommentNotification($owner, $comment, $entity, $this->user->identity))->emit(); + + $this->flashFail("succ", "Комментарий добавлен", "Ваш комментарий появится на странице."); + } + + function renderDeleteComment(int $id): void + { + $this->assertUserLoggedIn(); + + $comment = (new Comments)->get($id); + if(!$comment) $this->notFound(); + if($comment->getOwner()->getId() !== $this->user->id) + if($comment->getTarget()->getOwner()->getId() !== $this->user->id) + $this->throwError(403, "Forbidden", "У вас недостаточно прав чтобы редактировать этот ресурс."); + + $comment->delete(); + $this->flashFail( + "succ", + "Успешно", + "Этот комментарий больше не будет показыватся.
      Отметить как спам?" + ); + } +} diff --git a/Web/Presenters/ContentSearchPresenter.php b/Web/Presenters/ContentSearchPresenter.php new file mode 100644 index 000000000..f0898fe3e --- /dev/null +++ b/Web/Presenters/ContentSearchPresenter.php @@ -0,0 +1,23 @@ +repo = $repo; + } + + function renderIndex(): void + { + if($_SERVER["REQUEST_METHOD"] === "POST") + { + $this->template->results = $repo->find([ + "query" => $this->postParam("query"), + ]); + } + } +} diff --git a/Web/Presenters/GroupPresenter.php b/Web/Presenters/GroupPresenter.php new file mode 100644 index 000000000..6f2c0a435 --- /dev/null +++ b/Web/Presenters/GroupPresenter.php @@ -0,0 +1,209 @@ +clubs = $clubs; + + parent::__construct(); + } + + function renderView(int $id): void + { + $club = $this->clubs->get($id); + if(!$club) { + $this->notFound(); + } else { + if($club->getShortCode()) + if(parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH) !== "/" . $club->getShortCode()) + $this->redirect("/" . $club->getShortCode(), static::REDIRECT_TEMPORARY_PRESISTENT); + + $this->template->club = $club; + $this->template->albums = (new Albums)->getClubAlbums($club, 1, 3); + $this->template->albumsCount = (new Albums)->getClubAlbumsCount($club); + } + } + + function renderCreate(): void + { + $this->assertUserLoggedIn(); + + if($_SERVER["REQUEST_METHOD"] === "POST") { + if(!empty($this->postParam("name"))) + { + $club = new Club; + $club->setName($this->postParam("name")); + $club->setAbout(empty($this->postParam("about")) ? NULL : $this->postParam("about")); + $club->setOwner($this->user->id); + + try { + $club->save(); + } catch(\PDOException $ex) { + if($ex->getCode() == 23000) + $this->flashFail("err", "Ошибка", "Произошла ошибка на стороне сервера. Обратитесь к системному администратору."); + else + throw $ex; + } + + $club->toggleSubscription($this->user->identity); + header("HTTP/1.1 302 Found"); + header("Location: /club" . $club->getId()); + }else{ + $this->flashFail("err", "Ошибка", "Вы не ввели название группы."); + } + } + } + + function renderSub(): void + { + $this->assertUserLoggedIn(); + + if($_SERVER["REQUEST_METHOD"] !== "POST") exit("Invalid state"); + + $club = $this->clubs->get((int) $this->postParam("id")); + if(!$club) exit("Invalid state"); + + $club->toggleSubscription($this->user->identity); + + header("HTTP/1.1 302 Found"); + header("Location: /club" . $club->getId()); + exit; + } + + function renderFollowers(int $id): void + { + $this->assertUserLoggedIn(); + + $this->template->club = $this->clubs->get($id); + $this->template->followers = $this->template->club->getFollowers((int) ($this->queryParam("p") ?? 1)); + $this->template->count = $this->template->club->getFollowersCount(); + $this->template->paginatorConf = (object) [ + "count" => $this->template->count, + "page" => $this->queryParam("p") ?? 1, + "amount" => NULL, + "perPage" => OPENVK_DEFAULT_PER_PAGE, + ]; + } + + function renderModifyAdmin(int $id): void + { + $user = is_null($this->queryParam("user")) ? $this->postParam("user") : $this->queryParam("user"); + $comment = $this->postParam("comment"); + //$index = $this->queryParam("index"); + if(!$user) + $this->badRequest(); + + $club = $this->clubs->get($id); + $user = (new Users)->get((int) $user); + if(!$user || !$club) + $this->notFound(); + + if(!$club->canBeModifiedBy($this->user->identity ?? NULL) && $club->getOwner()->getId() !== $user->getId()) + $this->flashFail("err", "Ошибка доступа", "У вас недостаточно прав, чтобы изменять этот ресурс."); + + /* if(!empty($index)){ + $manager = (new Managers)->get($index); + $manager->setComment($comment); + $this->flashFail("succ", "Операция успешна", "Комментарий к администратору изменён"); + }else{ */ + if($comment) { + $manager = (new Managers)->getByUserAndClub($user->getId(), $club->getId()); + $manager->setComment($comment); + $manager->save(); + $this->flashFail("succ", "Операция успешна", "."); + }else{ + if($club->canBeModifiedBy($user)) { + $club->removeManager($user); + $this->flashFail("succ", "Операция успешна", $user->getCanonicalName() . " более не администратор."); + } else { + $club->addManager($user); + + (new ClubModeratorNotification($user, $club, $this->user->identity))->emit(); + $this->flashFail("succ", "Операция успешна", $user->getCanonicalName() . " назначен(а) администратором."); + } + } + + } + + function renderEdit(int $id): void + { + $this->assertUserLoggedIn(); + + $club = $this->clubs->get($id); + if(!$club->canBeModifiedBy($this->user->identity)) + $this->notFound(); + else + $this->template->club = $club; + + if($_SERVER["REQUEST_METHOD"] === "POST") { + $club->setName(empty($this->postParam("name")) ? $club->getName() : $this->postParam("name")); + $club->setAbout(empty($this->postParam("about")) ? NULL : $this->postParam("about")); + $club->setShortcode(empty($this->postParam("shortcode")) ? NULL : $this->postParam("shortcode")); + $club->setWall(empty($this->postParam("wall")) ? 0 : 1); + + if($_FILES["ava"]["error"] === UPLOAD_ERR_OK) { + $photo = new Photo; + try { + $photo->setOwner($this->user->id); + $photo->setDescription("Profile image"); + $photo->setFile($_FILES["ava"]); + $photo->setCreated(time()); + $photo->save(); + + (new Albums)->getClubAvatarAlbum($club)->addPhoto($photo); + } catch(ISE $ex) { + $name = $album->getName(); + $this->flashFail("err", "Неизвестная ошибка", "Не удалось сохранить фотографию."); + } + } + + try { + $club->save(); + } catch(\PDOException $ex) { + if($ex->getCode() == 23000) + $this->flashFail("err", "Ошибка", "Произошла ошибка на стороне сервера. Обратитесь к системному администратору."); + else + throw $ex; + } + + $this->flash("succ", "Изменения сохранены", "Новые данные появятся в вашей группе."); + } + } + + function renderStatistics(int $id): void + { + $this->assertUserLoggedIn(); + + if(!eventdb()) + $this->flashFail("err", "Ошибка подключения", "Не удалось подключится к службе телеметрии."); + + $club = $this->clubs->get($id); + if(!$club->canBeModifiedBy($this->user->identity)) + $this->notFound(); + else + $this->template->club = $club; + + $this->template->reach = $club->getPostViewStats(true); + $this->template->views = $club->getPostViewStats(false); + } + + function renderAdmin(int $clb, int $id): void + { + $this->assertUserLoggedIn(); + + $manager = (new Managers)->get($id); + if($manager->getClub()->canBeModifiedBy($this->user->identity)){ + $this->template->manager = $manager; + $this->template->club = $manager->getClub(); + }else{ + $this->notFound(); + } + } +} diff --git a/Web/Presenters/HelloPresenter.php b/Web/Presenters/HelloPresenter.php new file mode 100644 index 000000000..32f0c74ef --- /dev/null +++ b/Web/Presenters/HelloPresenter.php @@ -0,0 +1,11 @@ +template->name = $name; + } +} diff --git a/Web/Presenters/InternalAPIPresenter.php b/Web/Presenters/InternalAPIPresenter.php new file mode 100644 index 000000000..4469ec241 --- /dev/null +++ b/Web/Presenters/InternalAPIPresenter.php @@ -0,0 +1,71 @@ + 1, + "error" => [ + "code" => $code, + "message" => $message, + ], + "id" => hexdec(hash("crc32b", (string) time())), + ])); + } + + private function succ($payload): void + { + exit(MessagePack::pack([ + "brpc" => 1, + "result" => $payload, + "id" => hexdec(hash("crc32b", (string) time())), + ])); + } + + function renderRoute(): void + { + if($_SERVER["REQUEST_METHOD"] !== "POST") + exit("ты дебил это точка апи"); + + try { + $input = (object) MessagePack::unpack(file_get_contents("php://input")); + } catch (\Exception $ex) { + $this->fail(-32700, "Parse error"); + } + + if(is_null($input->brpc ?? NULL) || is_null($input->method ?? NULL)) + $this->fail(-32600, "Invalid BIN-RPC"); + else if($input->brpc !== 1) + $this->fail(-32610, "Invalid version"); + + $method = explode(".", $input->method); + if(sizeof($method) !== 2) + $this->fail(-32601, "Procedure not found"); + + [$class, $method] = $method; + $class = '\openvk\ServiceAPI\\' . $class; + if(!class_exists($class)) + $this->fail(-32601, "Procedure not found"); + + $handler = new $class(is_null($this->user) ? NULL : $this->user->identity); + if(!is_callable([$handler, $method])) + $this->fail(-32601, "Procedure not found"); + + try { + $params = array_merge($input->params ?? [], [function($data) { + $this->succ($data); + }, function($data) { + $this->fail($data); + }]); + $handler->{$method}(...$params); + } catch(\TypeError $te) { + $this->fail(-32602, "Invalid params"); + } catch(\Exception $ex) { + $this->fail(-32603, "Uncaught " . get_class($ex)); + } + } +} \ No newline at end of file diff --git a/Web/Presenters/MessengerPresenter.php b/Web/Presenters/MessengerPresenter.php new file mode 100644 index 000000000..34b9746d8 --- /dev/null +++ b/Web/Presenters/MessengerPresenter.php @@ -0,0 +1,107 @@ +messages = $messages; + $this->signaler = SignalManager::i(); + parent::__construct(); + } + + private function getCorrespondent(int $id): object + { + if($id > 0) + return (new Users)->get($id); + else if($id < 0) + return (new Clubs)->get(abs($id)); + else if($id === 0) + return $this->user->identity; + } + + function renderIndex(): void + { + $this->assertUserLoggedIn(); + + if(isset($_GET["sel"])) + $this->pass("openvk!Messenger->app", $_GET["sel"]); + + $page = $_GET["p"] ?? 1; + $correspondences = iterator_to_array($this->messages->getCorrespondencies($this->user->identity, $page)); + + $this->template->corresps = $correspondences; + } + + function renderApp(int $sel): void + { + $this->assertUserLoggedIn(); + + $correspondent = $this->getCorrespondent($sel); + if(!$correspondent) + $this->notFound(); + + $this->template->selId = $sel; + $this->template->correspondent = $correspondent; + } + + function renderEvents(int $randNum): void + { + $this->assertUserLoggedIn(); + + header("Content-Type: application/json"); + $this->signaler->listen(function($event, $id) { + exit(json_encode([[ + "UUID" => $id, + "event" => $event->getLongPoolSummary(), + ]])); + }, $this->user->id); + } + + function renderApiGetMessages(int $sel, int $offset): void + { + $this->assertUserLoggedIn(); + + $correspondent = $this->getCorrespondent($sel); + if(!$correspondent) + $this->notFound(); + + $messages = []; + $correspondence = new Correspondence($this->user->identity, $correspondent); + foreach($correspondence->getMessages($offset === 0 ? null : $offset) as $message) + $messages[] = $message->simplify(); + + header("Content-Type: application/json"); + exit(json_encode($messages)); + } + + function renderApiWriteMessage(int $sel): void + { + $this->assertUserLoggedIn(); + + if(empty($this->postParam("content"))) { + header("HTTP/1.1 400 Bad Request"); + exit("Argument error: param 'content' expected to be string, undefined given."); + } + + $sel = $this->getCorrespondent($sel); + if($sel->getId() !== $this->user->id && $sel->getSubscriptionStatus($this->user->identity) !== 3) + exit(header("HTTP/1.1 403 Forbidden")); + + $cor = new Correspondence($this->user->identity, $sel); + $msg = new Message; + $msg->setContent($this->postParam("content")); + $cor->sendMessage($msg); + + header("HTTP/1.1 202 Accepted"); + header("Content-Type: application/json"); + exit(json_encode($msg->simplify())); + } +} diff --git a/Web/Presenters/NotesPresenter.php b/Web/Presenters/NotesPresenter.php new file mode 100644 index 000000000..6ba82e247 --- /dev/null +++ b/Web/Presenters/NotesPresenter.php @@ -0,0 +1,66 @@ +notes = $notes; + + parent::__construct(); + } + + function renderList(int $owner): void + { + $user = (new Users)->get($owner); + if(!$user) $this->notFound(); + + $this->template->notes = $this->notes->getUserNotes($user, $this->queryParam("p") ?? 1); + $this->template->count = $this->notes->getUserNotesCount($user); + $this->template->owner = $user; + $this->template->paginatorConf = (object) [ + "count" => $this->template->count, + "page" => $this->queryParam("p") ?? 1, + "amount" => NULL, + "perPage" => OPENVK_DEFAULT_PER_PAGE, + ]; + } + + function renderView(int $owner, int $id): void + { + $note = $this->notes->get($id); + if(!$note || $note->getOwner()->getId() !== $owner) + $this->notFound(); + + $this->template->cCount = $note->getCommentsCount(); + $this->template->cPage = (int) ($this->queryParam("p") ?? 1); + $this->template->comments = iterator_to_array($note->getComments($this->template->cPage)); + $this->template->note = $note; + } + + function renderCreate(): void + { + $this->assertUserLoggedIn(); + + $id = $this->user->id; #TODO: when ACL'll be done, allow admins to edit users via ?GUID=(chandler guid) + + if(!$id) + $this->notFound(); + + if($_SERVER["REQUEST_METHOD"] === "POST") { + $note = new Note; + $note->setOwner($this->user->id); + $note->setCreated(time()); + $note->setName($this->postParam("name")); + $note->setSource($this->postParam("html")); + $note->save(); + + $this->redirect("/note" . $this->user->id . "_" . $note->getId()); + } + } +} diff --git a/Web/Presenters/NotificationPresenter.php b/Web/Presenters/NotificationPresenter.php new file mode 100644 index 000000000..dac075f78 --- /dev/null +++ b/Web/Presenters/NotificationPresenter.php @@ -0,0 +1,19 @@ +assertUserLoggedIn(); + + $archive = $this->queryParam("act") === "archived"; + $this->template->mode = $archive ? "archived" : "new"; + $this->template->page = (int) ($this->queryParam("p") ?? 1); + $this->template->iterator = iterator_to_array($this->user->identity->getNotifications($this->template->page, $archive)); + $this->template->count = $this->user->identity->getNotificationsCount($archive); + + $this->user->identity->updateNotificationOffset(); + $this->user->identity->save(); + } +} diff --git a/Web/Presenters/OpenVKPresenter.php b/Web/Presenters/OpenVKPresenter.php new file mode 100644 index 000000000..11f222f78 --- /dev/null +++ b/Web/Presenters/OpenVKPresenter.php @@ -0,0 +1,177 @@ +path; + + return "$path?" . http_build_query(array_merge($_GET, $data)); + } + + protected function flash(string $type, string $title, ?string $message = NULL, ?int $code = NULL): void + { + Session::i()->set("_error", json_encode([ + "type" => $type, + "title" => $title, + "msg" => $message, + "code" => $code, + ])); + } + + protected function flashFail(string $type, string $title, ?string $message = NULL, ?int $code = NULL): void + { + $this->flash($type, $title, $message, $code); + $referer = $_SERVER["HTTP_REFERER"] ?? "/"; + + header("HTTP/1.1 302 Found"); + header("Location: $referer"); + exit; + } + + protected function assertUserLoggedIn(bool $returnUrl = true): void + { + if(is_null($this->user)) { + $loginUrl = "/login"; + if($returnUrl && $_SERVER["REQUEST_METHOD"] === "GET") { + $currentUrl = function_exists("get_current_url") ? get_current_url() : $_SERVER["REQUEST_URI"]; + $loginUrl .= "?jReturnTo=" . rawurlencode($currentUrl); + } + + $this->flash("err", "Недостаточно прав", "Чтобы просматривать эту страницу, нужно зайти на сайт."); + header("HTTP/1.1 302 Found"); + header("Location: $loginUrl"); + exit; + } + } + + protected function hasPermission(string $model, string $action, int $context): bool + { + if(is_null($this->user)) { + if($model !== "user") { + $this->flash("info", "Недостаточно прав", "Чтобы просматривать эту страницу, нужно зайти на сайт."); + + header("HTTP/1.1 302 Found"); + header("Location: /login"); + exit; + } + + return ($action === "register" || $action === "login"); + } + + return (bool) $this->user->raw->can($action)->model($model)->whichBelongsTo($context); + } + + protected function assertPermission(string $model, string $action, int $context, bool $throw = false): void + { + if($this->hasPermission($model, $action, $context)) return; + + if($throw) + throw new SecurityPolicyViolationException("Permission error"); + else + $this->flashFail("err", "Недостаточно прав", "У вас недостаточно прав чтобы выполнять это действие."); + } + + protected function assertCaptchaCheckPassed(): void + { + if(!check_captcha($_POST["captcha"])) + $this->flashFail("err", "Неправильно введены символы", "Пожалуйста, убедитесь, что вы правильно заполнили поле с капчей."); + } + + protected function signal(object $event): bool + { + return (SignalManager::i())->triggerEvent($event, $this->user->id); + } + + protected function logEvent(string $type, array $data): bool + { + $db = eventdb(); + if(!$db) + return false; + + $data = array_merge([ + "timestamp" => time(), + "verified" => (int) true, + ], $data); + $columns = implode(", ", array_map(function($col) { + return "`" . addslashes($col) . "`"; + }, array_keys($data))); + $values = implode(", ", array_map(function($val) { + return "'" . addslashes((string) (int) $val) . "'"; + }, array_values($data))); + + $db->getConnection()->query("INSERT INTO " . $type . "s($columns) VALUES ($values);"); + + return true; + } + + /** + * @override + */ + protected function sendmail(string $to, string $template, array $params = []): void + { + parent::sendmail($to, __DIR__ . "/../../Email/$template", $params); + } + + function getTemplatingEngine(): TemplatingEngine + { + $latte = parent::getTemplatingEngine(); + $latte->addFilter("translate", function($s) { + return tr($s); + }); + + return $latte; + } + + function onStartup(): void + { + $user = Authenticator::i()->getUser(); + + if(!is_null($user)) { + $this->user = (object) []; + $this->user->raw = $user; + $this->user->identity = (new Users)->getByChandlerUser($user); + $this->user->id = $this->user->identity->getId(); + $this->template->thisUser = $this->user->identity; + $this->template->userTainted = $user->isTainted(); + + if($this->user->identity->isBanned() && !$this->banTolerant) { + header("HTTP/1.1 403 Forbidden"); + $this->getTemplatingEngine()->render(__DIR__ . "/templates/@banned.xml", [ + "thisUser" => $this->user->identity, + ]); + exit; + } + + $this->user->identity->setOnline(time()); + $this->user->identity->save(); + } + + setlocale(LC_TIME, ...(explode(";", tr("__locale")))); + + parent::onStartup(); + } + + function onBeforeRender(): void + { + parent::onBeforeRender(); + + if(!is_null(Session::i()->get("_error"))) { + $this->template->flashMessage = json_decode(Session::i()->get("_error")); + Session::i()->set("_error", NULL); + } + } +} diff --git a/Web/Presenters/PhotosPresenter.php b/Web/Presenters/PhotosPresenter.php new file mode 100644 index 000000000..a09362c82 --- /dev/null +++ b/Web/Presenters/PhotosPresenter.php @@ -0,0 +1,247 @@ +users = $users; + $this->photos = $photos; + $this->albums = $albums; + + parent::__construct(); + } + + function renderAlbumList(int $owner): void + { + if($owner > 0) { + $user = $this->users->get($owner); + if(!$user) $this->notFound(); + $this->template->albums = $this->albums->getUserAlbums($user, $this->queryParam("p") ?? 1); + $this->template->count = $this->albums->getUserAlbumsCount($user); + $this->template->owner = $user; + $this->template->canEdit = false; + if(!is_null($this->user)) + $this->template->canEdit = $this->user->id === $user->getId(); + } else { + $club = (new Clubs)->get(abs($owner)); + if(!$club) $this->notFound(); + $this->template->albums = $this->albums->getClubAlbums($club, $this->queryParam("p") ?? 1); + $this->template->count = $this->albums->getClubAlbumsCount($club); + $this->template->owner = $club; + $this->template->canEdit = false; + if(!is_null($this->user)) + $this->template->canEdit = $club->canBeModifiedBy($this->user->identity); + } + + $this->template->paginatorConf = (object) [ + "count" => $this->template->count, + "page" => $this->queryParam("p") ?? 1, + "amount" => NULL, + "perPage" => OPENVK_DEFAULT_PER_PAGE, + ]; + } + + function renderCreateAlbum(): void + { + $this->assertUserLoggedIn(); + + if(!is_null($gpid = $this->queryParam("gpid"))) { + $club = (new Clubs)->get((int) $gpid); + if(!$club->canBeModifiedBy($this->user->identity)) + $this->notFound(); + + $this->template->club = $club; + } + + if($_SERVER["REQUEST_METHOD"] === "POST") { + $album = new Album; + $album->setOwner(isset($club) ? $club->getId() * -1 : $this->user->id); + $album->setName($this->postParam("name")); + $album->setDescription($this->postParam("desc")); + $album->setCreated(time()); + $album->save(); + + $this->redirect("/album" . $album->getOwner()->getId() . "_" . $album->getId(), static::REDIRECT_TEMPORARY); + } + } + + function renderEditAlbum(int $owner, int $id): void + { + $this->assertUserLoggedIn(); + + $album = $this->albums->get($id); + if(!$album) $this->notFound(); + if($album->getPrettyId() !== $owner . "_" . $id || $album->isDeleted()) $this->notFound(); + if(is_null($this->user) || !$album->canBeModifiedBy($this->user->identity) || $album->isDeleted()) + $this->flashFail("err", "Ошибка доступа", "Недостаточно прав для модификации данного ресурса."); + $this->template->album = $album; + + if($_SERVER["REQUEST_METHOD"] === "POST") { + $album->setName(empty($this->postParam("name")) ? $album->getName() : $this->postParam("name")); + $album->setDescription(empty($this->postParam("desc")) ? NULL : $this->postParam("desc")); + $album->setEdited(time()); + $album->save(); + + $this->flash("succ", "Изменения сохранены", "Новые данные приняты."); + } + } + + function renderDeleteAlbum(int $owner, int $id): void + { + $this->assertUserLoggedIn(); + $this->assertNoCSRF(); + + $album = $this->albums->get($id); + if(!$album) $this->notFound(); + if($album->getPrettyId() !== $owner . "_" . $id || $album->isDeleted()) $this->notFound(); + if(is_null($this->user) || !$album->canBeModifiedBy($this->user->identity)) + $this->flashFail("err", "Ошибка доступа", "Недостаточно прав для модификации данного ресурса."); + + $name = $album->getName(); + $album->delete(); + $this->flash("succ", "Альбом удалён", "Альбом $name был успешно удалён."); + $this->redirect("/albums" . $this->user->id); + } + + function renderAlbum(int $owner, int $id): void + { + $album = $this->albums->get($id); + if(!$album) $this->notFound(); + if($album->getPrettyId() !== $owner . "_" . $id || $album->isDeleted()) + $this->notFound(); + + $this->template->album = $album; + $this->template->photos = iterator_to_array($album->getPhotos($this->queryParam("page") ?? 1)); + $this->template->paginatorConf = (object) [ + "count" => $album->getPhotosCount(), + "page" => $this->queryParam("p") ?? 1, + "amount" => sizeof($this->template->photos), + "perPage" => OPENVK_DEFAULT_PER_PAGE, + ]; + } + + function renderPhoto(int $ownerId, int $photoId): void + { + $photo = $this->photos->getByOwnerAndVID($ownerId, $photoId); + if(!$photo || $photo->isDeleted()) $this->notFound(); + + if(!is_null($this->queryParam("from"))) { + if(preg_match("%^album([0-9]++)$%", $this->queryParam("from"), $matches) === 1) { + $album = $this->albums->get((int) $matches[1]); + if($album) + if($album->hasPhoto($photo) && !$album->isDeleted()) + $this->template->album = $album; + } + } + + $this->template->photo = $photo; + $this->template->cCount = $photo->getCommentsCount(); + $this->template->cPage = (int) ($this->queryParam("p") ?? 1); + $this->template->comments = iterator_to_array($photo->getComments($this->template->cPage)); + } + + function renderEditPhoto(int $ownerId, int $photoId): void + { + $this->assertUserLoggedIn(); + + $photo = $this->photos->getByOwnerAndVID($ownerId, $photoId); + if(!$photo) $this->notFound(); + if(is_null($this->user) || $this->user->id != $ownerId) + $this->flashFail("err", "Ошибка доступа", "Недостаточно прав для модификации данного ресурса."); + + if($_SERVER["REQUEST_METHOD"] === "POST") { + $photo->setDescription(empty($this->postParam("desc")) ? NULL : $this->postParam("desc")); + $photo->save(); + + $this->flash("succ", "Изменения сохранены", "Обновлённое описание появится на странице с фоткой."); + $this->redirect("/photo" . $photo->getPrettyId(), static::REDIRECT_TEMPORARY); + } + + $this->template->photo = $photo; + } + + function renderUploadPhoto(): void + { + $this->assertUserLoggedIn(); + + if(is_null($this->queryParam("album"))) + $this->flashFail("err", "Неизвестная ошибка", "Не удалось сохранить фотографию в DELETED."); + + [$owner, $id] = explode("_", $this->queryParam("album")); + $album = $this->albums->get((int) $id); + if(!$album) + $this->flashFail("err", "Неизвестная ошибка", "Не удалось сохранить фотографию в DELETED."); + if(is_null($this->user) || !$album->canBeModifiedBy($this->user->identity)) + $this->flashFail("err", "Ошибка доступа", "Недостаточно прав для модификации данного ресурса."); + + if($_SERVER["REQUEST_METHOD"] === "POST") { + if(!isset($_FILES["blob"])) + $this->flashFail("err", "Нету фотографии", "Выберите файл."); + + try { + $photo = new Photo; + $photo->setOwner($this->user->id); + $photo->setDescription($this->postParam("desc")); + $photo->setFile($_FILES["blob"]); + $photo->setCreated(time()); + $photo->save(); + } catch(ISE $ex) { + $name = $album->getName(); + $this->flashFail("err", "Неизвестная ошибка", "Не удалось сохранить фотографию в $name."); + } + + $album->addPhoto($photo); + $this->redirect("/photo" . $photo->getPrettyId(), static::REDIRECT_TEMPORARY); + } else { + $this->template->album = $album; + } + } + + function renderUnlinkPhoto(int $owner, int $albumId, int $photoId): void + { + $this->assertUserLoggedIn(); + + $album = $this->albums->get($albumId); + $photo = $this->photos->get($photoId); + if(!$album || !$photo) $this->notFound(); + if(!$album->hasPhoto($photo)) $this->notFound(); + if(is_null($this->user) || !$album->canBeModifiedBy($this->user->identity)) + $this->flashFail("err", "Ошибка доступа", "Недостаточно прав для модификации данного ресурса."); + + if($_SERVER["REQUEST_METHOD"] === "POST") { + $this->assertNoCSRF(); + $album->removePhoto($photo); + + $this->flash("succ", "Фотография удалена", "Эта фотография была успешно удалена."); + $this->redirect("/album" . $album->getPrettyId(), static::REDIRECT_TEMPORARY); + } + } + + function renderDeletePhoto(int $ownerId, int $photoId): void + { + $this->assertUserLoggedIn(); + $this->assertNoCSRF(); + + $photo = $this->photos->getByOwnerAndVID($ownerId, $photoId); + if(!$photo) $this->notFound(); + if(is_null($this->user) || $this->user->id != $ownerId) + $this->flashFail("err", "Ошибка доступа", "Недостаточно прав для модификации данного ресурса."); + + $photo->isolate(); + $photo->delete(); + exit("Фотография успешно удалена!"); + } +} diff --git a/Web/Presenters/SearchPresenter.php b/Web/Presenters/SearchPresenter.php new file mode 100644 index 000000000..779b5e4fd --- /dev/null +++ b/Web/Presenters/SearchPresenter.php @@ -0,0 +1,46 @@ +users = $users; + $this->clubs = $clubs; + + parent::__construct(); + } + + function renderIndex(): void + { + $query = $this->queryParam("query") ?? ""; + $type = $this->queryParam("type") ?? "users"; + $page = (int) ($this->queryParam("p") ?? 1); + + // https://youtu.be/pSAWM5YuXx8 + + switch($type) { + case "groups": + $iterator = $this->clubs->find($query, $page); + $count = $this->clubs->getFoundCount($query); + break; + case "users": + $iterator = $this->users->find($query, $page); + $count = $this->users->getFoundCount($query); + break; + } + + $this->template->iterator = iterator_to_array($iterator); + $this->template->count = $count; + $this->template->type = $type; + $this->template->page = $page; + } +} diff --git a/Web/Presenters/SupportPresenter.php b/Web/Presenters/SupportPresenter.php new file mode 100644 index 000000000..255455d0d --- /dev/null +++ b/Web/Presenters/SupportPresenter.php @@ -0,0 +1,191 @@ +tickets = $tickets; + $this->comments = $ticketComments; + + parent::__construct(); + } + + function renderIndex(): void + { + $this->assertUserLoggedIn(); + $this->template->mode = in_array($this->queryParam("act"), ["faq", "new", "list"]) ? $this->queryParam("act") : "faq"; + + $tickets = $this->tickets->getTicketsByuId($this->user->id); + if ($tickets) { + $this->template->tickets = $tickets; + } + + + if($_SERVER["REQUEST_METHOD"] === "POST") + { + + if(!empty($this->postParam("name")) && !empty($this->postParam("text"))) + { + $this->assertNoCSRF(); + $ticket = new Ticket; + $ticket->setType(0); + $ticket->setUser_id($this->user->id); + $ticket->setName($this->postParam("name")); + $ticket->setText($this->postParam("text")); + $ticket->setcreated(time()); + $ticket->save(); + header("HTTP/1.1 302 Found"); + header("Location: /support/view/" . $ticket->getId()); + } else { + $this->flashFail("err", "Ошибка", "Вы не ввели имя или текст "); + } + // $this->template->test = 'cool post'; + } + } + + function renderList(): void + { + $this->assertUserLoggedIn(); + $this->assertPermission('openvk\Web\Models\Entities\TicketReply', 'write', 0); + + $act = $this->queryParam("act") ?? "open"; + switch($act) { + default: + case "open": + $state = 0; + break; + case "answered": + $state = 1; + break; + case "closed": + $state = 2; + } + + $this->template->act = $act; + $this->template->page = (int) ($this->queryParam("p") ?? 1); + $this->template->count = $this->tickets->getTicketCount($state); + $this->template->iterator = $this->tickets->getTickets($state, $this->template->page); + } + + function renderView(int $id): void + { + $this->assertUserLoggedIn(); + $ticket = $this->tickets->get($id); + $ticketComments1 = $this->comments->getCommentsById($id); + if(!$ticket || $ticket->isDeleted() != 0 || $ticket->authorId() !== $this->user->id) { + $this->notFound(); + } else { + $this->template->ticket = $ticket; + $this->template->comments = $ticketComments1; + $this->template->id = $id; + } + } + + function renderDelete(int $id): void + { + $this->assertUserLoggedIn(); + if (!empty($id)) { + $ticket = $this->tickets->get($id); + if (!$ticket || $ticket->isDeleted() != 0 || $ticket->authorId() !== $this->user->id) + { + $this->notFound(); + } else { + $ticket->delete(); + header("HTTP/1.1 302 Found"); + header("Location: /support"); + } + } + } + + function renderMakeComment(int $id): void + { + $ticket = $this->tickets->get($id); + + if($ticket->isDeleted() === 1 || $ticket->getType() === 2 || $ticket->authorId() !== $this->user->id) { + header("HTTP/1.1 403 Forbidden"); + header("Location: /support/view/" . $id); + exit; + } + + if($_SERVER["REQUEST_METHOD"] === "POST") + { + if(!empty($this->postParam("text"))) + { + $ticket->setType(0); + $ticket->save(); + + $this->assertNoCSRF(); + $comment = new TicketComment; + $comment->setUser_id($this->user->id); + $comment->setUser_type(0); + $comment->setText($this->postParam("text")); + $comment->setTicket_id($id); + $comment->setCreated(time()); + $comment->save(); + + header("HTTP/1.1 302 Found"); + header("Location: /support/view/" . $id); + } else { + $this->flashFail("err", "Ошибка", "Вы не ввели текст"); + } + } + } + + function renderAnswerTicket(int $id): void + { + $this->assertPermission('openvk\Web\Models\Entities\TicketReply', 'write', 0); + $ticket = $this->tickets->get($id); + $ticketComments = $this->comments->getCommentsById($id); + $this->template->ticket = $ticket; + $this->template->comments = $ticketComments; + $this->template->id = $id; + } + + function renderAnswerTicketReply(int $id): void + { + $this->assertPermission('openvk\Web\Models\Entities\TicketReply', 'write', 0); + + $ticket = $this->tickets->get($id); + + if($_SERVER["REQUEST_METHOD"] === "POST") + { + if(!empty($this->postParam("text")) && !empty($this->postParam("status"))) + { + $ticket->setType($this->postParam("status")); + $ticket->save(); + + $this->assertNoCSRF(); + $comment = new TicketComment; + $comment->setUser_id($this->user->id); + $comment->setUser_type(1); + $comment->setText('Здравствуйте, '.$ticket->getUser()->getFirstName().'!

      '.$this->postParam("text").'

      С уважением,
      Команда поддержки OpenVK.'); + $comment->setTicket_id($id); + $comment->setCreated(time()); + $comment->save(); + } elseif (empty($this->postParam("text"))) { + $ticket->setType($this->postParam("status")); + $ticket->save(); + } + + $this->flashFail("succ", "Тикет изменён", "Изменения вступят силу через несколько секунд."); + } + + } +} diff --git a/Web/Presenters/UnknownTextRouteStrategyPresenter.php b/Web/Presenters/UnknownTextRouteStrategyPresenter.php new file mode 100644 index 000000000..6032a4a00 --- /dev/null +++ b/Web/Presenters/UnknownTextRouteStrategyPresenter.php @@ -0,0 +1,21 @@ += 2) { + $user = (new Users)->getByShortURL($data); + if($user) + $this->pass("openvk!User->view", $user->getId()); + $club = (new Clubs)->getByShortURL($data); + if($club) + $this->pass("openvk!Group->view", "public", $club->getId()); + } + + $this->notFound(); + } +} diff --git a/Web/Presenters/UserPresenter.php b/Web/Presenters/UserPresenter.php new file mode 100644 index 000000000..73c125997 --- /dev/null +++ b/Web/Presenters/UserPresenter.php @@ -0,0 +1,292 @@ +users = $users; + + parent::__construct(); + } + + function renderView(int $id): void + { + $user = $this->users->get($id); + if(!$user || $user->isDeleted()) + $this->notFound(); + else { + if($user->getShortCode()) + if(parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH) !== "/" . $user->getShortCode()) + $this->redirect("/" . $user->getShortCode(), static::REDIRECT_TEMPORARY_PRESISTENT); + + $then = date_create("@" . $user->getOnline()->timestamp()); + $now = date_create(); + $diff = date_diff($now, $then); + + $this->template->albums = (new Albums)->getUserAlbums($user); + $this->template->albumsCount = (new Albums)->getUserAlbumsCount($user); + $this->template->videos = (new Videos)->getByUser($user, 1, 2); + $this->template->videosCount = (new Videos)->getUserVideosCount($user); + $this->template->notes = (new Notes)->getUserNotes($user, 1, 4); + $this->template->notesCount = (new Notes)->getUserNotesCount($user); + $this->template->user = $user; + $this->template->diff = $diff; + } + } + + function renderFriends(int $id): void + { + $this->assertUserLoggedIn(); + + $user = $this->users->get($id); + $page = abs($this->queryParam("p") ?? 1); + if(!$user) + $this->notFound(); + else + $this->template->user = $user; + + $this->template->mode = in_array($this->queryParam("act"), [ + "incoming", "outcoming", "friends" + ]) ? $this->queryParam("act") + : "friends"; + $this->template->page = $page; + + if(!is_null($this->user)) { + if($this->template->mode !== "friends" && $this->user->id !== $id) { + $name = $user->getFullName(); + $this->flash("err", "Ошибка доступа", "Вы не можете просматривать полный список подписок $name."); + + $this->redirect("/id$id", static::REDIRECT_TEMPORARY_PRESISTENT); + } + } + } + + function renderGroups(int $id): void + { + $this->assertUserLoggedIn(); + + $user = $this->users->get($id); + if(!$user) { + $this->notFound(); + } else { + $this->template->user = $user; + $this->template->page = $this->queryParam("p") ?? 1; + } + } + + function renderEdit(): void + { + $this->assertUserLoggedIn(); + + $id = $this->user->id; #TODO: when ACL'll be done, allow admins to edit users via ?GUID=(chandler guid) + + if(!$id) + $this->notFound(); + else + $user = $this->users->get($id); + if($_SERVER["REQUEST_METHOD"] === "POST") { + if($_GET['act'] === "main" || $_GET['act'] == NULL) { + $user->setFirst_Name(empty($this->postParam("first_name")) ? $user->getFirstName() : $this->postParam("first_name")); + $user->setLast_Name(empty($this->postParam("last_name")) ? "" : $this->postParam("last_name")); + $user->setPseudo(empty($this->postParam("pseudo")) ? NULL : $this->postParam("pseudo")); + $user->setStatus(empty($this->postParam("status")) ? NULL : $this->postParam("status")); + + if ($this->postParam("marialstatus") <= 8 && $this->postParam("marialstatus") >= 0) + $user->setMarital_Status($this->postParam("marialstatus")); + + if ($this->postParam("politViews") <= 8 && $this->postParam("politViews") >= 0) + $user->setPolit_Views($this->postParam("politViews")); + + if ($this->postParam("gender") <= 1 && $this->postParam("gender") >= 0) + $user->setSex($this->postParam("gender")); + + if(!empty($this->postParam("phone")) && $this->postParam("phone") !== $user->getPhone()) { + if(!OPENVK_ROOT_CONF["openvk"]["credentials"]["zadarma"]["enable"]) + $this->flashFail("err", "Ошибка сегментации", "котлетки"); + + $code = $user->setPhoneWithVerification($this->postParam("phone")); + + if(!Sms::send($this->postParam("phone"), "OPENVK | Your verification code is: $code")) + $this->flashFail("err", "Ошибка сегментации", "котлетки: Remote err!"); + } + } elseif($_GET['act'] === "contacts") { + $user->setEmail_Contact(empty($this->postParam("email_contact")) ? NULL : $this->postParam("email_contact")); + $user->setTelegram(empty($this->postParam("telegram")) ? NULL : $this->postParam("telegram")); + $user->setCity(empty($this->postParam("city")) ? NULL : $this->postParam("city")); + $user->setAddress(empty($this->postParam("address")) ? NULL : $this->postParam("address")); + } elseif($_GET['act'] === "interests") { + $user->setInterests(empty($this->postParam("interests")) ? NULL : $this->postParam("interests")); + $user->setFav_Music(empty($this->postParam("fav_music")) ? NULL : $this->postParam("fav_music")); + $user->setFav_Films(empty($this->postParam("fav_films")) ? NULL : $this->postParam("fav_films")); + $user->setFav_Shows(empty($this->postParam("fav_shows")) ? NULL : $this->postParam("fav_shows")); + $user->setFav_Books(empty($this->postParam("fav_books")) ? NULL : $this->postParam("fav_books")); + $user->setFav_Quote(empty($this->postParam("fav_quote")) ? NULL : $this->postParam("fav_quote")); + $user->setAbout(empty($this->postParam("about")) ? NULL : $this->postParam("about")); + } + + try { + $user->save(); + } catch(\PDOException $ex) { + if($ex->getCode() == 23000) + $this->flashFail("err", "Ошибка", "Данный короткий адрес уже занят."); + else + throw $ex; + } + + $this->flash("succ", "Изменения сохранены", "Новые данные появятся на вашей странице."); + } + + $this->template->mode = in_array($this->queryParam("act"), [ + "main", "contacts", "interests", "avatar" + ]) ? $this->queryParam("act") + : "main"; + + $this->template->user = $user; + } + + function renderVerifyPhone(): void + { + $this->assertUserLoggedIn(); + + $user = $this->user->identity; + if(!$user->hasPendingNumberChange()) + exit; + else + $this->template->change = $user->getPendingPhoneVerification(); + + if($_SERVER["REQUEST_METHOD"] === "POST") { + if(!$user->verifyNumber($this->postParam("code") ?? 0)) + $this->flashFail("err", "Ошибка", "Не удалось подтвердить номер телефона: неверный код."); + + $this->flash("succ", "Изменения сохранены", "Новые данные появятся на вашей странице."); + } + } + + function renderSub(): void + { + $this->assertUserLoggedIn(); + + if($_SERVER["REQUEST_METHOD"] !== "POST") exit("Invalid state"); + + $user = $this->users->get((int) $this->postParam("id")); + if(!$user) exit("Invalid state"); + + $user->toggleSubscription($this->user->identity); + + header("HTTP/1.1 302 Found"); + header("Location: /id" . $user->getId()); + exit; + } + + function renderSetAvatar(): void + { + $this->assertUserLoggedIn(); + + $photo = new Photo; + try { + $photo->setOwner($this->user->id); + $photo->setDescription("Profile image"); + $photo->setFile($_FILES["blob"]); + $photo->setCreated(time()); + $photo->save(); + } catch(ISE $ex) { + $name = $album->getName(); + $this->flashFail("err", "Неизвестная ошибка", "Не удалось сохранить фотографию."); + } + + (new Albums)->getUserAvatarAlbum($this->user->identity)->addPhoto($photo); + $this->flashFail("succ", "Фотография сохранена", "Новое изображения профиля появится у вас на странице."); + } + + function renderSettings(): void + { + $this->assertUserLoggedIn(); + + $id = $this->user->id; #TODO: when ACL'll be done, allow admins to edit users via ?GUID=(chandler guid) + + if(!$id) + $this->notFound(); + else + $user = $this->users->get($id); + if($_SERVER["REQUEST_METHOD"] === "POST") { + if($_GET['act'] === "main" || $_GET['act'] == NULL) { + if($this->postParam("old_pass") && $this->postParam("new_pass") && $this->postParam("repeat_pass")) { + if($this->postParam("new_pass") === $this->postParam("repeat_pass")) { + if(!$this->user->identity->getChandlerUser()->updatePassword($this->postParam("new_pass"), $this->postParam("old_pass"))) + $this->flashFail("err", "Ошибка", "Старый пароль не совпадает."); + } else { + $this->flashFail("err", "Ошибка", "Новые пароли не совпадают."); + } + } + + if(!$user->setShortCode(empty($this->postParam("sc")) ? NULL : $this->postParam("sc"))) + $this->flashFail("err", "Ошибка", "Короткий адрес имеет некорректный формат."); + }elseif($_GET['act'] === "privacy") { + $settings = [ + "page.read", + "page.info.read", + "groups.read", + "photos.read", + "videos.read", + "notes.read", + "friends.read", + "friends.add", + "wall.write", + ]; + foreach($settings as $setting) { + $input = $this->postParam(str_replace(".", "_", $setting)); + $user->setPrivacySetting($setting, min(3, abs($input ?? $user->getPrivacySetting($setting)))); + } + }elseif($_GET['act'] === "interface") { + if ($this->postParam("style") <= 20 && $this->postParam("style") >= 0) + $user->setStyle((int)$this->postParam("style")); + + if ($this->postParam("style_avatar") <= 2 && $this->postParam("style_avatar") >= 0) + $user->setStyle_Avatar((int)$this->postParam("style_avatar")); + + if (in_array($this->postParam("rating"), [0, 1])) + $user->setShow_Rating((int) $this->postParam("rating")); + }elseif($_GET['act'] === "lMenu") { + $settings = [ + "menu_bildoj" => "photos", + "menu_filmetoj" => "videos", + "menu_mesagoj" => "messages", + "menu_notatoj" => "notes", + "menu_grupoj" => "groups", + "menu_novajoj" => "news", + ]; + foreach($settings as $checkbox => $setting) + $user->setLeftMenuItemStatus($setting, $this->checkbox($checkbox)); + } + + try { + $user->save(); + } catch(\PDOException $ex) { + if($ex->getCode() == 23000) + $this->flashFail("err", "Ошибка", "Данный короткий адрес уже занят."); + else + throw $ex; + } + + $this->flash( + "succ", + "Изменения сохранены", + "Новые данные появятся на вашей странице.
      Если вы изменили стиль, перезагрузите страницу." + ); + } + $this->template->mode = in_array($this->queryParam("act"), [ + "main", "privacy", "interface" + ]) ? $this->queryParam("act") + : "main"; + $this->template->user = $user; + } +} diff --git a/Web/Presenters/VideosPresenter.php b/Web/Presenters/VideosPresenter.php new file mode 100644 index 000000000..a1d2cc6bd --- /dev/null +++ b/Web/Presenters/VideosPresenter.php @@ -0,0 +1,102 @@ +videos = $videos; + $this->users = $users; + + parent::__construct(); + } + + function renderList(int $id): void + { + $user = $this->users->get($id); + if(!$user) $this->notFound(); + + $this->template->user = $user; + $this->template->videos = $this->videos->getByUser($user, $this->queryParam("p") ?? 1); + $this->template->count = $this->videos->getUserVideosCount($user); + $this->template->paginatorConf = (object) [ + "count" => $this->template->count, + "page" => $this->queryParam("p") ?? 1, + "amount" => NULL, + "perPage" => OPENVK_DEFAULT_PER_PAGE, + ]; + } + + function renderView(int $owner, int $vId): void + { + $user = $this->users->get($owner); + if(!$user) $this->notFound(); + + $this->template->user = $user; + $this->template->video = $this->videos->getByOwnerAndVID($owner, $vId); + $this->template->cCount = $this->template->video->getCommentsCount(); + $this->template->cPage = (int) ($this->queryParam("p") ?? 1); + $this->template->comments = iterator_to_array($this->template->video->getComments($this->template->cPage)); + } + + function renderUpload(): void + { + $this->assertUserLoggedIn(); + + if($_SERVER["REQUEST_METHOD"] === "POST") { + if(empty($this->postParam("name"))) { + $video = new Video; + $video->setOwner($this->user->id); + $video->setName($this->postParam("name")); + $video->setDescription($this->postParam("desc")); + $video->setCreated(time()); + + try { + if(isset($_FILES["blob"]) && file_exists($_FILES["blob"]["tmp_name"])) + $video->setFile($_FILES["blob"]); + else if(!empty($this->postParam("link"))) + $video->setLink($this->postParam("link")); + else + $this->flashFail("err", "Нету видеозаписи", "Выберите файл или укажите ссылку."); + } catch(ISE $ex) { + $this->flashFail("err", "Произошла ошибка", "Возможно, ссылка некорректна."); + } + + $video->save(); + + $this->redirect("/video" . $video->getPrettyId(), static::REDIRECT_TEMPORARY); + }else{ + $this->flashFail("err", "Произошла ошибка", "Видео не может быть опубликовано без названия."); + } + } + } + + function renderEdit(int $owner, int $vId): void + { + $this->assertUserLoggedIn(); + + $video = $this->videos->getByOwnerAndVID($owner, $vId); + if(!$video) + $this->notFound(); + if(is_null($this->user) || $this->user->id !== $owner) + $this->flashFail("err", "Ошибка доступа", "Вы не имеете права редактировать этот ресурс."); + + if($_SERVER["REQUEST_METHOD"] === "POST") { + $video->setName(empty($this->postParam("name")) ? NULL : $this->postParam("name")); + $video->setDescription(empty($this->postParam("desc")) ? NULL : $this->postParam("desc")); + $video->save(); + + $this->flash("succ", "Изменения сохранены", "Обновлённое описание появится на странице с видосиком."); + $this->redirect("/video" . $video->getPrettyId(), static::REDIRECT_TEMPORARY); + } + + $this->template->video = $video; + } +} diff --git a/Web/Presenters/WallPresenter.php b/Web/Presenters/WallPresenter.php new file mode 100644 index 000000000..61be11eb4 --- /dev/null +++ b/Web/Presenters/WallPresenter.php @@ -0,0 +1,288 @@ +posts = $posts; + + parent::__construct(); + } + + private function logPostView(Post $post, int $wall): void + { + if(is_null($this->user)) + return; + + $this->logEvent("postView", [ + "profile" => $this->user->identity->getId(), + "post" => $post->getId(), + "owner" => abs($wall), + "group" => $wall < 0, + "subscribed" => $wall < 0 ? $post->getOwner()->getSubscriptionStatus($this->user->identity) : false, + ]); + } + + private function logPostsViewed(array &$posts, int $wall): void + { + $x = array_values($posts); # clone array (otherwise Nette DB objects will become kinda gay) + + foreach($x as $post) + $this->logPostView($post, $wall); + } + + function renderWall(int $user): void + { + if(false) + exit("Ошибка доступа: " . (string) random_int(0, 255)); + + $owner = ($user < 0 ? (new Clubs) : (new Users))->get(abs($user)); + if(is_null($this->user)) + $canPost = false; + else if($user > 0) + $canPost = $owner->getPrivacyPermission("wall.write", $this->user->identity); + else if($user < 0) + if($owner->canBeModifiedBy($this->user->identity)) + $canPost = true; + else + $canPost = $owner->canPost(); + else + $canPost = false; + + $this->template->oObj = $owner; + $this->template->owner = $user; + $this->template->canPost = $canPost; + $this->template->count = $this->posts->getPostCountOnUserWall($user); + $this->template->posts = iterator_to_array($this->posts->getPostsFromUsersWall($user, (int) ($_GET["p"] ?? 1))); + $this->template->paginatorConf = (object) [ + "count" => $this->template->count, + "page" => (int) ($_GET["p"] ?? 1), + "amount" => sizeof($this->template->posts), + "perPage" => OPENVK_DEFAULT_PER_PAGE, + ]; + + $this->logPostsViewed($this->template->posts, $user); + } + + function renderFeed(): void + { + $this->assertUserLoggedIn(); + + $id = $this->user->id; + $subs = DatabaseConnection::i() + ->getContext() + ->table("subscriptions") + ->where("follower", $id); + $ids = array_map(function($rel) { + return $rel->target * ($rel->model === "openvk\Web\Models\Entities\User" ? 1 : -1); + }, iterator_to_array($subs)); + $ids[] = $this->user->id; + + $perPage = (int) ($_GET["posts"] ?? OPENVK_DEFAULT_PER_PAGE); + $posts = DatabaseConnection::i() + ->getContext() + ->table("posts") + ->select("id") + ->where("wall IN (?)", $ids) + ->where("deleted", 0) + ->order("created DESC"); + $this->template->paginatorConf = (object) [ + "count" => sizeof($posts), + "page" => (int) ($_GET["p"] ?? 1), + "amount" => sizeof($posts->page((int) ($_GET["p"] ?? 1), $perPage)), + "perPage" => $perPage, + ]; + $this->template->posts = []; + foreach($posts->page((int) ($_GET["p"] ?? 1), $perPage) as $post) + $this->template->posts[] = $this->posts->get($post->id); + } + + function renderHashtagFeed(string $hashtag): void + { + $hashtag = rawurldecode($hashtag); + + $page = (int) ($_GET["p"] ?? 1); + $posts = $this->posts->getPostsByHashtag($hashtag, $page); + $count = $this->posts->getPostCountByHashtag($hashtag); + + $this->template->hashtag = $hashtag; + $this->template->posts = $posts; + $this->template->paginatorConf = (object) [ + "count" => 0, + "page" => $page, + "amount" => $count, + "perPage" => OPENVK_DEFAULT_PER_PAGE, + ]; + } + + function renderMakePost(int $wall): void + { + $this->assertUserLoggedIn(); + + $wallOwner = ($wall > 0 ? (new Users)->get($wall) : (new Clubs)->get($wall * -1)) + ?? $this->flashFail("err", "Не удалось опубликовать пост", "Такого пользователя не существует."); + if($wall > 0) + $canPost = $wallOwner->getPrivacyPermission("wall.write", $this->user->identity); + else if($wall < 0) + if($wallOwner->canBeModifiedBy($this->user->identity)) + $canPost = true; + else + $canPost = $wallOwner->canPost(); + else + $canPost = false; + + + if(!$canPost) + $this->flashFail("err", "Ошибка доступа", "Вам нельзя писать на эту стену."); + + $flags = 0; + if($this->postParam("as_group") === "on") + $flags |= 0b10000000; + if($this->postParam("force_sign") === "on") + $flags |= 0b01000000; + if($this->postParam("nsfw") === "on") + $flags |= 0b00100000; + + + if($_FILES["_pic_attachment"]["error"] === UPLOAD_ERR_OK) { + try { + $post = new Post; + $post->setOwner($this->user->id); + $post->setWall($wall); + $post->setCreated(time()); + $post->setContent($this->postParam("text")); + $post->setFlags($flags); + $post->save(); + + $photo = new Photo; + $photo->setOwner($this->user->id); + $photo->setDescription(iconv_substr($this->postParam("text"), 0, 36) . "..."); + $photo->setCreated(time()); + $photo->setFile($_FILES["_pic_attachment"]); + $photo->save(); + + if($wall > 0 && $wall === $this->user->id) { + (new Albums)->getUserWallAlbum($wallOwner)->addPhoto($photo); + } + } catch(ISE $ex) { + $this->flashFail("err", "Не удалось опубликовать пост", "Файл повреждён."); + } + + $post->attach($photo); + } elseif($this->postParam("text")) { + try { + $post = new Post; + $post->setOwner($this->user->id); + $post->setWall($wall); + $post->setCreated(time()); + $post->setContent($this->postParam("text")); + $post->setFlags($flags); + $post->save(); + } catch(\LogicException $ex) { + $this->flashFail("err", "Не удалось опубликовать пост", "Нельзя опубликовать пустой пост."); + } + } else { + $this->flashFail("err", "Не удалось опубликовать пост", "Нельзя опубликовать пустой пост."); + } + + if($wall > 0 && $wall !== $this->user->identity->getId()) + (new WallPostNotification($wallOwner, $post, $this->user->identity))->emit(); + + if($wall > 0) + $this->redirect("/id$wall", 2); #Will exit + + $wall = $wall * -1; + $this->redirect("/club$wall", 2); + } + + function renderPost(int $wall, int $post_id): void + { + $this->assertUserLoggedIn(); + + $post = $this->posts->getPostById($wall, $post_id); + if(!$post || $post->isDeleted()) + $this->notFound(); + + $this->logPostView($post, $wall); + + $this->template->post = $post; + $this->template->cCount = $post->getCommentsCount(); + $this->template->cPage = (int) ($_GET["p"] ?? 1); + $this->template->comments = iterator_to_array($post->getComments($this->template->cPage)); + } + + function renderLike(int $wall, int $post_id): void + { + $this->assertUserLoggedIn(); + $this->assertNoCSRF(); + + $post = $this->posts->getPostById($wall, $post_id); + if(!$post || $post->isDeleted()) $this->notFound(); + + if(!is_null($this->user)) { + $post->toggleLike($this->user->identity); + + if($post->getOwner(false)->getId() !== $this->user->identity->getId() && !($post->getOwner() instanceof Club)) + (new LikeNotification($post->getOwner(false), $post, $this->user->identity))->emit(); + } + + $this->redirect( + "$_SERVER[HTTP_REFERER]#postGarter=" . $post->getId(), + static::REDIRECT_TEMPORARY + ); + } + + function renderShare(int $wall, int $post_id): void + { + $this->assertUserLoggedIn(); + $this->assertNoCSRF(); + + $post = $this->posts->getPostById($wall, $post_id); + if(!$post || $post->isDeleted()) $this->notFound(); + + if(!is_null($this->user)) { + $nPost = new Post; + $nPost->setOwner($this->user->id); + $nPost->setWall($this->user->id); + $nPost->setContent(""); + $nPost->save(); + $nPost->attach($post); + + if($post->getOwner(false)->getId() !== $this->user->identity->getId() && !($post->getOwner() instanceof Club)) + (new RepostNotification($post->getOwner(false), $post, $this->user->identity))->emit(); + }; + + $this->flash("succ", "Успешно", "Запись появится на вашей стене. Вернуться к записи."); + $this->redirect($this->user->identity->getURL()); + } + + function renderDelete(int $wall, int $post_id): void + { + $this->assertUserLoggedIn(); + + $post = $this->posts->getPostById($wall, $post_id); + if(!$post) + $this->notFound(); + $user = $this->user->id; + + if(!is_null($user)) { + if($post->getOwnerPost() == $user || $post->getTargetWall() == $user) { + $post->unwire(); + $post->delete(false); + } + } else { + $this->flashFail("err", "Не удалось удалить пост", "Вы не вошли в аккаунт."); + } + + $this->redirect($_SERVER["HTTP_REFERER"], static::REDIRECT_TEMPORARY); + exit; + } +} diff --git a/Web/Presenters/templates/@CanonicalListView.xml b/Web/Presenters/templates/@CanonicalListView.xml new file mode 100644 index 000000000..9ad98739e --- /dev/null +++ b/Web/Presenters/templates/@CanonicalListView.xml @@ -0,0 +1,50 @@ +
      +
      + {include tabs} +
      + +
      + {var data = is_array($iterator) ? $iterator : iterator_to_array($iterator)} + + {if sizeof($data) > 0} +
      + + + + + + + +
      + + {include preview, x => $dat} + + + + + {include name, x => $dat} + + +
      + + {include description, x => $dat} +
      +
      + +
      + {include "components/paginator.xml", conf => (object) [ + "page" => $page, + "count" => $count, + "amount" => sizeof($data), + "perPage" => $perPage ?? OPENVK_DEFAULT_PER_PAGE, + ]} +
      + {else} + {ifset customErrorMessage} + {include customErrorMessage} + {else} + {include "components/nothing.xml"} + {/ifset} + {/if} +
      +
      \ No newline at end of file diff --git a/Web/Presenters/templates/@MilkshakeListView.xml b/Web/Presenters/templates/@MilkshakeListView.xml new file mode 100644 index 000000000..9cc4e389a --- /dev/null +++ b/Web/Presenters/templates/@MilkshakeListView.xml @@ -0,0 +1,57 @@ +
      +
      + {var data = is_array($iterator) ? $iterator : iterator_to_array($iterator)} + + {if sizeof($data) > 0} + + + + + + + + +
      + {include preview, x => $dat} + + + +
      + {include description, x => $dat} +
      +
      +
      + +
      + {include "components/paginator.xml", conf => (object) [ + "page" => $page, + "count" => $count, + "amount" => sizeof($data), + "perPage" => $perPage ?? OPENVK_DEFAULT_PER_PAGE, + ]} +
      + {else} + {ifset customErrorMessage} + {include customErrorMessage} + {else} + {include "components/nothing.xml"} + {/ifset} + {/if} +
      + +
      + {include actions} +
      + +
      +
      \ No newline at end of file diff --git a/Web/Presenters/templates/@banned.xml b/Web/Presenters/templates/@banned.xml new file mode 100644 index 000000000..d75e09bbe --- /dev/null +++ b/Web/Presenters/templates/@banned.xml @@ -0,0 +1,22 @@ +{extends "@layout.xml"} +{block title}Вам бан{/block} + +{block header} + Вы были верискокнуты +{/block} + +{block content} +
      + Пользователь заблокирован. +
      +

      + Извините, {$thisUser->getCanonicalName()}, но вы были верискокнуты.
      + А причина этому проста: {$thisUser->getBanReason()}. К сожалению, на этот раз + нам пришлось заблокировать вас навсегда. +

      +
      +

      + Вы всё ещё можете написать в службу поддержки, если считаете что произошла ошибка + или выйти. +

      +{/block} \ No newline at end of file diff --git a/Web/Presenters/templates/@error.xml b/Web/Presenters/templates/@error.xml new file mode 100644 index 000000000..05d755022 --- /dev/null +++ b/Web/Presenters/templates/@error.xml @@ -0,0 +1,66 @@ + + + + Произошёл троллинг... | OpenVK + + + + + + + + + +
      +

      {$desc}

      + Ouch +

      {$desc}

      +
      + + diff --git a/Web/Presenters/templates/@layout.xml b/Web/Presenters/templates/@layout.xml new file mode 100644 index 000000000..601a13cbd --- /dev/null +++ b/Web/Presenters/templates/@layout.xml @@ -0,0 +1,294 @@ + + + + {ifset title}{include title} - {/ifset}OpenVK + + + + + + {css "css/style.css"} + {css "css/dialog.css"} + {script "js/node_modules/umbrellajs/umbrella.min.js"} + {script "js/openvk.cls.js"} + {ifset $thisUser} + {var style = (int) ($_GET['__ovkStyleOverride'] ?? $thisUser->getStyle())} + + {if $style == 1} + {css "css/vkontakte.css"} + {/if} + {if $style == 2} + {css "css/ovkdan.5.css"} + {/if} + {if $style == 3} + {css "css/vkontakte.css"} + {css "css/vkontakte2006.css"} + {/if} + {if $style == 4} + {css "css/ovkg.css"} + {/if} + {if $style == 5} + {css "css/black.css"} + {/if} + {if $style == 6} + {css "css/ash_oss_themes/vk2015.2.css"} + {/if} + {if $style == 7} + {css "css/spacepink.css"} + {/if} + {if $style == 9} + {css "css/vriska.css"} + {/if} + {if $style == 8} + {css "css/ash_oss_themes/vk2015.2.css"} + {/if} + {if $style == 10} + {css "css/ash_oss_themes/ВСоюзе.css"} + {/if} + {if $style == 11} + {css "css/ash_oss_themes/fb2005.css"} + {/if} + {if $style == 12} + {css "css/ooer.css"} + {/if} + {if $style == 13} + {css "css/ash_oss_themes/Twitter2007.css"} + {/if} + {if $style == 14} + {css "css/kos.css"} + {/if} + {if $style == 15} + {css "css/ash_oss_themes/pager.css"} + {/if} + {if $style == 16} + {css "css/ash_oss_themes/vkdark.css"} + {/if} + {if $style == 17} + {css "css/ash_oss_themes/vtlenu.css"} + {/if} + {if $style == 18} + {css "css/ash_oss_themes/Fb2006.css"} + {/if} + {if $style == 19} + {css "css/ash_oss_themes/vkdefenders08.css"} + {/if} + {if $style == 20} + {css "css/ash_oss_themes/tlenta.css"} + {/if} + {if $thisUser->getStyleAvatar() == 1} + {css "css/avatar.1.css"} + {/if} + {if $thisUser->getStyleAvatar() == 2} + {css "css/avatar.2.css"} + {/if} + {/ifset} + + {if true} + {css "css/nsfw-posts.css"} + {/if} + + {ifset headIncludes}{include headIncludes}{/ifset} + + +
      +

      + Вы вошли как {$thisUser->getCanonicalName()}. Пожалуйста, уважайте + право на тайну переписки других людей и не злоупотребляйте подменой пользователя. + Нажмите здесь, чтобы выйти. +

      +
      + +
      FOR TESTING PURPOSES ONLY
      +
      +
      + ⬆ Вверх +
      + +
      + {ifset $thisUser} + {if in_array($thisUser->getStyle(), [4, 5, 6, 20])} +
      + {/if} + {/ifset} + + + + +
      +
      +
      +
      + {include header} +
      +
      +
      +
      +
      + {ifset wrap} + {ifset $flashMessage} +
      + {$flashMessage->title}
      + {$flashMessage->msg|noescape} +
      + {/ifset} + + {include wrap} + {else} +
      +
      +
      +
      + {ifset $flashMessage} +
      + {$flashMessage->title}
      + {$flashMessage->msg|noescape} +
      + {/ifset} + + {include content} +
      +
      +
      +
      + {/ifset} +
      +
      +
      +
      + + + + + {script "js/node_modules/ky/umd.js"} + {script "js/messagebox.js"} + {script "js/scroll.js"} + {script "js/al_wall.js"} + {script "js/al_api.js"} + + + + + + +{if isset($parentModule) && substr($parentModule, 0, 21) !== 'libchandler:absolute.'} + + + {include content} +{/if} diff --git a/Web/Presenters/templates/@listView.xml b/Web/Presenters/templates/@listView.xml new file mode 100644 index 000000000..7aac1fa1d --- /dev/null +++ b/Web/Presenters/templates/@listView.xml @@ -0,0 +1,54 @@ +{extends "@layout.xml"} + +{block wrap} +
      +
      + {include tabs} +
      + +
      + {var data = is_array($iterator) ? $iterator : iterator_to_array($iterator)} + + {if sizeof($data) > 0} +
      + + + + + + + +
      + + {include preview, x => $dat} + + + + + {include name, x => $dat} + + +
      + + {include description, x => $dat} +
      +
      + +
      + {include "components/paginator.xml", conf => (object) [ + "page" => $page, + "count" => $count, + "amount" => sizeof($data), + "perPage" => $perPage ?? OPENVK_DEFAULT_PER_PAGE, + ]} +
      + {else} + {ifset customErrorMessage} + {include customErrorMessage} + {else} + {include "components/nothing.xml"} + {/ifset} + {/if} +
      +
      +{/block} diff --git a/Web/Presenters/templates/About/BB.xml b/Web/Presenters/templates/About/BB.xml new file mode 100644 index 000000000..17ab3b0f9 --- /dev/null +++ b/Web/Presenters/templates/About/BB.xml @@ -0,0 +1,12 @@ +{extends "../@layout.xml"} +{block title}Ваш браузер устарел{/block} + +{block header} + Устаревший браузер +{/block} + +{block content} + Для просмотра этого контента вам понадобится Firefox ESR 52+ или + эквивалентный по функционалу навигатор по всемирной сети интернет.
      + Сожалеем об этом. +{/block} diff --git a/Web/Presenters/templates/About/Donate.xml b/Web/Presenters/templates/About/Donate.xml new file mode 100644 index 000000000..d8c2fc6b2 --- /dev/null +++ b/Web/Presenters/templates/About/Donate.xml @@ -0,0 +1,10 @@ +{extends "../@layout.xml"} +{block title}Пожертвование{/block} + +{block header} + Пожертвование +{/block} + +{block content} + это заглушка пока ок +{/block} diff --git a/Web/Presenters/templates/About/Help.xml b/Web/Presenters/templates/About/Help.xml new file mode 100644 index 000000000..060f1381b --- /dev/null +++ b/Web/Presenters/templates/About/Help.xml @@ -0,0 +1,13 @@ +{extends "../@layout.xml"} +{block title}Часто задаваемые вопросы{/block} + +{block header} + Часто задаваемые вопросы +{/block} + +{block content} +
      Для кого этот сайт?
      +
      Сайт предназначен для поиска друзей и знакомых, а также просмотр данных пользователя. Это как справочник города, с помощью которого люди могут быстро найти актуальную информацию о человеке. Также этот сайт подойдёт для ностальгираторов и тех, кто решил слезть с трубы "ВКонтакте", которого клон и является.
      + Я попозже допишу ок ~~ veselcraft - 12.01.2020 - 22:05 GMT+3 + +{/block} diff --git a/Web/Presenters/templates/About/Index.xml b/Web/Presenters/templates/About/Index.xml new file mode 100644 index 000000000..64e0cd19d --- /dev/null +++ b/Web/Presenters/templates/About/Index.xml @@ -0,0 +1,26 @@ +{extends "../@layout.xml"} +{block title}{_"welcome"}{/block} + +{block header} + {_"welcome"} +{/block} + +{block content} + OpenVK - универсальное средство поиска коллег основанное на структуре ВКонтакте.
      +

      Мы хотим, чтобы друзья, однокурсники, одноклассники, соседи и коллеги всегда могли быть в контакте.

      +

      + Нас уже {$stats->all} пользователя и {$stats->online} из + {$stats->active} активных сейчас в сети. +

      +

      С помощью этого сайта Вы можете:

      +
        +
      • Найти людей, с которыми Вы когда-либо учились, работали или отдыхали.
      • +
      • Узнать больше о людях, которые Вас окружают, и найти новых друзей.
      • +
      • Всегда оставаться в контакте с теми, кто Вам дорог.
      • +
      • Продвигать своё творчество и/или мнение.
      • +
      +
      + {_"log_in"} + {_"registration"}
      + +{/block} diff --git a/Web/Presenters/templates/About/Invite.xml b/Web/Presenters/templates/About/Invite.xml new file mode 100644 index 000000000..0708ea767 --- /dev/null +++ b/Web/Presenters/templates/About/Invite.xml @@ -0,0 +1,18 @@ +{extends "../@layout.xml"} +{block title}Пригласить{/block} + +{block header} + Пригласить +{/block} + +{block content} + Вы можете пригласить своих друзей или знакомых в сеть с помощью индивидуальной ссылки:

      +
      + getRefLinkId()}" size="50" /> +
      +

      Приложите эту ссылку к вашему сообщению. Пользователь зарегистрируется, и он сразу появится у вас в друзьях.

      +
      + Пока не работает
      + Надо индивидуальный токен создать для такой ссылки, а то пиздец будет. +
      +{/block} diff --git a/Web/Presenters/templates/About/Language.xml b/Web/Presenters/templates/About/Language.xml new file mode 100644 index 000000000..6a53a31b8 --- /dev/null +++ b/Web/Presenters/templates/About/Language.xml @@ -0,0 +1,17 @@ +{extends "../@layout.xml"} +{block title}{_"select_language"}{/block} + +{block header} + {_"select_language"} +{/block} + +{block content} + +{/block} diff --git a/Web/Presenters/templates/About/Privacy.xml b/Web/Presenters/templates/About/Privacy.xml new file mode 100644 index 000000000..a93e6d3cd --- /dev/null +++ b/Web/Presenters/templates/About/Privacy.xml @@ -0,0 +1,11 @@ +{extends "../@layout.xml"} +{block title}Приватность{/block} + +{block header} + Политика приватности +{/block} + +{block content} +

      Hello guys DeSinc here


      +

      Последняя дата редактирования: 21 апреля 2020

      +{/block} \ No newline at end of file diff --git a/Web/Presenters/templates/About/Rules.xml b/Web/Presenters/templates/About/Rules.xml new file mode 100644 index 000000000..2403b28df --- /dev/null +++ b/Web/Presenters/templates/About/Rules.xml @@ -0,0 +1,10 @@ +{extends "../@layout.xml"} +{block title}Правила{/block} + +{block header} + Правила +{/block} + +{block content} + {$rules|noescape} +{/block} diff --git a/Web/Presenters/templates/About/Sandbox.xml b/Web/Presenters/templates/About/Sandbox.xml new file mode 100644 index 000000000..05219cc72 --- /dev/null +++ b/Web/Presenters/templates/About/Sandbox.xml @@ -0,0 +1,10 @@ +{extends "../@layout.xml"} +{block title}Sandbox{/block} + +{block header} + Sandbox для разработчиков +{/block} + +{block content} + {$manager->getUser()->getFullName()} +{/block} diff --git a/Web/Presenters/templates/About/Version.xml b/Web/Presenters/templates/About/Version.xml new file mode 100644 index 000000000..4233d6c28 --- /dev/null +++ b/Web/Presenters/templates/About/Version.xml @@ -0,0 +1,26 @@ +{extends "../@layout.xml"} + +{block title}Об OpenVK{/block} + +{block header} + Об OpenVK +{/block} + +{block content} + OpenVK 2 + + + + + + + + + + + + +
      Версия: {php echo OPENVK_VERSION}
      Chandlerd: {php echo CHANDLER_VER}
      +{/block} diff --git a/Web/Presenters/templates/Admin/@layout.xml b/Web/Presenters/templates/Admin/@layout.xml new file mode 100644 index 000000000..9854e2f9e --- /dev/null +++ b/Web/Presenters/templates/Admin/@layout.xml @@ -0,0 +1,145 @@ + + + + + + {include title} + + +
      + +
      +
      +
      + +
      +
      +
      +
      +
      +

      {include heading}

      +
      +
      +
      +
      + {include content} +
      +
      +
      +
      +
      + +
      +
      + + diff --git a/Web/Presenters/templates/Admin/Clubs.xml b/Web/Presenters/templates/Admin/Clubs.xml new file mode 100644 index 000000000..f1b609d90 --- /dev/null +++ b/Web/Presenters/templates/Admin/Clubs.xml @@ -0,0 +1,73 @@ +{extends "@layout.xml"} +{var search = true} + +{block title} + Группы +{/block} + +{block heading} + Бутылки +{/block} + +{block searchTitle}Поиск бутылок{/block} + +{block content} + {var clubs = iterator_to_array($clubs)} + {var amount = sizeof($clubs)} + + + + + + + + + + + + + + + + + + + + + + +
      #ИмяАвторОписаниеКороткий адресДействия
      {$club->getId()} + + + {$club->getCanonicalName()} + + + + {$club->getCanonicalName()} + + {var user = $club->getOwner()} + + + + {$user->getCanonicalName()} + + + + {$user->getCanonicalName()} + {$club->getDescription() ?? "(не указано)"}{$club->getShortCode()} + + Редактировать + +
      +
      +
      + {var isLast = ((10 * (($_GET['p'] ?? 1) - 1)) + $amount) < $count} + + + ⭁ туда + + + ⭇ сюда + +
      +{/block} diff --git a/Web/Presenters/templates/Admin/Index.xml b/Web/Presenters/templates/Admin/Index.xml new file mode 100644 index 000000000..f860f86fb --- /dev/null +++ b/Web/Presenters/templates/Admin/Index.xml @@ -0,0 +1,13 @@ +{extends "@layout.xml"} + +{block title} + Сводка +{/block} + +{block heading} + Сводка +{/block} + +{block content} + Да! +{/block} diff --git a/Web/Presenters/templates/Admin/User.xml b/Web/Presenters/templates/Admin/User.xml new file mode 100644 index 000000000..7d2a47e93 --- /dev/null +++ b/Web/Presenters/templates/Admin/User.xml @@ -0,0 +1,97 @@ +{extends "@layout.xml"} + +{block title} + Редактировать {$user->getCanonicalName()} +{/block} + +{block heading} + {$user->getCanonicalName()} +{/block} + +{block content} +
      + + + < +
      +
      + + + + + + +
      +
      + + +
      +
      + + +
      +
      + + +
      +
      + + +
      +
      + + +
      +
      + + +
      +
      + + +
      +
      + + +
      +
      + + +
      +
      + +
      +{/block} diff --git a/Web/Presenters/templates/Admin/Users.xml b/Web/Presenters/templates/Admin/Users.xml new file mode 100644 index 000000000..17baab8dd --- /dev/null +++ b/Web/Presenters/templates/Admin/Users.xml @@ -0,0 +1,70 @@ +{extends "@layout.xml"} +{var search = true} + +{block title} + Пользователи +{/block} + +{block heading} + Пиздюки +{/block} + +{block searchTitle}Поиск пиздюков{/block} + +{block content} + {var users = iterator_to_array($users)} + {var amount = sizeof($users)} + + + + + + + + + + + + + + + + + + + + + + +
      #ИмяПолКороткий адресДата регистрацииДействия
      {$user->getId()} + + + {$user->getCanonicalName()} + + + + {$user->getCanonicalName()} + + + заблокирован + + {$user->isFemale() ? "Женский" : "Мужской"}{$user->getShortCode() ?? "(отсутствует)"}{$user->getRegistrationTime()} + + Редактировать + + + + +
      +
      +
      + {var isLast = ((10 * (($_GET['p'] ?? 1) - 1)) + $amount) < $count} + + + ⭁ туда + + + ⭇ сюда + +
      +{/block} diff --git a/Web/Presenters/templates/Audios/App.xml b/Web/Presenters/templates/Audios/App.xml new file mode 100644 index 000000000..c9481cfd7 --- /dev/null +++ b/Web/Presenters/templates/Audios/App.xml @@ -0,0 +1,52 @@ +{extends "../@layout.xml"} +{block title}Аудиопроигрыватель{/block} + +{block header} + {$user->getCanonicalName()} + » + Аудиозаписи +{/block} + + + +{block content} + +
      +
      + + +
      +
      + Track - not selected + +
      +
      +
      +
      +
      +
      + + +
      +{/block} diff --git a/Web/Presenters/templates/Audios/Upload.xml b/Web/Presenters/templates/Audios/Upload.xml new file mode 100644 index 000000000..229456815 --- /dev/null +++ b/Web/Presenters/templates/Audios/Upload.xml @@ -0,0 +1,79 @@ +{extends "../@layout.xml"} +{block title}Загрузить аудиозапись{/block} + +{block header} + {$thisUser->getCanonicalName()} + » + Аудиозаписи + » + Загрузить +{/block} + +{block content} +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      Исполнитель:
      Название песни:
      Название песни: + +
      Текст песни:
      Аудиозапись:
      + + +
      +
      +{/block} diff --git a/Web/Presenters/templates/Auth/FinishRestoringPassword.xml b/Web/Presenters/templates/Auth/FinishRestoringPassword.xml new file mode 100644 index 000000000..b89ea9eb1 --- /dev/null +++ b/Web/Presenters/templates/Auth/FinishRestoringPassword.xml @@ -0,0 +1,24 @@ +{extends "../@layout.xml"} + +{block title} + Восстановление доступа +{/block} + +{block header} + Восстановить доступ к странице +{/block} + +{block content} +

      + Введите ваш новый пароль. Все текущие сеансы будут приостановлены и токены доступа будут аннулированы. +

      + +
      + + +

      + + + +
      +{/block} diff --git a/Web/Presenters/templates/Auth/Login.xml b/Web/Presenters/templates/Auth/Login.xml new file mode 100644 index 000000000..4a280822e --- /dev/null +++ b/Web/Presenters/templates/Auth/Login.xml @@ -0,0 +1,41 @@ +{extends "../@layout.xml"} +{block title}{_"log_in"}{/block} + +{block header} + {_"log_in"} +{/block} + +{block content} +
      + + + + + + + + + + + + + + + +
      + {_"email"}: + + +
      + {_"password"}: + + +
      + + + + + {_"registration"} +
      +
      +{/block} diff --git a/Web/Presenters/templates/Auth/Register.xml b/Web/Presenters/templates/Auth/Register.xml new file mode 100644 index 000000000..2a641bc9c --- /dev/null +++ b/Web/Presenters/templates/Auth/Register.xml @@ -0,0 +1,78 @@ +{extends "../@layout.xml"} +{block title}{_"registration"}{/block} + +{block header} + {_"registration"} +{/block} + +{block content} +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      + {_"name"}: + + +
      + {_"surname"}: + + +
      + {_"gender"}: + + {var femalePreferred = OPENVK_ROOT_CONF["openvk"]["preferences"]["femaleGenderPriority"]} + +
      + {_"email"}: + + +
      + {_"password"}: + + +
      + CAPTCHA: + + {captcha_template()|noescape} +
      + + + + + {_"log_in"} +
      +
      +{/block} diff --git a/Web/Presenters/templates/Auth/Restore.xml b/Web/Presenters/templates/Auth/Restore.xml new file mode 100644 index 000000000..4c1bba39e --- /dev/null +++ b/Web/Presenters/templates/Auth/Restore.xml @@ -0,0 +1,24 @@ +{extends "../@layout.xml"} + +{block title} + Восстановление доступа +{/block} + +{block header} + Восстановить доступ к странице +{/block} + +{block content} +

      + Забыли пароль? Не волнуйтесь, введите ваши данные и мы отправим вам email с инструкциями по восстановлению аккаунта. +

      + +
      + + +

      + + + +
      +{/block} diff --git a/Web/Presenters/templates/ContentSearch/Index.xml b/Web/Presenters/templates/ContentSearch/Index.xml new file mode 100644 index 000000000..e69de29bb diff --git a/Web/Presenters/templates/Group/Admin.xml b/Web/Presenters/templates/Group/Admin.xml new file mode 100644 index 000000000..1ebd93f30 --- /dev/null +++ b/Web/Presenters/templates/Group/Admin.xml @@ -0,0 +1,44 @@ +{extends "../@layout.xml"} +{block title}{$manager->getUser()->getFullName()}{/block} + +{block header} + {$club->getName()} » {_edit_group} » {$manager->getUser()->getFullName()} +{/block} + +{block content} +
      +
      + + + + + + + + + + + + + + + + +
      + {_"comment"}: + + +
      + + + +
      + + + + + +
      +
      +
      +{/block} diff --git a/Web/Presenters/templates/Group/Create.xml b/Web/Presenters/templates/Group/Create.xml new file mode 100644 index 000000000..1d2a86126 --- /dev/null +++ b/Web/Presenters/templates/Group/Create.xml @@ -0,0 +1,41 @@ +{extends "../@layout.xml"} +{block title}Создать группу{/block} + +{block header} + Создать группу +{/block} + +{block content} +

      Основная информация

      +
      + + + + + + + + + + + + + + + +
      + {_"name"}: + + +
      + {_"description"}: + + +
      + + + + +
      +
      +{/block} diff --git a/Web/Presenters/templates/Group/Edit.xml b/Web/Presenters/templates/Group/Edit.xml new file mode 100644 index 000000000..f58b6f498 --- /dev/null +++ b/Web/Presenters/templates/Group/Edit.xml @@ -0,0 +1,86 @@ +{extends "../@layout.xml"} +{block title}{_edit_group}{/block} + +{block header} + {$club->getName()} » {_edit_group} +{/block} + +{block content} + + +
      +

      Основная информация

      +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      + {_"name"}: + + +
      + {_"description"}: + + +
      + Адрес группы: + + +
      + Аватар: + + +
      + Стена: + + canPost()}checked{/if}/> Разрешить постить обычным пользователям +
      + + + + +
      +
      +
      +{/block} diff --git a/Web/Presenters/templates/Group/Followers.xml b/Web/Presenters/templates/Group/Followers.xml new file mode 100644 index 000000000..ea60bd5e2 --- /dev/null +++ b/Web/Presenters/templates/Group/Followers.xml @@ -0,0 +1,62 @@ +{extends "../@listView.xml"} +{var iterator = $followers} +{var count = $paginatorConf->count} +{var page = $paginatorConf->page} + +{block title}{_followers} {$club->getCanonicalName()}{/block} + +{block header} + {$club->getCanonicalName()} + » {_followers} +{/block} + +{block actions} + +{/block} + +{* BEGIN ELEMENTS DESCRIPTION *} + +{block link|strip|stripHtml} + /id{$x->getId()} +{/block} + +{block preview} + {$x->getCanonicalName()} +{/block} + +{block name} + {$x->getCanonicalName()} +{/block} + +{block description} + + + + + + + + + + + + + + + + + + + +
      {_"gender"}: {$x->isFemale() ? "женский" : "мужской"}
      {_"registration_date"}: {$x->getRegistrationTime()}
      {_role}: + {$club->canBeModifiedBy($x) ? tr("administrator") : tr("follower")} +
      {_actions}: + + {if $club->canBeModifiedBy($x)} + {_devote} + {else} + {_promote_to_admin} + {/if} + +
      +{/block} diff --git a/Web/Presenters/templates/Group/Statistics.xml b/Web/Presenters/templates/Group/Statistics.xml new file mode 100644 index 000000000..0a40154e4 --- /dev/null +++ b/Web/Presenters/templates/Group/Statistics.xml @@ -0,0 +1,57 @@ +{extends "../@layout.xml"} +{block title}Статистика группы{/block} + +{block header} + {$club->getName()} » Статистика +{/block} + +{block content} + + +
      +

      Охват

      +

      Этот график отображает охват за последние 7 дней.

      +
      + +

      Просмотры

      +

      Этот график отображает просмотры постов сообщества за последние 7 дней.

      +
      + + + {script "js/node_modules/plotly.js-dist/plotly.js"} + +
      +{/block} diff --git a/Web/Presenters/templates/Group/View.xml b/Web/Presenters/templates/Group/View.xml new file mode 100644 index 000000000..3f824df68 --- /dev/null +++ b/Web/Presenters/templates/Group/View.xml @@ -0,0 +1,151 @@ +{extends "../@layout.xml"} + +{block title}{$club->getName()}{/block} + +{block header} + {$club->getName()} + + Подтверждённая страница +{/block} + +{block content} +
      +
      + {_"information"} +
      + +
      + + + + + + + + + + + +
      {_"name_group"}:{$club->getName()}
      {_"description"}:{$club->getDescription()}
      +
      +
      + {var followersCount = $club->getFollowersCount()} + +
      + Подписчики +
      +
      +
      + {$followersCount} подписчиков +
      + Все +
      +
      +
      + + + + + + + + + +
      + + + +
      + {$follower->getFirstName()} +
      +
      +
      +
      + + {presenter "openvk!Wall->wall", -$club->getId()} + +
      +
      + + + + +
      +
      + {_"group_type"} +
      +
      + {_"group_type_open"} +
      +
      +
      +
      + {_"creator"} +
      +
      + {var author = $club->getOwner()} + +
      +
      +
      +
      + {_"albums"} +
      +
      +
      + {$albumsCount} альбомов + +
      +
      +
      +
      + {var cover = $album->getCoverPhoto()} + + +
      +
      + {$album->getName()}
      + Обновлён {$album->getEditTime() ?? $album->getCreationTime()} +
      +
      +
      +
      +
      +
      + +{/block} diff --git a/Web/Presenters/templates/Messenger/App.xml b/Web/Presenters/templates/Messenger/App.xml new file mode 100644 index 000000000..44082dacc --- /dev/null +++ b/Web/Presenters/templates/Messenger/App.xml @@ -0,0 +1,256 @@ +{extends "../@layout.xml"} +{block title}{$correspondent->getCanonicalName()}{/block} + +{block header} + +{_my_messages} » +{$correspondent->getCanonicalName()} +
      + {var diff = date_diff(date_create(), date_create('@' . $online))} + {if 5 >= $diff->i} + Online + {else} + {$correspondent->isFemale() ? "заходила" : "заходил"} {$correspondent->getOnline()} + {/if} +
      +{/block} + +{block wrap} +
      +
      +
      +
      + + +
      + Monday, the 1st of January 1970 00:00:00 +
      +
      +
      +
      +
      + {if $correspondent->getId() === $thisUser->getId() || ($correspondent->getSubscriptionStatus($thisUser) === 3)} + {$thisUser->getCanonicalName()} +
      + + +
      + {$correspondent->getCanonicalName()} + {else} +
      + {/if} +
      +
      + + + +{/block} diff --git a/Web/Presenters/templates/Messenger/Index.xml b/Web/Presenters/templates/Messenger/Index.xml new file mode 100644 index 000000000..5f9278632 --- /dev/null +++ b/Web/Presenters/templates/Messenger/Index.xml @@ -0,0 +1,53 @@ +{extends "../@layout.xml"} +{block title}{_my_messages}{/block} + +{block header}{/block} + +{block content} + + +
      +
      + +
      +
      + + {if sizeof($corresps) > 0} +
      +
      + {var recipient = $coresp->getCorrespondents()[1]} + {var lastMsg = $coresp->getPreviewMessage()} + +
      + Фотография пользователя +
      + +
      + {var _author = $lastMsg->getSender()} + +
      + Фотография пользователя +
      +
      + {$lastMsg->getText()|truncate:60} +
      +
      +
      +
      + {else} +
      +
      +
      Вам никто не писал. Пока что.
      + {/if} +{/block} diff --git a/Web/Presenters/templates/Notes/Create.xml b/Web/Presenters/templates/Notes/Create.xml new file mode 100644 index 000000000..cf5b3a16f --- /dev/null +++ b/Web/Presenters/templates/Notes/Create.xml @@ -0,0 +1,42 @@ +{extends "../@layout.xml"} + +{block title}{_create_note}{/block} + +{block header} + {_create_note} +{/block} + +{block content} +
      + +

      + +
      + +

      Кое-что из (X)HTML поддерживается.

      + + + +
      + + {script "js/node_modules/monaco-editor/min/vs/loader.js"} + {script "js/node_modules/requirejs/bin/r.js"} + +{/block} diff --git a/Web/Presenters/templates/Notes/List.xml b/Web/Presenters/templates/Notes/List.xml new file mode 100644 index 000000000..2c7ceef5d --- /dev/null +++ b/Web/Presenters/templates/Notes/List.xml @@ -0,0 +1,37 @@ +{extends "../@listView.xml"} +{var iterator = iterator_to_array($notes)} +{var page = $paginatorConf->page} + +{block title}{_notes}{/block} + +{block header} + {$owner->getCanonicalName()} + » + {_notes} + + +{/block} + +{block actions} + +{/block} + +{* BEGIN ELEMENTS DESCRIPTION *} + +{block link|strip|stripHtml} + /note{$x->getOwner()->getId()}_{$x->getId()} +{/block} + +{block preview} +
      {_note}
      +{/block} + +{block name} + {$x->getName()} +{/block} + +{block description} + {$x->getPreview(250)} +{/block} diff --git a/Web/Presenters/templates/Notes/View.xml b/Web/Presenters/templates/Notes/View.xml new file mode 100644 index 000000000..9d7123933 --- /dev/null +++ b/Web/Presenters/templates/Notes/View.xml @@ -0,0 +1,49 @@ +{extends "../@layout.xml"} + +{block title}{$note->getName()}{/block} + +{block header} + {var author = $note->getOwner()} + {$author->getCanonicalName()} + » + {_notes} + » + {$note->getName()} +{/block} + +{block content} + + +
      + {$note->getText()|noescape} +
      + +
      + {include "../components/comments.xml", + comments => $comments, + count => $cCount, + page => $cPage, + model => "notes", + parent => $note} +
      +{/block} diff --git a/Web/Presenters/templates/Notification/Feed.xml b/Web/Presenters/templates/Notification/Feed.xml new file mode 100644 index 000000000..848d55e81 --- /dev/null +++ b/Web/Presenters/templates/Notification/Feed.xml @@ -0,0 +1,45 @@ +{extends "../@listView.xml"} +{var sorting = false} + +{block title} + {_feedback} +{/block} + +{block header} + {$thisUser->getCanonicalName()} » + {_feedback} +{/block} + +{block tabs} +
      + {_unread} +
      + +{/block} + +{block actions} + +{/block} + +{* BEGIN ELEMENTS DESCRIPTION *} + +{block link|strip|stripHtml} + javascript:void(0) +{/block} + +{block preview} + +{/block} + +{block name} + +{/block} + +{block description} + {include $x->getTemplatePath(), notification => $x} +{/block} diff --git a/Web/Presenters/templates/Photos/Album.xml b/Web/Presenters/templates/Photos/Album.xml new file mode 100644 index 000000000..0b9b7b9ff --- /dev/null +++ b/Web/Presenters/templates/Photos/Album.xml @@ -0,0 +1,51 @@ +{extends "../@layout.xml"} + +{block title}Альбом {$album->getName()}{/block} + +{block header} + {var isClub = ($album->getOwner() instanceof openvk\Web\Models\Entities\Club)} + + + {$album->getOwner()->getCanonicalName()} + + {if $isClub} + » {_"albums"} + {else} + » {_"albums"} + {/if} + » {$album->getName()} +{/block} + +{block content} + + {$album->getPhotosCount()} фотографий + + + {if !is_null($thisUser) && $album->canBeModifiedBy($thisUser) && !$album->isCreatedBySystem()} +  |  + {_"upload_photo"} +  |  + {_"edit_album"} + {/if} +
      + {if $album->getPhotosCount() > 0} + {foreach $photos as $photo} + {php if($photo->isDeleted()) continue; } + + {/foreach} + + {include "../components/paginator.xml", conf => $paginatorConf} + {else} + {include "../components/nothing.xml"} + {/if} +{/block} diff --git a/Web/Presenters/templates/Photos/AlbumList.xml b/Web/Presenters/templates/Photos/AlbumList.xml new file mode 100644 index 000000000..c612a5e74 --- /dev/null +++ b/Web/Presenters/templates/Photos/AlbumList.xml @@ -0,0 +1,47 @@ +{extends "../@listView.xml"} +{var iterator = iterator_to_array($albums)} +{var page = $paginatorConf->page} + +{block title}Альбомы {$owner->getCanonicalName()}{/block} + +{block header} + {$owner->getCanonicalName()} + » {_"albums"} + +
      + {var isClub = ($owner instanceof openvk\Web\Models\Entities\Club)} + {_"create_album"} +
      +{/block} + +{block actions} + +{/block} + +{* BEGIN ELEMENTS DESCRIPTION *} + +{block link|strip|stripHtml} + /album{$x->getPrettyId()} +{/block} + +{block preview} + {var cover = $x->getCoverPhoto()} + {var preview = is_null($cover) ? "/assets/packages/static/openvk/img/camera_200.png" : $cover->getURL()} + +
      + + {$x->getName()} + +
      +{/block} + +{block name} + {$x->getName()} +{/block} + +{block description} + {$x->getDescription() ?? $x->getName()}
      + {$x->getPhotosCount()} фотографий
      + Создан {$x->getCreationTime()}
      + Изменён {$x->getEditTime() ?? $x->getCreationTime()} +{/block} diff --git a/Web/Presenters/templates/Photos/CreateAlbum.xml b/Web/Presenters/templates/Photos/CreateAlbum.xml new file mode 100644 index 000000000..02ec36fe2 --- /dev/null +++ b/Web/Presenters/templates/Photos/CreateAlbum.xml @@ -0,0 +1,40 @@ +{extends "../@layout.xml"} +{block title}Создать альбом{/block} + +{block header} + {ifset $club} + {$club->getName()} + » + {_"albums"} + {else} + {$thisUser->getCanonicalName()} + » + {_"albums"} + {/ifset} + » + {_"creating_album"} +{/block} + +{block content} +
      + + + + + + + + + + + + + + + +
      {_"name"}:
      {_"description"}:
      + + +
      +
      +{/block} diff --git a/Web/Presenters/templates/Photos/EditAlbum.xml b/Web/Presenters/templates/Photos/EditAlbum.xml new file mode 100644 index 000000000..0317a065b --- /dev/null +++ b/Web/Presenters/templates/Photos/EditAlbum.xml @@ -0,0 +1,48 @@ +{extends "../@layout.xml"} +{block title}Изменить альбом{/block} + +{block header} + {$album->getOwner()->getCanonicalName()} + » + {if $album->getOwner() instanceof openvk\Web\Models\Entities\Club} + {_"albums"} + {else} + {_"albums"} + {/if} + » + {$album->getName()} +{/block} + +{block content} +
      + + + + + + + + + + + + + + + +
      {_"name"}:
      {_"description"}: + +
      + + +
      +
      +
      +
      + Вы также можете + + удалить + + альбом. +
      +{/block} diff --git a/Web/Presenters/templates/Photos/EditPhoto.xml b/Web/Presenters/templates/Photos/EditPhoto.xml new file mode 100644 index 000000000..004550ac4 --- /dev/null +++ b/Web/Presenters/templates/Photos/EditPhoto.xml @@ -0,0 +1,30 @@ +{extends "../@layout.xml"} +{block title}Изменить фотографию{/block} + +{block header} + {$thisUser->getCanonicalName()} + » + Фотография +{/block} + +{block content} +
      + + + + + + + + + + + +
      {_"description"}: + +
      + + +
      +
      +{/block} diff --git a/Web/Presenters/templates/Photos/Photo.xml b/Web/Presenters/templates/Photos/Photo.xml new file mode 100644 index 000000000..444ff57a4 --- /dev/null +++ b/Web/Presenters/templates/Photos/Photo.xml @@ -0,0 +1,52 @@ +{extends "../@layout.xml"} + +{block title}Фотография{/block} + +{block header} + {ifset $album} + + {$album->getOwner()->getCanonicalName()} + + {if ($album->getOwner() instanceof openvk\Web\Models\Entities\Club)} + » {_"albums"} + {else} + » {_"albums"} + {/if} + » {$album->getName()} + {else} + {$photo->getOwner()->getCanonicalName()} + {/ifset} + » Фотография +{/block} + +{block content} +
      + +
      + +
      + +
      +
      + {include "../components/comments.xml", comments => $comments, count => $cCount, page => $cPage, model => "photos", parent => $photo} +
      +
      +
      +

      Информация

      + Описание: + {$photo->getDescription() ?? "(отсутствует)"}
      + Загрузил: + {$photo->getOwner()->getFullName()}
      + Дата создания: + {$photo->getPublicationTime()} +
      +
      +

      Действия

      + + Открыть оригинал +
      +
      +{/block} diff --git a/Web/Presenters/templates/Photos/UnlinkPhoto.xml b/Web/Presenters/templates/Photos/UnlinkPhoto.xml new file mode 100644 index 000000000..54498e06d --- /dev/null +++ b/Web/Presenters/templates/Photos/UnlinkPhoto.xml @@ -0,0 +1,20 @@ +{extends "../@layout.xml"} + +{block title}Удалить фотографию?{/block} + +{block header} + Удаление фотографии +{/block} + +{block content} + Вы уверены что хотите удалить эту фотографию? +
      +
      +
      + + + Нет +   + +
      +{/block} diff --git a/Web/Presenters/templates/Photos/UploadPhoto.xml b/Web/Presenters/templates/Photos/UploadPhoto.xml new file mode 100644 index 000000000..9f4d16f8a --- /dev/null +++ b/Web/Presenters/templates/Photos/UploadPhoto.xml @@ -0,0 +1,38 @@ +{extends "../@layout.xml"} +{block title}{_"upload_photo"}{/block} + +{block header} + {$thisUser->getCanonicalName()} + » + {_"albums"} + » + {$album->getName()} + » + {_"upload_photo"} +{/block} + +{block content} +
      + + + + + + + + + + + + + + + +
      {_"description"}:
      {_"photo"}:
      + + +
      + + +
      +{/block} diff --git a/Web/Presenters/templates/Search/Index.xml b/Web/Presenters/templates/Search/Index.xml new file mode 100644 index 000000000..c3645f7ec --- /dev/null +++ b/Web/Presenters/templates/Search/Index.xml @@ -0,0 +1,73 @@ +{extends "../@listView.xml"} + +{block title} + {if $type === "users"} + {tr("search_for_people")} + {else} + {tr("search_for_groups")} + {/if} +{/block} + +{block header} + OpenVK » + {if $type === "users"} + {tr("search_for_people")} + {else} + {tr("search_for_groups")} + {/if} +{/block} + +{block tabs} +
      + + + +
      + +

      + {$count} результатов +

      +{/block} + +{* BEGIN ELEMENTS DESCRIPTION *} + +{block link|strip|stripHtml} + {$x->getURL()} +{/block} + +{block preview} + Фотография +{/block} + +{block name} +  {$x->getCanonicalName()} +{/block} + +{block description} + + + {if $type === "users"} + + + + + + + + + + + + + {/if} + + + + + +
      {_"gender"}: {$x->isFemale() ? "женский" : "мужской"}
      {_"relationship"}:{var $marialStatus = $x->getMaritalStatus()}{_"relationship_$marialStatus"}
      {_"registration_date"}: {$x->getRegistrationTime()}
      + Описание: + + {$x->getDescription() ?? "(отсутствует)"} +
      +{/block} diff --git a/Web/Presenters/templates/Support/AnswerTicket.xml b/Web/Presenters/templates/Support/AnswerTicket.xml new file mode 100644 index 000000000..5c2a2a54b --- /dev/null +++ b/Web/Presenters/templates/Support/AnswerTicket.xml @@ -0,0 +1,85 @@ +{extends "../@layout.xml"} +{block title}Помощь{/block} + +{block header} + {$ticket->getName()} +{/block} + +{block content} +
      +
      + {$ticket->getContext()} +

      +
      +
      + {$ticket->getTime()} |  + Удалить +

      +
      +
      + +
      + +
      + +
      + + +
      +
      +
      +

      Комментарии отсутствуют

      + + + + {if $comment->getUType() === 0} + + {else} + + {/if} + + + +
      + + + + + {if $comment->getUType() === 0} + + {elseif ($comment->getUType() === 1)} + + {/if} +
      +
      + {$comment->getContext()|noescape} +
      + {if $comment->getUType() === 0} +
      + Удалить +
      + {/if} +
      +
      +{/block} diff --git a/Web/Presenters/templates/Support/Delete.xml b/Web/Presenters/templates/Support/Delete.xml new file mode 100644 index 000000000..e69de29bb diff --git a/Web/Presenters/templates/Support/Index.xml b/Web/Presenters/templates/Support/Index.xml new file mode 100644 index 000000000..a8247cd16 --- /dev/null +++ b/Web/Presenters/templates/Support/Index.xml @@ -0,0 +1,68 @@ +{extends "../@layout.xml"} +{block title}Помощь{/block} + +{block header} + Помощь +{/block} + +{block content} + +{var isMain = $mode === 'faq'} +{var isNew = $mode === 'new'} +{var isList = $mode === 'list'} + +{if $thisUser} + +
      + +{if $isNew} +
      +
      +

      +

      + +

      +
      +
      +{/if}{/if} + +{if $isMain} +

      Часто задаваемые вопросы


      +
      +
      Для кого этот сайт?
      +
      Сайт предназначен для поиска друзей и знакомых, а также просмотр данных пользователя. Это как справочник города, с помощью которого люди могут быстро найти актуальную информацию о человеке. Также этот сайт подойдёт для ностальгираторов и тех, кто решил слезть с трубы "ВКонтакте", которого клон и является.
      +
      +{/if} + +{if $isList} + + + + + + + +
      +
      Заметка
      +
      + +
      + Статус: {$ticket->getStatus()} +
      +
      +{/if} +{/block} diff --git a/Web/Presenters/templates/Support/List.xml b/Web/Presenters/templates/Support/List.xml new file mode 100644 index 000000000..27672ccbe --- /dev/null +++ b/Web/Presenters/templates/Support/List.xml @@ -0,0 +1,44 @@ +{extends "../@listView.xml"} + +{block title} + Helpdesk +{/block} + +{block header} + Helpdesk » Тикеты +{/block} + +{block tabs} + + + +{/block} + +{* BEGIN ELEMENTS DESCRIPTION *} + +{block link|strip|stripHtml} + /support/reply/{$x->getId()} +{/block} + +{block preview} +
      + Тикет +
      +{/block} + +{block name} + {$x->getName()} +{/block} + +{block description} + {var author = $x->getUser()} + + {ovk_proc_strtr($x->getContext(), 50)}
      + Автор: {$author->getCanonicalName()} +{/block} diff --git a/Web/Presenters/templates/Support/View.xml b/Web/Presenters/templates/Support/View.xml new file mode 100644 index 000000000..e49876d19 --- /dev/null +++ b/Web/Presenters/templates/Support/View.xml @@ -0,0 +1,85 @@ +{extends "../@layout.xml"} +{block title}Помощь{/block} + +{block header} + {$ticket->getName()} +{/block} + +{block content} +{if $ticket->isDeleted() == 0 } +
      +
      + {$ticket->getContext()} +

      +
      +
      + {$ticket->getTime()} |  + Удалить +
      +{if $ticket->getType() !== 2} +
      +
      +
      + +
      + +
      + +
      + +
      +
      +{/if} +
      +

      Комментарии отсутствуют

      + + + + {if $comment->getUType() === 0} + + {else} + + {/if} + + + +
      + + + + + {if $comment->getUType() === 0} + + {elseif ($comment->getUType() === 1)} + + {/if} +
      +
      + {$comment->getContext()|noescape} +
      + {if $comment->getUType() === 0} +
      + Удалить +
      + {/if} +
      +
      +{/if} +{/block} diff --git a/Web/Presenters/templates/User/Edit.xml b/Web/Presenters/templates/User/Edit.xml new file mode 100644 index 000000000..46f85ca28 --- /dev/null +++ b/Web/Presenters/templates/User/Edit.xml @@ -0,0 +1,300 @@ +{extends "../@layout.xml"} +{block title}{_"edit_page"}{/block} + +{block header} + {_"edit_page"} +{/block} + +{block content} + +{var isMain = $mode === 'main'} +{var isContacts = $mode === 'contacts'} +{var isInterests = $mode === 'interests'} +{var isAvatar = $mode === 'avatar'} +
      + Подтверждение номера телефона
      + Введите код для подтверждения смены номера: ввести код. +
      + + + +
      + {if $isMain} + +

      {_"main_information"}

      +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      + {_"name"}: + + +
      + {_"surname"}: + + +
      + {_"nickname"}: + + +
      + Телефон: + + +
      + {_"status"}: + + +
      + {_"relationship"}: + + +
      + {_"politViews"}: + + +
      + {_"gender"}: + + +
      + + + + +
      +
      + + {elseif $isContacts} + +

      {_"contact_information"}

      +
      + + + + + + + + + + + + + + + + + + + + + + + +
      + {_"email"}: + + +
      + {_"telegram"}: + + +
      + {_"city"}: + + +
      + {_"address"}: + + +
      + + + + +
      +
      + + {elseif $isInterests} + +

      {_"personal_information"}

      +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      + {_"interests"}: + + +
      + {_"favorite_music"}: + + +
      + {_"favorite_films"}: + + +
      + {_"favorite_shows"}: + + +
      + {_"favorite_books"}: + + +
      + {_"favorite_quotes"}: + + +
      + {_"information_about"}: + + +
      + + + + +
      +
      + + {elseif $isAvatar} + +

      {_"profile_picture"}

      +
      + + + + + + + + + + + +
      + {_"picture"}: + + +
      + + + + +
      +
      + + {/if} +
      + +{/block} diff --git a/Web/Presenters/templates/User/Friends.xml b/Web/Presenters/templates/User/Friends.xml new file mode 100644 index 000000000..11f7a576c --- /dev/null +++ b/Web/Presenters/templates/User/Friends.xml @@ -0,0 +1,81 @@ +{extends "../@listView.xml"} +{var perPage = 6} {* Why 6? Check User::_abstractRelationGenerator *} + +{var act = $_GET["act"] ?? "friends"} + +{if $act == "incoming"} + {var iterator = iterator_to_array($user->getFollowers($page))} + {var count = $user->getFollowersCount()} +{elseif $act == "outcoming"} + {var iterator = iterator_to_array($user->getSubscriptions($page))} + {var count = $user->getSubscriptionsCount()} +{else} + {var iterator = iterator_to_array($user->getFriends($page))} + {var count = $user->getFriendsCount()} +{/if} + +{block title} + {if $act == "incoming"} + {_"incoming_req"} + {elseif $act == "outcoming"} + {_"outcoming_req"} + {else} + {_"friends"} + {/if} +{/block} + +{block header} + {$user->getCanonicalName()} » + {if $act == "incoming"} + {_"incoming_req"} + {elseif $act == "outcoming"} + {_"outcoming_req"} + {else} + {_"friends"} + {/if} +{/block} + +{block tabs} + + + +{/block} + +{* BEGIN ELEMENTS DESCRIPTION *} + +{block link|strip|stripHtml} + {$x->getURL()} +{/block} + +{block preview} + Фотография группы +{/block} + +{block name} + {$x->getCanonicalName()} +{/block} + +{block description} + + + + + + + + + + + + + + + +
      {_"gender"}: {$x->isFemale() ? "женский" : "мужской"}
      {_"relationship"}:{var $marialStatus = $x->getMaritalStatus()}{_"relationship_$marialStatus"}
      {_"registration_date"}: {$x->getRegistrationTime()}
      +{/block} diff --git a/Web/Presenters/templates/User/Groups.xml b/Web/Presenters/templates/User/Groups.xml new file mode 100644 index 000000000..171eddd69 --- /dev/null +++ b/Web/Presenters/templates/User/Groups.xml @@ -0,0 +1,43 @@ +{extends "../@listView.xml"} +{var iterator = $user->getClubs($page)} +{var count = $user->getClubCount()} + +{block title}Группы{/block} + +{block header} + {$user->getCanonicalName()} » {_"groups"} + + +{/block} + +{block actions} + +{/block} + +{* BEGIN ELEMENTS DESCRIPTION *} + +{block link|strip|stripHtml} + {$x->getURL()} +{/block} + +{block preview} + Фотография группы +{/block} + +{block name} + {$x->getName()} +{/block} + +{block description} + {$x->getDescription()} +{/block} diff --git a/Web/Presenters/templates/User/Settings.xml b/Web/Presenters/templates/User/Settings.xml new file mode 100644 index 000000000..571ed67bd --- /dev/null +++ b/Web/Presenters/templates/User/Settings.xml @@ -0,0 +1,411 @@ +{extends "../@layout.xml"} +{block title}{_"my_settings"}{/block} + +{block header} + {_"my_settings"} +{/block} + +{block content} + +{var isMain = $mode === 'main'} +{var isPrivacy = $mode === 'privacy'} +{var isInterface = $mode === 'interface'} + +
      +
      + {_"main"} +
      + + +
      + +
      + {if $isMain} + +
      +

      {_"change_password"}

      + + + + + + + + + + + + + + + + + + + +
      + {_"old_password"} + + +
      + {_"new_password"} + + +
      + {_"repeat_password"} + + +
      + + + + +
      +
      +

      {_your_email_address}

      + + + + + + + +
      + {_current_email_address} + + {$user->getEmail()} +
      +
      +

      {_your_page_address}

      + + + + + + + + + + + + + + + +
      + {_page_id} + + {$user->getId()} +
      + {_page_address} + + +
      + + + + +
      +
      + +
      + {_you_can_also} {_delete_your_page}. +
      + + {elseif $isPrivacy} + +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      + {_privacy_setting_access_page} + + +
      + {_privacy_setting_read_info} + + +
      + {_privacy_setting_see_groups} + + +
      + {_privacy_setting_see_photos} + + +
      + {_privacy_setting_see_videos} + + +
      + {_privacy_setting_see_notes} + + +
      + {_privacy_setting_see_friends} + + +
      + {_privacy_setting_add_to_friends} + + +
      + {_privacy_setting_write_wall} + + +
      + + + + +
      +
      + + {elseif $isInterface} + +

      {_ui_settings_interface}

      +
      + + + + + + + + + + + + + + + + + + + +
      + {_"avatars_style"} + + +
      + {_"style"} + + +
      + {_ui_settings_rating} + + +
      + + + + +
      +
      + +

      {_ui_settings_sidebar}

      +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      + + + {_my_photos} +
      + + + {_my_videos} +
      + + + {_my_messages} +
      + + + {_my_notes} +
      + + + {_my_groups} +
      + + + {_my_feed} +
      + + + + +
      +
      + + {/if} +
      + +{/block} diff --git a/Web/Presenters/templates/User/VerifyPhone.xml b/Web/Presenters/templates/User/VerifyPhone.xml new file mode 100644 index 000000000..d65b6bb41 --- /dev/null +++ b/Web/Presenters/templates/User/VerifyPhone.xml @@ -0,0 +1,17 @@ +{extends "../@layout.xml"} +{block title}Подтвердить номер телефона{/block} + +{block header} + Подтвердить номер телефона +{/block} + +{block content} +
      +

      Мы отправили SMS с кодом на номер {substr_replace($change->number, "*****", 5, 5)}, введите его сюда:

      + +
      + + +
      +
      +{/block} diff --git a/Web/Presenters/templates/User/View.xml b/Web/Presenters/templates/User/View.xml new file mode 100644 index 000000000..fa723e499 --- /dev/null +++ b/Web/Presenters/templates/User/View.xml @@ -0,0 +1,448 @@ +{extends "../@layout.xml"} + +{block title}{$user->getCanonicalName()}{/block} + +{block headIncludes} + + + + + + + + + + + + +{/block} + +{block header} + {$user->getCanonicalName()} + + ({_"this_is_you"}) + + +
      + {if $diff->i <= 5} + {_online} + {else} + {_was_online} {$user->getOnline()} + {/if} +
      +{/block} + +{block content} + {if !$user->isBanned()} + + {if !$user->getPrivacyPermission('page.read', $thisUser ?? NULL)} +
      + Ошибка доступа
      + Настройки приватности этого пользователя не разрешают вам смотреть на его страницу. +
      + {else} + +
      +
      + + Фотография пользователя {$user->getCanonicalName()} + +
      + +
      + {var completeness = $user->getProfileCompletenessReport()} + +
      +
      + {$completeness->total}% +
      + + {if !is_null($thisUser) && $user->getId() === $thisUser->getId() && sizeof($completeness->unfilled) > 0} +
      + + Указать интересы + Указать {_interests} (+20%) + + + Указать email + Указать Email (+20%) + + + Указать номер телефона + Указать {_phone} (+20%) + + + Указать Telegram + Указать Telegram (+10%) + + + Написать статус + Написать {_status} (+10%) + + {/if} +
      +
      +
      + {var friendCount = $user->getFriendsCount()} + +
      + {_"friends"} +
      +
      +
      + {tr("friends", $friendCount)} + +
      + +
      +
      +
      + {var followersCount = $user->getFollowersCount()} + +
      + {_followers} +
      +
      +
      + {tr("followers", $followersCount)} + +
      + +
      +
      +
      +
      + {_"albums"} +
      +
      +
      + {tr("albums", $albumsCount)} + +
      +
      +
      +
      + {var cover = $album->getCoverPhoto()} + + +
      +
      + {$album->getName()}
      + Обновлён {$album->getEditTime() ?? $album->getPublicationTime()} +
      +
      +
      +
      +
      +
      +
      + {_videos} +
      +
      +
      + {tr("videos", $videosCount)} + +
      +
      +
      +
      + +
      + +
      +
      +
      +
      +
      +
      + {_notes} +
      +
      +
      + {tr("notes", $notesCount)} + +
      +
      +
      +
      + +
      +
      + {$note->getName()}
      + {$note->getPreview(35)} +
      +
      +
      +
      +
      +
      + {var clubsCount = $user->getClubCount()} +
      + {_"groups"} +
      +
      +
      + {tr("groups", $clubsCount)} + +
      +
      +
      + {$club->getName()} {if !$iterator->last}•{/if} +
      +
      +
      +
      +
      + {var meetingCount = $user->getMeetingCount()} +
      + {_meetings} +
      +
      +
      + {tr("meetings", $meetingCount)} + +
      +
      +
      + {$meeting->getName()} {if !$iterator->last}•{/if} +
      +
      +
      +
      + +
      + +
      +
      +

      + {$user->getFullName()} + + {if !is_null($user->getStatus())} +
      {$user->getStatus()}
      + {elseif !is_null($thisUser) && $user->getId() == $thisUser->getId()} + + {/if} +

      + + + + + + + + + + + + + + + + + + + + + + + +
      {_"gender"}: {$user->isFemale() ? tr("female") : tr("male")}
      {_"relationship"}:{var $marialStatus = $user->getMaritalStatus()}{_"relationship_$marialStatus"}
      {_"registration_date"}: {$user->getRegistrationTime()}
      {_"hometown"}:{$user->getHometown()}
      {_"politViews"}:{var $pviews = $user->getPoliticalViews()}{_"politViews_$pviews"}
      +
      +
      +
      + {_"information"} +
      + + {capture $contactInfo_Tmp} + + + + + + + + + + + + + + + + + + + + + +
      {_"email"}: + + {$user->getContactEmail()} + +
      {_"telegram"}: + + @{$user->getTelegram()} + +
      {_"city"}:{$user->getCity()}
      {_"address"}:{$user->getPhysicalAddress()}
      + {/capture} + {capture $uInfo_Tmp} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      {_"interests"}: {$user->getInterests()}
      {_"favorite_music"}: {$user->getFavoriteMusic()}
      {_"favorite_films"}: {$user->getFavoriteFilms()}
      {_"favorite_shows"}: {$user->getFavoriteShows()}
      {_"favorite_books"}: {$user->getFavoriteBooks()}
      {_"favorite_quotes"}: {$user->getFavoriteQuote()}
      О себе: {$user->getDescription()}
      + {/capture} + +
      + {if !empty($contactInfo_Tmp)} +

      {_"contact_information"} {ifset $thisUser}{if $thisUser->getId() == $user->getId()}[ {_"edit"} ]{/if}{/ifset}

      + {if !empty($contactInfo_Tmp)} + {$contactInfo_Tmp|noescape} + {else} +
      Информация отсутствует.
      + {/if} +
      + {/if} +

      {_"personal_information"} {ifset $thisUser}{if $thisUser->getId() == $user->getId()}[ {_"edit"} ]{/if}{/ifset}

      + {if !empty($uInfo_Tmp)} + {$uInfo_Tmp|noescape} + {else} +
      Информация отсутствует.
      + {/if} +
      + +

      + Пользователь предпочёл оставить о себе только воздух тайны. +

      +
      + + {presenter "openvk!Wall->wall", $user->getId()} +
      + + {/if} + + {else} {* isBanned() *} + {include "banned.xml"} + {/if} +{/block} diff --git a/Web/Presenters/templates/User/banned.xml b/Web/Presenters/templates/User/banned.xml new file mode 100644 index 000000000..e5e2824bd --- /dev/null +++ b/Web/Presenters/templates/User/banned.xml @@ -0,0 +1,7 @@ +
      + Пользователь заблокирован. +

      + К сожалению, нам пришлось заблокировать страницу пользователя {$user->getFirstName()}.
      + Комментарий модератора: {$user->getBanReason()}. +

      +
      diff --git a/Web/Presenters/templates/Videos/Edit.xml b/Web/Presenters/templates/Videos/Edit.xml new file mode 100644 index 000000000..a0fdc6fa7 --- /dev/null +++ b/Web/Presenters/templates/Videos/Edit.xml @@ -0,0 +1,36 @@ +{extends "../@layout.xml"} +{block title}Изменить видеозапись{/block} + +{block header} + {$thisUser->getCanonicalName()} + » + Видеозапись +{/block} + +{block content} +
      + + + + + + + + + + + + + + + +
      {_"name"}: + +
      {_"description"}: + +
      + + +
      +
      +{/block} diff --git a/Web/Presenters/templates/Videos/List.xml b/Web/Presenters/templates/Videos/List.xml new file mode 100644 index 000000000..0f04b482b --- /dev/null +++ b/Web/Presenters/templates/Videos/List.xml @@ -0,0 +1,41 @@ +{extends "../@listView.xml"} +{var iterator = $videos} +{var count = $paginatorConf->count} +{var page = $paginatorConf->page} + +{block title}{_"videos"} {$user->getCanonicalName()}{/block} + +{block header} + {$user->getCanonicalName()} + » {_"videos"} + + +{/block} + +{block actions} + +{/block} + +{* BEGIN ELEMENTS DESCRIPTION *} + +{block link|strip|stripHtml} + /video{$x->getPrettyId()} +{/block} + +{block preview} + {$x->getName()} +{/block} + +{block name} + {$x->getName()} +{/block} + +{block description} + {$x->getDescription() ?? ""}
      + {_"video_uploaded"} {$x->getPublicationTime()}
      + {_"video_updated"} {$x->getEditTime() ?? $x->getPublicationTime()} +{/block} diff --git a/Web/Presenters/templates/Videos/Upload.xml b/Web/Presenters/templates/Videos/Upload.xml new file mode 100644 index 000000000..e4e9f9eb2 --- /dev/null +++ b/Web/Presenters/templates/Videos/Upload.xml @@ -0,0 +1,42 @@ +{extends "../@layout.xml"} +{block title}Загрузить видео{/block} + +{block header} + {$thisUser->getCanonicalName()} + » + {_"videos"} + » + {_"upload_video"} +{/block} + +{block content} +
      + + + + + + + + + + + + + + + + + + + + + + + +
      {_"name"}:
      {_"description"}:
      {_"video"}:
      {_"video_link_to_yt"}:
      + + +
      +
      +{/block} diff --git a/Web/Presenters/templates/Videos/View.xml b/Web/Presenters/templates/Videos/View.xml new file mode 100644 index 000000000..01b03d84f --- /dev/null +++ b/Web/Presenters/templates/Videos/View.xml @@ -0,0 +1,59 @@ +{extends "../@layout.xml"} + +{block title}Видеозапись{/block} + +{block header} + {$user->getCanonicalName()} + » + {_"videos"} + » + {_"video"} +{/block} + +{block content} +
      + {if $video->getType() === 0} + + {else} + {var driver = $video->getVideoDriver()} + {if !$driver} + Эта видеозапись не поддерживается в вашей версии OpenVK. + {else} + {$driver->getEmbed()|noescape} + {/if} + {/if} +
      + +
      + +
      +
      + {include "../components/comments.xml", + comments => $comments, + count => $cCount, + page => $cPage, + model => "videos", + parent => $video} +
      +
      +
      +

      {_"information"}

      + {_"video_name"}: + {$video->getName()}
      + {_"video_description"}: + {$video->getDescription() ?? "(отсутствует)"}
      + {_"video_uploaded_by"}: + {$user->getFullName()}
      + {_"video_upload_date"}: + {$video->getPublicationTime()} +
      +
      +
      +

      {_"actions"}

      + + {_"edit"} + +
      +
      +
      +{/block} diff --git a/Web/Presenters/templates/Wall/Feed.xml b/Web/Presenters/templates/Wall/Feed.xml new file mode 100644 index 000000000..b0f097c3b --- /dev/null +++ b/Web/Presenters/templates/Wall/Feed.xml @@ -0,0 +1,59 @@ +{extends "../@layout.xml"} +{block title}{_"feed"}{/block} + +{block header} + {_"feed"} +{/block} + +{block content} +
      +
      + {_"publish_post"} +
      + +
      +
      + +
      + {foreach $posts as $post} + + + {include "../components/post.xml", post => $post, onWallOf => true} + {/foreach} + {include "../components/paginator.xml", conf => $paginatorConf} +
      + Количество постов на странице: + + +
      +{/block} diff --git a/Web/Presenters/templates/Wall/HashtagFeed.xml b/Web/Presenters/templates/Wall/HashtagFeed.xml new file mode 100644 index 000000000..12320c432 --- /dev/null +++ b/Web/Presenters/templates/Wall/HashtagFeed.xml @@ -0,0 +1,19 @@ +{extends "../@layout.xml"} +{block title}#{$hashtag}{/block} + +{block header} + #{$hashtag} +{/block} + +{block content} +
      + {foreach $posts as $post} + + + {include "../components/post.xml", post => $post, onWallOf => true} + + {php $paginatorConf->count++} + {/foreach} + {include "../components/paginator.xml", conf => $paginatorConf} +
      +{/block} diff --git a/Web/Presenters/templates/Wall/Post.xml b/Web/Presenters/templates/Wall/Post.xml new file mode 100644 index 000000000..9341dd2d7 --- /dev/null +++ b/Web/Presenters/templates/Wall/Post.xml @@ -0,0 +1,26 @@ +{extends "../@layout.xml"} +{block title}{_"post"}{/block} + +{block header} + {_"post"} +{/block} + +{block content} + {include "../components/post.xml", post => $post, forceNoCommentsLink => TRUE, forceNoDeleteLink => TRUE} +
      +
      + {include "../components/comments.xml", + comments => $comments, + count => $cCount, + page => $cPage, + model => "posts", + parent => $post } +
      +
      +

      Действия

      + {if $post->getOwnerPost() == $thisUser->getId() || $post->getTargetWall() == $thisUser->getId()} + Удалить + {/if} + Пожаловаться +
      +{/block} diff --git a/Web/Presenters/templates/Wall/Wall.xml b/Web/Presenters/templates/Wall/Wall.xml new file mode 100644 index 000000000..410cf6ada --- /dev/null +++ b/Web/Presenters/templates/Wall/Wall.xml @@ -0,0 +1,68 @@ +
      +
      + {_"wall"} +
      +
      +
      + {tr("wall", $count)} + + +
      + +
      + {if sizeof($posts) > 0} + {foreach $posts as $post} + + + {include "../components/post.xml", post => $post} + {/foreach} + {include "../components/paginator.xml", conf => $paginatorConf} + {else} + Здесь никто ничего не написал... Пока. + {/if} +
      +
      +
      diff --git a/Web/Presenters/templates/components/attachment.xml b/Web/Presenters/templates/components/attachment.xml new file mode 100644 index 000000000..ca3e31e13 --- /dev/null +++ b/Web/Presenters/templates/components/attachment.xml @@ -0,0 +1,22 @@ +{if $attachment instanceof \openvk\Web\Models\Entities\Photo} + {if !$attachment->isDeleted()} + + {$attachment->getDescription()} + + {else} + + Фотография недоступна + + {/if} +{elseif $attachment instanceof \openvk\Web\Models\Entities\Post} + {php $GLOBALS["_nesAttGloCou"] = (isset($GLOBALS["_nesAttGloCou"]) ? $GLOBALS["_nesAttGloCou"] : 0) + 1} + {if $GLOBALS["_nesAttGloCou"] > 2} + Смотреть запись + {else} + {include "post.xml", post => $attachment, compact => true} + {/if} +{else} + Не удалось отобразить это вложение. Возможно, БД несовместима с этой версией OpenVK. +{/if} + +{php $GLOBALS["_nesAttGloCou"] = NULL} diff --git a/Web/Presenters/templates/components/comment.xml b/Web/Presenters/templates/components/comment.xml new file mode 100644 index 000000000..cff491a85 --- /dev/null +++ b/Web/Presenters/templates/components/comment.xml @@ -0,0 +1,42 @@ +{var author = $comment->getOwner()} + + + + + + + + + +
      + + + +
      +
      + {$comment->getText()|noescape} +
      +
      + {var canDelete = $comment->getOwner()->getId() == $thisUser->getId()} + {var canDelete = $canDelete || $comment->getTarget()->getOwner()->getId() == $thisUser->getId()} + {if $canDelete} + {_"delete"} + {/if} + + +
      +
      + +
      diff --git a/Web/Presenters/templates/components/comments.xml b/Web/Presenters/templates/components/comments.xml new file mode 100644 index 000000000..0ee9e1929 --- /dev/null +++ b/Web/Presenters/templates/components/comments.xml @@ -0,0 +1,30 @@ +

      {_"comments"} ({$count})

      + +
      +
      + +
      + + +
      +
      + +{if sizeof($comments) > 0} + {foreach $comments as $comment} + {include "comment.xml", comment => $comment} + {/foreach} + {include "paginator.xml", conf => (object) ["page" => $page, "count" => $count, "amount" => sizeof($comments), "perPage" => 10]} +{else} + + {_"comments_tip"} +{/if} diff --git a/Web/Presenters/templates/components/error.xml b/Web/Presenters/templates/components/error.xml new file mode 100644 index 000000000..288129a27 --- /dev/null +++ b/Web/Presenters/templates/components/error.xml @@ -0,0 +1,7 @@ +
      + Ошибка +

      {$title}

      +

      + {$description} +

      +
      diff --git a/Web/Presenters/templates/components/nothing.xml b/Web/Presenters/templates/components/nothing.xml new file mode 100644 index 000000000..c571300bf --- /dev/null +++ b/Web/Presenters/templates/components/nothing.xml @@ -0,0 +1 @@ +{include "error.xml", title => tr("no_data"), description => tr("no_data_description")} diff --git a/Web/Presenters/templates/components/notifications/0/_14_18_.xml b/Web/Presenters/templates/components/notifications/0/_14_18_.xml new file mode 100644 index 000000000..878935546 --- /dev/null +++ b/Web/Presenters/templates/components/notifications/0/_14_18_.xml @@ -0,0 +1,11 @@ +{var post = $notification->getModel(0)} +{var user = $notification->getModel(1)} + +{$user->getCanonicalName()} +{$notification->getDateTime()} {_nt_liked_yours} +{_nt_post_nominative} {_nt_from} {$post->getPublicationTime()}. + +getURL().'">'.$user->getCanonicalName().'', '', '', $post->getPublicationTime())} +?> diff --git a/Web/Presenters/templates/components/notifications/1/_14_18_.xml b/Web/Presenters/templates/components/notifications/1/_14_18_.xml new file mode 100644 index 000000000..959947bbe --- /dev/null +++ b/Web/Presenters/templates/components/notifications/1/_14_18_.xml @@ -0,0 +1,6 @@ +{var post = $notification->getModel(0)} +{var user = $notification->getModel(1)} + +{$user->getCanonicalName()} +{$notification->getDateTime()} {_nt_shared_yours} +{_nt_post_instrumental} {_nt_from} {$post->getPublicationTime()}. diff --git a/Web/Presenters/templates/components/notifications/2/@default.xml b/Web/Presenters/templates/components/notifications/2/@default.xml new file mode 100644 index 000000000..acbb30cb5 --- /dev/null +++ b/Web/Presenters/templates/components/notifications/2/@default.xml @@ -0,0 +1,4 @@ +{var user = $notification->getModel(1)} + +{$user->getCanonicalName()} +{$notification->getDateTime()} {_nt_commented_yours} {include under}: "{$notification->getData()}". diff --git a/Web/Presenters/templates/components/notifications/2/_10_18_.xml b/Web/Presenters/templates/components/notifications/2/_10_18_.xml new file mode 100644 index 000000000..1ee23c33f --- /dev/null +++ b/Web/Presenters/templates/components/notifications/2/_10_18_.xml @@ -0,0 +1,6 @@ +{extends "@default.xml"} +{var post = $notification->getModel(0)} + +{block under} + {_nt_yours_feminitive_adjective} {_nt_note_instrumental} +{/block} diff --git a/Web/Presenters/templates/components/notifications/2/_13_18_.xml b/Web/Presenters/templates/components/notifications/2/_13_18_.xml new file mode 100644 index 000000000..19e246ba4 --- /dev/null +++ b/Web/Presenters/templates/components/notifications/2/_13_18_.xml @@ -0,0 +1,6 @@ +{extends "@default.xml"} +{var post = $notification->getModel(0)} + +{block under} + {_nt_yours_feminitive_adjective} {_nt_photo_instrumental} +{/block} diff --git a/Web/Presenters/templates/components/notifications/2/_14_18_.xml b/Web/Presenters/templates/components/notifications/2/_14_18_.xml new file mode 100644 index 000000000..a340bbd7c --- /dev/null +++ b/Web/Presenters/templates/components/notifications/2/_14_18_.xml @@ -0,0 +1,6 @@ +{extends "@default.xml"} +{var post = $notification->getModel(0)} + +{block under} + {_nt_yours_adjective} {_nt_post_instrumental} {_nt_from} {$post->getPublicationTime()} +{/block} diff --git a/Web/Presenters/templates/components/notifications/2/_19_18_.xml b/Web/Presenters/templates/components/notifications/2/_19_18_.xml new file mode 100644 index 000000000..69770904f --- /dev/null +++ b/Web/Presenters/templates/components/notifications/2/_19_18_.xml @@ -0,0 +1,6 @@ +{extends "@default.xml"} +{var post = $notification->getModel(0)} + +{block under} + {_nt_yours_adjective} {_video} +{/block} diff --git a/Web/Presenters/templates/components/notifications/3/_14_18_.xml b/Web/Presenters/templates/components/notifications/3/_14_18_.xml new file mode 100644 index 000000000..1e6ec0a3b --- /dev/null +++ b/Web/Presenters/templates/components/notifications/3/_14_18_.xml @@ -0,0 +1,6 @@ +{var post = $notification->getModel(0)} +{var user = $notification->getModel(1)} + +{$user->getCanonicalName()} +{$notification->getDateTime()} {_nt_written_on_your_wall} +{_nt_post_nominative}: "{$notification->getData()}". diff --git a/Web/Presenters/templates/components/notifications/5/_5_18_.xml b/Web/Presenters/templates/components/notifications/5/_5_18_.xml new file mode 100644 index 000000000..905dbaf44 --- /dev/null +++ b/Web/Presenters/templates/components/notifications/5/_5_18_.xml @@ -0,0 +1,6 @@ +{var club = $notification->getModel(0)} +{var user = $notification->getModel(1)} + +{$user->getCanonicalName()} +{_nt_made_you_admin} +{$club->getCanonicalName()} {$notification->getDateTime()}. diff --git a/Web/Presenters/templates/components/paginator.xml b/Web/Presenters/templates/components/paginator.xml new file mode 100644 index 000000000..984f1678c --- /dev/null +++ b/Web/Presenters/templates/components/paginator.xml @@ -0,0 +1,14 @@ +
      +
      +
      + << Назад + + Страница {$conf->page} + + Вперёд >> +
      +
      diff --git a/Web/Presenters/templates/components/post.xml b/Web/Presenters/templates/components/post.xml new file mode 100644 index 000000000..53401be82 --- /dev/null +++ b/Web/Presenters/templates/components/post.xml @@ -0,0 +1,107 @@ +{var author = $post->getOwner()} + + + + + + + + +
      + + + +
      +
      + {$post->getText()|noescape} + +
      +
      + {include "attachment.xml", attachment => $attachment} +
      +
      +
      +
      +
      +  ! Этот пост был размещён за взятку. +
      +
      + {var acutalAuthor = $post->getOwner(false)} + + Автор: + + {$acutalAuthor->getCanonicalName()} + + +
      +
      +
      + {if ($post->getOwnerPost() == $thisUser->getId() || $post->getTargetWall() == $thisUser->getId()) && !($forceNoDeleteLink ?? false)} + {_"delete"} |  + {/if} + + {if !($forceNoCommentsLink ?? false)} + + {if $post->getCommentsCount() > 0} + {_"comments"} ({$post->getCommentsCount()}) + {else} + {_"comments"} + {/if} + +  |  + {/if} + + + {if $post->getRepostCount() > 0} + {_"share"} + ({$post->getRepostCount()}) + {else} + {_"share"} + {/if} + + +
      + {var liked = $post->hasLikeFrom($thisUser)} + + + + ❤ + + + {$post->getLikesCount()} + +
      +
      +
      diff --git a/Web/Presenters/templates/components/wall.xml b/Web/Presenters/templates/components/wall.xml new file mode 100644 index 000000000..248f63fad --- /dev/null +++ b/Web/Presenters/templates/components/wall.xml @@ -0,0 +1,30 @@ +
      +
      + {_"wall"} +
      +
      +
      + {l18n_number("wall", $count)} + +
      + +
      + {foreach $posts->page($__page, 10) as $post} + {include "post.xml", post => $post} + {/foreach} + + {include "paginator.xml", page => $__page, bag => $posts} +
      +
      +
      diff --git a/Web/Util/Bitmask.php b/Web/Util/Bitmask.php new file mode 100644 index 000000000..7651514a5 --- /dev/null +++ b/Web/Util/Bitmask.php @@ -0,0 +1,87 @@ +data = str_pad(decbin($data), 63, "0", STR_PAD_RIGHT); + $this->length = $length; + if((sizeof($mapping) - 1) > (64 / $length)) + throw new \OutOfRangeException("Mapping contains more keys than a bitmask can fit in itself."); + else + $this->mapping = $mapping; + } + + private function getOffsetByKey(string $key): int + { + $offset = array_search($key, $this->mapping); + if($offset === false) + throw new \OutOfBoundsException("Key '$key' is not present in bitmask."); + + return $offset; + } + + function toInteger(): int + { + return (int) bindec($this->data); + } + + function __toString(): string + { + return (string) $this->toInteger(); + } + + function getNumberByOffset(int $offset): float + { + $offset *= $this->length; + if($offset > (64 / $this->length)) + return (float) 'NaN'; + + return (float) bindec(substr($this->data, $offset, $this->length)); + } + + function getBoolByOffset(int $offset): ?bool + { + if($this->length !== 1) + return NULL; + + $number = $this->getNumberByOffset($offset); + return is_nan($number) ? NULL : (bool) $number; + } + + function setByOffset(int $offset, int $number): void + { + $offset *= $this->length; + if(($offset + $this->length) > 64) + throw new \OutOfRangeException("$offset is invalid offset. Bitmask length is 64 bits."); + + $this->data = substr_replace($this->data, str_pad(decbin($number), $this->length, "0", STR_PAD_LEFT), $offset, $this->length); + } + + function set($key, int $data): Bitmask + { + if(gettype($key) === "string") + $this->setByOffset($this->getOffsetByKey($key), $data); + else if(gettype($key) === "int") + $this->setByOffset($key, $data); + else + throw new TypeError("Key must be either offset (int) or a string index"); + + return $this; + } + + function get($key) + { + if(gettype($key) === "string") + $key = $this->getOffsetByKey($key); + else if(gettype($key) !== "int") + throw new TypeError("Key must be either offset (int) or a string index"); + + return $this->length === 1 ? $this->getBoolByOffset($key) : $this->getNumberByOffset($key); + } +} diff --git a/Web/Util/DateTime.php b/Web/Util/DateTime.php new file mode 100644 index 000000000..a8c1e3163 --- /dev/null +++ b/Web/Util/DateTime.php @@ -0,0 +1,86 @@ +timestamp = $timestamp ?? time(); + $this->localizator = Localizator::i(); + } + + protected function zmdate(): string + { + $then = date_create("@" . $this->timestamp); + $now = date_create(); + $diff = date_diff($now, $then); + if($diff->invert === 0) return __OPENVK_ERROR_CLOCK_IN_FUTURE; + + if($this->timestamp >= strtotime("midnight")) { # Today + if($diff->h >= 1) + return tr("time_today") . tr("time_at_sp") . strftime("%X", $this->timestamp); + else if($diff->i < 2) + return tr("time_just_now"); + else + return $diff->i === 5 ? tr("time_exactly_five_minutes_ago") : tr("time_minutes_ago", $diff->i); + } else if($this->timestamp >= strtotime("-1day midnight")) { # Yesterday + return tr("time_yesterday") . tr("time_at_sp") . strftime("%X", $this->timestamp); + } else if(strftime("%G", $this->timestamp) === strftime("%G")) { # In this year + return strftime("%e %h ", $this->timestamp) . tr("time_at_sp") . strftime(" %R %p", $this->timestamp); + } else { + return strftime("%e %B %G ", $this->timestamp) . tr("time_at_sp") . strftime(" %X", $this->timestamp); + } + } + + function format(string $format, bool $useDate = false): string + { + if(!$useDate) + return strftime($format, $this->timestamp); + else + return date($format, $this->timestamp); + } + + function relative(int $type = 0): string + { + switch($type) { + case static::RELATIVE_FORMAT_NORMAL: + return mb_convert_case($this->zmdate(), MB_CASE_TITLE_SIMPLE); + break; + case static::RELATIVE_FORMAT_LOWER: + return $this->zmdate(); + break; + case static::RELATIVE_FORMAT_SHORT: + return ""; + break; + } + } + + function html(bool $capitalize = false, bool $short = false): string + { + if($short) + $dt = $this->relative(static::RELATIVE_FORMAT_SHORT); + else if($capitalize) + $dt = $this->relative(static::RELATIVE_FORMAT_NORMAL); + else + $dt = $this->relative(static::RELATIVE_FORMAT_LOWER); + + return ""; + } + + function timestamp(): int + { + return $this->timestamp; + } + + function __toString(): string + { + return $this->relative(static::RELATIVE_FORMAT_LOWER); + } +} diff --git a/Web/Util/Localizator.php b/Web/Util/Localizator.php new file mode 100644 index 000000000..eae656740 --- /dev/null +++ b/Web/Util/Localizator.php @@ -0,0 +1,64 @@ +$%Xm", $string, $matches); + for($i = 0; $i < sizeof($matches[1]); $i++) { + $directive = $matches[1][$i]; + if($directive === "include") { + $includes[] = dirname(__FILE__) . "/../../locales/" . $matches[2][$i] . ".strings"; + } else { + trigger_error("Unknown preprocessor directive \"$directive\" in locale file, skipping. + This will throw an error in a future version of Localizator::_getIncludes.", E_USER_DEPRECATED); + } + } + + return $includes; + } + + protected function parse($file): array + { + $hash = sha1($file); + if(isset($GLOBALS["localizationCache_$hash"])) return $GLOBALS["localizationCache_$hash"]; + + $string = file_get_contents($file); + $string = preg_replace("%^\%{.*\%}$%m", "", $string); #Remove comments + $array = []; + + foreach(preg_split("%;[\\r\\n]++%", $string) as $statement) { + $s = explode(" = ", trim($statement)); + + try { + $array[eval("return $s[0];")] = eval("return $s[1];"); + } catch(\ParseError $ex) { + throw new \ParseError($ex->getMessage(). " near " . $s[0]); + } + } + + foreach(self::_getIncludes($string) as $include) + $array = array_merge(@self::parse($include), $array); + + $GLOBALS["localizationCache_$hash"] = $array; + return $array; + } + + function _($id, $lang = NULL): string + { + $lang = is_null($lang) ? static::DEFAULT_LANG : $lang; + $array = @self::parse(dirname(__FILE__) . "/../../locales/$lang.strings"); + + return $array[$id] ?? "@$id"; + } + + use TSimpleSingleton; +} diff --git a/Web/Util/Shell/Exceptions/ShellUnavailableException.php b/Web/Util/Shell/Exceptions/ShellUnavailableException.php new file mode 100644 index 000000000..4869ab372 --- /dev/null +++ b/Web/Util/Shell/Exceptions/ShellUnavailableException.php @@ -0,0 +1,10 @@ +command = $cmd; + } + + function execute(): string + { + return shell_exec($this->command); + } + + function start(): string + { + shell_exec("nohup " . $this->command . " > /dev/null 2>/dev/null &"); + + return $this->command; + } + }; + } +} diff --git a/Web/Util/Sms.php b/Web/Util/Sms.php new file mode 100644 index 000000000..dffea4069 --- /dev/null +++ b/Web/Util/Sms.php @@ -0,0 +1,21 @@ +enable) return false; + + try { + $api = new ZApi($conf->key, $conf->secret, false); + $res = $api->sendSms($to, $message, $conf->callerId); + } catch(ZException $e) { + return false; + } + + return true; + } +} diff --git a/Web/Util/debug.js b/Web/Util/debug.js new file mode 100644 index 000000000..3a106a532 --- /dev/null +++ b/Web/Util/debug.js @@ -0,0 +1 @@ +{"home":"\u0413\u043b\u0430\u0432\u043d\u0430\u044f","log_in":"\u0412\u0445\u043e\u0434","welcome":"\u0414\u043e\u0431\u0440\u043e \u043f\u043e\u0436\u0430\u043b\u043e\u0432\u0430\u0442\u044c","password":"\u041f\u0430\u0440\u043e\u043b\u044c","registration":"\u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f","select_language":"\u0412\u044b\u0431\u0440\u0430\u0442\u044c \u044f\u0437\u044b\u043a","edit":"\u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c","birth_date":"\u0414\u0435\u043d\u044c \u0440\u043e\u0436\u0434\u0435\u043d\u0438\u044f","registration_date":"\u0414\u0430\u0442\u0430 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438","hometown":"\u0420\u043e\u0434\u043d\u043e\u0439 \u0433\u043e\u0440\u043e\u0434","this_is_you":"\u044d\u0442\u043e \u0412\u044b","post_writes_m":"\u043d\u0430\u043f\u0438\u0441\u0430\u043b","post_writes_f":"\u043d\u0430\u043f\u0438\u0441\u0430\u043b\u0430","wall":"\u0421\u0442\u0435\u043d\u0430","post":"\u0417\u0430\u043f\u0438\u0441\u044c","write":"\u041d\u0430\u043f\u0438\u0441\u0430\u0442\u044c","publish":"\u041e\u043f\u0443\u0431\u043b\u0438\u043a\u043e\u0432\u0430\u0442\u044c","edit_page":"\u0420\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443","edit_group":"\u0420\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0433\u0440\u0443\u043f\u043f\u0443","change_status":"\u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u0441\u0442\u0430\u0442\u0443\u0441","name":"\u0418\u043c\u044f","surname":"\u0424\u0430\u043c\u0438\u043b\u0438\u044f","gender":"\u041f\u043e\u043b","male":"\u043c\u0443\u0436\u0441\u043a\u043e\u0439","female":"\u0436\u0435\u043d\u0441\u043a\u0438\u0439","description":"\u041e\u043f\u0438\u0441\u0430\u043d\u0438\u0435","save":"\u0421\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c","main_information":"\u041e\u0441\u043d\u043e\u0432\u043d\u0430\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f","friends":"\u0414\u0440\u0443\u0437\u044c\u044f","friends_add":"\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0432 \u0434\u0440\u0443\u0437\u044c\u044f","friends_delete":"\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u0438\u0437 \u0434\u0440\u0443\u0437\u0435\u0439","friends_reject":"\u041e\u0442\u043c\u0435\u043d\u0438\u0442\u044c \u0437\u0430\u044f\u0432\u043a\u0443","friends_accept":"\u041f\u0440\u0438\u043d\u044f\u0442\u044c \u0437\u0430\u044f\u0432\u043a\u0443","send_message":"\u041e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435","nickname":"\u041d\u0438\u043a\u043d\u0435\u0439\u043c","online":"\u041e\u043d\u043b\u0430\u0439\u043d","was_online_m":"\u0431\u044b\u043b \u0432 \u0441\u0435\u0442\u0438","was_online_f":"\u0431\u044b\u043b\u0430 \u0432 \u0441\u0435\u0442\u0438","delete":"\u0423\u0434\u0430\u043b\u0438\u0442\u044c","comments":"\u041a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0438","share":"\u041f\u043e\u0434\u0435\u043b\u0438\u0442\u044c\u0441\u044f","comments_tip":"\u0411\u0443\u0434\u044c\u0442\u0435 \u043f\u0435\u0440\u0432\u044b\u043c, \u043a\u0442\u043e \u043e\u0441\u0442\u0430\u0432\u0438\u0442 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0439!","incoming_req":"\u041f\u043e\u0434\u043f\u0438\u0441\u0447\u0438\u043a\u0438","outcoming_req":"\u0417\u0430\u044f\u0432\u043a\u0438","req":"\u0417\u0430\u044f\u0432\u043a\u0438","you_have":"\u0423 \u0432\u0430\u0441","all_title":"\u0412\u0441\u0435","your_comment":"\u0412\u0430\u0448 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0439","name_group":"\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435","subscribe":"\u041f\u043e\u0434\u043f\u0438\u0441\u0430\u0442\u044c\u0441\u044f","unsubscribe":"\u041e\u0442\u043f\u0438\u0441\u0430\u0442\u044c\u0441\u044f","join_community":"\u0412\u0441\u0442\u0443\u043f\u0438\u0442\u044c \u0432 \u0433\u0440\u0443\u043f\u043f\u0443","leave_community":"\u0412\u044b\u0439\u0442\u0438 \u0438\u0437 \u0433\u0440\u0443\u043f\u043f\u044b","min_6_community":"\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0434\u043e\u043b\u0436\u043d\u043e \u0431\u044b\u0442\u044c \u043d\u0435 \u043c\u0435\u043d\u0435\u0435 1 \u0441\u0438\u043c\u0432\u043e\u043b\u0430","participants":"\u0423\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u0438","groups":"\u0413\u0440\u0443\u043f\u043f\u044b","create_group":"\u0421\u043e\u0437\u0434\u0430\u0442\u044c \u0433\u0440\u0443\u043f\u043f\u0443","group_managers":"\u0420\u0443\u043a\u043e\u0432\u043e\u0434\u0441\u0442\u0432\u043e","group_type":"\u0422\u0438\u043f \u0433\u0440\u0443\u043f\u043f\u044b","group_type_open":"\u042d\u0442\u043e \u043e\u0442\u043a\u0440\u044b\u0442\u0430\u044f \u0433\u0440\u0443\u043f\u043f\u0430. \u0412 \u043d\u0435\u0451 \u043c\u043e\u0436\u0435\u0442 \u0432\u0441\u0442\u0443\u043f\u0438\u0442\u044c \u043b\u044e\u0431\u043e\u0439 \u0436\u0435\u043b\u0430\u044e\u0449\u0438\u0439.","creator":"\u0421\u043e\u0437\u0434\u0430\u0442\u0435\u043b\u044c","create":"\u0421\u043e\u0437\u0434\u0430\u0442\u044c","albums":"\u0410\u043b\u044c\u0431\u043e\u043c\u044b","create_album":"\u0421\u043e\u0437\u0434\u0430\u0442\u044c \u0430\u043b\u044c\u0431\u043e\u043c","edit_album":"\u0420\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0430\u043b\u044c\u0431\u043e\u043c","creating_album":"\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0430\u043b\u044c\u0431\u043e\u043c\u0430","upload_photo":"\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u044e","photo":"\u0424\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u044f","notes":"\u0417\u0430\u043c\u0435\u0442\u043a\u0438","name_note":"\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435","text_note":"\u0421\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435","create_notes":"\u0421\u043e\u0437\u0434\u0430\u0442\u044c \u0437\u0430\u043c\u0435\u0442\u043a\u0443","acl_welcome":"\u0420\u0435\u0434\u0430\u043a\u0442\u043e\u0440 \u0433\u0440\u0443\u043f\u043f\u043e\u0432\u044b\u0445 \u043f\u043e\u043b\u0438\u0442\u0438\u043a","status":"\u0421\u0442\u0430\u0442\u0443\u0441","actions":"\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u044f","upload_button":"\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c","feed":"\u041d\u043e\u0432\u043e\u0441\u0442\u0438","publish_post":"\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0437\u0430\u043f\u0438\u0441\u044c","edit_button":"\u0440\u0435\u0434.","my_page":"\u041c\u043e\u044f \u0421\u0442\u0440\u0430\u043d\u0438\u0446\u0430","my_friends":"\u041c\u043e\u0438 \u0414\u0440\u0443\u0437\u044c\u044f","my_photos":"\u041c\u043e\u0438 \u0424\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0438","my_videos":"\u041c\u043e\u0438 \u0412\u0438\u0434\u0435\u043e\u0437\u0430\u043f\u0438\u0441\u0438","my_messages":"\u041c\u043e\u0438 \u0421\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f","my_notes":"\u041c\u043e\u0438 \u0417\u0430\u043c\u0435\u0442\u043a\u0438","my_groups":"\u041c\u043e\u0438 \u0413\u0440\u0443\u043f\u043f\u044b","my_feed":"\u041c\u043e\u0438 \u041d\u043e\u0432\u043e\u0441\u0442\u0438","my_settings":"\u041c\u043e\u0438 \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438","bug_tracker":"\u0411\u0430\u0433-\u0442\u0440\u0435\u043a\u0435\u0440","header_home":"\u0433\u043b\u0430\u0432\u043d\u0430\u044f","header_groups":"\u0433\u0440\u0443\u043f\u043f\u044b","header_donate":"\u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0430\u0442\u044c","header_people":"\u043b\u044e\u0434\u0438","header_invite":"\u043f\u0440\u0438\u0433\u043b\u0430\u0441\u0438\u0442\u044c","header_help":"\u043f\u043e\u043c\u043e\u0449\u044c","header_log_out":"\u0432\u044b\u0439\u0442\u0438","header_search":"\u041f\u043e\u0438\u0441\u043a","friends_one":"%1 \u0434\u0440\u0443\u0433","friends_few":"%1 \u0434\u0440\u0443\u0433","friends_many":"%1 \u0434\u0440\u0443\u0433\u0430","friends_other":"%1 \u0434\u0440\u0443\u0437\u0435\u0439","wall_one":"%1 \u0437\u0430\u043f\u0438\u0441\u044c","wall_few":"%1 \u0437\u0430\u043f\u0438\u0441\u044c","wall_many":"%1 \u0437\u0430\u043f\u0438\u0441\u0438","wall_other":"%1 \u0437\u0430\u043f\u0438\u0441\u0435\u0439","participants_one":"%1 \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a","participants_few":"%1 \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a","participants_many":"%1 \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u0430","participants_other":"%1 \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u043e\u0432","groups_one":"%1 \u0433\u0440\u0443\u043f\u043f\u0430","groups_few":"%1 \u0433\u0440\u0443\u043f\u043f\u0430","groups_many":"%1 \u0433\u0440\u0443\u043f\u043f\u044b","groups_other":"%1 \u0433\u0440\u0443\u043f\u043f","notes_one":"%1 \u0437\u0430\u043c\u0435\u0442\u043a\u0430","notes_few":"%1 \u0437\u0430\u043c\u0435\u0442\u043a\u0438","notes_many":"%1 \u0437\u0430\u043c\u0435\u0442\u043a\u0438","notes_other":"%1 \u0437\u0430\u043c\u0435\u0442\u043e\u043a","albums_one":"%1 \u0430\u043b\u044c\u0431\u043e\u043c","albums_few":"%1 \u0430\u043b\u044c\u0431\u043e\u043c\u0430","albums_many":"%1 \u0430\u043b\u044c\u0431\u043e\u043c\u0430","albums_other":"%1 \u0430\u043b\u044c\u0431\u043e\u043c\u043e\u0432","information":"\u0418\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f","relationship":"\u0421\u0435\u043c\u0435\u0439\u043d\u043e\u0435 \u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435","relationship_0":"\u041d\u0435 \u0432\u044b\u0431\u0440\u0430\u043d\u043e","relationship_1":"\u041d\u0435 \u0436\u0435\u043d\u0430\u0442","relationship_2":"\u0412\u0441\u0442\u0440\u0435\u0447\u0430\u044e\u0441\u044c","relationship_3":"\u041f\u043e\u043c\u043e\u043b\u0432\u043b\u0435\u043d","relationship_4":"\u0416\u0435\u043d\u0430\u0442","relationship_5":"\u0412 \u0433\u0440\u0430\u0436\u0434\u0430\u043d\u0441\u043a\u043e\u043c \u0431\u0440\u0430\u043a\u0435","relationship_6":"\u0412\u043b\u044e\u0431\u043b\u0435\u043d","relationship_7":"\u0412\u0441\u0451 \u0441\u043b\u043e\u0436\u043d\u043e","relationship_8":"\u0412 \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u043c \u043f\u043e\u0438\u0441\u043a\u0435","politViews":"\u041f\u043e\u043b\u0438\u0442. \u0432\u0437\u0433\u043b\u044f\u0434\u044b","politViews_0":"\u041d\u0435 \u0432\u044b\u0431\u0440\u0430\u043d\u044b","politViews_1":"\u0418\u043d\u0434\u0438\u0444\u0444\u0435\u0440\u0435\u043d\u0442\u043d\u044b\u0435","politViews_2":"\u041a\u043e\u043c\u043c\u0443\u043d\u0438\u0441\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0435","politViews_3":"\u0421\u043e\u0446\u0438\u0430\u043b\u0438\u0441\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0435","politViews_4":"\u0423\u043c\u0435\u0440\u0435\u043d\u043d\u044b\u0435","politViews_5":"\u041b\u0438\u0431\u0435\u0440\u0430\u043b\u044c\u043d\u044b\u0435","politViews_6":"\u041a\u043e\u043d\u0441\u0435\u0440\u0432\u0430\u0442\u0438\u0432\u043d\u044b\u0435","politViews_7":"\u041c\u043e\u043d\u0430\u0440\u0445\u0438\u0447\u0435\u0441\u043a\u0438\u0435","politViews_8":"\u0423\u043b\u044c\u0442\u0440\u0430\u043a\u043e\u043d\u0441\u0435\u0440\u0432\u0430\u0442\u0438\u0432\u043d\u044b\u0435","politViews_9":"\u041b\u0438\u0431\u0435\u0440\u0442\u0430\u0440\u0438\u0430\u043d\u0441\u043a\u0438\u0435","contact_information":"\u041a\u043e\u043d\u0442\u0430\u043a\u0442\u043d\u0430\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f","email":"\u042d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u0430\u044f \u043f\u043e\u0447\u0442\u0430","phone":"\u0422\u0435\u043b\u0435\u0444\u043e\u043d","telegram":"Telegram","city":"\u0413\u043e\u0440\u043e\u0434","address":"\u0410\u0434\u0440\u0435\u0441","personal_information":"\u041b\u0438\u0447\u043d\u0430\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f","interests":"\u0418\u043d\u0442\u0435\u0440\u0435\u0441\u044b","favorite_music":"\u041b\u044e\u0431\u0438\u043c\u0430\u044f \u043c\u0443\u0437\u044b\u043a\u0430","favorite_films":"\u041b\u044e\u0431\u0438\u043c\u044b\u0435 \u0444\u0438\u043b\u044c\u043c\u044b","favorite_shows":"\u041b\u044e\u0431\u0438\u043c\u044b\u0435 \u0422\u0412-\u0448\u043e\u0443","favorite_books":"\u041b\u044e\u0431\u0438\u043c\u044b\u0435 \u043a\u043d\u0438\u0433\u0438","favorite_quotes":"\u041b\u044e\u0431\u0438\u043c\u044b\u0435 \u0446\u0438\u0442\u0430\u0442\u044b","information_about":"\u041e \u0441\u0435\u0431\u0435","main":"\u041e\u0441\u043d\u043e\u0432\u043d\u043e\u0435","contacts":"\u041a\u043e\u043d\u0442\u0430\u043a\u0442\u044b","avatar":"\u0410\u0432\u0430\u0442\u0430\u0440","profile_picture":"\u0418\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b","picture":"\u0418\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435","sort_randomly":"\u0421\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0440\u0430\u043d\u0434\u043e\u043c\u043d\u043e","sort_up":"\u0421\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u043e \u0434\u0430\u0442\u0435 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0432\u0432\u0435\u0440\u0445","sort_down":"\u0421\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u043e \u0434\u0430\u0442\u0435 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0432\u043d\u0438\u0437","videos":"\u0412\u0438\u0434\u0435\u043e\u0437\u0430\u043f\u0438\u0441\u0438","video":"\u0412\u0438\u0434\u0435\u043e\u0437\u0430\u043f\u0438\u0441\u044c","upload_video":"\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u0432\u0438\u0434\u0435\u043e","video_uploaded":"\u0417\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043e","video_updated":"\u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u043e","video_link_to_yt":"\u0421\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 YT","video_name":"\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435","video_description":"\u041e\u043f\u0438\u0441\u0430\u043d\u0438\u0435","video_uploaded_by":"\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u043b","video_upload_date":"\u0414\u0430\u0442\u0430 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438","privacy":"\u041f\u0440\u0438\u0432\u0430\u0442\u043d\u043e\u0441\u0442\u044c","interface":"\u0412\u043d\u0435\u0448\u043d\u0438\u0439 \u0432\u0438\u0434","change_password":"\u0418\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u043f\u0430\u0440\u043e\u043b\u044c","old_password":"\u0421\u0442\u0430\u0440\u044b\u0439 \u043f\u0430\u0440\u043e\u043b\u044c","new_password":"\u041d\u043e\u0432\u044b\u0439 \u043f\u0430\u0440\u043e\u043b\u044c","repeat_password":"\u041f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435 \u043f\u0430\u0440\u043e\u043b\u044c","avatars_style":"\u041e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u0430\u0432\u0430\u0442\u0430\u0440","style":"\u0421\u0442\u0438\u043b\u044c","default":"\u041f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e","cut":"\u041e\u0431\u0440\u0435\u0437\u043a\u0430","round_avatars":"\u041a\u0440\u0443\u0433\u043b\u044b\u0439 \u0430\u0432\u0430\u0442\u0430\u0440","search_for_groups":"\u041f\u043e\u0438\u0441\u043a \u0433\u0440\u0443\u043f\u043f","search_for_people":"\u041f\u043e\u0438\u0441\u043a \u043b\u044e\u0434\u0435\u0439","search_button":"\u041d\u0430\u0439\u0442\u0438","date_format_1":"%1 %2 %3 \u0433.","date_format_2":"%1 %2 %3 \u0433. \u0432 %4","date_month_1":"\u044f\u043d\u0432\u0430\u0440\u044f","date_month_2":"\u0444\u0435\u0432\u0440\u0430\u043b\u044f","date_month_3":"\u043c\u0430\u0440\u0442\u0430","date_month_4":"\u0430\u043f\u0440\u0435\u043b\u044f","date_month_5":"\u043c\u0430\u044f","date_month_6":"\u0438\u044e\u043d\u044f","date_month_7":"\u0438\u044e\u043b\u044f","date_month_8":"\u0430\u0432\u0433\u0443\u0441\u0442\u0430","date_month_9":"\u0441\u0435\u043d\u0442\u044f\u0431\u0440\u044f","date_month_10":"\u043e\u043a\u0442\u044f\u0431\u0440\u044f","date_month_11":"\u043d\u043e\u044f\u0431\u0440\u044f","date_month_12":"\u0434\u0435\u043a\u0430\u0431\u0440\u044f","error_1":"\u041d\u0435\u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u044b\u0439 \u0437\u0430\u043f\u0440\u043e\u0441","error_2":"\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043b\u043e\u0433\u0438\u043d \u0438\u043b\u0438 \u043f\u0430\u0440\u043e\u043b\u044c","error_3":"\u041d\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d","error_4":"\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442","information_-1":"\u041e\u043f\u0435\u0440\u0430\u0446\u0438\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430 \u0443\u0441\u043f\u0435\u0448\u043d\u043e","information_-2":"\u0412\u0445\u043e\u0434 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d \u0443\u0441\u043f\u0435\u0448\u043d\u043e","interface_translation":"\u041f\u0435\u0440\u0435\u0432\u043e\u0434 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430","translations":"\u041f\u0435\u0440\u0435\u0432\u043e\u0434\u044b","no_translation":"\u041d\u0415 \u041f\u0415\u0420\u0415\u0412\u0415\u0414\u0415\u041d\u041e!!!","languages":"\u042f\u0437\u044b\u043a\u0438"} \ No newline at end of file diff --git a/Web/di.yml b/Web/di.yml new file mode 100644 index 000000000..d0fcb8962 --- /dev/null +++ b/Web/di.yml @@ -0,0 +1,35 @@ +services: + - openvk\Web\Presenters\AwayPresenter + - openvk\Web\Presenters\AboutPresenter + - openvk\Web\Presenters\AuthPresenter + - openvk\Web\Presenters\UserPresenter + - openvk\Web\Presenters\WallPresenter + - openvk\Web\Presenters\CommentPresenter + - openvk\Web\Presenters\PhotosPresenter + - openvk\Web\Presenters\VideosPresenter + - openvk\Web\Presenters\AudiosPresenter + - openvk\Web\Presenters\BlobPresenter + - openvk\Web\Presenters\GroupPresenter + - openvk\Web\Presenters\SearchPresenter + - openvk\Web\Presenters\ContentSearchPresenter + - openvk\Web\Presenters\InternalAPIPresenter + - openvk\Web\Presenters\NotesPresenter + - openvk\Web\Presenters\UnknownTextRouteStrategyPresenter + - openvk\Web\Presenters\NotificationPresenter + - openvk\Web\Presenters\SupportPresenter + - openvk\Web\Presenters\AdminPresenter + - openvk\Web\Presenters\MessengerPresenter + - openvk\Web\Models\Repositories\Users + - openvk\Web\Models\Repositories\Posts + - openvk\Web\Models\Repositories\Photos + - openvk\Web\Models\Repositories\Albums + - openvk\Web\Models\Repositories\Clubs + - openvk\Web\Models\Repositories\Videos + - openvk\Web\Models\Repositories\Notes + - openvk\Web\Models\Repositories\Audios + - openvk\Web\Models\Repositories\Tickets + - openvk\Web\Models\Repositories\Messages + - openvk\Web\Models\Repositories\Restores + - openvk\Web\Models\Repositories\Notifications + - openvk\Web\Models\Repositories\TicketComments + - openvk\Web\Models\Repositories\ContentSearchRepository \ No newline at end of file diff --git a/Web/routes.yml b/Web/routes.yml new file mode 100644 index 000000000..d1038086b --- /dev/null +++ b/Web/routes.yml @@ -0,0 +1,187 @@ +static: "static" + +routes: + - url: "/" + handler: "About->index" + - url: "/terms" + handler: "About->rules" + - url: "/rpc" + handler: "InternalAPI->route" + - url: "/support" + handler: "Support->index" + - url: "/support/tickets" + handler: "Support->list" + - url: "/support/reply/{num}" + handler: "Support->AnswerTicket" + - url: "/support/view/{num}" + handler: "Support->view" + - url: "/al_comments.pl/create/support/{num}" + handler: "Support->makeComment" + - url: "/al_comments.pl/create/support/reply/{num}" + handler: "Support->AnswerTicketReply" + - url: "/support/delete/{num}" + handler: "Support->delete" + - url: "/language" + handler: "About->language" + - url: "/donate" + handler: "About->donate" + - url: "/about:{?!productName}" + handler: "About->version" + placeholders: + productName: "openvk[2]?|libresoc" + - url: "/privacy" + handler: "About->Privacy" + - url: "/badbrowser.php" + handler: "About->BB" + - url: "/login" + handler: "Auth->login" + - url: "/reg" + handler: "Auth->register" + - url: "/logout" + handler: "Auth->logout" + - url: "/restore.pl" + handler: "Auth->restore" + - url: "/restore.pl/internal-finish" + handler: "Auth->finishRestoringPassword" + - url: "/setSID/{slug}" + handler: "Auth->su" + - url: "/settings" + handler: "User->settings" + - url: "/id{num}" + handler: "User->view" + - url: "/friends{num}" + handler: "User->friends" + - url: "/edit" + handler: "User->edit" + - url: "/edit/verify_phone.pl" + handler: "User->verifyPhone" + - url: "/setSub/user" + handler: "User->sub" + - url: "/setSub/club" + handler: "Group->sub" + - url: "/setSub/v4/club" + handler: "Group->attend" + - url: "/al_comments.pl/create/{text}/{num}" + handler: "Comment->makeComment" + - url: "/comment{num}/like" + handler: "Comment->like" + - url: "/comment{num}/delete" + handler: "Comment->deleteComment" + - url: "/notifications" + handler: "Notification->feed" + - url: "/feed" + handler: "Wall->feed" + - url: "/feed/hashtag/{?hashTag}" + handler: "Wall->hashtagFeed" + placeholders: + hashTag: ".++" + - url: "/wall{num}" + handler: "Wall->wall" + - url: "/wall{num}/makePost" + handler: "Wall->makePost" + - url: "/wall{num}_{num}" + handler: "Wall->post" + - url: "/wall{num}_{num}/like" + handler: "Wall->like" + - url: "/wall{num}_{num}/repost" + handler: "Wall->share" + - url: "/wall{num}_{num}/delete" + handler: "Wall->delete" + - url: "/blob_{text}/{text}.{text}" + handler: "Blob->file" + - url: "/albums{num}" + handler: "Photos->albumList" + - url: "/albums/create" + handler: "Photos->createAlbum" + - url: "/album{num}_{num}" + handler: "Photos->album" + - url: "/album{num}_{num}/edit" + handler: "Photos->editAlbum" + - url: "/album{num}_{num}/delete.pl" + handler: "Photos->deleteAlbum" + - url: "/album{num}_{num}/remove_photo.pl/{num}" + handler: "Photos->unlinkPhoto" + - url: "/photos/upload" + handler: "Photos->uploadPhoto" + - url: "/photo{num}_{num}" + handler: "Photos->photo" + - url: "/photo{num}_{num}/edit" + handler: "Photos->editPhoto" + - url: "/photo{num}_{num}/delete" + handler: "Photos->deletePhoto" + - url: "/al_avatars.pl" + handler: "User->setAvatar" + - url: "/videos{num}" + handler: "Videos->list" + - url: "/videos/upload" + handler: "Videos->upload" + - url: "/video{num}_{num}" + handler: "Videos->view" + - url: "/video{num}_{num}/edit" + handler: "Videos->edit" + - url: "/video{num}_{num}/remove" + handler: "Videos->remove" + - url: "/audios{num}" + handler: "Audios->app" + - url: "/audios/upload" + handler: "Audios->upload" + - url: "/audios/api/list{num}/{num}.json" + handler: "Audios->apiList" + - url: "/{?!club}{num}" + handler: "Group->view" + placeholders: + club: "club|public|event" + - url: "/club{num}/edit" + handler: "Group->edit" + - url: "/club{num}/stats" + handler: "Group->statistics" + - url: "/club{num}/followers" + handler: "Group->followers" + - url: "/club{num}/followers/{num}" + handler: "Group->admin" + - url: "/club{num}/setAdmin.jsp" + handler: "Group->modifyAdmin" + - url: "/groups{num}" + handler: "User->groups" + - url: "/groups_create" + handler: "Group->create" + - url: "/im" + handler: "Messenger->index" + - url: "/im/sel{num}" + handler: "Messenger->app" + - url: "/im{num}" + handler: "Messenger->events" + - url: "/im/api/messages{num}/{num}.json" + handler: "Messenger->apiGetMessages" + - url: "/im/api/messages{num}/create.json" + handler: "Messenger->apiWriteMessage" + - url: "/search" + handler: "Search->index" + - url: "/search/content" + handler: "ContentSearch->index" + - url: "/notes{num}" + handler: "Notes->list" + - url: "/note{num}_{num}" + handler: "Notes->view" + - url: "/notes/create" + handler: "Notes->create" + - url: "/invite" + handler: "About->invite" + - url: "/away.php" + handler: "Away->away" + - url: "/admin" + handler: "Admin->index" + - url: "/admin/users" + handler: "Admin->users" + - url: "/admin/users/id{num}" + handler: "Admin->user" + - url: "/admin/clubs" + handler: "Admin->clubs" + - url: "/admin/clubs/id{num}" + handler: "Admin->club" + - url: "/sandbox_cocksex" + handler: "About->sandbox" + - url: "/{?shortCode}" + handler: "UnknownTextRouteStrategy->delegate" + placeholders: + shortCode: "[a-z][a-z0-9\\@\\.\\_]{0,30}[a-z0-9]" \ No newline at end of file diff --git a/Web/static/css/avatar.1.css b/Web/static/css/avatar.1.css new file mode 100644 index 000000000..6318aa95f --- /dev/null +++ b/Web/static/css/avatar.1.css @@ -0,0 +1,36 @@ +.left_small_block > div > div > div > table > tbody > tr > td > a > img, +table.User > tbody:nth-child(1) > tr:nth-child(1) > td:nth-child(1) > a:nth-child(1) > img:nth-child(1) +{ + width: 50px; + height: 50px; + object-fit: cover; +} + +.post > tbody:nth-child(1) > tr:nth-child(1) > td:nth-child(1) > img:nth-child(1) +{ + width: 50px; + height: 50px; + object-fit: cover; +} + +div.content > table:nth-child(1) > tbody:nth-child(1) > tr:nth-child(1) > td:nth-child(1) > a:nth-child(1) > img:nth-child(1) +{ + width: 75px; + height: 75px; + object-fit: cover; +} + +.crp-entry--image > img +{ + width: 64px; + height: 64px; + object-fit: cover; +} + +.crp-entry--message---av > img, +.ava +{ + width: 46px; + height: 46px; + object-fit: cover; +} diff --git a/Web/static/css/avatar.2.css b/Web/static/css/avatar.2.css new file mode 100644 index 000000000..68614ff2c --- /dev/null +++ b/Web/static/css/avatar.2.css @@ -0,0 +1,63 @@ +/* +By WerySkok (@WerySkok) +*/ + +.left_small_block > div > div > div > table > tbody > tr > td > a > img, +table.User > tbody:nth-child(1) > tr:nth-child(1) > td:nth-child(1) > a:nth-child(1) > img:nth-child(1) +{ + width: 50px; + height: 50px; + object-fit: cover; + border-radius: 100px; +} + +.post > tbody:nth-child(1) > tr:nth-child(1) > td:nth-child(1) > img:nth-child(1) +{ + width: 50px; + height: 50px; + object-fit: cover; + border-radius: 100px; +} + +div.content > table:nth-child(1) > tbody:nth-child(1) > tr:nth-child(1) > td:nth-child(1) > a:nth-child(1) > img:nth-child(1) +{ + width: 75px; + height: 75px; + object-fit: cover; + border-radius: 100px; +} + +.post-menu > div > a, +.post-menu > a +{ + border-radius: 3px; +} + +.crp-entry--image > img +{ + width: 64px; + height: 64px; + object-fit: cover; + border-radius: 100px; +} + +.crp-entry--message---av > img, +.ava +{ + width: 46px; + height: 46px; + object-fit: cover; + border-radius: 100px; +} + +.messenger-app--messages---message:hover +{ + border-radius: 3px; +} + +.right_small_block > a:nth-child(1) > img, +.left_small_block > div:nth-child(1) > a > img, +div.ovk-video > div > img +{ + border-radius: 2px; +} diff --git a/Web/static/css/black.css b/Web/static/css/black.css new file mode 100644 index 000000000..c1fa13325 --- /dev/null +++ b/Web/static/css/black.css @@ -0,0 +1,300 @@ +/* Black OpenVK + + Version: release; + Port: Daniel Myslivets; + Year: 2020; + +*/ + +.navigation { + padding: 0; +} + +.navigation .link { + + display: block; + padding: 3px 3px 3px 6px; + text-decoration: none; + border-top: 1px; + color: #fff; + border-bottom: 0; + border-left: 0; + border-right: 0; + font-size: 11px; + text-align: left; + cursor: pointer; + background: none; + margin-bottom: 1px; + + +} + +.navigation .link:hover { + background-color: rgba(0,0,0,.2)!important; + border-top: 0px; +} + + +.navigation_footer { + padding: 0; + color: #fff; +} + +.page_footer .link { + padding: 3px 7px; + display: inline-block; + color: #fff; + text-decoration: none; +} + +.page_footer .link:hover { + background-color: rgba(0,0,0,.2)!important;; +} + +.page_header + { + background-color: #222!important; + background: linear-gradient(to bottom,#2f2f2f 0%,#181818 100%)!important; + background-image: none; + box-shadow: 0 1px 2px rgba(0,0,0,.5); + height:41px; + z-index: 10000; + } +.page_header::before + { + /* content: ""; */ + position: fixed; + left: 0!important; + height: 40px; + z-index: -1; + width: 100%; + box-shadow: 0 1px 2px rgba(0,0,0,.5); + height:41px; + color:#fff; + background-color: #222!important; + background: linear-gradient(to bottom,#2f2f2f 0%,#181818 100%)!important; + } + +.home_button { + background: url(data:image/png;base64,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) no-repeat; + position: absolute; + width: 200px; + text-indent: -999px; + height: 42px; + + + /* background: #fff; */ +} + +.header_navigation { + text-align: center; + line-height: 20px; + padding-right: 8px; + text-transform: lowercase; + float: right; + /* position: fixed; */ +} + +.header_navigation .link { + display: inline-block; + height: 29px; + padding: 11px 4px 0 7px; + background-size: 1.5px 41px; + background: none; +} + + .content_title_expanded { + color: #000!important; + background-image: none; + background-repeat: no-repeat; + background-color: gray; + border-top: #8B8B8B solid 1px; + padding: 5px 10px; + font-weight: bold; + color: #000; + font-size: 11px; + cursor: pointer; + display: block; + } + +.content_title_unexpanded { + background-image: none; + background-repeat: no-repeat; + background-color: gray; + border-top: #8B8B8B solid 1px; + padding: 5px 10px; + font-weight: bold; + color: #fff; + font-size: 11px; + cursor: pointer; + display: block; +} + + +.page_status { + color: #fff; +} + +a { + color: gray; +} + +.post-author { + background-color: rgba(0,0,0,.2)!important; + border-top: #fff solid 0px; + border-bottom: #FFF solid 0px; + color:#fff; +} + +.post-content { +color:#fff; + border-bottom: 0px; +} + +body{ + background: url(data:image/jpeg;base64,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FFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABTogd+QcY702tXwP4cvvGHjDTPC2mxlp9QvoreJQMks7hR+pppXYN2R9A+Onb4Pf8E9/DPhJC0OofEPxBLql2obBNpbjy48+xYkj6V81yOXOSa+gv8Ago/4ksR8cLT4PaFOraf8PtCttBiEZ+Xzo0BnP/f1n/KvnurqfFYzpr3b9wooorM0CiiigAooooAKKKKAAcmuk8eB4Z7SyY/6jT4UI99oP9awLKA3V5FbL1klVR+JxXQfEx8+JrlF4COEHttUCqWxEt0c44HWm0ZJ6mipLCiiigAooooAewAUgUynt/qxTKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiinIhegAjQs3HrWhZ2u7GQD9aitbUlgB1PpWzaw2llELi8YRp/t9SapImRe0PRjJtPQd60dT8c6V4TiNtpyLcXW3HHKoff1rl9X8X3M8Rs9NJghxgkfeasMsT1Ofc1SnZaE8t3ct6zrup67etfalePJI3qxwo9AOwqnRRUN3ZoFFFFIAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAoooAJOBQAVLBayTnCjipLayZzufGMVa+1Q2qbcDPTpVR2Jb1JLPT7eH55cflUlzrMcC+XBxj0rOn1KWQ4zx2warMxY5Y0N2C1ya5vppzguce5qCiik9SgooopAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFKFLYApKt6Pps+p3qQQxkjcN1NK7B6HR/DLwfNruqxyGIlQ3519S+ANCtvD+lRjaFJUdB04rzz4O+CrfTbRLp4uQAa9B1DWo7eDYsgAAwBXdShyK5wVpc8rGnrXiT7PG2JRwOMVw/iLxQzFj53P1ql4i8TZ3jzq4zWdfE0hPm1c6tyIUral/V9f35G89+9YlzquT94/nWfe6qJcktVF78Mduawc7m6WhpyagS2Nxp8V4CeTWOLgE1dsW3yKM0J3Bqx0mhAySqR3r134R+FptW1G2torcvJK4VFUZJJNeX+DbBr25UKoPP3T39q+8/+CePwU8N2zal8ffitLFZ+EvBlo19qVzO2A5QbliX1JPFb0tNTCrrZHbftO/GvSf8Agmb+wWdO0l44viN8SbJoNMh3/vbOzIG+UjqM5wD7Gvxf1O/u9Uv5dSv52lmnkMksjnJZickmvbf+Chn7YfiX9tL9o/V/ifqkzppkMrWugWO7K21opwgH1HNeF1x1qnPI7KNP2cQJJ6miiisTYKKKKACiiigAooooAKKKKACiiigAooooAKKKKACnRLuamjqKs28YODigCQP9lty4HX0qkSSck5J6k1Pev83ljtUFABRRRQAUDk4op8MLSsAozz0oAl0+0a5mC4zz0r1D4aeCftEiTSRHGRiua8EeGWu7hSYe9fQPwt8GhfKLQdMcV1Uad5HNWqWid58IvAwaSFRFlmwOnbivsD4Q+GYNA02LMYDYHJFePfBjwlDb7b27jC7SNmR9K9x0y+jsrYEvg49K72uXQ81S5nc6zVPEa2dsQr4IFeSfE74gNtkVZuBkda0PGfjFY4HUTdM14x428RSajOyq+QDzVRjcJStoYXirWDe3DzM5OT61xGtXZO8578VtavdhiwXPFczqsu4EilN20CJg6tLkNXNavOAhArc1efBYVyfiC9AyB6VzPc64bGFr14Fz83c1x2r3oJP171r69fcE5PWuY1C5BrmlI6IxuZ15Kc4zVC4kyamuZOuTVVyN3WsZG0SN/vGkpzbcdqbUlBRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABXUfDTRDqGp+ewBCnuK5iNS7qg6kgCvVfhpow07R/tEg+Z+RxWtJa3IqO0S74juUtbQQjsvOK871u6Mku4E9TXVeNdTGCnp6Vw15NvYtnvTmyIKxXdtzH6VDJ2p7upPaoqxNgooooAKKKKAHqBt6VLar8xf+6tVwMnFWXBhtc/3jQBExDksR1qOg9aKAPUf2T/hFbfFv4sWtrrSiPRNJQ3+t3BHypbx/MQT/tfd/GmftUfF6b4xfFfUPEMHyWMT+Rp8C/dhgQbUUDsAoAr0a5Cfsy/sqw6M/wC58T+P1FzfD+KDT1P7tP8AgZ+b8K+bpZnmkaRjy3Wt6nuR5TGD5536DKKKKwNgooooAKKKKACiiigAoopVUscCgAX71XNF0m61vVoNMs0y80gUCqyqAQMV67+yv8O5PEXiQ63PDmOOQJFkd+prSnDnmkRUmoRbPq39kX4S2+l6baRGALFaxq0hK96uftReMkSWbT4JvliBXg1614N0K1+HfwybUboBJJIt5+lfJPx+8ZteXNzKZctIeRXqu0IWPKT553PDfibrYvLqQBj145rgLpwXxW94nvvtd5IQe9c9c9a8qo7yPUpq0SFup+tAGTgUAZOKljjxgkVmaDraIk49q6Pw5pImlUsmfqKzdLsPNmXiu+8IaDuCsB1PpWtOPMzKo7Gx4c0tYIBIVHHtVDxZqaxqYVPOema6HUiNOsuOMDrXFhjrGu7W/wBXH88n0reTtoYx0KXiGUadpC2RGGcb5Md81wd1L507HPGeldN461MSTybSeuFrkjknJrmm7s6KewrfepKKKgsKKKKACpFAz0pqAHORT0xnmgB+AOgpU+8KQckU8KAaBPYWiiikQIenNMIwTUhz2NRnqaZa2CmP1/Cn0YB6igZHRSt96koAKjbqfrTn6fjTaACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigBVxnmvoL/AIJueENN1T9ohfiR4js9+j+AdFvPEmqM0YZcW0RaNCD/AHpNij3YV8/R43c9O/FfTXwzI+C3/BOjxt8Qykaar8S/Elv4a0xy+H+xW2Lq6I/4GLYfia0prW/Yio9Ldz59+IPivUfHfjPVPGmsTGS71XUJrq4kJ5Zncsf51i06Qk/iabUN8zuUlZWCiiikMKKKKACiiigAooooA1fBNm974v022Cg5vo9wI7BgT+lS+Nbv7brU9x3eVm/M1o/DKARXl94hccWFk7R8f8tHG1f5msLW5jJckN97J3fjVtWiQneRRoooqCwooooAKB1FFOjXc1ADpuAAKjqW4GMD2qKgAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiinr0H0oAai7jirVvBu5JqDdHH0HNDzySdBj2FAF3+0LezGI1DuO56VTubu4u5N9xKWPbJ6VGqs5wozSvGyfeGKAG0UUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFGD6GgAopdrehpyRZ5figAji39anhRIjuJHHvUZlEYwo/GmGUseelAEs14cbE9agZixyxpXYEcGm0AFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUYJ6CgBVBZgo6mvTPg/4KaadbyeLuCciuO8FeHpNY1BCUyoYV7v4Y0+10HTtwADbRkVvSg9zCpUtojpIrmHSrIRw4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KZ0oAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKdEC0gQfxHHPvTQMnArq/gd8L9d+M/xe8O/C3w7bNLd65q0NrGoHQM4BJ9ABk5ppXdgbsrnus7N+zf/AME/FGxrfxD8XdTwCRh00m2bJ9wHkI+oU18wuwJJGBk5wK90/wCChHxO0Tx18fbnwf4IvPN8L+B7SLw74d2n5WhthseYf9dJfMk/4HXhJ68VpVa5rGdNe7cKKKKyNAooooAKKKKACiiigArd+HGjjWvGFlaycxrKJJfZV5P8qwq774MacIItU8RuoxBAIYm/2m6/oDVwV5EzdolT4lag13fzFj95jgegrjWByTit7xlctLfPk55rCZwRilJ3YoLQbRRRUlhRRRQAVcsUO3cR0FU+taNqhW3LHptoAoSndIxz3ptB5JooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACnRxlucUIhc1bghSIb880ASWlsqgMw7VLc3qwJtj61XnuxjCjGKqyTs7Hd60EpO4TXEkxyzGo8n1oooKCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigApexpKdFGZX2L1PSgC7oOmtqF0EK/KTXpmkwQaTYqdoBVe1c94Q0mKygSaZQDjJq3q+shQyIxwOgraHuowneUrDtc1uV2Khziucv9TZ2KljTL7VDJkEmsye4ZyeaTkVGI64udxOM1C0hYYqNnY96bUNlpMl3H1pNxqOpAcjNSUSwkMcAGt/w3pb3M6xqgYk/WsXTrcyzKo7nmvpD9if8AZ+tfib4qGt+LJBZeHtFgN5rd/JwsMSHJz7nGAPWtaUOaRlVnyRuey/s/QeGv2Jv2fb79rrx/YRya5dKbLwBpVwBmS5ZSDcbT/CmSc+oAr4R8d+NvEPxF8U6h428WalJeajql29xd3ErEszsxJP64r1b9uT9p+5/aM+J5i0KP7H4U8PxCw8NabGfkjgQ4Dkf3m+8TXiBx2/GqrVOb3UTRhyrmfUXI24pKKKwNwooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKUKW6UAIAT0FSE4GaRVK9aRz2xQA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StHwrpEmveIrPSIBlp51X9eaaV3YTdlc948EaOPCvwjsYHG2WdDNIB/tc/yrxzx3etLfOeDXvfxMdNG0FNPgIURW4jXHpjFfOnii5829cBs111laNjlo3crmSTk5ooorjOsKKKKAFUbjjNPjj+brTAcdKsWMDTOCfWhK4Gro0L5U7e1U/E0qtf8AlIchB1rbskFrbeaR90ZBNcveSedcvLn7zE1T0RK1ZFRRRUlBRRRQAUUUUAFFFFABRRRQAUUUUAAJHSl3t60lFAAST1ooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAdEjO4ArUgItYsg1Uso9vzSjjtS3c/y4B7013FcS6uzI5O6qrMWOTSE5OaKHqxhRRRSAKKKKACp7C0lvrqO1hiZndgqqo5JqFQWIUDJPQV6z8FvCemeF9In+LHjKDba2fNpE//AC2lHQAemauEeZ+RE5cqOhF9bfs2/C8G1dF8U67Btj/v20J/i9ia8HmnmnlaaaQs7klmJ5JPU1sePvGmq+PPE9z4j1aQs8z/ACITwi9gPSsTJPU1VWfM7LYVOLjq9wooorI0CiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigApVBLDFJU1vFnkjvQD0JrWIEgkVtaZAxIGOKoWVvuccd63tLtuV4rSC1MpyVjX0e3KgEDtXT6VFtAI64FYemxlcACui03hBn0rpgYt6m1pqj0roNP8AvCsDTvf0rf077wraG5hI2tPAzu9604Zti9RjvWVayBRxUzXIAI3VunYwauW573AI3cVj6lqAw2GpLy/CknNYep6gOfmqJzsOMSDVdRADEP0NcnrGpFicMKvatqKkn5u9cxqt8mTg1zykrnTCLKOp3ucndXOX93uJ5q5qV4WJ+asK7nyTg/WsJyuzojEgvJc9DVKSQYwKknkBJ5quxycisHubLYCxPU0lFFIYUUUUAFFFFABRRRQAUUUUAFFFFABRSoAc5FPjjDOABQA63iZuAOc1s6LprSyAYOccjHvVfTLB5JAMHP0r0j4R+BLjxFrkNu0Z8sHc7Y6AGtqVJykZVKqhE9B+DvhW18I+GZvE2oxgNIh8vcMEcV5N8YPGMurarL5chIJIPPbNesfHXxfa+G9JTw5YSqUjjx8vrXzZrN897dNKzkksT+tbV5ci5UYYeLm+eRVlJZtxNNoyfWiuM7AooooAKKKKACpLS2ku51giXJY1HW7o1rHpOntq9yAHI/dg01FsTdiPXporG3TSYD91fnwe9Yx7U+4ne4maZySWOTTKVxhUttEXcMRwKjAzwBkmr1pAY4wp6nrTsxN2FVSTipBGwAxUkUXoKeY+wWrIKxDZxnvSbW9KnZSCcUFGPFAEG009EY5zU0cW4dKmit+enegCGKAkirEUBJAC1PFbdPlq5bWRODjmqUXclyViG2szkEDmtSxsHJGF7VPY6cWIG2t3TNHO5fl+pNbKDuZuSsZ1w1tommSaldsFWNMgH+I9hXM+DdH1Hxf4m+0XCMzyyc8dBS+PdeOva0miacN1vbvtJXoz9Ca98/Y0+BGo+NvE1jZwWTO8s6rjaTwTSprnnpsKbcIa7n2R/wAEtP2TpfEWtWviPUbA/Z7bBDGPjNfpR8TdX074d+CTbqFjEMHA4GeKyP2Tvglpnwg+G1pZxQhWjhDSOwxlsc15X+2B8SftbT6VaXJ8uIkt83U+ldduaSS2OV+7G73Pjz9qHx3L4g1u4uprktvdgoJ6DNfNHiuczSOQe/Fep/F/Wv7T1eZVOQG6ZryvVbZ7mUqg781vJcqscsZXlc5ZtOaacsVzzxWponhvzXEjqeTWpp2gefKowcfxDFdz4L8DS6jKkcFsSueuKzhT967NZ1LLQq+DPBktw6BIuexxXfXmm2vhbRS023zWHJrrvDfgq28O6aLu4jUbUyS1eQ/HD4h29p5qRygIv3ea2do6mDvJo80+N3xDjsraaBJfmORnPtXy5418RS6nfyMzkgse9dZ8XvHcmq30ipPwSe9eYzy+bIzHJyeteXiKvM7I9fD0lGNxJHLPuBpCxPU0lFch1BRRRQAUUUqAHORQA4IAc09VBGTTakAA6UAAGBinLnA4oVQRkinAADFAuZBznpThGp5zTM8nBqTHGKb0FsKenFEYY9RSIrY5NSAYOO1WSJyckfhRh+lSADOQKU+5oAiG7OTR/DgipOvFI6gDFAEZGAKjl7cZqRjmo5ccUnsNbkLEhTio2JwTUj/dNROTnGagsaSScmiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigCW3tZLq4S2tkZ3kcKigZJJ6Cvp39tq5g+CfwU+Hf7H2luqXWk6ade8XBDgtqd2oOx/dIti1zX7Afww0PX/AIn3nxi+IFtnwp8PNPfW9ZZx8srx48iD6vIVAHfmvLfjP8Utf+MnxQ134meJJt95rF/LcS+ihmOFHsBgfgK1j7sGyH70rHKUUUVkWFFFFABRRRQAUUUUAFFFFABXqv7LPgn+2/Fr+J7qL/R9NjyGboZD0ryxEZ2CquSeg9a+qfhJ4U/4V18JbZLqMLc3cfn3GRgjcMgH6ZxXRhoc1T0OfET5YW7nL/G/XF2vFGeR714JqMxmuHfOeea9B+Lmvm51BwJCfavOJGLOST160q8rz0HQjaA2iiisDcKKKKAFQAnBFbGjWzsQay7WFpJAK6jRrMqgAH8NXCLkzObE1uYWukMoOGbgVyr9a2/GN7ukjtFPRSWHvxWESTyaUt7Dh8NwoooqSwooooAKKKeFUjpQAyiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAp8CF5Bxkd6YAScCrEQ8tDn86AJJZVQbF4NVpX3HrSSOWbk02mxBRRRSGFFFFABRRVzRNLu9a1GHTLGAvLK4VQBnOaaV2D0Og+FHgC48c6/Hbswjtof3l1O/CxxjknNaPxn+I1v4n1CLwx4bHlaNpa+VbRrwJCODIfqRmtPxvrVl8M/CA+HPh6VTe3SBtUuUP/AJDrzBj8xwx69TWspckeUyinOXMwJJbIpKKKxNQooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKVADnIpKVF3GgB6oCeBVq1jycEVDFGc1fs4hkH3px3E9i7p8WCPlrd02IZGBWVZx9DjHNbWnDGK6ImE2atgMNiug00LgcdqwtOAyMit3TeMZ9K2WxjI3NPADDjrW5Y8GsSx+8MelaltLxwa0SsYmtHOoBOaZNe4BAeqf2rA7fnVa5vQM4q+YnlHajfAIx3dK57UtRXn5u9Tajf8AJyTj61z2qX67iAfyrKUr6mtOOhV1fUAQfmxzXNajfnLZareq3pyTnv61z2o3eScGsJy1OiMb6le/uwSSGrJuJyzZ3VLdzkkkVRkl7Vg5amyjoErbgTUdFFS9SwooopAFFFFABRRRQAUUUUAFFFFABRRSou40AKisxwtXbKyaVwR69ajtLbzGxjv2rp9B0MyEEx5q4xbZLkkWfDugSTTqixktjmvoLwLodj8NfAkniK/UJcTRnYrCuX+Cfw5/tvV43uICIYsNIxHYUn7SvxJihJ8P6ZKBDboUULwOK7qa9nDmZwVH7SfKmeS/FXxnPr+rSN5u4FzyTXDMcsT71PqN213OZGPJPNV64Jyc5XO6EVCNgoooqSwooooAKKKWNHkcRoMknAFAF3QdKk1O9VNvyA5c+gqx4ov0muhp9qcRQ8DHetCUR+FtEER/4+J159QK5p2ZmLMeSc9ap+7oSnzag4wc5pKKVVLMFHU1JRPYWxmk344Xp7mtNIegK0lhaiKELtGc8mraR5atUrozlJXsRbNvOKUqfWpzGR0Wjyz/AHRT5SeZFcxDPI60JFnjGatCA46CnR2/tRyj5kQRWp7r9atRWxzwvf0qe3ts9h+VXLayyQMd/Smou5HtEQW9kSR8vetSy01jj5efpU1lpxOBgVt6ZpH3eB9a15GieZEOmaQxZfl71T+JXiZPCOhjTrNgb69Uou08xJ3b69B+NdVcNY+HNIl1nUWVYoULHPGT2A+teNXl/qHjvxTLqFwhLTP8gHSNewoqScVZbip2k7vY3Pg/4GuvEmtQoISxZ+pHH41+xn/BKj9kkWFnB461jS2RpEC2aOmCo/vfj/Sviv8A4J9/svXPj7xbZxz2DGAMrzsV/h/u/U1+43wI+HOl/DTwLE32VU2QAKAoG0BeMVtGMaNOy3ZhNyrVNdkHxU1608GeFTptoQrmIDAPbFfBf7S/jAH7QWlLuzHnP1r6j/aE8YGU3E7zYwMKuegr4d+N+rSahdys7fKGboa1owbdzGtJI8E8USNNdyuOSzk5rBi0d2JLRksx64rqbjTJryct5OQW4rpPBPw0udZuFllhOwsNo9a65JdTkTsYHgv4f3epzpFHbHDHltte7fD/AOF9votmJZYtpUAsWX2rofhx8JYbSIXEkAAXH8NXvi34o03wH4clbeocrtQYqXNaI0imldnk3x48eWXh/T30m2nUED5ipr4f+PHxPa5mksobjOeuGr0r9on4wPJNPI12CzE4BNfJnjbxRPrF87ebk5655rkxNVJcqOrDUuZ3Zk6xqEl9dOzsTz3NU6CSTkmivKu3ueokkgooooGFFFKgBPIoASngAdBRgegpaAHKoI5FOpF6CloAen3RS0i/dFKmC3Wgi12OCKQMinKOcEUKOcU7HoBVJBuKBg08IN2cfSjAz0qUDJ6fpVCGBGzlhTgoHbNOCPnOKCjAZPagBjqAMgc5qN8hce9SPnGKik6nPpQAxhgDNQuzY69/Wny9BzUDttzUt9CkuoxyeeajyT1NKzbjmkqSgooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAp8cbSOI0QszHChepJ6CmV7l+wf8HvDfxE+LjeOfiVbA+C/A1k2u+Ki5ws0MPMdsD/emk2xD/fJ7U4pydkJtJXOy/aCnT9l79knwv+zFYP5XibxmsXifx2BgPBE6n7FaPgkgiM+YynoXHpXy5lhXZ/tAfGXxJ8fPi94g+LPiu5aS71vUZLgrztiQn5EUdlVcAAcADAri6uq05aE001HUKKKKzLCiiigAooooAKKKKACiilHOBigDsfgZ4Hbx38QbHS5Ii1vHJ5t02OFReea+jfi1rcWl6S1qhwAm0Aelcv8Asi+Bf7F8I3PjG9gxLfEpCWH/ACzFUfjx4lAke3Eg7ivRpr2VG/VnnVJe1rW6I8X8aaib2/chz371z1WtTnM9053Z5qrXnyd2ehFNKwUUUUhhSqATg0lS26B2AxTSuD0LukWjyShgODXVWMYgjyR0Wsvw/Y4AbHatTV7lbLTnkHGFrWHuxMXrI5DXrkXWovIDkA4FUqWRy8jOT1JNJWT3NVogooopDCiiigApyE569qbT1AwOO1ADKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKAMkD3oAfEp3Bz0FOlcFSopCwX5c0xvvUAJRRRQAUUUUAFFFOXGOvOaAERSzABc+1ej+FILf4V+F28YakinU71CumQd1HILmsT4c+GLOZ5fFevrjT7AbnDf8tG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) no-repeat fixed; + background-size: cover; +} + +.post-author .date { + font-size: 9px; + color: #fff; +} + + +h4 { + color: #fff; + +} + +.nobold { + + font-weight: normal; + color: #fff; + +} + +.page_yellowheader { + padding: 8px 10px 8px; + font-weight: bold; + background: rgba(0,0,0,.2)!important;; + border-right: solid 1px #fff; + border-left: solid 1px #fff; + border-bottom: solid 0px #D7CF9E; + color: #fff; +} + +.page_yellowheader span { + color: #fff; +} + +.page_yellowheader a { + color: #fff; +} + +#write{ + background: rgba(0,0,0,.2)!important; +} + +.page_body{ + background: #000; +} + + +#wrapHI { + +border-right: solid 1px #000; + +border-left: solid 1px #000; +} +#wrapH { + + border-right: solid 1px #000; + border-left: solid 1px #000; + +} + +td {color:#fff;} +p {color: #fff;} + +.content_subtitle { + background-color: rgba(0,0,0,.2)!important; + border-bottom: 0px solid #EEEEEE; + border-top: #ccc solid 0px; +} + +.left_small_block { + border-right: 0px #eeeeee solid; +} + +.container_gray { + background: none; + width: 602px; + padding: 12px; + border-top: #EBEBEB solid 1px; + margin-left: -12px; + margin-bottom: -12px; +} + +.settings_delete { +background: none; + color: #fff; +} + + +#profile_link, .profile_link { + border-bottom: 0px solid #CCC; + color: #fff; + +} + + +#profile_link:hover, .profile_link:hover { + + background: rgba(0,0,0,.2)!important; + +} + +label { + color:#fff; +} + +#xhead { + height:41px; + left:0; + position:absolute; + width:100%; + z-index:-1; +} + +#xhead.dm { + background-color: #222!important; + background: linear-gradient(to bottom,#2f2f2f 0%,#181818 100%)!important; + background-image: none; + box-shadow: 0 1px 2px rgba(0,0,0,.5); +} + +.content-main-tile { + margin: -5px; + margin-top: 3pt; + padding: 7pt; + border-top: 0px solid #ebf0f4; + background-color: #000; +} + +span { + color: gray; +} + +.messenger-app--input { + background-color: #000; + height: 120px; + border-top: 1px solid #d6d6d6; + border-bottom: 1px solid #fff; +} + +.messenger-app--messages, .messenger-app--input { + border-right: 1px solid #fff; + border-left: 1px solid #fff; + +} diff --git a/Web/static/css/dialog.css b/Web/static/css/dialog.css new file mode 100644 index 000000000..d49247fd8 --- /dev/null +++ b/Web/static/css/dialog.css @@ -0,0 +1,60 @@ +body.dimmed > .dimmer { + z-index: 200; + position: fixed; + width: 100%; + height: 100%; + background-color: #000; + opacity: .5; +} + +.ovk-diag-cont { + z-index: 1024; + position: fixed; + width: 420px; + min-height: 200px; + top: 50%; + left: 50%; + margin-right: -50%; + transform: translate(-50%, -50%); + background-color: #c7bdbd94; +} + +.ovk-diag { + margin: auto; + margin-top: 10px; + margin-bottom: 10px; + width: 400px; + background-color: #fff; + border: 1px solid #505050; +} + +.ovk-diag-head, .ovk-diag-body, .ovk-diag-action { + width: 400px; + box-sizing: border-box; +} + +.ovk-diag-head { + height: 25%; + padding: 5px; + background-color: #757575; + border-bottom: 1px solid #3e3e3e; + color: #fff; + font-weight: 900; + font-size: 15px; +} + +.ovk-diag-body { + padding: 20px; + min-height: 110px; +} + +.ovk-diag-action { + padding: 10px; + height: 25%; + background-color: #d4d4d4; + text-align: right; +} + +.ovk-diag-action > .button { + margin-left: 10px; +} diff --git a/Web/static/css/facebook.css b/Web/static/css/facebook.css new file mode 100644 index 000000000..4eb55580d --- /dev/null +++ b/Web/static/css/facebook.css @@ -0,0 +1,306 @@ +/* body { + background-color: #f7f7f7; +} +#wrapHI, .container_gray { + background-color: #fff; +} +*/ + +body { + background-color: rgb(255, 255, 255); + margin: 0px; + padding: 0px; +} + +a { + text-decoration: none; + color: #29437b; + cursor: pointer; +} + +.page_footer .link { + display: inline-block; + color: #29437b; + text-decoration: none; + padding: 3px 7px; +} + +.page_header { + position: relative; + width: 791px; + + height: 78px; + background-repeat: no-repeat; + background-position: 0; + background: url(https://i.imgur.com/cqMsBNF.png); +} +/* https://i.imgur.com/LqsOsht.png */ + +/* https://i.imgur.com/kzwcBf8.png */ + +/* .header_navigation { + text-align: center; + line-height: 103px; + padding-right: 8px; + text-transform: lowercase; + float: right; + + display: none; +} + +.header_navigation .link { + background: url(https://i.imgur.com/c4W9P4X.png) no-repeat; +} +*/ + + + +.header_navigation { + text-align: center; + line-height: 20px; + padding-right: 0px; + text-transform: lowercase; + float: right; + padding-bottom: -8px; + margin-bottom: -5px; + margin-top: 29px; + +} + +.header_navigation .link { + display: inline-block; + height: 29px; + padding: 11px 4px 0 7px; + background-size: 1.5px 41px; + font-family: sans-serif; + font-size: 12px; +} + +.header_navigation .link a { + color: #dfe3ff; + text-decoration: none; + font-weight: normal; +} + +.header_navigation .link a:hover { + text-decoration: underline; + color: #5a9cff; +} +.header_navigation .link { + background: url(https://i.imgur.com/c4W9P4X.png) no-repeat; +} + + + +input[type="search"] { + display: none; +} + +.navigation .link { + + display: block; + padding: 1px 3px 3px 6px; + text-decoration: none; + border-top: 1px solid #fff; + color: #000; + border-bottom: 0; + border-left: 0; + border-right: 0; + font-size: 11px; + text-align: left; + cursor: pointer; + background: none; + margin-bottom: 1px; +} + + +.sidebar { + margin: 1px 1px 1px 3px; + border-style: dashed; + border-color: #3b5998; + border-width: 1px; + width: 137px; + +} + + + +.navigation .edit-button { + position: absolute; + right: 0px; + top: 0px; + background-color: rgb(255, 255, 255); + color: grey; +} + +.page_yellowheader { + padding: 3px 3px 3px; + font-weight: bold; + background: url(https://i.imgur.com/5S6mE3Z.png) repeat-x; + background-color: #3b5998; + border-right: solid 1px #3b5998; + border-left: solid 1px #3b5998; + border-bottom: solid 1px #3b5998; + overflow: hidden; + color: #dfe3ff; + +} + +.page_yellowheader span { + color: #dfe3ff; +} +.page_yellowheader a { + color: #dfe3ff; +} +.content_title_expanded { + border-top: #45688e solid 1px; + background-color: #dae2e8; + color: #45688e; +} +.navigation .link { +color:#2B587A; +} +.navigation .link:hover { + background: +#DAE1E8; +border-top: 1px solid + #CAD1D9; +} +.tabs { + border-bottom: 1px solid + #29437b; +} +#activetabs { + background: + #29437b; +} + +/*кнопки*/ + +.button { + border-radius: 0px; + border: #29437b; + font-size: 11px; + outline: none; + white-space: nowrap; + background: #29437b; + background-position: 0px -16px; + color: #edefff; + padding: 4px 8px 4px; + text-shadow: 0 1px 0 #29437b; + cursor: pointer; + text-decoration: none; +} +h4 { + color: #29437b; +} +#faqhead { + background: #3b5998; + margin: 0px 5px 0px 5px; + padding: 5px; + font-weight: bold; + border: 1px solid #3b5998; + border-bottom: 0px; +} + +#faqcontent { + background: #fafafa; + margin: 0px 5px 10px 5px; + padding: 5px 5px 5px 10px; + border: 1px solid #ddd; +} + + +.content_title_expanded { + border-top: #29437b solid 1px; + background-color: #cbd6ec; + color: #29437b; +} + +.content_subtitle { + background-color: #eaedf2; + display: block; + font-size: 11px; + color: rgb(119, 119, 119); + border-bottom: 1px solid rgb(238, 238, 238); + padding: 3px 8px; + border-top: 1px solid #cbd6ec; +} + +.content_title_unexpanded { + background-image: url("../img/flex_arrow_shut.gif"); + background-color: rgb(218, 226, 255); + font-weight: bold; + color: #445477; + font-size: 11px; + cursor: pointer; + display: block; + background-repeat: no-repeat; + border-top: 1px solid #29437b; + padding: 3px 24px; +} + +.completeness-gauge { + position: relative; + width: 95%; + height: 20px; + background-color: #D5DBE8; +} + +.completeness-gauge > div { + height: 100%; + background-color: #A5B1C9; +} + +.completeness-gauge > span { + position: absolute; + top: 55%; + left: 50%; + margin-right: -50%; + transform: translate(-50%, -50%); +} + + +/* post */ + +.post-author { + background-color: #F6F6F6; + border-top: #29437b solid 1px; + border-bottom: #e1e3e8 solid 1px; + font-size: 11px; + padding: 3px 5px 3px; +} + +.post-author .date { + font-size: 9px; + color: black; +} + + + +#wrapHI { + border-right: 1px solid #3b5998; + border-left: 1px solid #3b5998; +} + +.wrap1 { + border-right-color: #fff; + border-bottom-color: #3b5998; +} + +#wrapH { + border-right: 1px solid rgb(255, 255, 255); + border-left: 1px solid rgb(255, 255, 255); +} + +#auth { + padding: 9px; +} + +.content_title_expanded, .content_title_unexpanded { + background-image: unset !important; + font-weight: bold; + font-size: 11px; + display: block; + padding: 3px 10px;; +} diff --git a/Web/static/css/kos.css b/Web/static/css/kos.css new file mode 100644 index 000000000..361ffc955 --- /dev/null +++ b/Web/static/css/kos.css @@ -0,0 +1,136 @@ +@import "style.css"; + +.page_header { + position: relative; + background: linear-gradient(to top, #1b4692, #2358b8); + height: 86px; +} + +.page_header > .header_navigation { + box-sizing: border-box; + position: absolute; + background: linear-gradient(to left, #2050a7, #2358b8); + width: 100%; + bottom: 0; + float: unset; + text-align: right; + border-bottom: solid 1px #fff; +} + +.page_header > .header_navigation > .link { + background: none; + padding: 0 10px; + padding-top: 5px; +} + +.page_header::before {content: "OpenSpace";position: absolute;margin: 12px;color: #fff;font-size: x-large;} + +.sidebar { + box-sizing: border-box; + margin: 0; + margin-bottom: 2px; + float: unset; + width: 791px; + background: linear-gradient(to top, #1b4692, #2050a7); + color: #fff !important; +} + +img.psa-poster { + display: none; +} + +.navigation .link { + display: inline-block; + color: #fff; + border: none; + height: 24px; + padding: 0 5px; + padding-top: 10px; +} + +.edit-button { + display: none !important; +} + +.navigation > div { + display: none; +} + +.navigation .link:hover { + background-color: unset; + border-top: none; + font-style: oblique; +} + +.page_body { + box-sizing: border-box; + width: 791px; + float: unset; + margin-top: 10px; + padding: 10px; + border-radius: 10px; + box-shadow: 1px 1px 20px #8a8a8a; +} + +div#wrapH { + border-right: none; + border-left: none; +} + +div#wrapHI { + border-right: none; + border-left: none; +} + +.wrap2 { + border-right: none; + border-left: none; +} + +.wrap1 { + border-right: none; + border-left: none; + width: 100%; + box-sizing: b; +} + +div#auth { + width: 100%; + box-sizing: border-box; +} + +.page_content { + width: 100%; +} + +.page_yellowheader { + width: unset; + background: #aed5ff url(https://web.archive.org/web/20081001225238im_/http://x.myspacecdn.com/modules/common/static/img/header/bkg_menubar.png) top left repeat-x; + padding: 4px 11px 6px; + background-blend-mode: color-burn; + font-size: 11px; + color: #fff; + border: .5px solid #4a7db1; + margin: 0 0 10px; +} + +.page_yellowheader span { + color: whitesmoke; +} + +.right_big_block { + width: 540px; +} + +.content_subtitle { + background-color: #f0f4fe; + color: #2256b5; +} + +.content_title_expanded { + background-color: #fff; + background-image: none; + color: #45688e; + border-top: navajowhite; + padding-left: 8px; +} \ No newline at end of file diff --git a/Web/static/css/nsfw-posts.css b/Web/static/css/nsfw-posts.css new file mode 100644 index 000000000..8f9625a50 --- /dev/null +++ b/Web/static/css/nsfw-posts.css @@ -0,0 +1,28 @@ +.post-nsfw .post-content img.media { + filter: saturate(0.8) blur(5px); +} + +.post-nsfw .post-content .attachment { + overflow: hidden; + position: relative; +} + +.post-nsfw .post-content .attachment:hover img.media { + filter: none; +} + +.post-nsfw .post-content .attachment::after { + position: absolute; + top: calc(50% - 16px); + left: 0; + width: 100%; + padding: 8px 0; + background-color: hsla(0, 0%, 0%, .5); + color: #fff; + text-align: center; + content: attr(data-localized-nsfw-text); +} + +.post-nsfw .post-content .attachment:hover::after { + display: none; +} diff --git a/Web/static/css/ooer.css b/Web/static/css/ooer.css new file mode 100644 index 000000000..89f0f02f4 --- /dev/null +++ b/Web/static/css/ooer.css @@ -0,0 +1,119 @@ +@import "black.css"; + +@keyframes blink { + 0% { + color: white; + } + + 100% { + color: blue; + } +} + +@keyframes cock-blink { + 0% { + background-color: black; + } + + 100% { + background-color: red; + } +} + +@keyframes bg-heresy { + 0% { + background-position-x: -100px; + background-position-y: -100px; + background-size: 100% + } + + 100% { + background-position-x: 100px; + background-position-y: 100px; + background-size: 10% + } +} + +.text::after, .page_yellowheader::after, .ugc-table tbody *::after, a::after {content: "IM NOT GOOD AT COMPUTERS PLZ TO HELP";} + +body { + background: linear-gradient(45deg, crimson, yellow, red, orange, cadetblue); +} + +#auth { + background-image:none; + background-color: transparent; + color: red!important; +} + +#auth > * > * > * > * > * > * { + background: black; + color: red; + animation: cock-blink .1s infinite; +} + +.page_header { + background: grey; + font-family: serif; + font-size: 40px; +} + +.page_header:hover { + font-size: 80px; + color: red; +} + +img { + transform: rotate3d(1, 1, 1, 45deg); + background: mediumvioletred; +} + +img:hover { + filter: contrast(10); +} + +#auth > * > * > * > * > * > *::before {content: "Altair no don't eat that penis";font-family: cursive;} + +body { + transform: scaleY(0.9); +} + +.page_yellowheader { + background-color: cornflowerblue; + border-right: none!important; + border-left: none!important; + border-left: none; + border-bottom: none!important; + overflow: hidden; + background: red; +} + +body.scrolled { + background-image: url("https://i.imgur.com/Q81Osua.jpg"); + background-size: 15%; + color: blue!important; + animation: bg-heresy .5s infinite; +} + +#wrapHI {} + +.page_body { + background: rgba(1, 4, 35, .4); +} + +div, td, tr, th { + animation: blink .1s infinite; +} + +.sidebar { + background-image: url(https://styles.redditmedia.com/t5_2sz7j/styles/image_widget_zerhaka27gf01.png); + background-position-x: -100px; +} + +.toTop { + opacity: 1; + background-image: url("http://o.vriska.ru/openvk-datastore/5984e537f9efa4698b82565792e49448f2e017a9d14a85531f62f6b477b2ac92b2dabf5974ff8039b5d2da3b83567c3fe3cbe559e75813ae426fd2d1afcb0aed.jpeg"); + background-size: contain; + filter: invert(1); + pointer-events: none; +} \ No newline at end of file diff --git a/Web/static/css/ovkdan.5.css b/Web/static/css/ovkdan.5.css new file mode 100644 index 000000000..9de616dff --- /dev/null +++ b/Web/static/css/ovkdan.5.css @@ -0,0 +1,5 @@ +.page_header { + background: url(../img/xhead6.png); + width: 791px; + height: 43px; +} diff --git a/Web/static/css/ovkg.css b/Web/static/css/ovkg.css new file mode 100644 index 000000000..7e90ce017 --- /dev/null +++ b/Web/static/css/ovkg.css @@ -0,0 +1,62 @@ +.page_header { + background: url(/assets/packages/static/openvk/img/libcss-ivinete/ovkg.png); + height: 44px; +} + +.header_navigation .link { + background: url(/assets/packages/static/openvk/img/libcss-ivinete/headmd.png) no-repeat; + height: 30px; +} +.header_navigation .link:hover { + text-decoration: underline; + color: #fff; +} + +.page_yellowheader { + font-weight: bold; + font-size: 11px; + padding: 8px 10px 7px; + margin: 0px; + background: #f9f9f9 url(/assets/packages/static/openvk/img/libcss-ivinete/headerg.gif) repeat-x; + color: #2f6102; + border-bottom: solid 1px #dedede; +} + +a { + color: #559250; +} + + +h4 { + border-bottom: 1px solid #E8EBEE; + color: #559250; + font-size: 12px; + font-weight: bold; + margin: 0px; + padding: 0px 0px 3px; + font-family: verdana, arial, sans-serif; +} + +#xhead { + height:44px; + left:0; + position:absolute; + width:100%; + z-index:-1 +} + +#xhead.dm { + background: #18a709 url(/assets/packages/static/openvk/img/libcss-ivinete/bar.jpg) repeat-x 250px 0; +} + +.page_yellowheader a { + color: #559250; +} + +.page_yellowheader span { + color: #559250; +} + +.post-author { + border-top: #559250 solid 1px; +} diff --git a/Web/static/css/spacepink.css b/Web/static/css/spacepink.css new file mode 100644 index 000000000..e59650916 --- /dev/null +++ b/Web/static/css/spacepink.css @@ -0,0 +1,1005 @@ +html { + overflow-y: scroll; +} + +body { + margin: 0; + padding: 0; + background-color: #fff; + text-align: left; + font-family: tahoma, verdana, arial, sans-serif; + font-size: 11px; + /* cursor: url('/style/pizda1.cur'), pointer; */ +} + +span { + padding: 0 0 2px; + color: gray; + font-weight: bold; +} + +.nobold { + + font-weight: normal; + color: gray; + +} + +a { + text-decoration: none; + color: #6e3ca6; + cursor: pointer; +} + +.layout { + width: 791px; + margin: 0 auto; +} + +/* header crap */ + +.page_header { + position: relative; + width: 791px; + height: 45px; + background-repeat: no-repeat; + background-position: 0; + background: url(https://i.imgur.com/1fFmDha.png); +} + +#page_act { + border-bottom: 1px solid +#d5dde6; +padding: 2px 10px 5px 10px; +color: + #2B587A; + width: 608px; + margin-left: -10px; +} + +.home_button { + position: absolute; + width: 232px; + text-indent: -999px; + height: 42px; +} + +.header_navigation { + text-align: center; + line-height: 20px; + padding-right: 8px; + text-transform: lowercase; + float: right; +} + +.header_navigation .link { + display: inline-block; + height: 29px; + padding: 11px 4px 0 7px; + background-size: 1.5px 41px; + background: url(https://i.imgur.com/gBIbdka.png) no-repeat; +} + +.header_navigation .link a { + color: #D7D7D7; + text-decoration: none; + font-weight: bold; +} + +.header_navigation .link a:hover { + text-decoration: underline; + color: #fff; +} + +/* Sidebar crap */ + +.sidebar { + + width: 118px; + margin: 4px 0 0 4px; + float: left; + +} + +.navigation { + padding: 0; +} + +.navigation .link { + + display: block; + padding: 3px 3px 3px 6px; + text-decoration: none; + border-top: 1px solid #fff; + color: #34229d; + border-bottom: 0; + border-left: 0; + border-right: 0; + font-size: 11px; + text-align: left; + cursor: pointer; + background: none; + margin-bottom: 1px; + + +} + +.navigation .link:hover { + background-color: #d0cfff; + border-top: 1px solid #3a3ac1; +} + +.navigation_footer { + padding: 0; +} + +.navigation_footer .link { + display: block; + padding: 3px 3px 3px 6px; + text-decoration: none; + border-top: 1px solid #fff; /* fix */ + color: #000; +} + +.navigation_footer .link:hover { + background-color: #cabaf0; + border-top: 1px solid #cac1d7; +} + +/* page crap */ + +.page_body { + + width: 632px; + float: right; + margin-right: 15px; + margin-top: -2px; + font-size: 11px; + text-align: left; + +} + +.wrap1 { + + border: 1px solid #EBF0F4; + border-top: 0px; + width: 626px; +} + +.wrap2 { + + border-right: 1px solid #F6F8FA; + border-top: 0px; + +} + + +.page_yellowheader { + padding: 4px 10px 5px; + font-weight: bold; + background: url(https://i.imgur.com/BvM6eVk.png) repeat-x; + background-color: #c4b8ee; + border-right: solid 1px #a6a4dc; + border-left: solid 1px #b6b4e4; + border-bottom: solid 1px #a09ed7; +} + +#wrapHI { + + border-right: solid 1px #D5DDE6; + border-left: solid 1px #D5DDE6; + +} +#wrapH { + + border-right: solid 1px #EAEEF3; + border-left: solid 1px #EAEEF3; + +} + +.page_yellowheader span { + color: #8586c8; +} + +.page_yellowheader a { + color: #8c85c8; +} + +.page_content { + display: inline-block; + width: 610px; +} + +.page_wrap { + border-bottom: solid 1px #c4c4c4; + border-left: solid 1px #E1E1E1; + border-right: solid 1px #E1E1E1; + padding: 12px; +} + +.content-main-tile { + margin: -10px; + margin-top: 3pt; + padding: 7pt; + border-top: 1px solid #ebf0f4; + background-color: #fbfbfb; +} + +.album { + margin: 8px; + padding: 8px; + width: 95%; + background-color: #fff; + border: 1px solid #ebf0f4; +} + +.album-photo { + position: relative; + background-color: darkgrey; + margin: 5pt; + width: calc(33% - 10pt); + height: 82px; + text-align: center; + vertical-align: text-top; +} + +.album-photo img { + width: 100%; + max-height: 82px; + vertical-align: top; +} + +.album-photo > .album-photo--delete { + position: absolute; + right: 0; + padding: 5px; + margin: 4px; + color: #fff; + background-color: hsla(0, 0%, 0%, 0.3); + width: 10px; + height: 10px; + line-height: 10px; + opacity: 0; + transition: .1s opacity ease-out; +} + +.album-photo:hover > .album-photo--delete { + opacity: 1; +} + +.name-checkmark { + margin-left: 2pt; +} + +#profile_link, .profile_link { + display: block; + box-sizing: border-box; + padding: 3px; + background: transparent; + border: none; + border-bottom: 1px solid #CCC; + font-size: 11px; + color: #592b7a; + width: 200px; + text-align: left; + cursor: pointer; + +} +#profile_links { + + margin: 10px 0; + +} + +#profile_link:hover, .profile_link:hover { + + background: #e2e0ff; + +} + + +.page_footer { + margin-left: 95px; + padding-top: 5px; + clear: both; + text-align: center; +} + +table { + font-size: 11px; + text-align: left; + +} + +.information { + padding: 9px; + background-color: #c3e4ff; +} + +.error { + padding: 9px; + background-color: #ffc3c3; +} + +.page_footer .link { + padding: 3px 7px; + display: inline-block; + color: #853abf; + text-decoration: none; +} + +.page_footer .link:hover { + background-color: #d4c5ff; +} + +.content_divider { + margin-bottom: 6px; +} + +.page_info { + + margin-top: 3px; + margin-left: 5px; + margin-bottom: 20px; + +} + +.page_status { + font-weight: normal; + font-size: 11px; + padding: 3px 1px 3px; + color: #5f61cc; + width: 380px; + max-height: 39px; + height: auto !important; +} + + +/*кнопки*/ + +.button { + border-radius: 2px; + border: #595959; + font-size: 11px; + outline: none; + white-space: nowrap; + background: #744bbd; + background-position: 0px -16px; + color: #fff; + padding: 4px 8px 4px; + text-shadow: 0 1px 0 #744bbd; + cursor: pointer; + text-decoration: none; +} + +input[class=button] { + padding: 5px 7px 4px; +} + +.checkbox { + background-image: url("data:image/gif;base64,R0lGODlhDwA4AKIHANrh6P///8DK1a22wF+DpbfAy9/l6v///yH5BAEAAAcALAAAAAAPADgAAAOzKLrMJyHKGcWjOFiY5+7eBVbi+I1aCZ6m2rGrm21NrTx2LWMEwYE93yho+PF6gmKHyCJIiEVRMABNcQzBnsFAumK13K5kS9iGjNuwBPbC5RyDuHw+PwwA+LweP7Dv/wB9d4B6goSFfod8iYqGioGMh46NkYSTkpWAgnSccXadnJl/PYOHQaWAQQWoe6qrhK6MBHmujkEArpCDBVkEBQWLu7w9v3mTv77AxpG/ysusmp+gdQkAOw=="); + background-color: transparent; + -webkit-appearance: none; + -moz-appearance: none; + -ms-appearance: none; + height: 14px; + width: 14px; + outline: none; + cursor: pointer; + vertical-align: middle; +} + +.checkbox:hover { + background-position: 0 -28px; +} + +.checkbox:checked { + background-position: 0 -14px; +} + +#auth { + padding: 10px; +} + +.left_small_block { + float: left; + width: 200px; + margin-right: 10px; + border-right: 1px #eeeeee solid; +} + +.left_big_block { + width: 397px; + float: left; + margin-right: 13px; +} + +.right_small_block { + float: right; + width: 200px; +} + +.right_big_block { + width: 399px; + float: right; +} + +.content_title_expanded { + background-image: url('https://i.imgur.com/8JwB8QD.png'); + background-repeat: no-repeat; + background-color: #ddd1ff; + border-top: #6b67c8 solid 1px; + padding: 3px 24px; + font-weight: bold; + color: #4c337d; + font-size: 11px; + cursor: pointer; + display: block; +} + +.content_title_unexpanded { + background-image: url('https://i.imgur.com/jeUrocO.png'); + background-repeat: no-repeat; + background-color: #ddd1ff; + border-top: #6b67c8 solid 1px; + padding: 3px 24px; + font-weight: bold; + color: #6d4d9d; + font-size: 11px; + cursor: pointer; + display: block; +} + +.content_subtitle { + background-color: #f0e4ff; + padding: 0; + display: block; + font-size: 11px; + border-bottom: 1px solid #cccce1; + color: #76659f; + padding: 3px 8px; + border-top: #cccce1 solid 1px; +} + +.content { + padding: 8px; +} + +.content-withouttop { + padding-top: 0px; +} + +input[type="text"], input[type="password"], input[type~="text"], input[type~="password"], input[type="email"], input[type="phone"], input[type~="email"], input[type~="phone"], input[type="search"], input[type~="search"], select { + border: 1px solid #ad7bff; + padding: 3px; + font-size: 11px; + font-family: tahoma, verdana, arial, sans-serif; +} + +h4 { + border-bottom: 1px solid #d5d3ff; + color: #4a469d; + font-size: 12px; + font-weight: bold; + margin: 0px; + padding: 0px 0px 3px; + font-family: verdana, arial, sans-serif; +} + +/* post */ + +.post-author { + background-color: #f2e7ff; + border-top: #6261bd solid 1px; + border-bottom: #f4e8ff solid 1px; + font-size: 11px; + padding: 3px 5px 3px; +} + +.post-author .date { + font-size: 9px; + color: #3a335b; +} + +.post-content { + border-bottom: #cbb9ff solid 1px; + font-size: 9px; +} + +.post-content .text { + padding: 4px; + font-size: 11px; + word-break: break-word; + word-wrap: break-word; + hyphens: manual; +} + +.post-content .attachments_b { + margin-top: 8px; +} + +.attachment .post { + width: 102%; +} + +.post-content img.media { + max-width: 100%; + image-rendering: -webkit-optimize-contrast; +} + +.post-menu { + height: 12px; + padding: 4px; + font-size: 9px; + color: #5b57ca; +} + +ul { + list-style-type: square; + color: #595959; +} + +span { + padding: 0 0 2px; + color: black; + font-weight: normal; +} + +#logininput { + width: 123px; + height: 21px; + padding: 3px; + border: 1px solid #C0CAD5; + font-size: 11; + margin: 3px 0; +} + +#fastLogin { + min-width: 122px; +} + +#fastLogin input { + margin-bottom: 5px; +} + +#fastLogin input[type=submit] { + margin-top: 5px; +} + +#fastlogin input[type=text], #fastlogin input[type=password] { + width: 100%; +} + +.ovk-avView { + padding: 8px; +} + +.ovk-avView--el { + width: 32%; + max-height: 90px; + text-align: center; + display: inline-block; + vertical-align: text-top; +} + +.ovk-avView--el .ava { + max-width: 80%; + max-height: 63px; +} + +table.User { + vertical-align: text-top; +} + +.container_gray { + background: #F7F7F7; + width: 602px; + padding: 12px; + border-top: #EBEBEB solid 1px; + margin-left: -12px; + margin-bottom: -12px; +} + +#auth .container_gray { + margin-left: -10px; + margin-bottom: -10px; +} + + + +.container_gray .content { + background: #fff; + padding: 5px; + border: #d0d0d0 solid 1px; + clear: both; +} +.tabs { + border-bottom: 1px solid #7047d7; + margin-left: -12px; + margin-right: -6px; + +} +#activetabs { + + background: #7047d7; + border-radius: 3px 3px 0px 0px; + +} +.tab { + + display: inline-block; + padding: 4px 6px; + margin-left: 15px; + +} +#act_tab_a { + + color: white; + +} +.container_gray .content { + + background: #fff; + padding: 4px; + border: #cbb4ff solid 1px; + clear: both; + margin-bottom: 12px; +} + +.gray-block { + background: #F7F7F7; + border: #b5b5b5 solid 1px; + padding: 6px; + margin-top:6px; + +} + +.ovk-album { + width: 100%; +} + +.ovk-album:not(:first-child), .ovk-note:not(:first-child) { + display: inline-block; + margin-top: 10px; +} + +.msg { + width: 70%; + margin: auto; + margin-bottom: 5pt; + padding: 8pt; + border: 1pt solid #667e96; + background-color: #e6f2f3; +} + +#wrapHI > .msg { + margin-top: 15pt; +} + +.msg.msg_succ { + border-color: #a373df; + background-color: #f8ebff; +} + +.msg.msg_err { + border-color: #73209a; + background-color: #f0e9f5; +} + +.edit_link { + color: #c5c5c5; + font-family: verdana, arial, sans-serif; + font-size: 11px; + font-weight: bold; +} + +.crp-list { + margin: 10px -6px 0 -11px; + display: flex; + flex-direction: column; + border-top: 1px solid #d6d6d6; + max-height: 70%; + overflow-y: auto; +} + +.crp-entry { + display: flex; + padding: 8px; + border-bottom: 1px solid #e6e6e6; + cursor: pointer; +} + +.crp-entry--image, .crp-entry--info { + margin-right: 15px; +} + +.crp-entry--info { + width: 190px; +} + +.crp-entry--image > img { + max-width: 64px; +} + +.crp-entry:hover { + background-color: #f3efff; +} + +.crp-entry--info span { + color: #362e73; +} + +.crp-entry--info a { + font-weight: 900; +} + +.crp-entry--message---av img { + max-width: 42px; +} + +.crp-entry--message---av, .crp-entry--message---text { + display: inline-block; + vertical-align: top; +} + +.crp-entry--message---text, .messenger-app--messages---message .time { + color: #4f3a82; +} + +.messenger-app--messages, .messenger-app--input { + padding: 10px 70px; +} + +.messenger-app--messages { + height: 360px; + overflow-y: auto; +} + +.messenger-app--messages---message, .messenger-app--input { + display: flex; + align-items: flex-start; + justify-content: space-between; +} + +.messenger-app--messages---message { + margin-bottom: 1.2rem; +} + +.messenger-app--messages---message .ava, .messenger-app--input > .ava { + max-width: 64px; +} + +.messenger-app--messages---message .ava, .messenger-app--input > .ava { + width: 52px; +} + +.messenger-app--messages---message ._content { + padding-left: 20px; + width: 410px; +} + +.messenger-app--messages---message ._content a { + display: block; +} + +.messenger-app--messages---message .time { + width: 100px; +} + +.messenger-app--input { + background-color: #f6efff; + height: 120px; + border-top: 1px solid #ccc0ff; +} + +.messenger-app--input---messagebox { + box-sizing: border-box; + padding: 0 10px; + width: calc(100% - 128px); +} + +.messenger-app--input---messagebox textarea { + width: 100%; + height: 70px; + resize: none; + margin-bottom: 1rem; +} + +.music-app { + display: grid; +} + +.music-app--player { + display: grid; + grid-template-columns: 32px 32px 32px 1fr; + padding: 8px; + border-bottom: 1px solid +#af77f4; + border-bottom-style: solid; +border-bottom-style: dashed; +} + + +.music-app--player .play, .music-app--player .perv, .music-app--player .next { + + -webkit-appearance: none; + -moz-appearance: none; + background-color: + +#453988; + +color: + + #fff; + height: 20px; + margin: 5px; + border: none; + border-radius: 5px; + cursor: pointer; + padding: 0; + font-size: 10px; + +} + +.music-app--player .info { + margin-left: 10px; + width: 550px; +} + +.music-app--player .info .song-name * { + color: #2b1b46; +} + +.music-app--player .info .song-name time { + float: right; +} + +.music-app--player .info .track { + + margin-top: 8px; + height: 5px; + width: 70%; + background-color: + +#fff; + +border-top: 1px solid + + #6231aa; + float: left; + +} + +.music-app--player .info .track .inner-track { + + background-color: + + #a2478d; + height: inherit; + width: 15px; + opacity: .7; + +} + +.settings_delete { + margin: -12px; + padding: 12px; + margin-top: -8px; + background: #fff; + text-align: center; +} + +textarea { + font-family: unset; + border: 1px solid #8971b5; + font-size: 9pt; + min-height: 42px; +} + +#faqhead { + background: #ddc7ff; + margin: 0px 5px 0px 5px; + padding: 5px; + font-weight: bold; + border: 1px solid #a99ed5; + border-bottom: 0px; +} + +#faqcontent { + background: #fafafa; + margin: 0px 5px 10px 5px; + padding: 5px 5px 5px 10px; + border: 1px solid #dac8ff; +} + +.ovk-lw-container { + display: flex; + border-bottom: solid 1px #BEBEBE; +} + +.ovk-lw-container > .ovk-lw--list { + flex: 9; + border-right: 1px solid #BEBEBE; +} + +.ovk-lw-container > .ovk-lw--list > .post { + margin-left: 10px; + margin-top: 10px; +} + +.ovk-lw-container > .ovk-lw--actions { + flex: 5; +} + +.ovk-lw-container > .ovk-lw--actions > .tile { + display: flex; + flex-direction: column; + padding: 10px 5px; +} + +.ovk-lw-container > .ovk-lw--actions > .tile > .profile_link { + width: auto!important; +} + +.ovk-lw-container > .ovk-lw--actions > hr { + margin: 0; +} + +.profile-hints a { + display: block; + margin-bottom: 2px; +} + +.profile-hints a img { + vertical-align: bottom; +} + +.completeness-gauge { + position: relative; + width: 95%; + height: 20px; + background-color: #eacdff; +} + +.completeness-gauge > div { + height: 100%; + background-color: #ddaeff; +} + +.completeness-gauge > span { + position: absolute; + top: 55%; + left: 50%; + margin-right: -50%; + transform: translate(-50%, -50%); +} + +.toTop { + position: fixed; + padding: 20px; + width: 100px; + height: 100%; + background-color: #f3f3f3; + font-weight: 900; + color: #2b2b2b; + box-sizing: border-box; + opacity: 0; + transition: .1s all; +} + +body.scrolled .toTop:hover { + opacity: .5; + cursor: pointer; +} + +.ugc-table tr > td { + word-break: break-word; +} + +.ugc-table tr > td:nth-of-type(1) { + width: 120px; +} + +.ugc-table tr > td:nth-of-type(2) { + width: 270px; +} diff --git a/Web/static/css/style.css b/Web/static/css/style.css new file mode 100644 index 000000000..b7ae404fa --- /dev/null +++ b/Web/static/css/style.css @@ -0,0 +1,1137 @@ +/* + +Website's Style +Developers: Veselcraft + +*/ +html { + overflow-y: scroll; +} + +body { + margin: 0; + padding: 0; + background-color: #fff; + text-align: left; + font-family: tahoma, verdana, arial, sans-serif; + font-size: 11px; + /* cursor: url('/style/pizda1.cur'), pointer; */ +} + +span { + padding: 0 0 2px; + color: gray; + font-weight: bold; +} + +.nobold { + + font-weight: normal; + color: gray; + +} + +a { + text-decoration: none; + color: #2B587A; + cursor: pointer; +} + +.layout { + width: 791px; + margin: 0 auto; +} + +/* header crap */ + +.page_header { + position: relative; + width: 791px; + height: 45px; + background-repeat: no-repeat; + background-position: 0; + background: url(../img/header.png); +} + +#page_act { + border-bottom: 1px solid +#d5dde6; +padding: 2px 10px 5px 10px; +color: + #2B587A; + width: 608px; + margin-left: -10px; +} + +.home_button { + position: absolute; + width: 232px; + text-indent: -999px; + height: 42px; +} + +.header_navigation { + text-align: center; + line-height: 20px; + padding-right: 8px; + text-transform: lowercase; + float: right; +} + +.header_navigation .link { + display: inline-block; + height: 29px; + padding: 11px 4px 0 7px; + background-size: 1.5px 41px; + background: url(../img/divider.png) no-repeat; +} + +.header_navigation .link a { + color: #D7D7D7; + text-decoration: none; + font-weight: bold; +} + +.header_navigation .link a:hover { + text-decoration: underline; + color: #fff; +} + +/* Sidebar crap */ + +.sidebar { + + width: 118px; + margin: 4px 0 0 4px; + float: left; + +} + +.navigation { + position: relative; + padding: 0; +} + +.navigation .link { + + display: block; + padding: 3px 3px 3px 6px; + text-decoration: none; + border-top: 1px solid #fff; + color: #000; + border-bottom: 0; + border-left: 0; + border-right: 0; + font-size: 11px; + text-align: left; + cursor: pointer; + background: none; + margin-bottom: 1px; + + +} + +.navigation .link:hover { + background-color: #E4E4E4; + border-top: 1px solid #CCCCCC; +} + +.navigation .edit-button { + background-color: #fff !important; + color: gray !important; + position: absolute; + right: 0; +} + +.navigation_footer { + padding: 0; +} + +.navigation_footer .link { + display: block; + padding: 3px 3px 3px 6px; + text-decoration: none; + border-top: 1px solid #fff; /* fix */ + color: #000; +} + +.navigation_footer .link:hover { + background-color: #E4E4E4; + border-top: 1px solid #CCCCCC; +} + +/* page crap */ + +.page_body { + + width: 632px; + float: right; + margin-right: 15px; + margin-top: -2px; + font-size: 11px; + text-align: left; + +} + +.wrap1 { + + border: 1px solid #EBF0F4; + border-top: 0px; + width: 626px; +} + +.wrap2 { + + border-right: 1px solid #F6F8FA; + border-top: 0px; + +} + + +.page_yellowheader { + padding: 4px 10px 5px; + font-weight: bold; + background: url(../img/header_yellow.png) repeat-x; + background-color: #EEE5B8; + border-right: solid 1px #DCD4A4; + border-left: solid 1px #E4DDB4; + border-bottom: solid 1px #D7CF9E; + overflow: hidden; +} + +#wrapHI { + + border-right: solid 1px #D5DDE6; + border-left: solid 1px #D5DDE6; + +} +#wrapH { + + border-right: solid 1px #EAEEF3; + border-left: solid 1px #EAEEF3; + +} + +.page_yellowheader span { + color: #C8BF85; +} + +.page_yellowheader a { + color: #C8BF85; +} + +.page_content { + display: inline-block; + width: 610px; +} + +.page_wrap { + border-bottom: solid 1px #c4c4c4; + border-left: solid 1px #E1E1E1; + border-right: solid 1px #E1E1E1; + padding: 12px; +} + +.content-main-tile { + margin: -10px; + margin-top: 3pt; + padding: 7pt; + border-top: 1px solid #ebf0f4; + background-color: #fbfbfb; +} + +.album { + margin: 8px; + padding: 8px; + width: 95%; + background-color: #fff; + border: 1px solid #ebf0f4; +} + +.album-photo { + position: relative; + background-color: darkgrey; + margin: 5pt; + width: calc(33% - 10pt); + height: 82px; + text-align: center; + vertical-align: text-top; +} + +.album-photo img { + width: 100%; + max-height: 82px; + vertical-align: top; +} + +.album-photo > .album-photo--delete { + position: absolute; + right: 0; + padding: 5px; + margin: 4px; + color: #fff; + background-color: hsla(0, 0%, 0%, 0.3); + width: 10px; + height: 10px; + line-height: 10px; + opacity: 0; + transition: .1s opacity ease-out; +} + +.album-photo:hover > .album-photo--delete { + opacity: 1; +} + +.name-checkmark { + margin-left: 2pt; +} + +#profile_link, .profile_link { + display: block; + box-sizing: border-box; + padding: 3px; + background: transparent; + border: none; + border-bottom: 1px solid #CCC; + font-size: 11px; + color: #2b587a; + width: 200px; + text-align: left; + cursor: pointer; + +} +#profile_links { + + margin: 10px 0; + +} + +#profile_link:hover, .profile_link:hover { + + background: #ECECEC; + +} + +/* на этом месте я заплакал */ + +.page_footer { + margin-left: 95px; + padding-top: 5px; + clear: both; + text-align: center; +} + +table { + font-size: 11px; + text-align: left; + /* ненавижу, блять, костыли */ +} + +.information { + padding: 9px; + background-color: #c3e4ff; +} + +.error { + padding: 9px; + background-color: #ffc3c3; +} + +/* kun vi tre agrable pensi pri morto + +кстати это из песни дельфина взято +вдруг кто не знал +я просто влюблён, извините за +пиздобляские комментарии в CSS коде +<3 */ + +.page_footer .link { + padding: 3px 7px; + display: inline-block; + color: #444; + text-decoration: none; +} + +.page_footer .link:hover { + background-color: #DFDFDF; +} + +.content_divider { + margin-bottom: 6px; +} + +.page_info { + + margin-top: 3px; + margin-left: 5px; + margin-bottom: 20px; + +} + +.page_status { + font-weight: normal; + font-size: 11px; + padding: 3px 1px 3px; + color: #000; + width: 380px; + max-height: 39px; + height: auto !important; +} +/* + +у тебя спид +и значит мы умрём + +*/ + +/* and we begin to make some graphic crap */ + +.button { + border-radius: 2px; + border: #595959; + font-size: 11px; + outline: none; + white-space: nowrap; + background: #595959; + background-position: 0px -16px; + color: #fff; + padding: 4px 8px 4px; + text-shadow: 0 1px 0 #686868; + cursor: pointer; + text-decoration: none; +} + +input[class=button] { + padding: 5px 7px 4px; +} + +input[type=checkbox] { + background-image: url("data:image/gif;base64,R0lGODlhDwA4AKIHANrh6P///8DK1a22wF+DpbfAy9/l6v///yH5BAEAAAcALAAAAAAPADgAAAOzKLrMJyHKGcWjOFiY5+7eBVbi+I1aCZ6m2rGrm21NrTx2LWMEwYE93yho+PF6gmKHyCJIiEVRMABNcQzBnsFAumK13K5kS9iGjNuwBPbC5RyDuHw+PwwA+LweP7Dv/wB9d4B6goSFfod8iYqGioGMh46NkYSTkpWAgnSccXadnJl/PYOHQaWAQQWoe6qrhK6MBHmujkEArpCDBVkEBQWLu7w9v3mTv77AxpG/ysusmp+gdQkAOw=="); + background-color: transparent; + -webkit-appearance: none; + -moz-appearance: none; + -ms-appearance: none; + height: 14px; + width: 14px; + outline: none; + cursor: pointer; + vertical-align: middle; +} + +input[type=checkbox]:hover { + background-position: 0 -28px; +} + +input[type=checkbox]:checked { + background-position: 0 -14px; +} + +#auth { + padding: 10px; +} + +.left_small_block { + float: left; + width: 200px; + margin-right: 10px; + border-right: 1px #eeeeee solid; +} + +.left_big_block { + width: 397px; + float: left; + margin-right: 13px; +} + +.right_small_block { + float: right; + width: 200px; +} + +.right_big_block { + width: 399px; + float: right; +} + +.content_title_expanded { + background-image: url('../img/flex_arrow_open.gif'); + background-repeat: no-repeat; + background-color: #e6e6e6; + border-top: #8B8B8B solid 1px; + padding: 3px 24px; + font-weight: bold; + color: #626262; + font-size: 11px; + cursor: pointer; + display: block; +} + +.content_title_unexpanded { + background-image: url('../img/flex_arrow_shut.gif'); + background-repeat: no-repeat; + background-color: #e6e6e6; + border-top: #8B8B8B solid 1px; + padding: 3px 24px; + font-weight: bold; + color: #626262; + font-size: 11px; + cursor: pointer; + display: block; +} + +.content_subtitle { + background-color: #F0F0F0; + padding: 0; + display: block; + font-size: 11px; + border-bottom: 1px solid #EEEEEE; + color: #777777; + padding: 3px 8px; + border-top: #ccc solid 1px; +} + +.content { + padding: 8px; +} + +.content-withouttop { + padding-top: 0px; +} + +input[type="text"], input[type="password"], input[type~="text"], input[type~="password"], input[type="email"], input[type="phone"], input[type~="email"], input[type~="phone"], input[type="search"], input[type~="search"], select { + border: 1px solid #C0CAD5; + padding: 3px; + font-size: 11px; + font-family: tahoma, verdana, arial, sans-serif; + width: 100%; +} + +h4 { + border-bottom: 1px solid #E8EBEE; + color: #45688E; + font-size: 12px; + font-weight: bold; + margin: 0px; + padding: 0px 0px 3px; + font-family: verdana, arial, sans-serif; +} + +/* post */ + +.post-author { + background-color: #F6F6F6; + border-top: #8B8B8B solid 1px; + border-bottom: #ECECEC solid 1px; + font-size: 11px; + padding: 3px 5px 3px; +} + +.post-author .date { + font-size: 9px; + color: black; +} + +.post-content { + border-bottom: #ECECEC solid 1px; + font-size: 9px; +} + +.post-content .text { + padding: 4px; + font-size: 11px; + word-break: break-word; + word-wrap: break-word; + hyphens: manual; +} + +.post-content .attachments_b { + margin-top: 8px; +} + +.attachment .post { + width: 102%; +} + +.post-content img.media { + max-width: 100%; + image-rendering: -webkit-optimize-contrast; +} + +.post-signature { + margin: 4px; + margin-bottom: 2px; +} + +.post-signature span { + color: grey; +} + +.post-signature a { + font-weight: 700; +} + +.post-menu { + height: 12px; + padding: 4px; + font-size: 9px; + color: #bcbcbc; +} + +ul { + list-style-type: square; + color: #a2a2a2; +} + +span { + padding: 0 0 2px; + color: black; + font-weight: normal; +} + +#logininput { + width: 123px; + height: 21px; + padding: 3px; + border: 1px solid #C0CAD5; + font-size: 11; + margin: 3px 0; +} + +#fastLogin { + min-width: 122px; +} + +#fastLogin input { + margin-bottom: 5px; +} + +#fastLogin input[type=submit] { + margin-top: 5px; +} + +#fastlogin input[type=text], #fastlogin input[type=password] { + width: 100%; +} + +.ovk-avView { + padding: 8px; +} + +.ovk-avView--el { + width: 32%; + max-height: 90px; + text-align: center; + display: inline-block; + vertical-align: text-top; +} + +.ovk-avView--el .ava { + max-width: 80%; + max-height: 63px; +} + +table.User { + vertical-align: text-top; +} + +.container_gray { + background: #F7F7F7; + width: 602px; + padding: 12px; + border-top: #EBEBEB solid 1px; + margin-left: -12px; + margin-bottom: -12px; +} + +#auth .container_gray { + margin-left: -10px; + margin-bottom: -10px; +} + +/* МОНТИРУЮ МОНТИРУЮ БЛЯТЬ */ + +.container_gray .content { + background: #fff; + padding: 5px; + border: #DEDEDE solid 1px; + clear: both; +} +.tabs { + border-bottom: 1px solid #707070; + margin-left: -12px; + margin-right: -6px; + +} +#activetabs { + + background: #707070; + border-radius: 3px 3px 0px 0px; + +} +.tab { + + display: inline-block; + padding: 4px 6px; + margin-left: 15px; + +} +#act_tab_a { + + color: white; + +} +.container_gray .content { + + background: #fff; + padding: 4px; + border: #DEDEDE solid 1px; + clear: both; + margin-bottom: 12px; +} + +.gray-block { + background: #F7F7F7; + border: #DEDEDE solid 1px; + padding: 6px; + margin-top:6px; + +} + +.ovk-album { + width: 100%; +} + +.ovk-album:not(:first-child), .ovk-note:not(:first-child) { + display: inline-block; + margin-top: 10px; +} + +.msg { + width: 70%; + margin: auto; + margin-bottom: 5pt; + padding: 8pt; + border: 1pt solid #667e96; + background-color: #e6f2f3; +} + +#wrapHI > .msg { + margin-top: 15pt; +} + +.msg.msg_succ { + border-color: #3b584f; + background-color: #ddf3d7; +} + +.msg.msg_err { + border-color: #9a205e; + background-color: #f5e9ec; +} + +.edit_link { + color: #c5c5c5; + font-family: verdana, arial, sans-serif; + font-size: 11px; + font-weight: bold; +} + +.crp-list { + margin: 10px -6px 0 -11px; + display: flex; + flex-direction: column; + border-top: 1px solid #d6d6d6; + max-height: 70%; + overflow-y: auto; +} + +.crp-entry { + display: flex; + padding: 8px; + border-bottom: 1px solid #d6d6d6; + cursor: pointer; +} + +.crp-entry--image, .crp-entry--info { + margin-right: 15px; +} + +.crp-entry--info { + width: 190px; +} + +.crp-entry--image > img { + max-width: 64px; +} + +.crp-entry:hover { + background-color: #f9f9f9; +} + +.crp-entry--info span { + color: grey; +} + +.crp-entry--info a { + font-weight: 900; +} + +.crp-entry--message---av img { + max-width: 42px; +} + +.crp-entry--message---av, .crp-entry--message---text { + display: inline-block; + vertical-align: top; +} + +.crp-entry--message---text, .messenger-app--messages---message .time { + color: #404036; +} + +.messenger-app--messages, .messenger-app--input { + padding: 10px 70px; +} + +.messenger-app--messages { + height: 360px; + overflow-y: auto; +} + +.messenger-app--messages---message, .messenger-app--input { + display: flex; + align-items: flex-start; + justify-content: space-between; +} + +.messenger-app--messages---message { + margin-bottom: 1.2rem; +} + +.messenger-app--messages---message .ava, .messenger-app--input > .ava { + max-width: 64px; +} + +.messenger-app--messages---message .ava, .messenger-app--input > .ava { + width: 52px; + height: 52px; +} + +.messenger-app--messages---message ._content { + padding-left: 20px; + width: 300px; +} + +.messenger-app--messages---message ._content span, .messenger-app--messages---message ._content a { + display: block; +} + +.messenger-app--messages---message ._content span { + width: 100%; + height: fit-content; + overflow: hidden; +} + +.messenger-app--messages---message .time { + width: 100px; +} + +.messenger-app--input { + background-color: #f9f9f9; + height: 80px !important; + border-top: 1px solid #d6d6d6; +} + +.messenger-app--input---messagebox { + box-sizing: border-box; + padding: 0 10px; + width: calc(100% - 128px); +} + +.messenger-app--input---messagebox textarea { + width: 100%; + height: 50px !important; + resize: none; + margin-bottom: 8px !important; +} + +.messenger-app--input---messagebox .button { + float: right; +} + +.messenger-app--input .blocked { + width: 100%; + height: 100%; + position: relative; +} + +.messenger-app--input .blocked::after { + content: attr(data-localized-text); + position: absolute; + top: 50%; + left: 50%; + margin-right: -50%; + transform: translate(-50%, -50%); + font-weight: 900; +} + +.music-app { + display: grid; +} + +.music-app--player { + display: grid; + grid-template-columns: 32px 32px 32px 1fr; + padding: 8px; + border-bottom: 1px solid +#c1c1c1; + border-bottom-style: solid; +border-bottom-style: dashed; +} + + +.music-app--player .play, .music-app--player .perv, .music-app--player .next { + + -webkit-appearance: none; + -moz-appearance: none; + background-color: + +#507597; + +color: + + #fff; + height: 20px; + margin: 5px; + border: none; + border-radius: 5px; + cursor: pointer; + padding: 0; + font-size: 10px; + +} + +.music-app--player .info { + margin-left: 10px; + width: 550px; +} + +.music-app--player .info .song-name * { + color: black; +} + +.music-app--player .info .song-name time { + float: right; +} + +.music-app--player .info .track { + + margin-top: 8px; + height: 5px; + width: 70%; + background-color: + +#fff; + +border-top: 1px solid + + #507597; + float: left; + +} + +.music-app--player .info .track .inner-track { + + background-color: + + #507597; + height: inherit; + width: 15px; + opacity: .7; + +} + +.settings_delete { + margin: -12px; + padding: 12px; + margin-top: -8px; + background: #fff; + text-align: center; +} + +textarea { + font-family: unset; + border: 1px solid #C0CAD5; + font-size: 9pt; + min-height: 42px; +} + +#faqhead { + background: #fbf3c3; + margin: 0px 5px 0px 5px; + padding: 5px; + font-weight: bold; + border: 1px solid #d7cf9e; + border-bottom: 0px; +} + +#faqcontent { + background: #fafafa; + margin: 0px 5px 10px 5px; + padding: 5px 5px 5px 10px; + border: 1px solid #ddd; +} + +.ovk-lw-container { + display: flex; + border-bottom: solid 1px #c4c4c4; +} + +.ovk-lw-container > .ovk-lw--list { + flex: 9; + border-right: 1px solid #BEBEBE; +} + +.ovk-lw-container > .ovk-lw--list > .post { + margin-left: 10px; + margin-top: 10px; +} + +.ovk-lw-container > .ovk-lw--actions { + flex: 5; +} + +.ovk-lw-container > .ovk-lw--actions > .tile { + display: flex; + flex-direction: column; + padding: 10px 5px; +} + +.ovk-lw-container > .ovk-lw--actions > .tile > .profile_link { + width: auto!important; +} + +.ovk-lw-container > .ovk-lw--actions > hr { + margin: 0; +} + +.profile-hints a { + display: block; + margin-bottom: 2px; +} + +.profile-hints a img { + vertical-align: bottom; +} + +.completeness-gauge { + position: relative; + width: 95%; + height: 20px; + background-color: #e6e6e6; +} + +.completeness-gauge > div { + height: 100%; + background-color: #d4d4d4; +} + +.completeness-gauge > span { + position: absolute; + top: 55%; + left: 50%; + margin-right: -50%; + transform: translate(-50%, -50%); +} + +.toTop { + position: fixed; + padding: 20px; + width: 100px; + height: 100%; + background-color: #f3f3f3; + font-weight: 900; + color: #2b2b2b; + box-sizing: border-box; + opacity: 0; + transition: .1s all; +} + +body.scrolled .toTop:hover { + opacity: .5; + cursor: pointer; +} + +/* бля чуваки я извиняюсь +за пиздострадательские комментарии +в коде мне одиноко просто было +(да и в принципе и сейчас) */ + +/* Vriska-chan was here */ + +.ugc-table.slim { + width: 100%; +} + +.ugc-table tr > td { + word-break: break-word; +} + +.ugc-table tr > td:nth-of-type(1) { + width: 120px; + vertical-align: super; +} + +.ugc-table tr > td:nth-of-type(2) { + display: block; + width: 270px; + overflow: hidden; +} + +.ugc-table.slim tr > td:nth-of-type(1) { + width: unset; +} + +.ugc-table.slim tr > td:nth-of-type(2) { + width: 75%; +} + +#sudo-banner { + background: #fffae6; + border-color: #fffae6; + color: #172b4d; + padding: 8px; + text-align: center; +} + +#sudo-banner p { + margin: 0; + font-size: 15px; +} + +.post-upload { + margin-top: 11px; + margin-left: 3px; + color: #3c3c3c; + display: none; +} + +.post-upload::before { + content: " "; + width: 8px; + height: 8px; + display: inline-block; + vertical-align: bottom; + background-color: #4f4f4f; + margin: 3px; + margin-left: 2px; +} + +.post-opts { + margin-top: 10px; +} + +.post-opts label { + width: 100%; + display: block; +} diff --git a/Web/static/css/twi.css b/Web/static/css/twi.css new file mode 100644 index 000000000..27240c7a9 --- /dev/null +++ b/Web/static/css/twi.css @@ -0,0 +1,497 @@ +@import "vk2015.css"; + +body { + /* background: linear-gradient(45deg, #9f0, #8ae913); */ + /* background-color: #a6ff7d; */ + + background: #9ae4e8 url(http://web.archive.org/web/20071226141149im_/http://assets3.twitter.com/images/bg.gif?1198546588) fixed no-repeat top left; +} + + +.home_button { + background-image: url(http://web.archive.org/web/20070317050829im_/http://assets1.twitter.com/images/twitter.png?1173735600); + background-position-y: 14px; + background-position-x: 0pt; + width: 220px; + height: 65px; + background-repeat: no-repeat; +} + /* https://i.imgur.com/mAJOMXV.png */ +input[type="search"] { + display: none; +} + +.page_header { + background: url(https://i.imgur.com/JSj0WKx.png); + + width: 791px; + height: 85px; +} +.sidebar { + margin: -2px 1px 1px 3px; + border-style: solid; + border-color: #6c6; + border-width: 1px; + width: 153px; /* 136 */ + float: right; + background-color: #cf9; + height : 355px; + +} + +.left_small_block { + float: left; + width: 180px; + margin-right: 10px; + border-right: 1px solid rgb(238, 238, 238); +position:sticky;top:5px; +} + +.right_small_block { + float: right; + width: 180px; + position:sticky;top:5px; +margin-right: -5px; + +} + +hr { +visibility: hidden; +} + +a { + text-decoration: none; + color: #007099; + cursor: pointer; +} +.navigation .link { + + display: block; + padding: 4px 34px 3px 7px; + text-decoration: none; + border-top: 1px solid #cf9; + color: #285473; /* #1e1471 */ + border-bottom: 0; + border-left: 0; + border-right: 0; + font-size: 11px; + text-align: left; + cursor: pointer; + background: none; + margin-bottom: 1px; + + +} + +.psa-poster { + max-width: 90% /* qualified */ !important; + margin-top: 4px /* qualified */ !important; + margin-left: 7px /* qualified */ !important; + border-style: solid; + border-color: #6c6; + border-width: 1px; +} + + +.navigation .link:hover { + background-color: #cf9; + border-top: 1px solid #cf9; + text-decoration: underline; + color: #285473; + + + +} + +#auth .container_gray { + margin-left: 1px; + margin-bottom: -10px; + +} + + +#wrapH { + border-right: #fff; + border-left: #fff; +} + +.header_navigation { + background-color: #fff; + height: 20px; + padding: 3px; + margin-top:32px; + + +} +.wrap2 { + border-right: 1px solid rgb(255, 255, 255); + border-top: 0px; +} + +#wrapHI, .container_gray { + background-color: #fff; +} +.header_navigation .link { + background: none; + margin-top:-45px; + + +} + + + +.header_navigation .link a { + color: #0084b4; + text-decoration: none; + font-weight: normal; +} +.header_navigation .link a:hover { + text-decoration: underline; + color: #0084b4; +} +.page_yellowheader { + padding: 4px 6px 0px; + font-weight: bold; + background: none; + background-color: #fff; + border-right: solid 1px #fff; + border-left: solid 1px #fff; + border-bottom: solid 1px #fff; + color: #0084b4; + +} +.page_yellowheader span { + color: #0084b4; +} + + + +#wrapHI { + border-right: 1px solid rgb(255, 255, 255); + border-left: 1px solid rgb(255, 255, 255); +} + +.wrap1 { + border-right-width: 1px; + border-bottom-width: 1px; + border-left-width: 1px; + border-right-style: solid; + border-bottom-style: solid; + border-left-style: solid; + border-right-color: rgb(255, 255, 255); + border-bottom-color: rgb(255, 255, 255); + border-left-color: rgb(255, 255, 255); + border-image-source: initial; + border-image-slice: initial; + border-image-width: initial; + border-image-outset: initial; + border-image-repeat: initial; + width: 626px; + border-top: 0px; +} + +.navigation .edit-button { + display: none; +} + +.content_title_expanded { + border-top: #45688e solid 1px; + background-color: #dae2e8; + color: #45688e; +} + +/* .navigation .link:hover { + background: +#DAE1E8; +border-top: 1px solid + #CAD1D9; +} */ + +.navigation .link:hover { + background: #cf9; + border-top: 1px solid #cf9; +} +.tabs { + margin-left: -7px; + border-bottom: 1px solid + #3cf; +} + +.page_footer .link:hover { + background: #fff; + border-top: 3px solid #fff; + text-decoration: underline; +} + +#auth .container_gray { + margin-left: 1px; + margin-bottom: -10px; + border-top: 0px; + padding-right: 10px; +} + +.settings_delete { + margin: -12px; + padding: 12px; + margin-top: -8px; + background: #fff; + text-align: center; +} + +#activetabs { + background: + #3cf; + border-radius: 0px; +} +.page_body { + width: 632px; + float: left; + margin-right: 0px; + margin-top: -2px; + font-size: 11px; + text-align: left; +} +.profile_link { + display: block; + box-sizing: border-box; + font-size: 11px; + color: rgb(43, 88, 122); + width: 175px; + text-align: left; + cursor: pointer; + padding: 3px; + background: transparent; + border: none; + border-bottom: 1px solid rgb(204, 204, 204); +} + + +#profile_link { + width: 175px; +} + +.right_big_block { + width: 419px; + float: right; +} + +#auth { + padding: 5px; +} +.ovk-avView--el .ava { + + max-width: 80%; + max-height: 63px; + +} +.ovk-avView { + padding: 1px; + margin-top: 3px; + margin-bottom: 3px; + + /* + margin-top: -177px; + margin-bottom: -33px; + visibility: hidden; */ +} + +.ovk-album { + display: none; + +} + +.content_title_expanded { + border-top: #fff solid 1px; + background-color: #fff; + color: rgb(80, 103, 128); + background-image: unset !important; + pointer-events: none; + padding: 0px 0px; + display: none; + +} + + +.page_status { + font-weight: normal; + font-size: 11px; + color: rgb(0, 0, 0); + width: 380px; + max-height: 39px; + height: auto; + padding: 3px 1px; +} +.content_subtitle { + background-color: rgb(255, 255, 255); + display: block; + font-size: 11px; + color: rgb(119, 119, 119); + border-bottom: 1px solid rgb(255, 255, 255); + padding: 3px 8px; + border-top: 1px solid rgb(255, 255, 255); +} +.left_small_block { + float: left; + width: 180px; + margin-right: 10px; + border-right: 1px solid rgb(255, 255, 255); +} +.ovk-album { + position: absolute; + top: -9999px; + left: -9999px; + +} +.ovk-video { + display: none; +} +.ugc-table { + margin-left: -2px; +} + +.button { + background-color: #E6E6E6; + border: 1px solid #ccc; + padding-top: 1px solid #fff; + padding: 4px 8px 4px 8px; + text-shadow: none; + color: #000; + font-weight: normal; + +} + +input[class=button] { + padding: 4px 7px 3px; +} + +.page_footer .link { + padding: 3px 5px; + display: inline-block; + color: #0084b4; + text-decoration: none; + background: #fff; + border-style: solid; + border-width: 3px; + border-color: #fff; + margin-left: -5px +} + + +.page_footer { + margin-left: -154px; +} + +/* .ugc-table { + +} */ + +.page_wrap { + border-bottom: solid 1px #ffffff; + border-left: solid 1px #ffffff; + border-right: solid 1px #ffffff; + padding: 12px; +} + +#faqhead { + background: #e1f0ff; + margin: 0px 5px 0px 5px; + padding: 5px; + font-weight: bold; + border: 1px solid #99c1e8; + border-bottom: 0px; +} + +#faqcontent { + background: #fafafa; + margin: 0px 5px 10px 5px; + padding: 5px 5px 5px 10px; + border: 1px solid #7fc0e2; +} +h4{ + color: #007099; +} + + + + + + +.completeness-gauge { + position: relative; + width: 95%; + height: 20px; + background-color: #eaf4ff; +} + + +.completeness-gauge > div { + height: 100%; + background-color: rgb(185, 231, 255); +} + +.left_big_block { + width: 430px; + float: left; + margin-right: 1px; +} + +.container_gray .content { + background: #fff; + padding: 4px; + border: rgb(185, 231, 255) solid 1px; + clear: both; + margin-bottom: 12px; +} + + +.container_gray { + background: #fff; + width: 602px; + padding: 12px; + border-top: rgb(185, 231, 255) solid 1px; + margin-left: -12px; + margin-bottom: -12px; +} + +input[type="text"], input[type="password"], input[type~="text"], input[type~="password"], input[type="email"], input[type="phone"], input[type~="email"], input[type~="phone"], input[type="search"], input[type~="search"], select { + border-color: rgb(114, 174, 197); + padding: 3px; +} + +main { + display: flex; + justify-content: space-evenly; + align-items: center; + height: 97vh; + background: #9ae4e8 url(http://web.archive.org/web/20071226141149im_/http://assets3.twitter.com/images/bg.gif?1198546588) no-repeat; +} + + + +#error { + width: 500px; + max-width: 85vw; +} + + + + + + + + +.page_info { + margin-top: 0px; + margin-left: 0px; + margin-bottom: 1px; +} + +.right_big_block > div:nth-of-type(2) > div:nth-of-type(2) > br { +display: none!important; +} + + + +.right_big_block > div:nth-of-type(2) > div:nth-of-type(2) { +padding: 0px 0px!important; +} \ No newline at end of file diff --git a/Web/static/css/union.css b/Web/static/css/union.css new file mode 100644 index 000000000..a6bb8db75 --- /dev/null +++ b/Web/static/css/union.css @@ -0,0 +1,1026 @@ +html { + overflow-y: scroll; +} + +body { + margin: 0; + padding: 0; + background-color: #fff; + text-align: left; + font-family: tahoma, verdana, arial, sans-serif; + font-size: 11px; + /* cursor: url('/style/pizda1.cur'), pointer; */ +} + +span { + padding: 0 0 2px; + color: gray; + font-weight: bold; +} + +.nobold { + + font-weight: normal; + color: gray; + +} + +a { + text-decoration: none; + color: #d22f2f; + cursor: pointer; +} + +.layout { + width: 791px; + margin: 0 auto; +} + +/* header crap */ + +.page_header { + position: relative; + width: 791px; + height: 45px; + background-repeat: no-repeat; + background-position: 0; + background: url(https://i.imgur.com/NuTpZQD.png); +} + +#page_act { + border-bottom: 1px solid +#d5dde6; +padding: 2px 10px 5px 10px; +color: + #2B587A; + width: 608px; + margin-left: -10px; +} + +.home_button { + position: absolute; + width: 232px; + text-indent: -999px; + height: 42px; +} + +.header_navigation { + text-align: center; + line-height: 20px; + padding-right: 8px; + text-transform: lowercase; + float: right; +} + +.header_navigation .link { + display: inline-block; + height: 29px; + padding: 11px 4px 0 7px; + background-size: 1.5px 41px; + background: url(https://i.imgur.com/Fmewdj7.png) no-repeat; +} + +.header_navigation .link a { + color: #eac6c6; + text-decoration: none; + font-weight: bold; +} + +.header_navigation .link a:hover { + text-decoration: underline; + color: #fff; +} + +/* Sidebar crap */ + +.sidebar { + + width: 118px; + margin: 4px 0 0 4px; + float: left; + +} + +.navigation { + padding: 0; +} + +.navigation .link { + + display: block; + padding: 3px 3px 3px 6px; + text-decoration: none; + border-top: 1px solid #fff; + color: #ac1515; + border-bottom: 0; + border-left: 0; + border-right: 0; + font-size: 11px; + text-align: left; + cursor: pointer; + background: none; + margin-bottom: 1px; + + +} + +.navigation .link:hover { + background-color: #fffbfb; + border-top: 1px solid #ff4e4e; +} + +.navigation_footer { + padding: 0; +} + +.navigation_footer .link { + display: block; + padding: 3px 3px 3px 6px; + text-decoration: none; + border-top: 1px solid #fff; /* fix */ + color: #000; +} + +.navigation_footer .link:hover { + background-color: #E4E4E4; + border-top: 1px solid #CCCCCC; +} + +/* page crap */ + +.page_body { + + width: 632px; + float: right; + margin-right: 15px; + margin-top: -2px; + font-size: 11px; + text-align: left; + +} + +.wrap1 { + + border: 1px solid #EBF0F4; + border-top: 0px; + width: 626px; +} + +.wrap2 { + + border-right: 1px solid #F6F8FA; + border-top: 0px; + +} + + +.page_yellowheader { + padding: 4px 10px 5px; + font-weight: bold; + background: url(../img/header_yellow.png) repeat-x; + background-color: #EEE5B8; + border-right: solid 1px #DCD4A4; + border-left: solid 1px #E4DDB4; + border-bottom: solid 1px #D7CF9E; +} + +#wrapHI { + + border-right: solid 1px #D5DDE6; + border-left: solid 1px #D5DDE6; + +} +#wrapH { + + border-right: solid 1px #EAEEF3; + border-left: solid 1px #EAEEF3; + +} + +.page_yellowheader span { + color: #C8BF85; +} + +.page_yellowheader a { + color: #C8BF85; +} + +.page_content { + display: inline-block; + width: 610px; +} + +.page_wrap { + border-bottom: solid 1px #c4c4c4; + border-left: solid 1px #E1E1E1; + border-right: solid 1px #E1E1E1; + padding: 12px; +} + +.content-main-tile { + margin: -10px; + margin-top: 3pt; + padding: 7pt; + border-top: 1px solid #ebf0f4; + background-color: #fbfbfb; +} + +.album { + margin: 8px; + padding: 8px; + width: 95%; + background-color: #fff; + border: 1px solid #ebf0f4; +} + +.album-photo { + position: relative; + background-color: darkgrey; + margin: 5pt; + width: calc(33% - 10pt); + height: 82px; + text-align: center; + vertical-align: text-top; +} + +.album-photo img { + width: 100%; + max-height: 82px; + vertical-align: top; +} + +.album-photo > .album-photo--delete { + position: absolute; + right: 0; + padding: 5px; + margin: 4px; + color: #fff; + background-color: hsla(0, 0%, 0%, 0.3); + width: 10px; + height: 10px; + line-height: 10px; + opacity: 0; + transition: .1s opacity ease-out; +} + +.album-photo:hover > .album-photo--delete { + opacity: 1; +} + +.name-checkmark { + margin-left: 2pt; +} + +#profile_link, .profile_link { + display: block; + box-sizing: border-box; + padding: 3px; + background: transparent; + border: none; + border-bottom: 1px solid #ccc; + font-size: 11px; + color: #2b587a; + width: 200px; + text-align: left; + cursor: pointer; + +} +#profile_links { + + margin: 10px 0; + +} + +#profile_link:hover, .profile_link:hover { + + background: #ECECEC; + +} + +/* на этом месте я заплакал */ + +.page_footer { + margin-left: 95px; + padding-top: 5px; + clear: both; + text-align: center; +} + +table { + font-size: 11px; + text-align: left; + /* ненавижу, блять, костыли */ +} + +.information { + padding: 9px; + background-color: #c3e4ff; +} + +.error { + padding: 9px; + background-color: #ffc3c3; +} + +/* kun vi tre agrable pensi pri morto + +кстати это из песни дельфина взято +вдруг кто не знал +я просто влюблён, извините за +пиздобляские комментарии в CSS коде +<3 */ + +.page_footer .link { + padding: 3px 7px; + display: inline-block; + color: #d60000; + text-decoration: none; +} + +.page_footer .link:hover { + background-color: #ffe4e4; +} + +.content_divider { + margin-bottom: 6px; +} + +.page_info { + + margin-top: 3px; + margin-left: 5px; + margin-bottom: 20px; + +} + +.page_status { + font-weight: normal; + font-size: 11px; + padding: 3px 1px 3px; + color: #000; + width: 380px; + max-height: 39px; + height: auto !important; +} +/* + +у тебя спид +и значит мы умрём + +*/ + +/* and we begin to make some graphic crap */ + +.button { + border-radius: 2px; + border: #d20404; + font-size: 11px; + outline: none; + white-space: nowrap; + background: #d20404; + background-position: 0px -16px; + color: #fff; + padding: 4px 8px 4px; + text-shadow: 0 1px 0 #d20404; + cursor: pointer; + text-decoration: none; +} + +input[class=button] { + padding: 5px 7px 4px; +} + +.checkbox { + background-image: url("data:image/gif;base64,R0lGODlhDwA4AKIHANrh6P///8DK1a22wF+DpbfAy9/l6v///yH5BAEAAAcALAAAAAAPADgAAAOzKLrMJyHKGcWjOFiY5+7eBVbi+I1aCZ6m2rGrm21NrTx2LWMEwYE93yho+PF6gmKHyCJIiEVRMABNcQzBnsFAumK13K5kS9iGjNuwBPbC5RyDuHw+PwwA+LweP7Dv/wB9d4B6goSFfod8iYqGioGMh46NkYSTkpWAgnSccXadnJl/PYOHQaWAQQWoe6qrhK6MBHmujkEArpCDBVkEBQWLu7w9v3mTv77AxpG/ysusmp+gdQkAOw=="); + background-color: transparent; + -webkit-appearance: none; + -moz-appearance: none; + -ms-appearance: none; + height: 14px; + width: 14px; + outline: none; + cursor: pointer; + vertical-align: middle; +} + +.checkbox:hover { + background-position: 0 -28px; +} + +.checkbox:checked { + background-position: 0 -14px; +} + +#auth { + padding: 10px; +} + +.left_small_block { + float: left; + width: 200px; + margin-right: 10px; + border-right: 1px #eeeeee solid; +} + +.left_big_block { + width: 397px; + float: left; + margin-right: 13px; +} + +.right_small_block { + float: right; + width: 200px; +} + +.right_big_block { + width: 399px; + float: right; +} + +.content_title_expanded { + background-image: url('../img/flex_arrow_open.gif'); + background-repeat: no-repeat; + background-color: #ffd3d3; + border-top: #cc0000 solid 1px; + padding: 3px 24px; + font-weight: bold; + color: #bb3f3f; + font-size: 11px; + cursor: pointer; + display: block; +} + +.content_title_unexpanded { + background-image: url('../img/flex_arrow_shut.gif'); + background-repeat: no-repeat; + background-color: #ffe2e2; + border-top: #d60000 solid 1px; + padding: 3px 24px; + font-weight: bold; + color: #d60000; + font-size: 11px; + cursor: pointer; + display: block; +} + +.content_subtitle { + background-color: #ffebeb; + padding: 0; + display: block; + font-size: 11px; + border-bottom: 1px solid #ffd3d3; + color: #d22f2f; + padding: 3px 8px; + border-top: #ffd3d3 solid 1px; +} + +.content { + padding: 8px; +} + +.content-withouttop { + padding-top: 0px; +} + +input[type="text"], input[type="password"], input[type~="text"], input[type~="password"], input[type="email"], input[type="phone"], input[type~="email"], input[type~="phone"], input[type="search"], input[type~="search"], select { + border: 1px solid #C0CAD5; + padding: 3px; + font-size: 11px; + font-family: tahoma, verdana, arial, sans-serif; +} + +h4 { + border-bottom: 1px solid #E8EBEE; + color: #a81c1c; + font-size: 12px; + font-weight: bold; + margin: 0px; + padding: 0px 0px 3px; + font-family: verdana, arial, sans-serif; +} + +/* post */ + +.post-author { + background-color: #ffebeb; + border-top: #cc0000 solid 1px; + border-bottom: #ffd3d3 solid 1px; + font-size: 11px; + padding: 3px 5px 3px; +} + +.post-author .date { + font-size: 9px; + color: #cc1e1e; +} + +.post-content { + border-bottom: #ECECEC solid 1px; + font-size: 9px; +} + +.post-content .text { + padding: 4px; + font-size: 11px; + word-break: break-word; + word-wrap: break-word; + hyphens: manual; +} + +.post-content .attachments_b { + margin-top: 8px; +} + +.attachment .post { + width: 102%; +} + +.post-content img.media { + max-width: 100%; + image-rendering: -webkit-optimize-contrast; +} + +.post-menu { + height: 12px; + padding: 4px; + font-size: 9px; + color: #bcbcbc; +} + +ul { + list-style-type: square; + color: #a2a2a2; +} + +span { + padding: 0 0 2px; + color: black; + font-weight: normal; +} + +#logininput { + width: 123px; + height: 21px; + padding: 3px; + border: 1px solid #C0CAD5; + font-size: 11; + margin: 3px 0; +} + +#fastLogin { + min-width: 122px; +} + +#fastLogin input { + margin-bottom: 5px; +} + +#fastLogin input[type=submit] { + margin-top: 5px; +} + +#fastlogin input[type=text], #fastlogin input[type=password] { + width: 100%; +} + +.ovk-avView { + padding: 8px; +} + +.ovk-avView--el { + width: 32%; + max-height: 90px; + text-align: center; + display: inline-block; + vertical-align: text-top; +} + +.ovk-avView--el .ava { + max-width: 80%; + max-height: 63px; +} + +table.User { + vertical-align: text-top; +} + +.container_gray { + background: #F7F7F7; + width: 602px; + padding: 12px; + border-top: #EBEBEB solid 1px; + margin-left: -12px; + margin-bottom: -12px; +} + +#auth .container_gray { + margin-left: -10px; + margin-bottom: -10px; +} + +/* МОНТИРУЮ МОНТИРУЮ БЛЯТЬ */ + +.container_gray .content { + background: #fff; + padding: 5px; + border: #DEDEDE solid 1px; + clear: both; +} +.tabs { + border-bottom: 1px solid #d60000; + margin-left: -12px; + margin-right: -6px; + +} +#activetabs { + + background: #d60000; + border-radius: 3px 3px 0px 0px; + +} +.tab { + + display: inline-block; + padding: 4px 6px; + margin-left: 15px; + +} +#act_tab_a { + + color: white; + +} +.container_gray .content { + + background: #fff; + padding: 4px; + border: #DEDEDE solid 1px; + clear: both; + margin-bottom: 12px; +} + +.gray-block { + background: #F7F7F7; + border: #DEDEDE solid 1px; + padding: 6px; + margin-top:6px; + +} + +.ovk-album { + width: 100%; +} + +.ovk-album:not(:first-child), .ovk-note:not(:first-child) { + display: inline-block; + margin-top: 10px; +} + +.msg { + width: 70%; + margin: auto; + margin-bottom: 5pt; + padding: 8pt; + border: 1pt solid #667e96; + background-color: #e6f2f3; +} + +#wrapHI > .msg { + margin-top: 15pt; +} + +.msg.msg_succ { + border-color: #3b584f; + background-color: #ddf3d7; +} + +.msg.msg_err { + border-color: #9a205e; + background-color: #f5e9ec; +} + +.edit_link { + color: #c5c5c5; + font-family: verdana, arial, sans-serif; + font-size: 11px; + font-weight: bold; +} + +.crp-list { + margin: 10px -6px 0 -11px; + display: flex; + flex-direction: column; + border-top: 1px solid #d6d6d6; + max-height: 70%; + overflow-y: auto; +} + +.crp-entry { + display: flex; + padding: 8px; + border-bottom: 1px solid #d6d6d6; + cursor: pointer; +} + +.crp-entry--image, .crp-entry--info { + margin-right: 15px; +} + +.crp-entry--info { + width: 190px; +} + +.crp-entry--image > img { + max-width: 64px; +} + +.crp-entry:hover { + background-color: #f9f9f9; +} + +.crp-entry--info span { + color: grey; +} + +.crp-entry--info a { + font-weight: 900; +} + +.crp-entry--message---av img { + max-width: 42px; +} + +.crp-entry--message---av, .crp-entry--message---text { + display: inline-block; + vertical-align: top; +} + +.crp-entry--message---text, .messenger-app--messages---message .time { + color: #404036; +} + +.messenger-app--messages, .messenger-app--input { + padding: 10px 70px; +} + +.messenger-app--messages { + height: 360px; + overflow-y: auto; +} + +.messenger-app--messages---message, .messenger-app--input { + display: flex; + align-items: flex-start; + justify-content: space-between; +} + +.messenger-app--messages---message { + margin-bottom: 1.2rem; +} + +.messenger-app--messages---message .ava, .messenger-app--input > .ava { + max-width: 64px; +} + +.messenger-app--messages---message .ava, .messenger-app--input > .ava { + width: 52px; +} + +.messenger-app--messages---message ._content { + padding-left: 20px; + width: 410px; +} + +.messenger-app--messages---message ._content a { + display: block; +} + +.messenger-app--messages---message .time { + width: 100px; +} + +.messenger-app--input { + background-color: #f9f9f9; + height: 120px; + border-top: 1px solid #d6d6d6; +} + +.messenger-app--input---messagebox { + box-sizing: border-box; + padding: 0 10px; + width: calc(100% - 128px); +} + +.messenger-app--input---messagebox textarea { + width: 100%; + height: 70px; + resize: none; + margin-bottom: 1rem; +} + +.music-app { + display: grid; +} + +.music-app--player { + display: grid; + grid-template-columns: 32px 32px 32px 1fr; + padding: 8px; + border-bottom: 1px solid +#c1c1c1; + border-bottom-style: solid; +border-bottom-style: dashed; +} + + +.music-app--player .play, .music-app--player .perv, .music-app--player .next { + + -webkit-appearance: none; + -moz-appearance: none; + background-color: + +#507597; + +color: + + #fff; + height: 20px; + margin: 5px; + border: none; + border-radius: 5px; + cursor: pointer; + padding: 0; + font-size: 10px; + +} + +.music-app--player .info { + margin-left: 10px; + width: 550px; +} + +.music-app--player .info .song-name * { + color: black; +} + +.music-app--player .info .song-name time { + float: right; +} + +.music-app--player .info .track { + + margin-top: 8px; + height: 5px; + width: 70%; + background-color: + +#fff; + +border-top: 1px solid + + #507597; + float: left; + +} + +.music-app--player .info .track .inner-track { + + background-color: + + #507597; + height: inherit; + width: 15px; + opacity: .7; + +} + +.settings_delete { + margin: -12px; + padding: 12px; + margin-top: -8px; + background: #fff; + text-align: center; +} + +textarea { + font-family: unset; + border: 1px solid #C0CAD5; + font-size: 9pt; + min-height: 42px; +} + +#faqhead { + background: #fbf3c3; + margin: 0px 5px 0px 5px; + padding: 5px; + font-weight: bold; + border: 1px solid #d7cf9e; + border-bottom: 0px; +} + +#faqcontent { + background: #fafafa; + margin: 0px 5px 10px 5px; + padding: 5px 5px 5px 10px; + border: 1px solid #ddd; +} + +.ovk-lw-container { + display: flex; + border-bottom: solid 1px #c4c4c4; +} + +.ovk-lw-container > .ovk-lw--list { + flex: 9; + border-right: 1px solid #BEBEBE; +} + +.ovk-lw-container > .ovk-lw--list > .post { + margin-left: 10px; + margin-top: 10px; +} + +.ovk-lw-container > .ovk-lw--actions { + flex: 5; +} + +.ovk-lw-container > .ovk-lw--actions > .tile { + display: flex; + flex-direction: column; + padding: 10px 5px; +} + +.ovk-lw-container > .ovk-lw--actions > .tile > .profile_link { + width: auto!important; +} + +.ovk-lw-container > .ovk-lw--actions > hr { + margin: 0; +} + +.profile-hints a { + display: block; + margin-bottom: 2px; +} + +.profile-hints a img { + vertical-align: bottom; +} + +.completeness-gauge { + position: relative; + width: 95%; + height: 20px; + background-color: #ffdede; +} + +.completeness-gauge > div { + height: 100%; + background-color: #ff7474; +} + +.completeness-gauge > span { + position: absolute; + top: 55%; + left: 50%; + margin-right: -50%; + transform: translate(-50%, -50%); +} + +.toTop { + position: fixed; + padding: 20px; + width: 100px; + height: 100%; + background-color: #f3f3f3; + font-weight: 900; + color: #2b2b2b; + box-sizing: border-box; + opacity: 0; + transition: .1s all; +} + +body.scrolled .toTop:hover { + opacity: .5; + cursor: pointer; +} + +/* бля чуваки я извиняюсь +за пиздострадательские комментарии +в коде мне одиноко просто было +(да и в принципе и сейчас) */ + +/* Vriska-chan was here */ + +.ugc-table tr > td { + word-break: break-word; +} + +.ugc-table tr > td:nth-of-type(1) { + width: 120px; +} + +.ugc-table tr > td:nth-of-type(2) { + width: 270px; +} diff --git a/Web/static/css/vk2015.css b/Web/static/css/vk2015.css new file mode 100644 index 000000000..1f8894719 --- /dev/null +++ b/Web/static/css/vk2015.css @@ -0,0 +1,1017 @@ +html { + overflow-y: scroll; +} + +body { + margin: 0; + padding: 0; + background-color: #fff; + text-align: left; + font-family: tahoma, verdana, arial, sans-serif; + font-size: 11px; + /* cursor: url('/style/pizda1.cur'), pointer; */ +} + +span { + padding: 0 0 2px; + color: gray; + font-weight: bold; +} + +.nobold { + + font-weight: normal; + color: gray; + +} + +a { + text-decoration: none; + color: #2B587A; + cursor: pointer; +} + +.layout { + width: 791px; + margin: 0 auto; +} + +/* header crap */ + +.page_header { + position: relative; + width: 791px; + height: 45px; + background-repeat: no-repeat; + background-position: 0; + background: url(https://i.imgur.com/NUWhKyn.png); +} + +#page_act { + border-bottom: 1px solid +#d5dde6; +padding: 2px 10px 5px 10px; +color: + #2B587A; + width: 608px; + margin-left: -10px; +} + +.home_button { + position: absolute; + width: 232px; + text-indent: -999px; + height: 42px; +} + +.header_navigation { + text-align: center; + line-height: 20px; + padding-right: 8px; + text-transform: lowercase; + float: right; +} + +.header_navigation .link { + display: inline-block; + height: 29px; + padding: 11px 4px 0 7px; + background-size: 1.5px 41px; + background: url(https://i.imgur.com/c4W9P4X.png) no-repeat; +} + +.header_navigation .link a { + color: #e8e8e8; + text-decoration: none; + font-weight: bold; +} + +.header_navigation .link a:hover { + text-decoration: underline; + color: #fff; +} + +/* Sidebar crap */ + +.sidebar { + + width: 118px; + margin: 4px 0 0 4px; + float: left; + +} + +.navigation { + padding: 0; +} +/* меню */ + +.navigation .link { + + display: block; + padding: 3px 3px 3px 6px; + text-decoration: none; + border-top: 1px solid #fff; + color: #285473; + border-bottom: 0; + border-left: 0; + border-right: 0; + font-size: 11px; + text-align: left; + cursor: pointer; + background: none; + margin-bottom: 1px; + + +} + +.navigation .edit-button { + position: absolute; + right: 0px; + top: 0px; + background-color: rgb(255, 255, 255); + color: grey; +} + +.navigation .link:hover { + background-color: #e2e8ee; + border-top: 1px solid #e2e8ee; +} + +.navigation_footer { + padding: 0; +} + +.navigation_footer .link { + display: block; + padding: 3px 3px 3px 6px; + text-decoration: none; + border-top: 1px solid #fff; /* fix */ + color: #000; +} + +.navigation_footer .link:hover { + background-color: #E4E4E4; + border-top: 1px solid #CCCCCC; +} + +/* page crap */ + +.page_body { + + width: 632px; + float: right; + margin-right: 15px; + margin-top: -2px; + font-size: 11px; + text-align: left; + +} + +.wrap1 { + + border: 1px solid #EBF0F4; + border-top: 0px; + width: 626px; +} + +.wrap2 { + + border-right: 1px solid #F6F8FA; + border-top: 0px; + +} + + +.page_yellowheader { + padding: 4px 10px 5px; + font-weight: bold; + background: url(https://i.imgur.com/pTz3u77.png) repeat-x; + background-color: #e9edf1; + border-right: solid 1px #e9edf1; + border-left: solid 1px #e9edf1; + border-bottom: solid 1px #e9edf1; + color: #2b587a; + +} + +#wrapHI { + + border-right: solid 1px #D5DDE6; + border-left: solid 1px #D5DDE6; + +} +#wrapH { + + border-right: solid 1px #EAEEF3; + border-left: solid 1px #EAEEF3; + +} + +.page_yellowheader span { + color: #607387; +} + +.page_yellowheader a { + color: #2b587a; +} + +.page_content { + display: inline-block; + width: 610px; +} + +.page_wrap { + border-bottom: solid 1px #c4c4c4; + border-left: solid 1px #E1E1E1; + border-right: solid 1px #E1E1E1; + padding: 12px; +} + +.content-main-tile { + margin: -10px; + margin-top: 3pt; + padding: 7pt; + border-top: 1px solid #ebf0f4; + background-color: #fbfbfb; +} + +.album { + margin: 8px; + padding: 8px; + width: 95%; + background-color: #fff; + border: 1px solid #ebf0f4; +} + +.album-photo { + position: relative; + background-color: darkgrey; + margin: 5pt; + width: calc(33% - 10pt); + height: 82px; + text-align: center; + vertical-align: text-top; +} + +.album-photo img { + width: 100%; + max-height: 82px; + vertical-align: top; +} + +.album-photo > .album-photo--delete { + position: absolute; + right: 0; + padding: 5px; + margin: 4px; + color: #fff; + background-color: hsla(0, 0%, 0%, 0.3); + width: 10px; + height: 10px; + line-height: 10px; + opacity: 0; + transition: .1s opacity ease-out; +} + +.album-photo:hover > .album-photo--delete { + opacity: 1; +} + +.name-checkmark { + margin-left: 2pt; +} + +#profile_link, .profile_link { + display: block; + box-sizing: border-box; + padding: 3px; + background: transparent; + border: none; + border-bottom: 1px solid #CCC; + font-size: 11px; + color: #2b587a; + width: 200px; + text-align: left; + cursor: pointer; + +} +#profile_links { + + margin: 10px 0; + +} + +#profile_link:hover, .profile_link:hover { + + background: #ECECEC; + +} + +/* на этом месте € заплакал */ + +.page_footer { + margin-left: 95px; + padding-top: 5px; + clear: both; + text-align: center; +} + +table { + font-size: 11px; + text-align: left; + /* ненавижу, бл€ть, костыли */ +} + +.information { + padding: 9px; + background-color: #c3e4ff; +} + +.error { + padding: 9px; + background-color: #ffc3c3; +} + +/* kun vi tre agrable pensi pri morto + +кстати это из песни дельфина вз€то +вдруг кто не знал +€ просто влюблЄн, извините за +пиздобл€ские комментарии в CSS коде +<3 */ + +.page_footer .link { + padding: 3px 7px; + display: inline-block; + color: #444; + text-decoration: none; +} + +.page_footer .link:hover { + background-color: #DFDFDF; +} + +.content_divider { + margin-bottom: 6px; +} + +.page_info { + + margin-top: 3px; + margin-left: 5px; + margin-bottom: 20px; + +} + +.page_status { + font-weight: normal; + font-size: 11px; + padding: 3px 1px 3px; + color: #000; + width: 380px; + max-height: 39px; + height: auto !important; +} +/* + +у теб€ спид +и значит мы умрЄм + +*/ + +/* кнопки */ + +.button { + border-radius: 2px; + border: #597da3; + font-size: 11px; + outline: none; + white-space: nowrap; + background: #597da3; + background-position: 0px -16px; + color: #fff; + padding: 4px 8px 4px; + text-shadow: 0 1px 0 #597da3; + cursor: pointer; + text-decoration: none; +} + +input[class=button] { + padding: 5px 7px 4px; +} + +.checkbox { + background-image: url("data:image/gif;base64,R0lGODlhDwA4AKIHANrh6P///8DK1a22wF+DpbfAy9/l6v///yH5BAEAAAcALAAAAAAPADgAAAOzKLrMJyHKGcWjOFiY5+7eBVbi+I1aCZ6m2rGrm21NrTx2LWMEwYE93yho+PF6gmKHyCJIiEVRMABNcQzBnsFAumK13K5kS9iGjNuwBPbC5RyDuHw+PwwA+LweP7Dv/wB9d4B6goSFfod8iYqGioGMh46NkYSTkpWAgnSccXadnJl/PYOHQaWAQQWoe6qrhK6MBHmujkEArpCDBVkEBQWLu7w9v3mTv77AxpG/ysusmp+gdQkAOw=="); + background-color: transparent; + -webkit-appearance: none; + -moz-appearance: none; + -ms-appearance: none; + height: 14px; + width: 14px; + outline: none; + cursor: pointer; + vertical-align: middle; +} + +.checkbox:hover { + background-position: 0 -28px; +} + +.checkbox:checked { + background-position: 0 -14px; +} + +#auth { + padding: 10px; +} + +.left_small_block { + float: left; + width: 200px; + margin-right: 10px; + border-right: 1px #eeeeee solid; +} + +.left_big_block { + width: 397px; + float: left; + margin-right: 13px; +} + +.right_small_block { + float: right; + width: 200px; +} + +.right_big_block { + width: 399px; + float: right; +} + +.content_title_expanded, .content_title_unexpanded { + background-color: #dfe6ec; + background-image: unset !important; + border-top: #dfe6ec solid 1px; + font-weight: bold; + color: #36638e; + font-size: 11px; + display: block; + pointer-events: none; + padding: 3px 10px;; +} + +.content_subtitle { + background-color: #F0F0F0; + padding: 0; + display: block; + font-size: 11px; + border-bottom: 1px solid #EEEEEE; + color: #777777; + padding: 3px 8px; + border-top: #dfe6ec solid 1px; +} + +.content { + padding: 8px; +} + +.content-withouttop { + padding-top: 0px; +} + +input[type="text"], input[type="password"], input[type~="text"], input[type~="password"], input[type="email"], input[type="phone"], input[type~="email"], input[type~="phone"], input[type="search"], input[type~="search"], select { + border: 1px solid #507398; + padding: 3px; + font-size: 11px; + font-family: tahoma, verdana, arial, sans-serif; +} + +h4 { + border-bottom: 1px solid #E8EBEE; + color: #45688E; + font-size: 12px; + font-weight: bold; + margin: 0px; + padding: 0px 0px 3px; + font-family: verdana, arial, sans-serif; +} + +/* post */ + +.post-author { + background-color: #fff; + border-top: #ececec solid 1px; + border-bottom: #fff solid 1px; + font-size: 11px; + padding: 3px 5px 3px; +} + +.post-author .date { + font-size: 9px; + color: black; +} + +.post-content { + border-bottom: #ECECEC solid 1px; + font-size: 9px; +} + +.post-content .text { + padding: 4px; + font-size: 11px; + word-break: break-word; + word-wrap: break-word; + hyphens: manual; +} + +.post-content .attachments_b { + margin-top: 8px; +} + +.attachment .post { + width: 102%; +} + +.post-content img.media { + max-width: 100%; + image-rendering: -webkit-optimize-contrast; +} + +.post-menu { + height: 12px; + padding: 4px; + font-size: 9px; + color: #bcbcbc; +} + +ul { + list-style-type: square; + color: #a2a2a2; +} + +span { + padding: 0 0 2px; + color: black; + font-weight: normal; +} + +#logininput { + width: 123px; + height: 21px; + padding: 3px; + border: 1px solid #C0CAD5; + font-size: 11; + margin: 3px 0; +} + +#fastLogin { + min-width: 122px; +} + +#fastLogin input { + margin-bottom: 5px; +} + +#fastLogin input[type=submit] { + margin-top: 5px; +} + +#fastlogin input[type=text], #fastlogin input[type=password] { + width: 100%; +} + +.ovk-avView { + padding: 8px; +} + +.ovk-avView--el { + width: 32%; + max-height: 90px; + text-align: center; + display: inline-block; + vertical-align: text-top; +} + +.ovk-avView--el .ava { + max-width: 80%; + max-height: 63px; +} + +table.User { + vertical-align: text-top; +} + +.container_gray { + background: #F7F7F7; + width: 602px; + padding: 12px; + border-top: #EBEBEB solid 1px; + margin-left: -12px; + margin-bottom: -12px; +} + +#auth .container_gray { + margin-left: -10px; + margin-bottom: -10px; +} + +/* ћќЌ“»–”ё ћќЌ“»–”ё ЅЋя“№ */ + +.container_gray .content { + background: #fff; + padding: 5px; + border: #DEDEDE solid 1px; + clear: both; +} +.tabs { + border-bottom: 1px solid #597da3; + margin-left: -12px; + margin-right: -6px; + +} +#activetabs { + + background: #597da3; + border-radius: 3px 3px 0px 0px; + +} +.tab { + + display: inline-block; + padding: 4px 6px; + margin-left: 15px; + +} +#act_tab_a { + + color: white; + +} +.container_gray .content { + + background: #fff; + padding: 4px; + border: #DEDEDE solid 1px; + clear: both; + margin-bottom: 12px; +} + +.gray-block { + background: #F7F7F7; + border: #DEDEDE solid 1px; + padding: 6px; + margin-top:6px; + +} + +.ovk-album { + width: 100%; +} + +.ovk-album:not(:first-child), .ovk-note:not(:first-child) { + display: inline-block; + margin-top: 10px; +} + +.msg { + width: 70%; + margin: auto; + margin-bottom: 5pt; + padding: 8pt; + border: 1pt solid #667e96; + background-color: #e6f2f3; +} + +#wrapHI > .msg { + margin-top: 15pt; +} + +.msg.msg_succ { + border-color: #3b584f; + background-color: #ddf3d7; +} + +.msg.msg_err { + border-color: #9a205e; + background-color: #f5e9ec; +} + +.edit_link { + color: #c5c5c5; + font-family: verdana, arial, sans-serif; + font-size: 11px; + font-weight: bold; +} + +.crp-list { + margin: 10px -6px 0 -11px; + display: flex; + flex-direction: column; + border-top: 1px solid #d6d6d6; + max-height: 70%; + overflow-y: auto; +} + +.crp-entry { + display: flex; + padding: 8px; + border-bottom: 1px solid #d6d6d6; + cursor: pointer; +} + +.crp-entry--image, .crp-entry--info { + margin-right: 15px; +} + +.crp-entry--info { + width: 190px; +} + +.crp-entry--image > img { + max-width: 64px; +} + +.crp-entry:hover { + background-color: #f9f9f9; +} + +.crp-entry--info span { + color: grey; +} + +.crp-entry--info a { + font-weight: 900; +} + +.crp-entry--message---av img { + max-width: 42px; +} + +.crp-entry--message---av, .crp-entry--message---text { + display: inline-block; + vertical-align: top; +} + +.crp-entry--message---text, .messenger-app--messages---message .time { + color: #404036; +} + +.messenger-app--messages, .messenger-app--input { + padding: 10px 70px; +} + +.messenger-app--messages { + height: 360px; + overflow-y: auto; +} + +.messenger-app--messages---message, .messenger-app--input { + display: flex; + align-items: flex-start; + justify-content: space-between; +} + +.messenger-app--messages---message { + margin-bottom: 1.2rem; +} + +.messenger-app--messages---message .ava, .messenger-app--input > .ava { + max-width: 64px; +} + +.messenger-app--messages---message .ava, .messenger-app--input > .ava { + width: 52px; +} + +.messenger-app--messages---message ._content { + padding-left: 20px; + width: 410px; +} + +.messenger-app--messages---message ._content a { + display: block; +} + +.messenger-app--messages---message .time { + width: 100px; +} + +.messenger-app--input { + background-color: #f9f9f9; + height: 120px; + border-top: 1px solid #d6d6d6; +} + +.messenger-app--input---messagebox { + box-sizing: border-box; + padding: 0 10px; + width: calc(100% - 128px); +} + +.messenger-app--input---messagebox textarea { + width: 100%; + height: 70px; + resize: none; + margin-bottom: 1rem; +} + +.music-app { + display: grid; +} + +.music-app--player { + display: grid; + grid-template-columns: 32px 32px 32px 1fr; + padding: 8px; + border-bottom: 1px solid +#c1c1c1; + border-bottom-style: solid; +border-bottom-style: dashed; +} + + +.music-app--player .play, .music-app--player .perv, .music-app--player .next { + + -webkit-appearance: none; + -moz-appearance: none; + background-color: + +#507597; + +color: + + #fff; + height: 20px; + margin: 5px; + border: none; + border-radius: 5px; + cursor: pointer; + padding: 0; + font-size: 10px; + +} + +.music-app--player .info { + margin-left: 10px; + width: 550px; +} + +.music-app--player .info .song-name * { + color: black; +} + +.music-app--player .info .song-name time { + float: right; +} + +.music-app--player .info .track { + + margin-top: 8px; + height: 5px; + width: 70%; + background-color: + +#fff; + +border-top: 1px solid + + #507597; + float: left; + +} + +.music-app--player .info .track .inner-track { + + background-color: + + #507597; + height: inherit; + width: 15px; + opacity: .7; + +} + +.settings_delete { + margin: -12px; + padding: 12px; + margin-top: -8px; + background: #fff; + text-align: center; +} + +textarea { + font-family: unset; + border: 1px solid #C0CAD5; + font-size: 9pt; + min-height: 42px; +} + +#faqhead { + background: #eaeef2; + margin: 0px 5px 0px 5px; + padding: 5px; + font-weight: bold; + border: 1px solid #d9dde1; + border-bottom: 0px; +} + +#faqcontent { + background: #fafafa; + margin: 0px 5px 10px 5px; + padding: 5px 5px 5px 10px; + border: 1px solid #ddd; +} + +.ovk-lw-container { + display: flex; + border-bottom: solid 1px #c4c4c4; +} + +.ovk-lw-container > .ovk-lw--list { + flex: 9; + border-right: 1px solid #BEBEBE; +} + +.ovk-lw-container > .ovk-lw--list > .post { + margin-left: 10px; + margin-top: 10px; +} + +.ovk-lw-container > .ovk-lw--actions { + flex: 5; +} + +.ovk-lw-container > .ovk-lw--actions > .tile { + display: flex; + flex-direction: column; + padding: 10px 5px; +} + +.ovk-lw-container > .ovk-lw--actions > .tile > .profile_link { + width: auto!important; +} + +.ovk-lw-container > .ovk-lw--actions > hr { + margin: 0; +} + +.profile-hints a { + display: block; + margin-bottom: 2px; +} + +.profile-hints a img { + vertical-align: bottom; +} + +.completeness-gauge { + position: relative; + width: 95%; + height: 20px; + background-color: #dee2ea; +} + +.completeness-gauge > div { + height: 100%; + background-color: #c2cad9; +} + +.completeness-gauge > span { + position: absolute; + top: 55%; + left: 50%; + margin-right: -50%; + transform: translate(-50%, -50%); +} + +.toTop { + position: fixed; + padding: 20px; + width: 100px; + height: 100%; + background-color: #f3f3f3; + font-weight: 900; + color: #2b2b2b; + box-sizing: border-box; + opacity: 0; + transition: .1s all; +} + +body.scrolled .toTop:hover { + opacity: .5; + cursor: pointer; +} + + +.ugc-table tr > td { + word-break: break-word; +} + +.ugc-table tr > td:nth-of-type(1) { + width: 120px; +} + +.ugc-table tr > td:nth-of-type(2) { + width: 270px; +} diff --git a/Web/static/css/vk2016.css b/Web/static/css/vk2016.css new file mode 100644 index 000000000..c199d4953 --- /dev/null +++ b/Web/static/css/vk2016.css @@ -0,0 +1,229 @@ +#xhead.dm { + position: absolute; + top: 0; + right: 0; + left: 0; + height: 45px; + background-color: #4a76a8; +} + +.home_button { + background-image: url(https://vk.com/images/svg_icons/ic_head_logo.svg); + background-position-y: center; + background-position-x: 10pt; + background-repeat: no-repeat; +} + +.page_header { + background: none; +} + +.header_navigation { + display: none; +} + +body { + background-color: #edeef0; +} + +.page_yellowheader { + display: none; +} + +.left_small_block > *, .right_big_block > *, .right_small_block > *, .left_big_block > *, .post { + background: #fff; + border-radius: 4px; + box-shadow: 0 1px 0 0 #d7d8db, 0 0 0 1px #e3e4e8; + margin: 15px 0 0; + padding: 15pt; +} + +.left_small_block > *:first-of-type, .right_big_block > *:first-of-type, .right_small_block > *:first-of-type, .left_big_block > *:first-of-type { + margin-top: 0; + margin-bottom: -10px; + /* border-bottom: 1px solid white; */ + border-radius: 4px 4px 0 0; + box-shadow: 0px -1px 0 0 #d7d8db, 0 0 0 1px #e3e4e8; +} + +.left_small_block > *:nth-of-type(2), .right_big_block > *:nth-of-type(2), .right_small_block > *:nth-of-type(2), .left_big_block > *:nth-of-type(2) { + margin-top: -5px; + box-shadow: 0px -9px 0 0 #fff, 0px 1px 0 1px #e3e4e8; +} + +.content_divider { + background: unset; + border-radius: unset; + box-shadow: none; + padding: 0; +} + +.User img { + width: 50px!important; + height: 50px; + border-radius: 100%; + object-fit: cover; +} + +.User { + /* width: unset!important; */ +} + +.content_title_expanded { + background: none; + border: none; + padding: 0; + color: #000; + font-weight: 100; + display: inline; + margin-bottom: 5px; + font-size: .73rem; +} + +.content_subtitle { + background: none; + padding: inherit; + border: none; + margin-bottom: 5pt; +} + +.page_info + div { + padding: 0 20px 10px; +} + +.page_info + div > div { + padding: 5px 0px!important; +} + +.page_info + div > div > h4 { + display: none!important; +} + +.page_info + div > div > br { + display: none; +} + +.layout { + width: 820px; +} + +#wrapH, #wrapHI, .wrap2, .wrap1, #auth { + border: none; +} + +.wrap1 { + width: inherit; +} + +.page_body { + width: 680px; +} + +.left_small_block, .right_small_block { + width: 240px; +} + +.page_content { + width: 680px; +} + +.left_big_block, .right_big_block { + width: 425px; +} + +.content_divider > .content_title_expanded { + display: none; +} + +.content_divider > div > .content_subtitle { + display: none; +} + +.content_divider > div > .content { + padding: 0; +} + +.content {} + +#profile_link, .profile_link { + background: none; + border: none; + width: 100%!important; + margin: 0; + margin-bottom: 5pt; + padding: 7px; + color: #fff!important; + text-align: center; + background-color: #5181b8; + border-radius: 6px; +} + +#profile_link:hover, .profile_link:hover { + background-color: #5b88bd; +} + +div#profile_links { + margin: -10px 0; +} + +.navigation .link, .navigation .link:hover { + border: none; + margin-top: 5px; + color: #305a78; +} + +.post .post { + box-shadow: none; + margin: 0; + padding: 0; +} + +.post-author { + background: none; + border: none; + padding: 0; +} + +.post img {} + +td {} + +.post td[width='54'] > img { + width: 50px; + height: 50px; + border-radius: 100%; + object-fit: cover; +} + +.post-content { + border: none; +} + +.post-menu { + margin-left: -70px; + margin-top: 20px; + font-size: unset; + display: flex; + flex-direction: row-reverse; + display: none; +} + +.post { + padding-bottom: 10px; +} + +.post-menu div[style='float: right; font-size: .7rem;'] { + float: unset!important; + font-size: unset!important; + margin-right: 162px; + /* padding-bottom: 10px; */ + /* background-color: white; */ +} + +.post-menu div[style='float: right; font-size: .7rem;'] > a > text { + font-size: 1.1rem; + vertical-align: middle; +} + +.post-menu > a { +} diff --git a/Web/static/css/vkontakte.css b/Web/static/css/vkontakte.css new file mode 100644 index 000000000..07d84fbf1 --- /dev/null +++ b/Web/static/css/vkontakte.css @@ -0,0 +1,30 @@ +.page_header { + background: url(../img/xhead2.gif); +} + +.header_navigation .link { + background: url(../img/header_divider.gif) no-repeat; +} + +.content_title_expanded { + border-top: #45688e solid 1px; + background-color: #dae2e8; + color: #45688e; +} +.navigation .link { +color:#2B587A; +} +.navigation .link:hover { + background: +#DAE1E8; +border-top: 1px solid + #CAD1D9; +} +.tabs { + border-bottom: 1px solid + #36638E; +} +#activetabs { + background: + #36638E; +} diff --git a/Web/static/css/vkontakte2006.css b/Web/static/css/vkontakte2006.css new file mode 100644 index 000000000..ff7bea06e --- /dev/null +++ b/Web/static/css/vkontakte2006.css @@ -0,0 +1,49 @@ +.page_header { + background: url(../img/xhead2006.gif) no-repeat; + background-position: 12px 9px; +} + +.header_navigation .link { + font-size: 12px; + padding: 4px 4px 0 7px; + background: none; +} + +.header_navigation .link a { + font-weight: normal; +} + +.header_navigation { + margin-top: 11px; + margin-right: 11px; +} + +.sidebar { + margin: 4px 0 0 17px; +} + +.page_yellowheader { + background-image: none; + font-size: 12px; + background-color: #F1EBCD; + border-right: solid 1px #d2bea7; + border-left: solid 1px #d2bea7; + border-bottom: solid 1px #D7CF9E; +} +.navigation .link { +color:#2B587A; +} +.navigation .link:hover { + background: +#DAE1E8; +border-top: 1px solid + #CAD1D9; +} +.tabs { + border-bottom: 1px solid + #36638E; +} +#activetabs { + background: + #36638E; +} diff --git a/Web/static/css/vriska.css b/Web/static/css/vriska.css new file mode 100644 index 000000000..4b386d4a8 --- /dev/null +++ b/Web/static/css/vriska.css @@ -0,0 +1,36 @@ +@import "vk2015.css"; + +body { + background-color: #f7f7f7; +} + +.page_header { + background: url('data:image/webp;base64,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') !important; +} + +#wrapHI, .container_gray { + background-color: #fff; +} + +.navigation .link { + border-top: none !important; + margin: 2px 0; +} + +.navigation .link::before { + display: inline-block; + width: 16px; + height: 16px; + margin-right: 2pt; + content: ""; + background-image: url('data:image/gif;base64,R0lGODlhEAAQALMJAMuxAf7oSdfX1/+kd9JEAP+AQP7eAf///wAAAP///wAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAkALAAAAAAQABAAAAQ9MMlJq50IXYsG0ZvUFcUXIgFJmlxgGGoJUugLr7Po2jDBYgceAJCpIA5Iw7DIQQqIOaNAwAwlqtasdmuNAAA7'); + vertical-align: top; +} + +.navigation .link:hover::before { + transform: rotate(180deg); +} + +.navigation .edit-button { + display: none !important; +} diff --git a/Web/static/img/banned.jpg b/Web/static/img/banned.jpg new file mode 100644 index 000000000..41d8b0613 Binary files /dev/null and b/Web/static/img/banned.jpg differ diff --git a/Web/static/img/bg.gif b/Web/static/img/bg.gif new file mode 100644 index 000000000..5b867886f Binary files /dev/null and b/Web/static/img/bg.gif differ diff --git a/Web/static/img/camera_200.png b/Web/static/img/camera_200.png new file mode 100644 index 000000000..794474d1e Binary files /dev/null and b/Web/static/img/camera_200.png differ diff --git a/Web/static/img/checkmark.png b/Web/static/img/checkmark.png new file mode 100644 index 000000000..f698aa933 Binary files /dev/null and b/Web/static/img/checkmark.png differ diff --git a/Web/static/img/checkmark.png.old b/Web/static/img/checkmark.png.old new file mode 100644 index 000000000..612567f72 Binary files /dev/null and b/Web/static/img/checkmark.png.old differ diff --git a/Web/static/img/divider.png b/Web/static/img/divider.png new file mode 100644 index 000000000..561f8e6e6 Binary files /dev/null and b/Web/static/img/divider.png differ diff --git a/Web/static/img/error.png b/Web/static/img/error.png new file mode 100644 index 000000000..2b3fae283 Binary files /dev/null and b/Web/static/img/error.png differ diff --git a/Web/static/img/escargot.gif b/Web/static/img/escargot.gif new file mode 100644 index 000000000..66eded343 Binary files /dev/null and b/Web/static/img/escargot.gif differ diff --git a/Web/static/img/favicons/favicon24.png b/Web/static/img/favicons/favicon24.png new file mode 100644 index 000000000..607dd2af2 Binary files /dev/null and b/Web/static/img/favicons/favicon24.png differ diff --git a/Web/static/img/favicons/favicon32.png b/Web/static/img/favicons/favicon32.png new file mode 100644 index 000000000..ea0780b32 Binary files /dev/null and b/Web/static/img/favicons/favicon32.png differ diff --git a/Web/static/img/favicons/favicon64.png b/Web/static/img/favicons/favicon64.png new file mode 100644 index 000000000..9d2065db1 Binary files /dev/null and b/Web/static/img/favicons/favicon64.png differ diff --git a/Web/static/img/favicons/icon.psd b/Web/static/img/favicons/icon.psd new file mode 100644 index 000000000..cde451b45 Binary files /dev/null and b/Web/static/img/favicons/icon.psd differ diff --git a/Web/static/img/flags/bs.gif b/Web/static/img/flags/bs.gif new file mode 100644 index 000000000..c0a741e5c Binary files /dev/null and b/Web/static/img/flags/bs.gif differ diff --git a/Web/static/img/flags/bt.gif b/Web/static/img/flags/bt.gif new file mode 100644 index 000000000..abe2f3ccb Binary files /dev/null and b/Web/static/img/flags/bt.gif differ diff --git a/Web/static/img/flags/bv.gif b/Web/static/img/flags/bv.gif new file mode 100644 index 000000000..6202d1f3a Binary files /dev/null and b/Web/static/img/flags/bv.gif differ diff --git a/Web/static/img/flags/bw.gif b/Web/static/img/flags/bw.gif new file mode 100644 index 000000000..986ab63c2 Binary files /dev/null and b/Web/static/img/flags/bw.gif differ diff --git a/Web/static/img/flags/by.gif b/Web/static/img/flags/by.gif new file mode 100644 index 000000000..43ffcd4c7 Binary files /dev/null and b/Web/static/img/flags/by.gif differ diff --git a/Web/static/img/flags/bz.gif b/Web/static/img/flags/bz.gif new file mode 100644 index 000000000..791737f0b Binary files /dev/null and b/Web/static/img/flags/bz.gif differ diff --git a/Web/static/img/flags/ca.gif b/Web/static/img/flags/ca.gif new file mode 100644 index 000000000..457d9662d Binary files /dev/null and b/Web/static/img/flags/ca.gif differ diff --git a/Web/static/img/flags/catalonia.gif b/Web/static/img/flags/catalonia.gif new file mode 100644 index 000000000..73df9a049 Binary files /dev/null and b/Web/static/img/flags/catalonia.gif differ diff --git a/Web/static/img/flags/cc.gif b/Web/static/img/flags/cc.gif new file mode 100644 index 000000000..3f7832702 Binary files /dev/null and b/Web/static/img/flags/cc.gif differ diff --git a/Web/static/img/flags/cd.gif b/Web/static/img/flags/cd.gif new file mode 100644 index 000000000..1df717ae5 Binary files /dev/null and b/Web/static/img/flags/cd.gif differ diff --git a/Web/static/img/flags/cf.gif b/Web/static/img/flags/cf.gif new file mode 100644 index 000000000..35787ca48 Binary files /dev/null and b/Web/static/img/flags/cf.gif differ diff --git a/Web/static/img/flags/cg.gif b/Web/static/img/flags/cg.gif new file mode 100644 index 000000000..e0a62a51c Binary files /dev/null and b/Web/static/img/flags/cg.gif differ diff --git a/Web/static/img/flags/ch.gif b/Web/static/img/flags/ch.gif new file mode 100644 index 000000000..d5c0e5b7f Binary files /dev/null and b/Web/static/img/flags/ch.gif differ diff --git a/Web/static/img/flags/ci.gif b/Web/static/img/flags/ci.gif new file mode 100644 index 000000000..844120a52 Binary files /dev/null and b/Web/static/img/flags/ci.gif differ diff --git a/Web/static/img/flags/ck.gif b/Web/static/img/flags/ck.gif new file mode 100644 index 000000000..2edb73994 Binary files /dev/null and b/Web/static/img/flags/ck.gif differ diff --git a/Web/static/img/flags/cl.gif b/Web/static/img/flags/cl.gif new file mode 100644 index 000000000..cbc370e6c Binary files /dev/null and b/Web/static/img/flags/cl.gif differ diff --git a/Web/static/img/flags/cm.gif b/Web/static/img/flags/cm.gif new file mode 100644 index 000000000..1fb102b29 Binary files /dev/null and b/Web/static/img/flags/cm.gif differ diff --git a/Web/static/img/flags/cn.gif b/Web/static/img/flags/cn.gif new file mode 100644 index 000000000..b05253097 Binary files /dev/null and b/Web/static/img/flags/cn.gif differ diff --git a/Web/static/img/flags/co.gif b/Web/static/img/flags/co.gif new file mode 100644 index 000000000..d0e15cafe Binary files /dev/null and b/Web/static/img/flags/co.gif differ diff --git a/Web/static/img/flags/cr.gif b/Web/static/img/flags/cr.gif new file mode 100644 index 000000000..0728dd6a4 Binary files /dev/null and b/Web/static/img/flags/cr.gif differ diff --git a/Web/static/img/flags/cs.gif b/Web/static/img/flags/cs.gif new file mode 100644 index 000000000..101db6493 Binary files /dev/null and b/Web/static/img/flags/cs.gif differ diff --git a/Web/static/img/flags/cu.gif b/Web/static/img/flags/cu.gif new file mode 100644 index 000000000..291255ca3 Binary files /dev/null and b/Web/static/img/flags/cu.gif differ diff --git a/Web/static/img/flags/cv.gif b/Web/static/img/flags/cv.gif new file mode 100644 index 000000000..43c6c6cb6 Binary files /dev/null and b/Web/static/img/flags/cv.gif differ diff --git a/Web/static/img/flags/cx.gif b/Web/static/img/flags/cx.gif new file mode 100644 index 000000000..a5b43089b Binary files /dev/null and b/Web/static/img/flags/cx.gif differ diff --git a/Web/static/img/flags/cy.gif b/Web/static/img/flags/cy.gif new file mode 100644 index 000000000..35c661e16 Binary files /dev/null and b/Web/static/img/flags/cy.gif differ diff --git a/Web/static/img/flags/cz.gif b/Web/static/img/flags/cz.gif new file mode 100644 index 000000000..0a605e581 Binary files /dev/null and b/Web/static/img/flags/cz.gif differ diff --git a/Web/static/img/flags/de.gif b/Web/static/img/flags/de.gif new file mode 100644 index 000000000..75728ddf2 Binary files /dev/null and b/Web/static/img/flags/de.gif differ diff --git a/Web/static/img/flags/dj.gif b/Web/static/img/flags/dj.gif new file mode 100644 index 000000000..212406d97 Binary files /dev/null and b/Web/static/img/flags/dj.gif differ diff --git a/Web/static/img/flags/dk.gif b/Web/static/img/flags/dk.gif new file mode 100644 index 000000000..03e75bd29 Binary files /dev/null and b/Web/static/img/flags/dk.gif differ diff --git a/Web/static/img/flags/dm.gif b/Web/static/img/flags/dm.gif new file mode 100644 index 000000000..2f87f3ca6 Binary files /dev/null and b/Web/static/img/flags/dm.gif differ diff --git a/Web/static/img/flags/do.gif b/Web/static/img/flags/do.gif new file mode 100644 index 000000000..f7d0bad39 Binary files /dev/null and b/Web/static/img/flags/do.gif differ diff --git a/Web/static/img/flags/dz.gif b/Web/static/img/flags/dz.gif new file mode 100644 index 000000000..ed580a7ce Binary files /dev/null and b/Web/static/img/flags/dz.gif differ diff --git a/Web/static/img/flags/ec.gif b/Web/static/img/flags/ec.gif new file mode 100644 index 000000000..9e41e0ec8 Binary files /dev/null and b/Web/static/img/flags/ec.gif differ diff --git a/Web/static/img/flags/ee.gif b/Web/static/img/flags/ee.gif new file mode 100644 index 000000000..9397a2d08 Binary files /dev/null and b/Web/static/img/flags/ee.gif differ diff --git a/Web/static/img/flags/eg.gif b/Web/static/img/flags/eg.gif new file mode 100644 index 000000000..6857c7dd5 Binary files /dev/null and b/Web/static/img/flags/eg.gif differ diff --git a/Web/static/img/flags/eh.gif b/Web/static/img/flags/eh.gif new file mode 100644 index 000000000..dd0391c28 Binary files /dev/null and b/Web/static/img/flags/eh.gif differ diff --git a/Web/static/img/flags/england.gif b/Web/static/img/flags/england.gif new file mode 100644 index 000000000..933a4f0b3 Binary files /dev/null and b/Web/static/img/flags/england.gif differ diff --git a/Web/static/img/flags/eo.gif b/Web/static/img/flags/eo.gif new file mode 100644 index 000000000..b0d199119 Binary files /dev/null and b/Web/static/img/flags/eo.gif differ diff --git a/Web/static/img/flags/er.gif b/Web/static/img/flags/er.gif new file mode 100644 index 000000000..3d4d612c7 Binary files /dev/null and b/Web/static/img/flags/er.gif differ diff --git a/Web/static/img/flags/es.gif b/Web/static/img/flags/es.gif new file mode 100644 index 000000000..c27d65e5f Binary files /dev/null and b/Web/static/img/flags/es.gif differ diff --git a/Web/static/img/flags/et.gif b/Web/static/img/flags/et.gif new file mode 100644 index 000000000..f77995d0a Binary files /dev/null and b/Web/static/img/flags/et.gif differ diff --git a/Web/static/img/flags/europeanunion.gif b/Web/static/img/flags/europeanunion.gif new file mode 100644 index 000000000..28a762a59 Binary files /dev/null and b/Web/static/img/flags/europeanunion.gif differ diff --git a/Web/static/img/flags/fam.gif b/Web/static/img/flags/fam.gif new file mode 100644 index 000000000..7d528852d Binary files /dev/null and b/Web/static/img/flags/fam.gif differ diff --git a/Web/static/img/flags/fi.gif b/Web/static/img/flags/fi.gif new file mode 100644 index 000000000..8d3a19182 Binary files /dev/null and b/Web/static/img/flags/fi.gif differ diff --git a/Web/static/img/flags/fj.gif b/Web/static/img/flags/fj.gif new file mode 100644 index 000000000..486151cb8 Binary files /dev/null and b/Web/static/img/flags/fj.gif differ diff --git a/Web/static/img/flags/fk.gif b/Web/static/img/flags/fk.gif new file mode 100644 index 000000000..37b5ecf30 Binary files /dev/null and b/Web/static/img/flags/fk.gif differ diff --git a/Web/static/img/flags/fm.gif b/Web/static/img/flags/fm.gif new file mode 100644 index 000000000..7f8723b7d Binary files /dev/null and b/Web/static/img/flags/fm.gif differ diff --git a/Web/static/img/flags/fo.gif b/Web/static/img/flags/fo.gif new file mode 100644 index 000000000..4a90fc043 Binary files /dev/null and b/Web/static/img/flags/fo.gif differ diff --git a/Web/static/img/flags/fr.gif b/Web/static/img/flags/fr.gif new file mode 100644 index 000000000..43d0b8017 Binary files /dev/null and b/Web/static/img/flags/fr.gif differ diff --git a/Web/static/img/flags/ga.gif b/Web/static/img/flags/ga.gif new file mode 100644 index 000000000..23fd5f0d2 Binary files /dev/null and b/Web/static/img/flags/ga.gif differ diff --git a/Web/static/img/flags/gb.gif b/Web/static/img/flags/gb.gif new file mode 100644 index 000000000..3c6bce15c Binary files /dev/null and b/Web/static/img/flags/gb.gif differ diff --git a/Web/static/img/flags/gd.gif b/Web/static/img/flags/gd.gif new file mode 100644 index 000000000..25ea31231 Binary files /dev/null and b/Web/static/img/flags/gd.gif differ diff --git a/Web/static/img/flags/ge.gif b/Web/static/img/flags/ge.gif new file mode 100644 index 000000000..faa7f126a Binary files /dev/null and b/Web/static/img/flags/ge.gif differ diff --git a/Web/static/img/flags/gf.gif b/Web/static/img/flags/gf.gif new file mode 100644 index 000000000..43d0b8017 Binary files /dev/null and b/Web/static/img/flags/gf.gif differ diff --git a/Web/static/img/flags/gh.gif b/Web/static/img/flags/gh.gif new file mode 100644 index 000000000..273fb7d1a Binary files /dev/null and b/Web/static/img/flags/gh.gif differ diff --git a/Web/static/img/flags/gi.gif b/Web/static/img/flags/gi.gif new file mode 100644 index 000000000..7b1984bc6 Binary files /dev/null and b/Web/static/img/flags/gi.gif differ diff --git a/Web/static/img/flags/gl.gif b/Web/static/img/flags/gl.gif new file mode 100644 index 000000000..ef445be00 Binary files /dev/null and b/Web/static/img/flags/gl.gif differ diff --git a/Web/static/img/flags/gm.gif b/Web/static/img/flags/gm.gif new file mode 100644 index 000000000..6847c5a8c Binary files /dev/null and b/Web/static/img/flags/gm.gif differ diff --git a/Web/static/img/flags/gn.gif b/Web/static/img/flags/gn.gif new file mode 100644 index 000000000..a982ac6f5 Binary files /dev/null and b/Web/static/img/flags/gn.gif differ diff --git a/Web/static/img/flags/gp.gif b/Web/static/img/flags/gp.gif new file mode 100644 index 000000000..31166db66 Binary files /dev/null and b/Web/static/img/flags/gp.gif differ diff --git a/Web/static/img/flags/gq.gif b/Web/static/img/flags/gq.gif new file mode 100644 index 000000000..8b4e0cc41 Binary files /dev/null and b/Web/static/img/flags/gq.gif differ diff --git a/Web/static/img/flags/gr.gif b/Web/static/img/flags/gr.gif new file mode 100644 index 000000000..b4c8c04e5 Binary files /dev/null and b/Web/static/img/flags/gr.gif differ diff --git a/Web/static/img/flags/gs.gif b/Web/static/img/flags/gs.gif new file mode 100644 index 000000000..ccc96ec00 Binary files /dev/null and b/Web/static/img/flags/gs.gif differ diff --git a/Web/static/img/flags/gt.gif b/Web/static/img/flags/gt.gif new file mode 100644 index 000000000..7e94d1dda Binary files /dev/null and b/Web/static/img/flags/gt.gif differ diff --git a/Web/static/img/flags/gu.gif b/Web/static/img/flags/gu.gif new file mode 100644 index 000000000..eafef683d Binary files /dev/null and b/Web/static/img/flags/gu.gif differ diff --git a/Web/static/img/flags/gw.gif b/Web/static/img/flags/gw.gif new file mode 100644 index 000000000..55f757115 Binary files /dev/null and b/Web/static/img/flags/gw.gif differ diff --git a/Web/static/img/flags/gy.gif b/Web/static/img/flags/gy.gif new file mode 100644 index 000000000..1cb4cd71d Binary files /dev/null and b/Web/static/img/flags/gy.gif differ diff --git a/Web/static/img/flags/hk.gif b/Web/static/img/flags/hk.gif new file mode 100644 index 000000000..798af96da Binary files /dev/null and b/Web/static/img/flags/hk.gif differ diff --git a/Web/static/img/flags/hm.gif b/Web/static/img/flags/hm.gif new file mode 100644 index 000000000..5269c6a0e Binary files /dev/null and b/Web/static/img/flags/hm.gif differ diff --git a/Web/static/img/flags/hn.gif b/Web/static/img/flags/hn.gif new file mode 100644 index 000000000..6c4ffe8e8 Binary files /dev/null and b/Web/static/img/flags/hn.gif differ diff --git a/Web/static/img/flags/hr.gif b/Web/static/img/flags/hr.gif new file mode 100644 index 000000000..557c66020 Binary files /dev/null and b/Web/static/img/flags/hr.gif differ diff --git a/Web/static/img/flags/ht.gif b/Web/static/img/flags/ht.gif new file mode 100644 index 000000000..059604ab2 Binary files /dev/null and b/Web/static/img/flags/ht.gif differ diff --git a/Web/static/img/flags/hu.gif b/Web/static/img/flags/hu.gif new file mode 100644 index 000000000..6142d8681 Binary files /dev/null and b/Web/static/img/flags/hu.gif differ diff --git a/Web/static/img/flags/id.gif b/Web/static/img/flags/id.gif new file mode 100644 index 000000000..865161b03 Binary files /dev/null and b/Web/static/img/flags/id.gif differ diff --git a/Web/static/img/flags/ie.gif b/Web/static/img/flags/ie.gif new file mode 100644 index 000000000..506ad2859 Binary files /dev/null and b/Web/static/img/flags/ie.gif differ diff --git a/Web/static/img/flags/il.gif b/Web/static/img/flags/il.gif new file mode 100644 index 000000000..c8483ae52 Binary files /dev/null and b/Web/static/img/flags/il.gif differ diff --git a/Web/static/img/flags/in.gif b/Web/static/img/flags/in.gif new file mode 100644 index 000000000..1cd80272e Binary files /dev/null and b/Web/static/img/flags/in.gif differ diff --git a/Web/static/img/flags/io.gif b/Web/static/img/flags/io.gif new file mode 100644 index 000000000..de7e7ab38 Binary files /dev/null and b/Web/static/img/flags/io.gif differ diff --git a/Web/static/img/flags/iq.gif b/Web/static/img/flags/iq.gif new file mode 100644 index 000000000..c34fe3c44 Binary files /dev/null and b/Web/static/img/flags/iq.gif differ diff --git a/Web/static/img/flags/ir.gif b/Web/static/img/flags/ir.gif new file mode 100644 index 000000000..156040fc5 Binary files /dev/null and b/Web/static/img/flags/ir.gif differ diff --git a/Web/static/img/flags/is.gif b/Web/static/img/flags/is.gif new file mode 100644 index 000000000..b42502de4 Binary files /dev/null and b/Web/static/img/flags/is.gif differ diff --git a/Web/static/img/flags/it.gif b/Web/static/img/flags/it.gif new file mode 100644 index 000000000..d79e90e99 Binary files /dev/null and b/Web/static/img/flags/it.gif differ diff --git a/Web/static/img/flags/jm.gif b/Web/static/img/flags/jm.gif new file mode 100644 index 000000000..0bed67c23 Binary files /dev/null and b/Web/static/img/flags/jm.gif differ diff --git a/Web/static/img/flags/jo.gif b/Web/static/img/flags/jo.gif new file mode 100644 index 000000000..03daf8af6 Binary files /dev/null and b/Web/static/img/flags/jo.gif differ diff --git a/Web/static/img/flags/jp.gif b/Web/static/img/flags/jp.gif new file mode 100644 index 000000000..444c1d05c Binary files /dev/null and b/Web/static/img/flags/jp.gif differ diff --git a/Web/static/img/flags/ke.gif b/Web/static/img/flags/ke.gif new file mode 100644 index 000000000..c2b5d45c4 Binary files /dev/null and b/Web/static/img/flags/ke.gif differ diff --git a/Web/static/img/flags/kg.gif b/Web/static/img/flags/kg.gif new file mode 100644 index 000000000..72a4d412c Binary files /dev/null and b/Web/static/img/flags/kg.gif differ diff --git a/Web/static/img/flags/kh.gif b/Web/static/img/flags/kh.gif new file mode 100644 index 000000000..30a183158 Binary files /dev/null and b/Web/static/img/flags/kh.gif differ diff --git a/Web/static/img/flags/ki.gif b/Web/static/img/flags/ki.gif new file mode 100644 index 000000000..4a0751a22 Binary files /dev/null and b/Web/static/img/flags/ki.gif differ diff --git a/Web/static/img/flags/km.gif b/Web/static/img/flags/km.gif new file mode 100644 index 000000000..5859595e8 Binary files /dev/null and b/Web/static/img/flags/km.gif differ diff --git a/Web/static/img/flags/kn.gif b/Web/static/img/flags/kn.gif new file mode 100644 index 000000000..bb9cc34a9 Binary files /dev/null and b/Web/static/img/flags/kn.gif differ diff --git a/Web/static/img/flags/kp.gif b/Web/static/img/flags/kp.gif new file mode 100644 index 000000000..6e0ca09e0 Binary files /dev/null and b/Web/static/img/flags/kp.gif differ diff --git a/Web/static/img/flags/kr.gif b/Web/static/img/flags/kr.gif new file mode 100644 index 000000000..1cddbe75b Binary files /dev/null and b/Web/static/img/flags/kr.gif differ diff --git a/Web/static/img/flags/kw.gif b/Web/static/img/flags/kw.gif new file mode 100644 index 000000000..1efc7347e Binary files /dev/null and b/Web/static/img/flags/kw.gif differ diff --git a/Web/static/img/flags/ky.gif b/Web/static/img/flags/ky.gif new file mode 100644 index 000000000..d3d02ee4d Binary files /dev/null and b/Web/static/img/flags/ky.gif differ diff --git a/Web/static/img/flags/kz.gif b/Web/static/img/flags/kz.gif new file mode 100644 index 000000000..24baebe05 Binary files /dev/null and b/Web/static/img/flags/kz.gif differ diff --git a/Web/static/img/flags/la.gif b/Web/static/img/flags/la.gif new file mode 100644 index 000000000..d14cf4d82 Binary files /dev/null and b/Web/static/img/flags/la.gif differ diff --git a/Web/static/img/flags/lb.gif b/Web/static/img/flags/lb.gif new file mode 100644 index 000000000..003d83af5 Binary files /dev/null and b/Web/static/img/flags/lb.gif differ diff --git a/Web/static/img/flags/lc.gif b/Web/static/img/flags/lc.gif new file mode 100644 index 000000000..f5fe5bffd Binary files /dev/null and b/Web/static/img/flags/lc.gif differ diff --git a/Web/static/img/flags/li.gif b/Web/static/img/flags/li.gif new file mode 100644 index 000000000..713c58e1d Binary files /dev/null and b/Web/static/img/flags/li.gif differ diff --git a/Web/static/img/flags/lk.gif b/Web/static/img/flags/lk.gif new file mode 100644 index 000000000..1b3ee7f57 Binary files /dev/null and b/Web/static/img/flags/lk.gif differ diff --git a/Web/static/img/flags/lr.gif b/Web/static/img/flags/lr.gif new file mode 100644 index 000000000..435af9e50 Binary files /dev/null and b/Web/static/img/flags/lr.gif differ diff --git a/Web/static/img/flags/ls.gif b/Web/static/img/flags/ls.gif new file mode 100644 index 000000000..427ae957e Binary files /dev/null and b/Web/static/img/flags/ls.gif differ diff --git a/Web/static/img/flags/lt.gif b/Web/static/img/flags/lt.gif new file mode 100644 index 000000000..dee9c601a Binary files /dev/null and b/Web/static/img/flags/lt.gif differ diff --git a/Web/static/img/flags/lu.gif b/Web/static/img/flags/lu.gif new file mode 100644 index 000000000..7d7293edd Binary files /dev/null and b/Web/static/img/flags/lu.gif differ diff --git a/Web/static/img/flags/lv.gif b/Web/static/img/flags/lv.gif new file mode 100644 index 000000000..17e71b7eb Binary files /dev/null and b/Web/static/img/flags/lv.gif differ diff --git a/Web/static/img/flags/ly.gif b/Web/static/img/flags/ly.gif new file mode 100644 index 000000000..a654c30af Binary files /dev/null and b/Web/static/img/flags/ly.gif differ diff --git a/Web/static/img/flags/ma.gif b/Web/static/img/flags/ma.gif new file mode 100644 index 000000000..fc784119d Binary files /dev/null and b/Web/static/img/flags/ma.gif differ diff --git a/Web/static/img/flags/mc.gif b/Web/static/img/flags/mc.gif new file mode 100644 index 000000000..02a7c8e1b Binary files /dev/null and b/Web/static/img/flags/mc.gif differ diff --git a/Web/static/img/flags/md.gif b/Web/static/img/flags/md.gif new file mode 100644 index 000000000..e4b8a7e3f Binary files /dev/null and b/Web/static/img/flags/md.gif differ diff --git a/Web/static/img/flags/me.gif b/Web/static/img/flags/me.gif new file mode 100644 index 000000000..a260453c2 Binary files /dev/null and b/Web/static/img/flags/me.gif differ diff --git a/Web/static/img/flags/mg.gif b/Web/static/img/flags/mg.gif new file mode 100644 index 000000000..a91b577d1 Binary files /dev/null and b/Web/static/img/flags/mg.gif differ diff --git a/Web/static/img/flags/mh.gif b/Web/static/img/flags/mh.gif new file mode 100644 index 000000000..92f5f485c Binary files /dev/null and b/Web/static/img/flags/mh.gif differ diff --git a/Web/static/img/flags/mk.gif b/Web/static/img/flags/mk.gif new file mode 100644 index 000000000..7aeb8311b Binary files /dev/null and b/Web/static/img/flags/mk.gif differ diff --git a/Web/static/img/flags/ml.gif b/Web/static/img/flags/ml.gif new file mode 100644 index 000000000..53d6f490c Binary files /dev/null and b/Web/static/img/flags/ml.gif differ diff --git a/Web/static/img/flags/mm.gif b/Web/static/img/flags/mm.gif new file mode 100644 index 000000000..9e0a2756d Binary files /dev/null and b/Web/static/img/flags/mm.gif differ diff --git a/Web/static/img/flags/mn.gif b/Web/static/img/flags/mn.gif new file mode 100644 index 000000000..dff8ea5a6 Binary files /dev/null and b/Web/static/img/flags/mn.gif differ diff --git a/Web/static/img/flags/mo.gif b/Web/static/img/flags/mo.gif new file mode 100644 index 000000000..66cf5b4f0 Binary files /dev/null and b/Web/static/img/flags/mo.gif differ diff --git a/Web/static/img/flags/mp.gif b/Web/static/img/flags/mp.gif new file mode 100644 index 000000000..73b7147e9 Binary files /dev/null and b/Web/static/img/flags/mp.gif differ diff --git a/Web/static/img/flags/mq.gif b/Web/static/img/flags/mq.gif new file mode 100644 index 000000000..570bc5dd1 Binary files /dev/null and b/Web/static/img/flags/mq.gif differ diff --git a/Web/static/img/flags/mr.gif b/Web/static/img/flags/mr.gif new file mode 100644 index 000000000..f52fcf093 Binary files /dev/null and b/Web/static/img/flags/mr.gif differ diff --git a/Web/static/img/flags/ms.gif b/Web/static/img/flags/ms.gif new file mode 100644 index 000000000..5e5a67aa8 Binary files /dev/null and b/Web/static/img/flags/ms.gif differ diff --git a/Web/static/img/flags/mt.gif b/Web/static/img/flags/mt.gif new file mode 100644 index 000000000..45c709f2b Binary files /dev/null and b/Web/static/img/flags/mt.gif differ diff --git a/Web/static/img/flags/mu.gif b/Web/static/img/flags/mu.gif new file mode 100644 index 000000000..081ab4533 Binary files /dev/null and b/Web/static/img/flags/mu.gif differ diff --git a/Web/static/img/flags/mv.gif b/Web/static/img/flags/mv.gif new file mode 100644 index 000000000..46b63875b Binary files /dev/null and b/Web/static/img/flags/mv.gif differ diff --git a/Web/static/img/flags/mw.gif b/Web/static/img/flags/mw.gif new file mode 100644 index 000000000..ad045a09c Binary files /dev/null and b/Web/static/img/flags/mw.gif differ diff --git a/Web/static/img/flags/mx.gif b/Web/static/img/flags/mx.gif new file mode 100644 index 000000000..ddc75d04d Binary files /dev/null and b/Web/static/img/flags/mx.gif differ diff --git a/Web/static/img/flags/my.gif b/Web/static/img/flags/my.gif new file mode 100644 index 000000000..fc7d52361 Binary files /dev/null and b/Web/static/img/flags/my.gif differ diff --git a/Web/static/img/flags/mz.gif b/Web/static/img/flags/mz.gif new file mode 100644 index 000000000..7d635082a Binary files /dev/null and b/Web/static/img/flags/mz.gif differ diff --git a/Web/static/img/flags/na.gif b/Web/static/img/flags/na.gif new file mode 100644 index 000000000..c0babe723 Binary files /dev/null and b/Web/static/img/flags/na.gif differ diff --git a/Web/static/img/flags/nc.gif b/Web/static/img/flags/nc.gif new file mode 100644 index 000000000..b1e91b9a8 Binary files /dev/null and b/Web/static/img/flags/nc.gif differ diff --git a/Web/static/img/flags/ne.gif b/Web/static/img/flags/ne.gif new file mode 100644 index 000000000..ff4eaf074 Binary files /dev/null and b/Web/static/img/flags/ne.gif differ diff --git a/Web/static/img/flags/nf.gif b/Web/static/img/flags/nf.gif new file mode 100644 index 000000000..c83424c2c Binary files /dev/null and b/Web/static/img/flags/nf.gif differ diff --git a/Web/static/img/flags/ng.gif b/Web/static/img/flags/ng.gif new file mode 100644 index 000000000..bdde7cb3b Binary files /dev/null and b/Web/static/img/flags/ng.gif differ diff --git a/Web/static/img/flags/ni.gif b/Web/static/img/flags/ni.gif new file mode 100644 index 000000000..d05894d0c Binary files /dev/null and b/Web/static/img/flags/ni.gif differ diff --git a/Web/static/img/flags/nl.gif b/Web/static/img/flags/nl.gif new file mode 100644 index 000000000..c1c8f46d0 Binary files /dev/null and b/Web/static/img/flags/nl.gif differ diff --git a/Web/static/img/flags/no.gif b/Web/static/img/flags/no.gif new file mode 100644 index 000000000..6202d1f3a Binary files /dev/null and b/Web/static/img/flags/no.gif differ diff --git a/Web/static/img/flags/np.gif b/Web/static/img/flags/np.gif new file mode 100644 index 000000000..1096893a7 Binary files /dev/null and b/Web/static/img/flags/np.gif differ diff --git a/Web/static/img/flags/nr.gif b/Web/static/img/flags/nr.gif new file mode 100644 index 000000000..2e4c0c5ca Binary files /dev/null and b/Web/static/img/flags/nr.gif differ diff --git a/Web/static/img/flags/nu.gif b/Web/static/img/flags/nu.gif new file mode 100644 index 000000000..618210a75 Binary files /dev/null and b/Web/static/img/flags/nu.gif differ diff --git a/Web/static/img/flags/nz.gif b/Web/static/img/flags/nz.gif new file mode 100644 index 000000000..028a5dc6e Binary files /dev/null and b/Web/static/img/flags/nz.gif differ diff --git a/Web/static/img/flags/om.gif b/Web/static/img/flags/om.gif new file mode 100644 index 000000000..2b8c77501 Binary files /dev/null and b/Web/static/img/flags/om.gif differ diff --git a/Web/static/img/flags/pa.gif b/Web/static/img/flags/pa.gif new file mode 100644 index 000000000..d518b2f97 Binary files /dev/null and b/Web/static/img/flags/pa.gif differ diff --git a/Web/static/img/flags/pe.gif b/Web/static/img/flags/pe.gif new file mode 100644 index 000000000..3bc763905 Binary files /dev/null and b/Web/static/img/flags/pe.gif differ diff --git a/Web/static/img/flags/pf.gif b/Web/static/img/flags/pf.gif new file mode 100644 index 000000000..849297a57 Binary files /dev/null and b/Web/static/img/flags/pf.gif differ diff --git a/Web/static/img/flags/pg.gif b/Web/static/img/flags/pg.gif new file mode 100644 index 000000000..2d20b0785 Binary files /dev/null and b/Web/static/img/flags/pg.gif differ diff --git a/Web/static/img/flags/ph.gif b/Web/static/img/flags/ph.gif new file mode 100644 index 000000000..12b380acd Binary files /dev/null and b/Web/static/img/flags/ph.gif differ diff --git a/Web/static/img/flags/pk.gif b/Web/static/img/flags/pk.gif new file mode 100644 index 000000000..f3f62c2eb Binary files /dev/null and b/Web/static/img/flags/pk.gif differ diff --git a/Web/static/img/flags/pl.gif b/Web/static/img/flags/pl.gif new file mode 100644 index 000000000..bf1064636 Binary files /dev/null and b/Web/static/img/flags/pl.gif differ diff --git a/Web/static/img/flags/pm.gif b/Web/static/img/flags/pm.gif new file mode 100644 index 000000000..99bf6fdb6 Binary files /dev/null and b/Web/static/img/flags/pm.gif differ diff --git a/Web/static/img/flags/pn.gif b/Web/static/img/flags/pn.gif new file mode 100644 index 000000000..4bc86a1d8 Binary files /dev/null and b/Web/static/img/flags/pn.gif differ diff --git a/Web/static/img/flags/pr.gif b/Web/static/img/flags/pr.gif new file mode 100644 index 000000000..6d5d58967 Binary files /dev/null and b/Web/static/img/flags/pr.gif differ diff --git a/Web/static/img/flags/ps.gif b/Web/static/img/flags/ps.gif new file mode 100644 index 000000000..6afa3b718 Binary files /dev/null and b/Web/static/img/flags/ps.gif differ diff --git a/Web/static/img/flags/pt.gif b/Web/static/img/flags/pt.gif new file mode 100644 index 000000000..e735f740e Binary files /dev/null and b/Web/static/img/flags/pt.gif differ diff --git a/Web/static/img/flags/pw.gif b/Web/static/img/flags/pw.gif new file mode 100644 index 000000000..5854510fa Binary files /dev/null and b/Web/static/img/flags/pw.gif differ diff --git a/Web/static/img/flags/py.gif b/Web/static/img/flags/py.gif new file mode 100644 index 000000000..f2e66af75 Binary files /dev/null and b/Web/static/img/flags/py.gif differ diff --git a/Web/static/img/flags/qa.gif b/Web/static/img/flags/qa.gif new file mode 100644 index 000000000..2e843ff9e Binary files /dev/null and b/Web/static/img/flags/qa.gif differ diff --git a/Web/static/img/flags/re.gif b/Web/static/img/flags/re.gif new file mode 100644 index 000000000..43d0b8017 Binary files /dev/null and b/Web/static/img/flags/re.gif differ diff --git a/Web/static/img/flags/ro.gif b/Web/static/img/flags/ro.gif new file mode 100644 index 000000000..f5d5f125b Binary files /dev/null and b/Web/static/img/flags/ro.gif differ diff --git a/Web/static/img/flags/rs.gif b/Web/static/img/flags/rs.gif new file mode 100644 index 000000000..3bd1fb2fd Binary files /dev/null and b/Web/static/img/flags/rs.gif differ diff --git a/Web/static/img/flags/ru.gif b/Web/static/img/flags/ru.gif new file mode 100644 index 000000000..b525c4623 Binary files /dev/null and b/Web/static/img/flags/ru.gif differ diff --git a/Web/static/img/flags/rw.gif b/Web/static/img/flags/rw.gif new file mode 100644 index 000000000..0d095f7ae Binary files /dev/null and b/Web/static/img/flags/rw.gif differ diff --git a/Web/static/img/flags/sa.gif b/Web/static/img/flags/sa.gif new file mode 100644 index 000000000..179961b69 Binary files /dev/null and b/Web/static/img/flags/sa.gif differ diff --git a/Web/static/img/flags/sb.gif b/Web/static/img/flags/sb.gif new file mode 100644 index 000000000..8f5ff837f Binary files /dev/null and b/Web/static/img/flags/sb.gif differ diff --git a/Web/static/img/flags/sc.gif b/Web/static/img/flags/sc.gif new file mode 100644 index 000000000..31b47677e Binary files /dev/null and b/Web/static/img/flags/sc.gif differ diff --git a/Web/static/img/flags/scotland.gif b/Web/static/img/flags/scotland.gif new file mode 100644 index 000000000..03f3f1de2 Binary files /dev/null and b/Web/static/img/flags/scotland.gif differ diff --git a/Web/static/img/flags/sd.gif b/Web/static/img/flags/sd.gif new file mode 100644 index 000000000..53ae214fa Binary files /dev/null and b/Web/static/img/flags/sd.gif differ diff --git a/Web/static/img/flags/se.gif b/Web/static/img/flags/se.gif new file mode 100644 index 000000000..80f628522 Binary files /dev/null and b/Web/static/img/flags/se.gif differ diff --git a/Web/static/img/flags/sg.gif b/Web/static/img/flags/sg.gif new file mode 100644 index 000000000..5663d39f8 Binary files /dev/null and b/Web/static/img/flags/sg.gif differ diff --git a/Web/static/img/flags/sh.gif b/Web/static/img/flags/sh.gif new file mode 100644 index 000000000..dcc7f3bcf Binary files /dev/null and b/Web/static/img/flags/sh.gif differ diff --git a/Web/static/img/flags/si.gif b/Web/static/img/flags/si.gif new file mode 100644 index 000000000..23852b50e Binary files /dev/null and b/Web/static/img/flags/si.gif differ diff --git a/Web/static/img/flags/sj.gif b/Web/static/img/flags/sj.gif new file mode 100644 index 000000000..6202d1f3a Binary files /dev/null and b/Web/static/img/flags/sj.gif differ diff --git a/Web/static/img/flags/sk.gif b/Web/static/img/flags/sk.gif new file mode 100644 index 000000000..1b3f22baf Binary files /dev/null and b/Web/static/img/flags/sk.gif differ diff --git a/Web/static/img/flags/sl.gif b/Web/static/img/flags/sl.gif new file mode 100644 index 000000000..f0f34923d Binary files /dev/null and b/Web/static/img/flags/sl.gif differ diff --git a/Web/static/img/flags/sm.gif b/Web/static/img/flags/sm.gif new file mode 100644 index 000000000..04d98de5a Binary files /dev/null and b/Web/static/img/flags/sm.gif differ diff --git a/Web/static/img/flags/sn.gif b/Web/static/img/flags/sn.gif new file mode 100644 index 000000000..6dac8709d Binary files /dev/null and b/Web/static/img/flags/sn.gif differ diff --git a/Web/static/img/flags/so.gif b/Web/static/img/flags/so.gif new file mode 100644 index 000000000..f1961694a Binary files /dev/null and b/Web/static/img/flags/so.gif differ diff --git a/Web/static/img/flags/sr.gif b/Web/static/img/flags/sr.gif new file mode 100644 index 000000000..0f7499ad9 Binary files /dev/null and b/Web/static/img/flags/sr.gif differ diff --git a/Web/static/img/flags/st.gif b/Web/static/img/flags/st.gif new file mode 100644 index 000000000..4f1e6e092 Binary files /dev/null and b/Web/static/img/flags/st.gif differ diff --git a/Web/static/img/flags/sv.gif b/Web/static/img/flags/sv.gif new file mode 100644 index 000000000..2d7b159a1 Binary files /dev/null and b/Web/static/img/flags/sv.gif differ diff --git a/Web/static/img/flags/sy.gif b/Web/static/img/flags/sy.gif new file mode 100644 index 000000000..dc8bd5094 Binary files /dev/null and b/Web/static/img/flags/sy.gif differ diff --git a/Web/static/img/flags/sz.gif b/Web/static/img/flags/sz.gif new file mode 100644 index 000000000..f37aaf801 Binary files /dev/null and b/Web/static/img/flags/sz.gif differ diff --git a/Web/static/img/flags/tc.gif b/Web/static/img/flags/tc.gif new file mode 100644 index 000000000..11a8c232f Binary files /dev/null and b/Web/static/img/flags/tc.gif differ diff --git a/Web/static/img/flags/td.gif b/Web/static/img/flags/td.gif new file mode 100644 index 000000000..7aa8a10df Binary files /dev/null and b/Web/static/img/flags/td.gif differ diff --git a/Web/static/img/flags/tf.gif b/Web/static/img/flags/tf.gif new file mode 100644 index 000000000..51a432509 Binary files /dev/null and b/Web/static/img/flags/tf.gif differ diff --git a/Web/static/img/flags/tg.gif b/Web/static/img/flags/tg.gif new file mode 100644 index 000000000..ca6b4e774 Binary files /dev/null and b/Web/static/img/flags/tg.gif differ diff --git a/Web/static/img/flags/th.gif b/Web/static/img/flags/th.gif new file mode 100644 index 000000000..013079240 Binary files /dev/null and b/Web/static/img/flags/th.gif differ diff --git a/Web/static/img/flags/tj.gif b/Web/static/img/flags/tj.gif new file mode 100644 index 000000000..2fe38d4ab Binary files /dev/null and b/Web/static/img/flags/tj.gif differ diff --git a/Web/static/img/flags/tk.gif b/Web/static/img/flags/tk.gif new file mode 100644 index 000000000..3d3a727fd Binary files /dev/null and b/Web/static/img/flags/tk.gif differ diff --git a/Web/static/img/flags/tl.gif b/Web/static/img/flags/tl.gif new file mode 100644 index 000000000..df22d5823 Binary files /dev/null and b/Web/static/img/flags/tl.gif differ diff --git a/Web/static/img/flags/tm.gif b/Web/static/img/flags/tm.gif new file mode 100644 index 000000000..36d0994fb Binary files /dev/null and b/Web/static/img/flags/tm.gif differ diff --git a/Web/static/img/flags/tn.gif b/Web/static/img/flags/tn.gif new file mode 100644 index 000000000..917d4288c Binary files /dev/null and b/Web/static/img/flags/tn.gif differ diff --git a/Web/static/img/flags/to.gif b/Web/static/img/flags/to.gif new file mode 100644 index 000000000..d7ed4d116 Binary files /dev/null and b/Web/static/img/flags/to.gif differ diff --git a/Web/static/img/flags/tr.gif b/Web/static/img/flags/tr.gif new file mode 100644 index 000000000..e407d553d Binary files /dev/null and b/Web/static/img/flags/tr.gif differ diff --git a/Web/static/img/flags/tt.gif b/Web/static/img/flags/tt.gif new file mode 100644 index 000000000..47d3b806b Binary files /dev/null and b/Web/static/img/flags/tt.gif differ diff --git a/Web/static/img/flags/tv.gif b/Web/static/img/flags/tv.gif new file mode 100644 index 000000000..3c3382778 Binary files /dev/null and b/Web/static/img/flags/tv.gif differ diff --git a/Web/static/img/flags/tw.gif b/Web/static/img/flags/tw.gif new file mode 100644 index 000000000..cacfd9b7a Binary files /dev/null and b/Web/static/img/flags/tw.gif differ diff --git a/Web/static/img/flags/tz.gif b/Web/static/img/flags/tz.gif new file mode 100644 index 000000000..82b52ca29 Binary files /dev/null and b/Web/static/img/flags/tz.gif differ diff --git a/Web/static/img/flags/ua.gif b/Web/static/img/flags/ua.gif new file mode 100644 index 000000000..5d6cd83f5 Binary files /dev/null and b/Web/static/img/flags/ua.gif differ diff --git a/Web/static/img/flags/ug.gif b/Web/static/img/flags/ug.gif new file mode 100644 index 000000000..58b731ad5 Binary files /dev/null and b/Web/static/img/flags/ug.gif differ diff --git a/Web/static/img/flags/um.gif b/Web/static/img/flags/um.gif new file mode 100644 index 000000000..3b4c84839 Binary files /dev/null and b/Web/static/img/flags/um.gif differ diff --git a/Web/static/img/flags/us.gif b/Web/static/img/flags/us.gif new file mode 100644 index 000000000..8f198f73a Binary files /dev/null and b/Web/static/img/flags/us.gif differ diff --git a/Web/static/img/flags/uy.gif b/Web/static/img/flags/uy.gif new file mode 100644 index 000000000..12848c741 Binary files /dev/null and b/Web/static/img/flags/uy.gif differ diff --git a/Web/static/img/flags/uz.gif b/Web/static/img/flags/uz.gif new file mode 100644 index 000000000..dc9daecaa Binary files /dev/null and b/Web/static/img/flags/uz.gif differ diff --git a/Web/static/img/flags/va.gif b/Web/static/img/flags/va.gif new file mode 100644 index 000000000..2bd74468d Binary files /dev/null and b/Web/static/img/flags/va.gif differ diff --git a/Web/static/img/flags/vc.gif b/Web/static/img/flags/vc.gif new file mode 100644 index 000000000..48213816a Binary files /dev/null and b/Web/static/img/flags/vc.gif differ diff --git a/Web/static/img/flags/ve.gif b/Web/static/img/flags/ve.gif new file mode 100644 index 000000000..19ce6c146 Binary files /dev/null and b/Web/static/img/flags/ve.gif differ diff --git a/Web/static/img/flags/vg.gif b/Web/static/img/flags/vg.gif new file mode 100644 index 000000000..1fc0f96ee Binary files /dev/null and b/Web/static/img/flags/vg.gif differ diff --git a/Web/static/img/flags/vi.gif b/Web/static/img/flags/vi.gif new file mode 100644 index 000000000..66f9e746b Binary files /dev/null and b/Web/static/img/flags/vi.gif differ diff --git a/Web/static/img/flags/vn.gif b/Web/static/img/flags/vn.gif new file mode 100644 index 000000000..f1e20c941 Binary files /dev/null and b/Web/static/img/flags/vn.gif differ diff --git a/Web/static/img/flags/vu.gif b/Web/static/img/flags/vu.gif new file mode 100644 index 000000000..8a8b2b065 Binary files /dev/null and b/Web/static/img/flags/vu.gif differ diff --git a/Web/static/img/flags/wales.gif b/Web/static/img/flags/wales.gif new file mode 100644 index 000000000..901d17507 Binary files /dev/null and b/Web/static/img/flags/wales.gif differ diff --git a/Web/static/img/flags/wf.gif b/Web/static/img/flags/wf.gif new file mode 100644 index 000000000..eaa954b13 Binary files /dev/null and b/Web/static/img/flags/wf.gif differ diff --git a/Web/static/img/flags/ws.gif b/Web/static/img/flags/ws.gif new file mode 100644 index 000000000..a51f939ed Binary files /dev/null and b/Web/static/img/flags/ws.gif differ diff --git a/Web/static/img/flags/ye.gif b/Web/static/img/flags/ye.gif new file mode 100644 index 000000000..7b0183d0e Binary files /dev/null and b/Web/static/img/flags/ye.gif differ diff --git a/Web/static/img/flags/yt.gif b/Web/static/img/flags/yt.gif new file mode 100644 index 000000000..a2267c054 Binary files /dev/null and b/Web/static/img/flags/yt.gif differ diff --git a/Web/static/img/flags/za.gif b/Web/static/img/flags/za.gif new file mode 100644 index 000000000..ede525891 Binary files /dev/null and b/Web/static/img/flags/za.gif differ diff --git a/Web/static/img/flags/zm.gif b/Web/static/img/flags/zm.gif new file mode 100644 index 000000000..b2851d2b4 Binary files /dev/null and b/Web/static/img/flags/zm.gif differ diff --git a/Web/static/img/flags/zw.gif b/Web/static/img/flags/zw.gif new file mode 100644 index 000000000..02901f627 Binary files /dev/null and b/Web/static/img/flags/zw.gif differ diff --git a/Web/static/img/flex_arrow_open.gif b/Web/static/img/flex_arrow_open.gif new file mode 100644 index 000000000..7e3a06035 Binary files /dev/null and b/Web/static/img/flex_arrow_open.gif differ diff --git a/Web/static/img/flex_arrow_shut.gif b/Web/static/img/flex_arrow_shut.gif new file mode 100644 index 000000000..3aa5164dd Binary files /dev/null and b/Web/static/img/flex_arrow_shut.gif differ diff --git a/Web/static/img/hat.gif b/Web/static/img/hat.gif new file mode 100644 index 000000000..1398a4878 Binary files /dev/null and b/Web/static/img/hat.gif differ diff --git a/Web/static/img/header.png b/Web/static/img/header.png new file mode 100644 index 000000000..1c9f63124 Binary files /dev/null and b/Web/static/img/header.png differ diff --git a/Web/static/img/header_divider.gif b/Web/static/img/header_divider.gif new file mode 100644 index 000000000..7e775c2f7 Binary files /dev/null and b/Web/static/img/header_divider.gif differ diff --git a/Web/static/img/header_yellow.png b/Web/static/img/header_yellow.png new file mode 100644 index 000000000..26eba3d0f Binary files /dev/null and b/Web/static/img/header_yellow.png differ diff --git a/Web/static/img/icon.ico b/Web/static/img/icon.ico new file mode 100644 index 000000000..7b7cd870b Binary files /dev/null and b/Web/static/img/icon.ico differ diff --git a/Web/static/img/libcss-ivinete/bar.jpg b/Web/static/img/libcss-ivinete/bar.jpg new file mode 100644 index 000000000..c6ee095d2 Binary files /dev/null and b/Web/static/img/libcss-ivinete/bar.jpg differ diff --git a/Web/static/img/libcss-ivinete/headerg.gif b/Web/static/img/libcss-ivinete/headerg.gif new file mode 100644 index 000000000..48dd84e05 Binary files /dev/null and b/Web/static/img/libcss-ivinete/headerg.gif differ diff --git a/Web/static/img/libcss-ivinete/headmd.png b/Web/static/img/libcss-ivinete/headmd.png new file mode 100644 index 000000000..dcbd4e160 Binary files /dev/null and b/Web/static/img/libcss-ivinete/headmd.png differ diff --git a/Web/static/img/libcss-ivinete/ovkg.png b/Web/static/img/libcss-ivinete/ovkg.png new file mode 100644 index 000000000..12d648d33 Binary files /dev/null and b/Web/static/img/libcss-ivinete/ovkg.png differ diff --git a/Web/static/img/logo.png b/Web/static/img/logo.png new file mode 100644 index 000000000..1018ffbea Binary files /dev/null and b/Web/static/img/logo.png differ diff --git a/Web/static/img/note_icon.png b/Web/static/img/note_icon.png new file mode 100644 index 000000000..0ce401357 Binary files /dev/null and b/Web/static/img/note_icon.png differ diff --git a/Web/static/img/oof.apng b/Web/static/img/oof.apng new file mode 100644 index 000000000..24f467b04 Binary files /dev/null and b/Web/static/img/oof.apng differ diff --git a/Web/static/img/search_icon.png b/Web/static/img/search_icon.png new file mode 100644 index 000000000..1834ac4bf Binary files /dev/null and b/Web/static/img/search_icon.png differ diff --git a/Web/static/img/xhead.png b/Web/static/img/xhead.png new file mode 100644 index 000000000..32842b17e Binary files /dev/null and b/Web/static/img/xhead.png differ diff --git a/Web/static/img/xhead2.gif b/Web/static/img/xhead2.gif new file mode 100644 index 000000000..3824ddac3 Binary files /dev/null and b/Web/static/img/xhead2.gif differ diff --git a/Web/static/img/xhead2006.gif b/Web/static/img/xhead2006.gif new file mode 100644 index 000000000..d770c76ac Binary files /dev/null and b/Web/static/img/xhead2006.gif differ diff --git a/Web/static/img/xhead2006early.gif b/Web/static/img/xhead2006early.gif new file mode 100644 index 000000000..bf4b92f3c Binary files /dev/null and b/Web/static/img/xhead2006early.gif differ diff --git a/Web/static/img/xhead4.png b/Web/static/img/xhead4.png new file mode 100644 index 000000000..bb5c5d654 Binary files /dev/null and b/Web/static/img/xhead4.png differ diff --git a/Web/static/img/xhead6.png b/Web/static/img/xhead6.png new file mode 100644 index 000000000..4ebfb61e8 Binary files /dev/null and b/Web/static/img/xhead6.png differ diff --git a/Web/static/js/al_api.js b/Web/static/js/al_api.js new file mode 100644 index 000000000..043444155 --- /dev/null +++ b/Web/static/js/al_api.js @@ -0,0 +1,37 @@ +window.API = new Proxy(Object.create(null), { + get(apiObj, name, recv) { + if(name === "Types") + return apiObj.Types; + + return new Proxy(new window.String(name), { + get(classSymbol, method, recv) { + return ((...args) => { + return new Promise((resolv, rej) => { + let xhr = new XMLHttpRequest(); + xhr.open("POST", "/rpc", true); + xhr.responseType = "arraybuffer"; + + xhr.onload = e => { + let resp = msgpack.decode(new Uint8Array(e.target.response)); + if(typeof resp.error !== "undefined") + rej(resp.error); + else + resolv(resp.result); + }; + + xhr.send(msgpack.encode({ + "brpc": 1, + "method": `${classSymbol.toString()}.${method}`, + "params": args + })); + }); + }) + } + }); + } +}); + +window.API.Types = {}; +window.API.Types.Message = (class Message { + +}); \ No newline at end of file diff --git a/Web/static/js/al_notifications.js b/Web/static/js/al_notifications.js new file mode 100644 index 000000000..e69de29bb diff --git a/Web/static/js/al_wall.js b/Web/static/js/al_wall.js new file mode 100644 index 000000000..5ca6f6edb --- /dev/null +++ b/Web/static/js/al_wall.js @@ -0,0 +1,75 @@ +function humanFileSize(bytes, si) { + var thresh = si ? 1000 : 1024; + if(Math.abs(bytes) < thresh) { + return bytes + ' B'; + } + var units = si + ? ['kB','MB','GB','TB','PB','EB','ZB','YB'] + : ['KiB','MiB','GiB','TiB','PiB','EiB','ZiB','YiB']; + var u = -1; + do { + bytes /= thresh; + ++u; + } while(Math.abs(bytes) >= thresh && u < units.length - 1); + return bytes.toFixed(1)+' '+units[u]; +} + +function trim(string) { + var newStr = string.substring(0, 10); + if(newStr.length !== string.length) + newStr += "…"; + + return newStr; +} + +function handleUpload() { + console.warn("блять..."); + var indicator = u(".post-upload"); + var file = this.files[0]; + if(typeof file === "undefined") { + indicator.attr("style", "display: none;"); + } else { + u("span", indicator.nodes[0]).text(trim(file.name) + " (" + humanFileSize(file.size, false) + ")"); + indicator.attr("style", "display: block;"); + } +} + +u("#wall-post-input").on("paste", function(e) { + if(e.clipboardData.files.length === 1) { + var input = u("input[name=_pic_attachment]").nodes[0]; + input.files = e.clipboardData.files; + + Reflect.apply(handleUpload, input, []); + } +}); + +u(".post-like-button").on("click", function(e) { + e.preventDefault(); + + var thisBtn = u(this).first(); + var link = u(this).attr("href"); + var heart = u(".heart", thisBtn); + var counter = u(".likeCnt", thisBtn); + var likes = counter.text(); + var isLiked = heart.attr("style") === 'color: red;'; + + ky(link); + heart.attr("style", isLiked ? 'filter: sepia(1);' : 'color: red;'); + counter.text(parseInt(likes) + (isLiked ? -1 : 1)); + + return false; +}); + +u("#wall-post-input").on("input", function(e) { + var boost = 5; + var textArea = e.target; + textArea.style.height = "5px"; + var newHeight = textArea.scrollHeight; + textArea.style.height = newHeight + boost; + return; + + // revert to original size if it is larger (possibly changed by user) + // textArea.style.height = (newHeight > originalHeight ? (newHeight + boost) : originalHeight) + "px"; +}); + +u("input[name=_pic_attachment]").on("change", handleUpload); \ No newline at end of file diff --git a/Web/static/js/messagebox.js b/Web/static/js/messagebox.js new file mode 100644 index 000000000..6c5bd8c02 --- /dev/null +++ b/Web/static/js/messagebox.js @@ -0,0 +1,33 @@ +Function.noop = () => {}; + +function MessageBox(title, body, buttons, callbacks) { + if(u(".ovk-diag-cont").length > 0) return false; + + let dialog = u( + `
      +
      +
      ${title}
      +
      ${body}
      +
      +
      +
      `); + u("body").addClass("dimmed").append(dialog); + + buttons.forEach((text, callback) => { + u(".ovk-diag-action").append(u(``)); + let button = u(u(".ovk-diag-action > button.button").last()); + + button.on("click", function(e) { + let __closeDialog = () => { + u("body").removeClass("dimmed"); + u(".ovk-diag-cont").remove(); + }; + + Reflect.apply(callbacks[callback], { + closeDialog: __closeDialog() + }, [e]); + + __closeDialog(); + }); + }); +} \ No newline at end of file diff --git a/Web/static/js/openvk.cls.js b/Web/static/js/openvk.cls.js new file mode 100644 index 000000000..8a868e1b7 --- /dev/null +++ b/Web/static/js/openvk.cls.js @@ -0,0 +1,58 @@ +function hidePanel(el,count=null) { + var info = el.parentNode.children[1]; + if (info.style.display=="none") { + info.style.display="block"; + el.className = "content_title_expanded"; + if (count) el.innerHTML=el.innerHTML.replace(" ("+count+")",""); + } else { + info.style.display="none"; + el.className = "content_title_unexpanded"; + if (count) el.innerHTML+=" ("+count+")"; + } +} + +function show_write_textarea() { + var el = document.getElementById('write'); + if (el.style.display == "none") { + el.style.display = "block"; + } else { + el.style.display = "none"; + } +} + +function edit_post(id, wid) { + var el = document.getElementById('text'+wid+'_'+id); + var ed = document.getElementById('text_edit'+wid+'_'+id); + if (el.style.display == "none") { + el.style.display = "block"; + ed.style.display = "none"; + } else { + el.style.display = "none"; + ed.style.display = "block"; + } +} + +document.addEventListener("DOMContentLoaded", function() { //BEGIN + +u("#_photoDelete").on("click", function(e) { + var formHtml = "
      "; + formHtml += ""; + formHtml += "
      "; + u("body").append(formHtml); + + MessageBox("Внимание", "Удаление нельзя отменить. Вы действительно уверены в том что хотите сделать?", [ + "Да", + "Нет" + ], [ + (function() { + u("#tmpPhDelF").nodes[0].submit(); + }), + (function() { + u("#tmpPhDelF").remove(); + }), + ]); + + return e.preventDefault(); +}); + +}); //END ONREADY DECLS \ No newline at end of file diff --git a/Web/static/js/package.json b/Web/static/js/package.json new file mode 100644 index 000000000..353f9a064 --- /dev/null +++ b/Web/static/js/package.json @@ -0,0 +1,11 @@ +{ + "dependencies": { + "@atlassian/aui": "^8.5.1", + "knockout": "^3.5.1", + "ky": "^0.19.0", + "monaco-editor": "^0.20.0", + "plotly.js-dist": "^1.52.3", + "requirejs": "^2.3.6", + "umbrellajs": "^3.1.0" + } +} diff --git a/Web/static/js/scroll.js b/Web/static/js/scroll.js new file mode 100644 index 000000000..19e1c63a3 --- /dev/null +++ b/Web/static/js/scroll.js @@ -0,0 +1,14 @@ +window.addEventListener("scroll", function(e) { + if(document.body.scrollTop < 100) { + document.body.classList.toggle("scrolled", false); + } else { + document.body.classList.toggle("scrolled", true); + } +}); + +u(".toTop").on("click", function(e) { + window.scrollTo({ + top: 0, + behavior: "smooth" + }); +}); \ No newline at end of file diff --git a/Web/static/js/yarn.lock b/Web/static/js/yarn.lock new file mode 100644 index 000000000..88f2fcf0e --- /dev/null +++ b/Web/static/js/yarn.lock @@ -0,0 +1,107 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@atlassian/aui@^8.5.1": + version "8.5.1" + resolved "https://registry.yarnpkg.com/@atlassian/aui/-/aui-8.5.1.tgz#6c585674240621a007e59b6ca8c79f45a335738d" + integrity sha512-u+cWuOTpDyNod5B1SZnDCa57ruad54jQ/4vUd2VGDdp8qqiYLeuEush1deuKejvFFWSS6zd7KhkzmO/m0tm1aw== + dependencies: + "@atlassian/brand-logos" "^1.2.0" + "@atlassian/tipsy" "^1.3.1" + backbone "^1.3.3" + css.escape "1.5.0" + fancy-file-input "~2.0.4" + jquery-ui "^1.12.1" + popper.js "^1.14.5" + skatejs "0.13.17" + skatejs-template-html "0.0.0" + trim-extra-html-whitespace "1.3.0" + underscore "^1.9.1" + +"@atlassian/brand-logos@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@atlassian/brand-logos/-/brand-logos-1.2.0.tgz#2110dba6ca49acaf1c51b15420a4567b01b4b4bd" + integrity sha512-rtTIQqvZO6Tr7+oNWvtED7xMpuE1NQoNF5vA1d+8ym3wTANvgbKxyHK2pj5jTW3dMoEE2Qac8YMebtN46B/mQg== + +"@atlassian/tipsy@^1.3.1": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@atlassian/tipsy/-/tipsy-1.3.2.tgz#ab759d461670d712425b2dac7573b79575a10502" + integrity sha512-H7qWMs66bztELt2QpOCLYDU9ZM3VZfE0knbRHHLBukH7v9dMkIS5ZwqcGREjWnVt0KNETaBeXxj0FD88TEOGVw== + +backbone@^1.3.3: + version "1.4.0" + resolved "https://registry.yarnpkg.com/backbone/-/backbone-1.4.0.tgz#54db4de9df7c3811c3f032f34749a4cd27f3bd12" + integrity sha512-RLmDrRXkVdouTg38jcgHhyQ/2zjg7a8E6sz2zxfz21Hh17xDJYUHBZimVIt5fUyS8vbfpeSmTL3gUjTEvUV3qQ== + dependencies: + underscore ">=1.8.3" + +css.escape@1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.0.tgz#95984d7887ce4ca90684e813966f42d1ef87ecea" + integrity sha1-lZhNeIfOTKkGhOgTlm9C0e+H7Oo= + +fancy-file-input@~2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/fancy-file-input/-/fancy-file-input-2.0.4.tgz#698c216482e07649a827681c4db3054fddc9a32b" + integrity sha512-l+J0WwDl4nM/zMJ/C8qleYnXMUJKsLng7c5uWH/miAiHoTvPDtEoLW1tmVO6Cy2O8i/1VfA+2YOwg/Q3+kgO6w== + +jquery-ui@^1.12.1: + version "1.12.1" + resolved "https://registry.yarnpkg.com/jquery-ui/-/jquery-ui-1.12.1.tgz#bcb4045c8dd0539c134bc1488cdd3e768a7a9e51" + integrity sha1-vLQEXI3QU5wTS8FIjN0+dop6nlE= + +knockout@^3.5.1: + version "3.5.1" + resolved "https://registry.yarnpkg.com/knockout/-/knockout-3.5.1.tgz#62c81e81843bea2008fd23c575edd9ca978e75cf" + integrity sha512-wRJ9I4az0QcsH7A4v4l0enUpkS++MBx0BnL/68KaLzJg7x1qmbjSlwEoCNol7KTYZ+pmtI7Eh2J0Nu6/2Z5J/Q== + +ky@^0.19.0: + version "0.19.0" + resolved "https://registry.yarnpkg.com/ky/-/ky-0.19.0.tgz#d6ad117e89efe2d85a1c2e91462d48ca1cda1f7a" + integrity sha512-RkDgbg5ahMv1MjHfJI2WJA2+Qbxq0iNSLWhreYiCHeHry9Q12sedCnP5KYGPt7sydDvsyH+8UcG6Kanq5mpsyw== + +monaco-editor@^0.20.0: + version "0.20.0" + resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.20.0.tgz#5d5009343a550124426cb4d965a4d27a348b4dea" + integrity sha512-hkvf4EtPJRMQlPC3UbMoRs0vTAFAYdzFQ+gpMb8A+9znae1c43q8Mab9iVsgTcg/4PNiLGGn3SlDIa8uvK1FIQ== + +plotly.js-dist@^1.52.3: + version "1.52.3" + resolved "https://registry.yarnpkg.com/plotly.js-dist/-/plotly.js-dist-1.52.3.tgz#4c16c6da6adab6cdba169087b5005bdddbf10834" + integrity sha512-kpuNwveRk6M/5cCW1ZgJTbMLD0bRZhJlLK0cUHVkTsP/PWKCVJqO3QiiqrypFGE/xEhWfHCY+YKAKjMmEbo4Gw== + +popper.js@^1.14.5: + version "1.16.1" + resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1.tgz#2a223cb3dc7b6213d740e40372be40de43e65b1b" + integrity sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ== + +requirejs@^2.3.6: + version "2.3.6" + resolved "https://registry.yarnpkg.com/requirejs/-/requirejs-2.3.6.tgz#e5093d9601c2829251258c0b9445d4d19fa9e7c9" + integrity sha512-ipEzlWQe6RK3jkzikgCupiTbTvm4S0/CAU5GlgptkN5SO6F3u0UD0K18wy6ErDqiCyP4J4YYe1HuAShvsxePLg== + +skatejs-template-html@0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/skatejs-template-html/-/skatejs-template-html-0.0.0.tgz#e990c1a7d4b58b7305ffcc3338939bf402023df7" + integrity sha1-6ZDBp9S1i3MF/8wzOJOb9AICPfc= + +skatejs@0.13.17: + version "0.13.17" + resolved "https://registry.yarnpkg.com/skatejs/-/skatejs-0.13.17.tgz#7a21fbb3434da45e52b47b61647168ee9e778071" + integrity sha1-eiH7s0NNpF5StHthZHFo7p53gHE= + +trim-extra-html-whitespace@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/trim-extra-html-whitespace/-/trim-extra-html-whitespace-1.3.0.tgz#b47efb0d1a5f2a56a85cc45cea525651e93404cf" + integrity sha1-tH77DRpfKlaoXMRc6lJWUek0BM8= + +umbrellajs@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/umbrellajs/-/umbrellajs-3.1.0.tgz#a4e6f0f6381f9d93110b5eee962e0e0864b10bd0" + integrity sha512-3qichMg1Q6EetLweBAT0L55O2W6CJe9qyiSt1RBnf+bcOqwJ4R7e2PDcoIUrCsg+uRo3DXOvurWdklBu0ia7fg== + +underscore@>=1.8.3, underscore@^1.9.1: + version "1.10.2" + resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.10.2.tgz#73d6aa3668f3188e4adb0f1943bd12cfd7efaaaf" + integrity sha512-N4P+Q/BuyuEKFJ43B9gYuOj4TQUHXX+j2FqguVOpjkssLUUrnJofCcBccJSCoeturDoZU6GorDTHSvUDlSQbTg== diff --git a/bootstrap.php b/bootstrap.php new file mode 100644 index 000000000..02c4a530f --- /dev/null +++ b/bootstrap.php @@ -0,0 +1,127 @@ +=")) + $problems[] = "Incompatible PHP version: " . PHP_VERSION . " (7.3+ required, 7.4+ recommended)"; + + if(!is_dir(__DIR__ . "/vendor")) + $problems[] = "Composer dependencies missing"; + + $requiredExtensions = [ + "imagick", + "fileinfo", + "PDO", + "pdo_mysql", + "pcre", + "hash", + "curl", + "Core", + "iconv", + "mbstring", + "sodium", + "openssl", + "json", + "tokenizer", + "libxml", + "date", + "session", + "SPL", + ]; + if(sizeof($missingExtensions = array_diff($requiredExtensions, get_loaded_extensions())) > 0) + foreach($missingExtensions as $extension) + $problems[] = "Missing extension $extension"; + + if(sizeof($problems) > 0) { + require __DIR__ . "/misc/install_err.phtml"; + exit; + } +} + +function ovk_proc_strtr(string $string, int $length = 0): string +{ + $newString = iconv_substr($string, 0, $length); + + return $newString . ($string !== $newString ? "…" : ""); #if cut hasn't happened, don't append "..." +} + +function bmask(int $input, array $options = []): Bitmask +{ + return new Bitmask($input, $options["length"] ?? 1, $options["mappings"] ?? []); +} + +function tr(string $stringId, ...$variables): string +{ + $localizer = Localizator::i(); + $lang = Session::i()->get("lang", "ru"); + $output = $localizer->_($stringId, $lang); + + if(sizeof($variables) > 0) { + if(gettype($variables[0]) === "integer") { + $numberedStringId = NULL; + $cardinal = $variables[0]; + switch($cardinal) { + case 0: + $numberedStringId = $stringId . "_zero"; + break; + case 1: + $numberedStringId = $stringId . "_one"; + break; + default: + $numberedStringId = $stringId . ($cardinal < 5 ? "_few" : "_other"); + } + + $newOutput = $localizer->_($numberedStringId, $lang); + $output = $newOutput === "@$numberedStringId" ? $output : $newOutput; + } + + for($i = 0; $i < sizeof($variables); $i++) + $output = preg_replace("%(? $db->dsn, + "user" => $db->user, + "password" => $db->password, + "caching" => [ + "folder" => __DIR__ . "/tmp", + ], + ]); +} + +#NOTICE: invalid name, kept for compatability +function ovk_proc_strtrim(string $string, int $length = 0): string +{ + trigger_error("ovk_proc_strtrim is deprecated, please use fully compatible ovk_proc_strtr.", E_USER_DEPRECATED); + + return ovk_proc_strtr($string, $length); +} + +return (function() { + _ovk_check_environment(); + require __DIR__ . "/vendor/autoload.php"; + + setlocale(LC_TIME, "POSIX"); + + define("OPENVK_VERSION", "Altair Preview (Build 10)", false); + define("OPENVK_DEFAULT_PER_PAGE", 10, false); + define("__OPENVK_ERROR_CLOCK_IN_FUTURE", "Server clock error: FK1200-DTF", false); +}); diff --git a/composer.json b/composer.json new file mode 100644 index 000000000..bf44fb066 --- /dev/null +++ b/composer.json @@ -0,0 +1,13 @@ +{ + "require": { + "p3k/emoji-detector": "^0.2.1", + "guzzlehttp/guzzle": "^6.5", + "komeiji-satori/curl": "dev-master", + "wapmorgan/cab-archive": "dev-master", + "whichbrowser/parser": "dev-master", + "zadarma/user-api-v1": "dev-master", + "james-heinrich/getid3": "^1.9@dev", + "rybakit/msgpack": "dev-master" + }, + "minimum-stability": "dev" +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 000000000..6d4a4c067 --- /dev/null +++ b/composer.lock @@ -0,0 +1,734 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "6b164abf6cdb4d7b7e3af3f86bb86439", + "packages": [ + { + "name": "guzzlehttp/guzzle", + "version": "6.5.x-dev", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "400cefd25a23a3098486bfb52685b5367a464171" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/400cefd25a23a3098486bfb52685b5367a464171", + "reference": "400cefd25a23a3098486bfb52685b5367a464171", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.0", + "guzzlehttp/psr7": "^1.6.1", + "php": ">=5.5" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0", + "psr/log": "^1.1" + }, + "suggest": { + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.5-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "time": "2019-12-30T04:52:42+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "ac2529fc650684c5cd687e2b462d046cdbed556e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/ac2529fc650684c5cd687e2b462d046cdbed556e", + "reference": "ac2529fc650684c5cd687e2b462d046cdbed556e", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "phpunit/phpunit": "^5.7.27 || ^7.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "time": "2019-12-17T17:19:17+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "1.x-dev", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "e193ff330a0f28b5e5f65ed3d804f5e2a89376e3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/e193ff330a0f28b5e5f65ed3d804f5e2a89376e3", + "reference": "e193ff330a0f28b5e5f65ed3d804f5e2a89376e3", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "psr/http-message": "~1.0", + "ralouphie/getallheaders": "^2.0.5 || ^3.0.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-zlib": "*", + "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8" + }, + "suggest": { + "zendframework/zend-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Schultze", + "homepage": "https://github.com/Tobion" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "time": "2020-02-04T14:54:51+00:00" + }, + { + "name": "james-heinrich/getid3", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/JamesHeinrich/getID3.git", + "reference": "9c679416351c286aa90001fbb55309f6542371b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/JamesHeinrich/getID3/zipball/9c679416351c286aa90001fbb55309f6542371b4", + "reference": "9c679416351c286aa90001fbb55309f6542371b4", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "jakub-onderka/php-parallel-lint": "^0.9 || ^1.0" + }, + "suggest": { + "ext-SimpleXML": "SimpleXML extension is required to analyze RIFF/WAV/BWF audio files (also requires `ext-libxml`).", + "ext-com_dotnet": "COM extension is required when loading files larger than 2GB on Windows.", + "ext-ctype": "ctype extension is required when loading files larger than 2GB on 32-bit PHP (also on 64-bit PHP on Windows) or executing `getid3_lib::CopyTagsToComments`.", + "ext-dba": "DBA extension is required to use the DBA database as a cache storage.", + "ext-exif": "EXIF extension is required for graphic modules.", + "ext-iconv": "iconv extension is required to work with different character sets (when `ext-mbstring` is not available).", + "ext-json": "JSON extension is required to analyze Apple Quicktime videos.", + "ext-libxml": "libxml extension is required to analyze RIFF/WAV/BWF audio files.", + "ext-mbstring": "mbstring extension is required to work with different character sets.", + "ext-mysql": "MySQL extension is required to use the MySQL database as a cache storage (deprecated in PHP 5.5, removed in PHP >= 7.0, use `ext-mysqli` instead).", + "ext-mysqli": "MySQLi extension is required to use the MySQL database as a cache storage.", + "ext-rar": "RAR extension is required for RAR archive module.", + "ext-sqlite3": "SQLite3 extension is required to use the SQLite3 database as a cache storage.", + "ext-xml": "XML extension is required for graphic modules to analyze the XML metadata.", + "ext-zlib": "Zlib extension is required for archive modules and compressed metadata." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.9.x-dev" + } + }, + "autoload": { + "classmap": [ + "getid3/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-1.0-or-later", + "LGPL-3.0-only", + "MPL-2.0" + ], + "description": "PHP script that extracts useful information from popular multimedia file formats", + "homepage": "https://www.getid3.org/", + "keywords": [ + "codecs", + "php", + "tags" + ], + "time": "2020-03-15T13:38:58+00:00" + }, + { + "name": "komeiji-satori/curl", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/komeiji-satori/curl.git", + "reference": "687d8fa621823809f5e90a7f5d2fd6a59b7e7948" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/komeiji-satori/curl/zipball/687d8fa621823809f5e90a7f5d2fd6a59b7e7948", + "reference": "687d8fa621823809f5e90a7f5d2fd6a59b7e7948", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "php": ">=5.6" + }, + "type": "library", + "autoload": { + "files": [ + "src/cURL.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Satori", + "email": "i@ttt.moe" + } + ], + "description": "一个轻量级的PHP网络操作类,实现GET、POST、UPLOAD、DOWNLOAD常用操作,出错自动重试,支持链式写法", + "homepage": "https://github.com/komeiji-satori/curl", + "keywords": [ + "PHP cURL", + "curl download", + "curl library", + "curl lightweight", + "curl post", + "curl upload" + ], + "time": "2019-04-20T16:06:47+00:00" + }, + { + "name": "p3k/emoji-detector", + "version": "0.2.1", + "source": { + "type": "git", + "url": "https://github.com/aaronpk/emoji-detector-php.git", + "reference": "fb0765845e554f04ccd2b594a7ab8ca44d804bab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/aaronpk/emoji-detector-php/zipball/fb0765845e554f04ccd2b594a7ab8ca44d804bab", + "reference": "fb0765845e554f04ccd2b594a7ab8ca44d804bab", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "require-dev": { + "phpunit/phpunit": "4.8" + }, + "type": "library", + "autoload": { + "files": [ + "src/Emoji.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Parecki", + "email": "aaron@parecki.com", + "homepage": "https://aaronparecki.com/" + } + ], + "description": "Detect and return all emoji found in a string", + "homepage": "https://github.com/aaronpk/emoji-detector-php", + "time": "2017-11-30T21:02:53+00:00" + }, + { + "name": "psr/cache", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "b9aa2cd4dd36cec02779bee07ee9dab8bd2d07d7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/b9aa2cd4dd36cec02779bee07ee9dab8bd2d07d7", + "reference": "b9aa2cd4dd36cec02779bee07ee9dab8bd2d07d7", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "time": "2019-10-04T14:04:41+00:00" + }, + { + "name": "psr/http-message", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "efd67d1dc14a7ef4fc4e518e7dee91c271d524e4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/efd67d1dc14a7ef4fc4e518e7dee91c271d524e4", + "reference": "efd67d1dc14a7ef4fc4e518e7dee91c271d524e4", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "time": "2019-08-29T13:16:46+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "time": "2019-03-08T08:55:37+00:00" + }, + { + "name": "rybakit/msgpack", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/rybakit/msgpack.php.git", + "reference": "23292371282e6fe17c6c94f51c3a8a0254fb0f27" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rybakit/msgpack.php/zipball/23292371282e6fe17c6c94f51c3a8a0254fb0f27", + "reference": "23292371282e6fe17c6c94f51c3a8a0254fb0f27", + "shasum": "" + }, + "require": { + "php": "^7.1.1" + }, + "require-dev": { + "ext-decimal": "*", + "ext-gmp": "*", + "friendsofphp/php-cs-fixer": "^2.14", + "phpunit/phpunit": "^7.1|^8.0" + }, + "suggest": { + "ext-decimal": "For converting overflowed integers to Decimal objects", + "ext-gmp": "For converting overflowed integers to GMP objects" + }, + "type": "library", + "autoload": { + "psr-4": { + "MessagePack\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Eugene Leonovich", + "email": "gen.work@gmail.com" + } + ], + "description": "A pure PHP implementation of the MessagePack serialization format.", + "keywords": [ + "messagepack", + "msgpack", + "pure", + "streaming" + ], + "time": "2020-02-24T18:59:16+00:00" + }, + { + "name": "wapmorgan/binary-stream", + "version": "0.4.0", + "source": { + "type": "git", + "url": "https://github.com/wapmorgan/BinaryStream.git", + "reference": "ca4989c15801635d79f65426b1b0fc6fe803b620" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wapmorgan/BinaryStream/zipball/ca4989c15801635d79f65426b1b0fc6fe803b620", + "reference": "ca4989c15801635d79f65426b1b0fc6fe803b620", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "wapmorgan\\BinaryStream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A handy tool for working with binary data", + "keywords": [ + "binary", + "binary-data", + "reader", + "writer" + ], + "time": "2017-06-18T01:02:52+00:00" + }, + { + "name": "wapmorgan/cab-archive", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/wapmorgan/CabArchive.git", + "reference": "c79134eaaf679cf27148d749359b3dd4a67d2960" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wapmorgan/CabArchive/zipball/c79134eaaf679cf27148d749359b3dd4a67d2960", + "reference": "c79134eaaf679cf27148d749359b3dd4a67d2960", + "shasum": "" + }, + "require": { + "wapmorgan/binary-stream": "~0.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "wapmorgan\\CabArchive\\": "src/" + }, + "psr-0": { + "CabArchive": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Reading and extraction of .cab-files", + "keywords": [ + "archive", + "cab", + "cabinet" + ], + "time": "2018-09-18T18:54:37+00:00" + }, + { + "name": "whichbrowser/parser", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/WhichBrowser/Parser-PHP.git", + "reference": "c7b71a46f5f51b5b7284ed36a5b16c62b6f9dc8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/WhichBrowser/Parser-PHP/zipball/c7b71a46f5f51b5b7284ed36a5b16c62b6f9dc8c", + "reference": "c7b71a46f5f51b5b7284ed36a5b16c62b6f9dc8c", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "psr/cache": "^1.0" + }, + "require-dev": { + "icomefromthenet/reverse-regex": "0.0.6.3", + "phpunit/php-code-coverage": "^2.2 || ^3.0", + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0", + "satooshi/php-coveralls": "^1.0", + "squizlabs/php_codesniffer": "2.5.*", + "symfony/yaml": "~2.8 || ~3.4 || ~4.2 || ~5.0" + }, + "suggest": { + "cache/array-adapter": "Allows testing of the caching functionality" + }, + "type": "library", + "autoload": { + "psr-4": { + "WhichBrowser\\": [ + "src/", + "tests/src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Niels Leenheer", + "email": "niels@leenheer.nl", + "role": "Developer" + } + ], + "description": "Useragent sniffing library for PHP", + "homepage": "http://whichbrowser.net", + "keywords": [ + "browser", + "sniffing", + "ua", + "useragent" + ], + "time": "2020-01-02T14:36:46+00:00" + }, + { + "name": "zadarma/user-api-v1", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/zadarma/user-api-v1.git", + "reference": "0006bddbe6395de982e20ecc5bb3e70051a0db82" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zadarma/user-api-v1/zipball/0006bddbe6395de982e20ecc5bb3e70051a0db82", + "reference": "0006bddbe6395de982e20ecc5bb3e70051a0db82", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "php": ">=5.5.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Zadarma_API\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Zadarma", + "email": "github@zadarma.com" + } + ], + "description": "PHP class for Zadarma API", + "homepage": "https://github.com/zadarma/user-api-v1", + "keywords": [ + "api", + "free calls", + "pbx", + "sip", + "zadarma" + ], + "time": "2020-03-23T06:03:47+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "dev", + "stability-flags": { + "komeiji-satori/curl": 20, + "wapmorgan/cab-archive": 20, + "whichbrowser/parser": 20, + "zadarma/user-api-v1": 20, + "james-heinrich/getid3": 20, + "rybakit/msgpack": 20 + }, + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "1.1.0" +} diff --git a/data/modelCodes.json b/data/modelCodes.json new file mode 100644 index 000000000..25a0ed4e6 --- /dev/null +++ b/data/modelCodes.json @@ -0,0 +1,20 @@ +{ + "openvk\\Web\\Models\\Entities\\Album":2, + "openvk\\Web\\Models\\Entities\\Attachable":3, + "openvk\\Web\\Models\\Entities\\Audio":4, + "openvk\\Web\\Models\\Entities\\Club":5, + "openvk\\Web\\Models\\Entities\\Comment":6, + "openvk\\Web\\Models\\Entities\\Correspondence":7, + "openvk\\Web\\Models\\Entities\\Media":8, + "openvk\\Web\\Models\\Entities\\Message":9, + "openvk\\Web\\Models\\Entities\\Note":10, + "openvk\\Web\\Models\\Entities\\Notification":11, + "openvk\\Web\\Models\\Entities\\PasswordReset":12, + "openvk\\Web\\Models\\Entities\\Photo":13, + "openvk\\Web\\Models\\Entities\\Post":14, + "openvk\\Web\\Models\\Entities\\Postable":15, + "openvk\\Web\\Models\\Entities\\Ticket":16, + "openvk\\Web\\Models\\Entities\\TicketComment":17, + "openvk\\Web\\Models\\Entities\\User":18, + "openvk\\Web\\Models\\Entities\\Video":19 +} \ No newline at end of file diff --git a/data/rules.xhtml b/data/rules.xhtml new file mode 100644 index 000000000..440d7476f --- /dev/null +++ b/data/rules.xhtml @@ -0,0 +1 @@ +Sample rules \ No newline at end of file diff --git a/locales/by.strings b/locales/by.strings new file mode 100644 index 000000000..4300257ba --- /dev/null +++ b/locales/by.strings @@ -0,0 +1,432 @@ +"__locale" = "by_BY.utf8;Bel"; + +%{ Check for https://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html %} + +%{ Main page %} + +"home" = "Галоўная"; +"welcome" = "Сардэчна запрашаем"; + +%{ Login %} + +"log_in" = "Уваход"; +"password" = "Пароль"; +"registration" = "Рэгістрацыя"; + +%{ Profile information %} + +"select_language" = "Выбраць мову"; +"edit" = "рэдагаваць"; +"birth_date" = "Дзень нараджэння"; +"registration_date" = "Дата рэгістрацыі"; +"hometown" = "Родны горад"; +"this_is_you" = "гэта Вы"; +"edit_page" = "Рэдагаваць старонку"; +"edit_group" = "Рэдагаваць групу"; +"change_status" = "змяніць статус"; +"name" = "Імя"; +"surname" = "Прозвішча"; +"gender" = "Пол"; +"male" = "мужчынскі"; +"female" = "жаночы"; +"description" = "Апісанне"; +"save" = "Захаваць"; +"main_information" = "Асноўная інфармацыя"; +"nickname" = "Нікнэйм"; +"online" = "Анлайн"; +"was_online" = "быў у сетцы"; +"was_online_m" = "быў у сетцы"; +"was_online_f" = "была у сетцы"; +"all_title" = "Усе"; +"information" = "Інфармацыя"; +"status" = "Статус"; + +"relationship" = "Сямейнае становішча"; + +"relationship_0" = "Не выбрана"; +"relationship_1" = "Ня женаты"; +"relationship_2" = "Сустракаюся"; +"relationship_3" = "Заручана"; +"relationship_4" = "Жанаты"; +"relationship_5" = "У грамадзянскім шлюбе"; +"relationship_6" = "Закаханы"; +"relationship_7" = "Усё складана"; +"relationship_8" = "У актыўным пошуку"; + +"politViews" = "Паліт. погляды"; + +"politViews_0" = "На выбраныя"; +"politViews_1" = "індыферэнтнага"; +"politViews_2" = "Камуністычныя"; +"politViews_3" = "Сацыялістычныя"; +"politViews_4" = "Памяркоўныя"; +"politViews_5" = "Ліберальныя"; +"politViews_6" = "Кансерватыўныя"; +"politViews_7" = "Манархічныя"; +"politViews_8" = "Ультраконсервативные"; +"politViews_9" = "Либертарианские"; + +"contact_information" = "Кантактная інфармацыя"; + +"email" = "Пошта"; +"phone" = "Тэлефон"; +"telegram" = "Telegram"; +"city" = "Горад"; +"address" = "Адрас"; + +"personal_information" = "Асабістая інфармацыя"; + +"interests" = "Інтарэсы"; +"favorite_music" = "Любімая музыка"; +"favorite_films" = "Любімыя фільмы"; +"favorite_shows" = "Любимыя ТБ-шоу"; +"favorite_books" = "Любімыя кнігі"; +"favorite_quotes" = "Любимыя цытаты"; +"information_about" = "Пра сябе"; + +%{ Wall %} + +"post_writes_m" = "напісаў"; +"post_writes_f" = "напісала"; +"wall" = "Сцяна"; +"post" = "Запіс"; +"write" = "Напісаць"; +"publish" = "Апублікаваць"; +"delete" = "Выдаліць"; +"comments" = "Каментары"; +"share" = "Падзяліцца"; +"comments_tip" = "Будзьце першым, хто пакіне каментарый!"; +"your_comment" = "Ваш каментарый"; +"comments" = "Каментары"; +"shown" = "Паказана"; +"x_out_of" = "$1 з"; +"wall_zero" = "няма запісаў"; +"wall_one" = "адзіная запіс"; +"wall_few" = "$1 складнік"; +"wall_many" = "$1 складнікаў"; +"wall_other" = "$1 складнікаў"; + +"nsfw_warning" = "Дадзены пост можа ўтрымліваць 18+ кантэнт"; + +%{ Friends %} + +"friends" = "Сябры"; +"followers" = "Падпісчыкі"; +"follower" = "Падпісчык"; +"friends_add" = "Дадаць у ліст сяброў"; +"friends_delete" = "Выдаліць з сяброў"; +"friends_reject" = "Адмяніць заяўку"; +"friends_accept" = "Прыняць заяўку"; +"send_message" = "Адправіць паведамленне"; +"incoming_req" = "Падпісанты"; +"outcoming_req" = "Заяўкі"; +"req" = "Заяўкі"; + +"firends_zero" = "Няма сяброў"; +"friends_one" = "$1 сябар"; +"friends_few" = "$1 аднаго"; +"friends_many" = "$1 сяброў"; +"friends_other" = "$1 сяброў"; + +"followers_zero" = "Няма падпісчыкаў"; +"followers_one" = "$1 падпісант"; +"followers_few" = "$1 падпісчыка"; +"followers_many" = "$1 падпісчыкаў"; +"followers_other" = "$1 падпісчыкаў"; + +"subscriptions_zero" = "Ні адной падпіскі"; +"subscriptions_one" = "$1 падпіска"; +"subscriptions_few" = "$1 падпіскі"; +"subscriptions_many" = "$1 падпісак"; +"subscriptions_other" = "$1 падпісак"; + +%{ Group %} + +"name_group" = "Назва"; +"subscribe" = "Падпісацца"; +"unsubscribe" = "Адпісацца"; +"subscriptions" = "Падпіскі"; +"join_community" = "Уступіць у групу"; +"leave_community" = "Выхад з групы"; +"min_6_community" = "Назва павінна быць не менш за 6 сімвалаў"; +"participants" = "Удзельнікі"; +"groups" = "Групы"; +"meetings" = "Сустрэчы"; +"create_group" = "Стварыць групу"; +"group_managers" = "Кіраўніцтва"; +"group_type" = "Тып групы"; +"group_type_open" = "Гэта адкрытая група. У яе можа ўступіць любы ахвочы."; +"group_type_closed" = "Гэта закрытая група. Для ўступлення неабходна падаваць заяўку."; +"creator" = "Стваральнік"; + +"role" = "Роля"; +"administrator" = "Адміністратар"; +"promote_to_admin" = "Падвысіць да адміністратара"; +"devote" = "Разжалаваць"; + +"participants_zero" = "Ніводнага ўдзельніка"; +"participants_one" = "Адзін удзельнік"; +"participants_few" = "$1 удзельніка"; +"participants_many" = "$1 удзельнікаў"; +"participants_other" = "$1 удзельнікаў"; + +"groups_zero" = "Ні адной групы"; +"groups_one" = "Адна група"; +"groups_few" = "$1 групы"; +"groups_many" = "$1 гуртоў"; +"groups_other" = "$1 гуртоў"; + +"meetings_zero" = "Ні адной сустрэчы"; +"meetings_one" = "Адна сустрэча"; +"meetings_few" = "$1 сустрэчы"; +"meetings_many" = "$1 сустрэч"; +"meetings_other" = "$1 сустрэч"; + +%{ Albums %} + +"create" = "Стварыць"; +"albums" = "Альбомы"; +"create_album" = "Стварыць альбом"; +"edit_album" = "Рэдагаваць альбом"; +"creating_album" = "Стварэнне альбома"; +"upload_photo" = "Загрузіць фатаграфію"; +"photo" = "Фатаграфія"; +"upload_button" = "Загрузіць"; + +"albums_zero" = "Няма альбомаў"; +"albums_one" = "Адзін альбом"; +"albums_few" = "$1 альбома"; +"albums_many" = "$1 альбомаў"; +"albums_other" = "$1 альбомаў"; + +%{ Notes %} + +"notes" = "Нататкі"; +"note" = "Нататка"; +"name_note" = "Назва"; +"text_note" = "Змест"; +"create_note" = "Стварыць нататку"; +"actions" = "Дзеянні"; +"feed" = "Навіны"; +"publish_post" = "Дадаць запіс"; + +"notes_zero" = "Ні адной нататкі"; +"notes_one" = "Адна нататка"; +"notes_few" = "$1 нататкі"; +"notes_many" = "$1 нататак"; +"notes_other" = "$1 нататак"; + +%{ Menus %} + +"edit_button" = "рэд."; +"my_page" = "Мая Старонка"; +"my_friends" = "Мае Сябры"; +"my_photos" = "Мае Фотаздымкі"; +"my_videos" = "Мае Відэазапісы"; +"my_messages" = "Мае Паведамленні"; +"my_notes" = "Мае Нататкі"; +"my_groups" = "Мае Групы"; +"my_feed" = "Мае Навіны"; +"my_feedback" = "Мае Адказы"; +"my_settings" = "Мае Налады"; +"bug_tracker" = "Баг-трэкер"; + +"menu_login" = "Уваход"; +"menu_registration" = "Рэгістрацыя"; +"menu_help" = "Дапамога"; + +"header_home" = "галоўная"; +"header_groups" = "групы"; +"header_donate" = "падтрымаць"; +"header_people" = "людзі"; +"header_invite" = "запрасіць"; +"header_help" = "дапамога"; +"header_log_out" = "выйсці"; +"header_search" = "Пошук"; + +"header_login" = "Уваход"; +"header_registration" = "Рэгістрацыя"; +"header_help" = "Дапамога"; + +"footer_blog" = "блог"; +"footer_help" = "дапамога"; +"footer_developers" = "распрацоўнікам"; +"footer_choose_language" = "выбраць мову"; +"footer_privacy" = "прыватнасць"; + +"notes_zero" = "Ні адной нататкі"; +"notes_one" = "Адна нататка"; +"notes_few" = "$1 нататкі"; +"notes_many" = "$1 нататак"; +"notes_other" = "$1 нататак"; + +%{ Settings %} + +"main" = "Асноўнае"; +"contacts" = "Кантакты"; +"avatar" = "Аватар"; +"privacy" = "Прыватнасць"; +"interface" = "Знешні выгляд"; + +"profile_picture" = "Малюнак старонкі"; + +"picture" = "Малюнак"; + +"change_password" = "Змяніць пароль"; +"old_password" = "Стары пароль"; +"new_password" = "Новы пароль"; +"repeat_password" = "Паўтарыце пароль"; + +"avatars_style" = "Адлюстраванне аватара"; +"style" = "Стыль"; + +"default" = "Па змаўчанні"; +"cut" = "Абразанне"; +"round_avatars" = "Круглы аватар"; + +"search_for_groups" = "Пошук груп"; +"search_for_people" = "Пошук людзей"; +"search_button" = "Знайсці"; +"privacy_setting_access_page" = "Каму ў інтэрнэце відаць маю старонку"; +"privacy_setting_read_info" = "Каму відаць асноўную інфармацыю маёй старонкі"; +"privacy_setting_see_groups" = "Каму відаць мае суполкі і сустрэчы"; +"privacy_setting_see_photos" = "Каму відаць мае фатаграфіі"; +"privacy_setting_see_videos" = "Каму відаць мае відэазапісы"; +"privacy_setting_see_notes" = "Каму відаць мае нататкі"; +"privacy_setting_see_friends" = "Каму відаць маіх сяброў"; +"privacy_setting_add_to_friends" = "Хто можа называць мяне сябрам"; +"privacy_setting_write_wall" = "Хто можа пісаць у мяне на сцяне"; +"privacy_value_anybody" = "Усе жадаючыя"; +"privacy_value_anybody_dative" = "Усім жадаючым"; +"privacy_value_users" = "Карыстальнікам OpenVK"; +"privacy_value_friends" = "Сябры"; +"privacy_value_friends_dative" = "Сябрам"; +"privacy_value_only_me_and_super_capite" = "Я і Даніілу Мысліўцу"; +"privacy_value_only_me_and_super_capite_dative" = "Мне і Даніілу Мысліўцу"; +"privacy_value_super_capite" = "Даніілу Мысліўцу"; + +"your_email_address" = "Адрас Вашай электроннай пошты"; +"your_page_address" = "Адрас Вашай старонкі"; +"page_address" = "Адрас старонкі"; +"current_email_address" = "Ныняшні адрас"; +"page_id" = "ID старонкі"; +"you_can_also" = "Вы таксама можаце"; +"delete_your_page" = "выдаліць сваю старонку"; +"delete_album" = "выдаліць альбом"; + +"ui_settings_interface" = "Інтэрфейс"; +"ui_settings_sidebar" = "Левае меню"; +"ui_settings_rating" = "Рэйтынг"; +"ui_settings_rating_show" = "Паказваць"; +"ui_settings_rating_hide" = "Хаваць"; + +%{ Sorting %} + +"sort_randomly" = "Сартаваць рандомные"; +"sort_up" = "Сартаваць па даце стварэння уверх"; +"sort_down" = "Сартаваць па даце стварэння ўніз"; + +%{ Videos %} + +"videos" = "Відэазапісы"; +"video" = "Відэазапіс"; +"upload_video" = "Загрузіць відэа"; +"video_uploaded" = "Загружана"; +"video_updated" = "Абнаўленне"; +"video_link_to_yt" = "Спасылка на YT"; + +"video_name" = "Назва"; +"video_description" = "Апісанне"; +"video_uploaded_by" = "Загрузіў"; +"video_upload_date" = "Дата загрузкі"; + +"videos_zero" = "Ні адной відэазапісы"; +"videos_one" = "Адна відэазапіс"; +"videos_few" = "$1 відэазапісы"; +"videos_many" = "$1 відэазапісаў"; +"videos_other" = "$1 відэазапісаў"; + + +%{ Notifications %} + +"feedback" = "Адказы"; +"unread" = "Непрачытаныя"; +"archive" = "Архіў"; + +"notifications_like" = "$1 спадабалася ваша $2запіс$3 ад $4"; +"notifications_repost" = "$1 падзяліўся(-лась) вашай $2запісью$3 от $4"; +"notifications_comment_under" = "$1 пакінул(-ла) комментарый пад $2"; +"notifications_under_note" = "вашай $3нататкай$4"; +"notifications_under_photo" = "вашым $3фота$4"; +"notifications_under_post" = "вашай $3запісью$4 ад $5"; +"notifications_under_video" = "вашым $3видэа$4"; +"notifications_post" = "$1 напісаў(-ла) $2запіс$3 на вашай сцяне: $4"; +"notifications_appoint" = "$1 назначыў вас кіраўніком суполкі $2"; + +"nt_liked_yours" = "спадабаўся ваш"; +"nt_shared_yours" = "падзяліўся(-ась) вашым"; +"nt_commented_yours" = "пакінуў(а) каментар пад"; +"nt_written_on_your_wall" = "напісал(а) на вашай сцяне"; +"nt_made_you_admin" = "прызначыў(ла) вас кіраўніком суполкі"; + +"nt_from" = "ад"; +"nt_yours_adjective" = "вашым"; +"nt_yours_feminitive_adjective" = "вашай"; +"nt_post_nominative" = "пост"; +"nt_post_instrumental" = "пастом"; +"nt_note_instrumental" = "нататкай"; +"nt_photo_instrumental" = "фатаграфіяй"; + +%{ Time %} + +"Time_at_sp" = " у "; +"Time_just_now" = "толькі што"; +"Time_exactly_five_minutes_ago" = "роўна 5 хвілін таму"; +"Time_minutes_ago" = "$1 хвілін таму"; +"Time_today" = "сёння"; +"Time_yesterday" = "учора"; + +"privacy_setting_access_page" = "Каму ў інтэрнэце відаць маю старонку"; +"privacy_setting_read_info" = "Каму відаць асноўную інфармацыю маёй старонкі"; +"privacy_setting_see_groups" = "Каму відаць мае суполкі і сустрэчы"; +"privacy_setting_see_photos" = "Каму відаць мае фатаграфіі"; +"privacy_setting_see_videos" = "Каму відаць мае відэазапісы"; +"privacy_setting_see_notes" = "Каму відаць мае нататкі"; +"privacy_setting_see_friends" = "Каму відаць маіх сяброў"; +"privacy_setting_add_to_friends" = "Хто можа называць мяне сябрам"; +"privacy_setting_write_wall" = "Хто можа пісаць у мяне на сцяне"; +"privacy_value_anybody" = "Усе жадаючыя"; +"privacy_value_anybody_dative" = "Усім жадаючым"; +"privacy_value_users" = "Карыстальнікам OpenVK"; +"privacy_value_friends" = "Сябры"; +"privacy_value_friends_dative" = "Сябрам"; +"privacy_value_only_me_and_super_capite" = "Я і Даніілу Мысліўцу"; +"privacy_value_only_me_and_super_capite_dative" = "Мне і Даніілу Мысліўцу"; +"privacy_value_super_capite" = "Даніілу Мысліўцу"; + +"your_email_address" = "Адрас Вашай электроннай пошты"; +"your_page_address" = "Адрас Вашай старонкі"; +"page_address" = "Адрас старонкі"; +"current_email_address" = "Ныняшні адрас"; +"page_id" = "ID старонкі"; +"you_can_also" = "Вы таксама можаце"; +"delete_your_page" = "выдаліць сваю старонку"; +"delete_album" = "выдаліць альбом"; + +"ui_settings_interface" = "Інтэрфейс"; +"ui_settings_sidebar" = "Левае меню"; +"ui_settings_rating" = "Рэйтынг"; +"ui_settings_rating_show" = "Паказваць"; +"ui_settings_rating_hide" = "Хаваць"; + +%{ Errors %} + +"error_1" = "Няправільны запыт"; +"error_2" = "Няправільны імя або пароль"; +"error_3" = "Ня аўтарызаваны"; +"error_4" = "Карыстальнік не існуе"; +"information_-1" = "Аперацыя выканана паспяхова"; +"information_-2" = "Уваход выкананы паспяхова"; + +"no_data" = "Няма дадзеных"; +"no_data_description" = "У гэтым прадстаўленні адсутнічаюць дадзеныя."; diff --git a/locales/de.strings b/locales/de.strings new file mode 100644 index 000000000..8a39de3c9 --- /dev/null +++ b/locales/de.strings @@ -0,0 +1,269 @@ +"__locale" = "de_DE.utf8"; + +"home" = "Home"; +"log_in" = "Einloggen"; +"welcome" = "Willkommen"; +"password" = "Passwort"; +"registration" = "Registrierung"; +"select_language" = "Sprache auswählen"; +"edit" = "bearbeiten"; +"birth_date" = "Geburtstag"; +"registration_date" = "Registrierungsdatum"; +"hometown" = "Heimatstadt"; +"this_is_you" = "das bist du"; +"post_writes_m" = "gepostet"; +"post_writes_f" = "gepostet"; +"wall" = "Feed"; +"post" = "Post"; +"write" = "Schreiben"; +"publish" = "Veröffentlichen"; +"edit_page" = "Seite bearbeiten"; +"edit_group" = "Gruppe bearbeiten"; +"change_status" = "Status ändern"; +"name" = "Vorame"; +"surname" = "Nachname"; +"gender" = "Geschlecht"; +"male" = "Männlich"; +"female" = "Weiblich"; +"description" = "Beschreibung"; +"save" = "Speichern"; +"main_information" = "Hauptinformationen"; +"friends" = "Freunde"; +"friends_add" = "Freund hinzufügen"; +"friends_delete" = "Freund entfernen"; +"friends_reject" = "Anfrage ablehnen"; +"friends_accept" = "Anfrage annehmen"; +"send_message" = "Nachricht senden"; +"nickname" = "Nickname"; +"online" = "Online"; +"delete" = "Löschen"; +"comments" = "Kommentare"; +"share" = "Teilen"; +"comments_tip" = "Sei der erste der einen Kommentar schreibt!"; +"incoming_req" = "Einkommende Anfragen"; +"outcoming_req" = "Ausgehende Anfragen"; +"req" = "Anfrage"; +"you_have" = "Du hast"; +"all_title" = "Alle"; +"your_comment" = "Dein Kommentar"; +"comments" = "Kommentare"; +"name_group" = "Name"; +"subscribe" = "Abonnieren"; +"unsubscribe" = "Deabonnieren"; +"join_community" = "Community beitreten"; +"leave_community" = "Community verlassen"; +"participants" = "Mitglieder"; +"groups" = "Gruppen"; +"create_group" = "Gruppe erstellen"; +"create" = "Erstellen"; +"albums" = "Alben"; +"create_album" = "Album erstellen"; +"edit_album" = "Album bearbeiten"; +"creating_album" = "Erstelle Album"; +"upload_photo" = "Foto hochladen"; +"photo" = "Foto"; +"notes" = "Notizen"; +"name_note" = "Name"; +"text_note" = "Inhalt"; +"create_notes" = "Notiz erstellen"; +"status" = "Status"; +"actions" = "Aktionen"; +"upload_button" = "Hochladen"; +"feed" = "Feed"; +"publish_post" = "Post veröffentlichen"; +"was_online" = "war online"; + +"edit_button" = "bearb."; +"my_page" = "Meine Seite"; +"my_friends" = "Meine Freunde"; +"my_photos" = "Meine Fotos"; +"my_videos" = "Meine Videos"; +"my_messages" = "Meine Nachrichten"; +"my_notes" = "Meine Notizen"; +"my_groups" = "Meine Gruppen"; +"my_feed" = "Mein Feed"; +"my_feedback" = "Mein Feedback"; +"my_settings" = "Meine Einstellungen"; +"bug_tracker" = "Bugtracker"; + +"header_home" = "home"; +"header_groups" = "gruppen"; +"header_donate" = "spenden"; +"header_people" = "personen"; +"header_invite" = "einladen"; +"header_help" = "hilfe"; +"header_log_out" = "ausloggen"; +"header_search" = "Suchen"; + +"footer_blog" = "blog"; +"footer_help" = "hilfe"; +"footer_developers" = "entwickler"; +"footer_choose_language" = "sprache auswählen"; +"footer_privacy" = "privatsphäre"; + +"friends_zero" = "Keine Freunde"; +"friends_one" = "$1 Freund"; +"friends_other" = "$1 Freunde"; + +"followers_zero" = "Keine Follower"; +"followers_one" = "$1 Follower"; +"followers_other" = "$1 Follower"; + +"subscriptions_zero" = "Keine Abonnements"; +"subscriptions_one" = "$1 Abonnement"; +"subscriptions_other" = "$1 Abonnements"; + +"wall_zero" = "Keine Posts"; +"wall_one" = "$1 Post"; +"wall_other" = "$1 Posts"; + +"participants_zero" = "No participants"; +"participants_one" = "$1 participant"; +"participants_other" = "$1 participants"; + +"groups_zero" = "Keine Gruppen"; +"groups_one" = "$1 Gruppe"; +"groups_other" = "$1 Gruppen"; + +"meetings_zero" = "Keine Meetings"; +"meetings_one" = "$1 Meeting"; +"meetings_other" = "$1 Meetings"; + +"notes_zero" = "Keine Notizen"; +"notes_one" = "$1 Notiz"; +"notes_other" = "$1 Notizen"; + +"albums_zero" = "Keine Alben"; +"albums_one" = "$1 Album"; +"albums_other" = "$1 Alben"; + +"videos_zero" = "Keine Videos"; +"videos_one" = "$1 Video"; +"videos_other" = "$1 Videos"; + +"relationship" = "Beziehung"; + +"relationship_0" = "Nicht ausgewählt"; +"relationship_1" = "Nicht verheiratet"; +"relationship_2" = "Dating"; +"relationship_3" = "Engagiert"; +"relationship_4" = "Verlobt"; +"relationship_5" = "In einer standesamtlichen Ehe"; +"relationship_6" = "Verliebt"; +"relationship_7" = "Alles ist kompliziert"; +"relationship_8" = "Sucht aktiv"; + +"politViews" = "Politische Ansichten"; + +"politViews_0" = "Nicht ausgewählt"; +"politViews_1" = "Gleichgültig"; +"politViews_2" = "Kommunist"; +"politViews_3" = "Sozialist"; +"politViews_4" = "Gemäßigt"; +"politViews_5" = "Liberal"; +"politViews_6" = "Konservativ"; +"politViews_7" = "Monarchisch"; +"politViews_8" = "Ultra-konservativ"; +"politViews_9" = "Libertär"; + +"information" = "Information"; + +"contact_information" = "Kontaktinformationen"; + +"email" = "E-Mail"; +"telegram" = "Telegram"; +"city" = "Stadt"; +"address" = "Adresse"; + +"personal_information" = "Persönliche Informationen"; + +"interests" = "Interessem"; +"favorite_music" = "Lieblingsmusik"; +"favorite_films" = "Lieblingsfilme"; +"favorite_shows" = "Lieblings TV-Serien"; +"favorite_books" = "Lieblingsbücher"; +"favorite_quotes" = "Lieblingssprüche"; +"information_about" = "Über"; + +"main" = "Hauptseite"; +"contacts" = "Kontakte"; +"avatar" = "Avatar"; + +"profile_picture" = "Profilbild"; + +"picture" = "Bild"; + +"sort_randomly" = "Zufällig sortieren"; +"sort_up" = "Nach Erstellungsdatum aufsteigend sortieren"; +"sort_down" = "Nach Erstellungsdatum absteigend sortieren"; + +"videos" = "Videos"; +"video" = "Video"; +"upload_video" = "Video hochladen"; +"video_uploaded" = "Hochladen"; +"video_updated" = "Aktualisiert"; +"video_link_to_yt" = "Link zu YT"; + +"video_name" = "Name"; +"video_description" = "Beschreibung"; +"video_uploaded_by" = "Hochgeladen von"; +"video_upload_date" = "Hochladedatum"; + +"privacy" = "Datenschutz"; +"interface" = "Interface"; + +"change_password" = "Passwort ändern"; +"old_password" = "Altes Passwort"; +"new_password" = "Neues Passwort"; +"repeat_password" = "Passwort wiederholen"; + +"avatars_style" = "Avatarstil"; +"style" = "Stil"; + +"default" = "Standard"; +"cut" = "Zuschneiden"; +"round_avatars" = "Avatar abrunden"; + +"search_for_groups" = "Nach Gruppen suchen"; +"search_for_people" = "Nach Personen suchen"; +"search_button" = "Suchen"; + +"interface_translation" = "Oberfächenübersetung"; +"translations" = "Übersetzungen"; +"no_translation" = "(nicht übersetzt)"; +"languages" = "Sprachen"; + +"notifications_like" = "$1 hat deinen $2Post$3 geliket, veröffentlicht am $4"; +"notifications_repost" = "$1 hat deinen $2Post$3 repostet, veröffentlicht am $4"; +"notifications_comment_under" = "$1 hat einen Kommentar bei $2 hinterlassen"; +"notifications_under_note" = "deiner $3Notiz$4"; +"notifications_under_photo" = "deinem $3Foto$4"; +"notifications_under_post" = "deinem $3Post$4, veröffentlicht am $5"; +"notifications_under_video" = "deinem $3Video$4"; +"notifications_post" = "$1 hat einen $2Post$3 an deiner Pinnwand geliket: $4"; +"notifications_appoint" = "$1 hat dich zum Besitzer der Community $2 gemacht"; + +"feedback" = "Feedback"; +"unread" = "Ungelesen"; +"archive" = "Archiv"; + +"date_format_1" = "%1 %2, %3"; +"date_format_2" = "%2 %1, %3 um %4"; +"date_month_1" = "Januar"; +"date_month_2" = "Februar"; +"date_month_3" = "März"; +"date_month_4" = "April"; +"date_month_5" = "Mai"; +"date_month_6" = "Juni"; +"date_month_7" = "Juli"; +"date_month_8" = "August"; +"date_month_9" = "September"; +"date_month_10" = "Oktober"; +"date_month_11" = "November"; +"date_month_12" = "Dezember"; +"error_1" = "Ungültige Anfrage"; +"error_2" = "Ungültiger Nutzername oder Passwort"; +"error_3" = "Nicht autorisiert"; +"error_4" = "Dieser Nutzer existiert nicht"; +"information_-1" = "Operation war erfolgreich"; +"information_-2" = "Erfolgreich angemeldet"; \ No newline at end of file diff --git a/locales/en.strings b/locales/en.strings new file mode 100644 index 000000000..76d742104 --- /dev/null +++ b/locales/en.strings @@ -0,0 +1,376 @@ +"__locale" = "en_US.UTF-8;Eng"; + +%{ Check for https://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html %} + +%{ Main page %} + +"home" = "Home"; +"welcome" = "Welcome"; + +%{ Login %} + +"log_in" = "Log in"; +"password" = "Password"; +"registration" = "Registration"; + +%{ Profile information %} + +"select_language" = "Choose language"; +"edit" = "edit"; +"birth_date" = "Birth date"; +"registration_date" = "Registration date"; +"hometown" = "Hometown"; +"this_is_you" = "that is You"; +"edit_page" = "Edit page"; +"edit_group" = "Edit group"; +"change_status" = "change status"; +"name" = "Name"; +"surname" = "Surname"; +"gender" = "Gender"; +"male" = "male"; +"female" = "female"; +"description" = "Description"; +"save" = "Save"; +"main_information" = "Main information"; +"nickname" = "Nickname"; +"online" = "Online"; +"was_online" = "was online"; +"was_online_m" = "was online"; +%{ For male and femail %} + +"was_online_f" = "was online"; +"all_title" = "All"; +"information" = "Information"; +"status" = "Status"; + +"relationship" = "Relationship"; + +"relationship_0" = "Not selected"; +"relationship_1" = "Not married"; +"relationship_2" = "Dating"; +"relationship_3" = "Engaged"; +"relationship_4" = "Married"; +"relationship_5" = "In a civil marriage"; +"relationship_6" = "In love"; +"relationship_7" = "Everything is complicated"; +"relationship_8" = "Actively searching"; + +"politViews" = "Polit. Views"; + +"politViews_0" = "Not Selected"; +"politViews_1" = "Indifferent"; +"politViews_2" = "Communist"; +"politViews_3" = "Socialist"; +"politViews_4" = "Moderate"; +"politViews_5" = "Liberal"; +"politViews_6" = "Conservative"; +"politViews_7" = "Monarchical"; +"politViews_8" = "Ultra-Conservative"; +"politViews_9" = "Libertarian"; + +"contact_information" = "Contact information"; + +"email" = "Email"; +"phone" = "Phone"; +"telegram" = "Telegram"; +"city" = "City"; +"address" = "Address"; + +"personal_information" = "Personal information"; + +"interests" = "Interests"; +"favorite_music" = "Favorite music"; +"favorite_films" = "Favorite flims"; +"favorite_shows" = "Favorite TV-shows"; +"favorite_books" = "Favorite books"; +"favorite_quotes" = "Favorite quotes"; +"information_about" = "About"; + +%{ Wall %} + +"post_writes_m" = "wrote"; +"post_writes_f" = "wrote"; +"wall" = "Wall"; +"post" = "Post"; +"write" = "Write"; +"publish" = "Publish"; +"delete" = "Delete"; +"comments" = "Comments"; +"share" = "Share"; +"comments_tip" = "Be first, who leaves a comment at this post!"; +"your_comment" = "Your comment"; +"comments" = "Comments"; +"shown" = "Shown"; +"x_out_of" = "$1 of"; +"wall_zero" = "no posts"; +"wall_one" = "$1 post"; +"wall_other" = "$1 posts"; +"feed" = "News"; +"publish_post" = "Add post"; + +"nsfw_warning" = "This post may have NSFW-content"; + +%{ Friends %} + +"friends" = "Friends"; +"followers" = "Followers"; +"follower" = "Follower"; +"friends_add" = "Add to friends"; +"friends_delete" = "Remove from friends"; +"friends_reject" = "Reject request"; +"friends_accept" = "Accept request"; +"send_message" = "Send a message"; +"incoming_req" = "Subscribers"; +"outcoming_req" = "Requests"; +"req" = "Requests"; + +"firends_zero" = "No friends"; +"friends_one" = "$1 friend"; +"friends_other" = "$1 friends"; + +"followers_zero" = "No followers"; +"followers_one" = "$1 follower"; +"followers_other" = "$1 followers"; + +"subscriptions_zero" = "No subscriptions"; +"subscriptions_one" = "$1 subscription"; +"subscriptions_other" = "$1 subscriptions"; + +%{ Group %} + +"name_group" = "Name"; +"subscribe" = "Subscribe"; +"unsubscribe" = "Unsubscribe"; +"subscriptions" = "Subscriptions"; +"join_community" = "Join community"; +"leave_community" = "Leave community"; +"min_6_community" = "Name of the group must have more that 6 characters"; +"participants" = "Participants"; +"groups" = "Groups"; +"meetings" = "Meetings"; +"create_group" = "Create group"; +"group_managers" = "Managers"; +"group_type" = "Group type"; +"group_type_open" = "This is an open group, anyone can enter it."; +"group_type_closed" = "This is an closed group. To enter, you must submit an request."; +"creator" = "Creator"; + +"role" = "Role"; +"administrator" = "Administrator"; +"promote_to_admin" = "Promote to admin"; +"devote" = "Devote"; + + +"participants_zero" = "No participants"; +"participants_one" = "$1 participant"; +"participants_other" = "$1 participant"; + +"groups_zero" = "No groups"; +"groups_one" = "$1 group"; +"groups_other" = "$1 groups"; + +"meetings_zero" = "No meetings"; +"meetings_one" = "$1 meeting"; +"meetings_other" = "$1 meetings"; + +%{ Albums %} + +"create" = "Create"; +"albums" = "Albums"; +"create_album" = "Create album"; +"edit_album" = "Edit album"; +"creating_album" = "Creating album"; +"upload_photo" = "Upload photo"; +"photo" = "Photo"; +"upload_button" = "Upload"; + +"albums_zero" = "No albums"; +"albums_one" = "$1 album"; +"albums_other" = "$1 albums"; + +%{ Notes %} + +"notes" = "Notes"; +"note" = "Note"; +"name_note" = "Title"; +"text_note" = "Content"; +"create_note" = "Create note"; +"actions" = "Actions"; + +"notes_zero" = "No notes"; +"notes_one" = "$1 note"; +"notes_other" = "$1 notes"; + +%{ Menus %} + +%{ Note that is string need to fit into the "My Page" link %} + +"edit_button" = "edit"; +"my_page" = "My Page"; +"my_friends" = "My Friends"; +"my_photos" = "My Photos"; +"my_videos" = "My Videos"; +"my_messages" = "My Messages"; +"my_notes" = "My Notes"; +"my_groups" = "My Groups"; +"my_feed" = "My Feed"; +"my_feedback" = "My Feedback"; +"my_settings" = "My Settings"; +"bug_tracker" = "Bug-tracker"; + +"menu_login" = "Login"; +"menu_registration" = "Registration"; +"menu_help" = "Help"; + +"header_home" = "home"; +"header_groups" = "groups"; +"header_donate" = "donate"; +"header_people" = "people"; +"header_invite" = "invite"; +"header_help" = "help"; +"header_log_out" = "log out"; +"header_search" = "Search"; + +"header_login" = "login"; +"header_registration" = "registration"; +"header_help" = "help"; + +"footer_blog" = "blog"; +"footer_help" = "help"; +"footer_developers" = "developers"; +"footer_choose_language" = "choose language"; +"footer_privacy" = "privacy"; + +%{ Settings %} + +"main" = "Main"; +"contacts" = "Contacts"; +"avatar" = "Avatar"; +"privacy" = "privacy"; +"interface" = "Interface"; + +"profile_picture" = "Profile picture"; + +"picture" = "Picture"; + +"change_password" = "Change password"; +"old_password" = "Old password"; +"new_password" = "New password"; +"repeat_password" = "Repeat password"; + +"avatars_style" = "Avatar style"; +"style" = "Style"; + +"default" = "Default"; +"cut" = "Cut"; +"round_avatars" = "Round avatars"; + +"search_for_groups" = "Search for groups"; +"search_for_people" = "Search for people"; +"search_button" = "Find"; +"privacy_setting_access_page" = "Who can view my page"; +"privacy_setting_read_info" = "Who can see main information of my page"; +"privacy_setting_see_groups" = "Who can see my groups and meetings"; +"privacy_setting_see_photos" = "Who can see my photos"; +"privacy_setting_see_videos" = "Who can see my videos"; +"privacy_setting_see_notes" = "Who can see my notes"; +"privacy_setting_see_friends" = "Who can see my friends"; +"privacy_setting_add_to_friends" = "Who can add me to friends"; +"privacy_setting_write_wall" = "Who can publish post on my wall"; +"privacy_value_anybody" = "Anybody"; +"privacy_value_anybody_dative" = "Anybody"; +"privacy_value_users" = "OpenVK users"; +"privacy_value_friends" = "Friends"; +"privacy_value_friends_dative" = "Friends"; +"privacy_value_only_me" = "Only me"; +"privacy_value_only_me_dative" = "Only me"; + +"your_email_address" = "Your Email address"; +"your_page_address" = "Your address page"; +"page_address" = "Address page"; +"current_email_address" = "Current email address"; +"page_id" = "Page ID"; +"you_can_also" = "You can also"; +"delete_your_page" = "delete your page"; +"delete_album" = "delete album"; + +"ui_settings_interface" = "Interface"; +"ui_settings_sidebar" = "Left menu"; +"ui_settings_rating" = "Rating"; +"ui_settings_rating_show" = "Show"; +"ui_settings_rating_hide" = "Hide"; + +%{ Sorting %} + +"sort_randomly" = "Sort randomly"; +"sort_up" = "Sort by ID up"; +"sort_down" = "Sort by ID down"; + +%{ Videos %} + +"videos" = "Videos"; +"video" = "Video"; +"upload_video" = "Upload video"; +"video_uploaded" = "Uploaded"; +"video_updated" = "Updated"; +"video_link_to_yt" = "YT link"; + +"video_name" = "Title"; +"video_description" = "Description"; +"video_uploaded_by" = "Uploaded by"; +"video_upload_date" = "Upload date"; + +"videos_zero" = "No videos"; +"videos_one" = "$1 video"; +"videos_other" = "$1 videos"; + +%{ Notifications %} + +"feedback" = "Feedback"; +"unread" = "Unread"; +"archive" = "Archive"; + +"notifications_like" = "$1 liked your $2post$3 from $4"; +"notifications_repost" = "$1 shared your $2post$3 from $4"; +"notifications_comment_under" = "$1 leaved a comment on $2"; +"notifications_under_note" = "your $3note$4"; +"notifications_under_photo" = "your $3photo$4"; +"notifications_under_post" = "your $3post$4 from $5"; +"notifications_under_video" = "your $3video$4"; +"notifications_post" = "$1 published $2a post$3 on your wall: $4"; +"notifications_appoint" = "$1 appointed you as community manager $2"; + +"nt_liked_yours" = "liked yours"; +"nt_shared_yours" = "shared yours"; +"nt_commented_yours" = "commented yours"; +"nt_written_on_your_wall" = "wrote on your wall"; +"nt_made_you_admin" = "appointed you in the community"; + +"nt_from" = "from"; +"nt_yours_adjective" = "yours"; +"nt_yours_feminitive_adjective" = "yours"; +"nt_post_nominative" = "post"; +"nt_post_instrumental" = "post"; +"nt_note_instrumental" = "note"; +"nt_photo_instrumental" = "photo"; + +%{ Time %} + +"time_at_sp" = " at "; +"time_just_now" = "just now"; +"time_exactly_five_minutes_ago" = "5 minutes ago"; +"time_minutes_ago" = "$1 minutes ago"; +"time_today" = "today"; +"time_yesterday" = "yesterday"; + +%{ Errors %} + +"error_1" = "Incorrect query"; +"error_2" = "Incorrect login and password"; +"error_3" = "Non authorized"; +"error_4" = "User does not exist"; +"information_-1" = "Success"; +"information_-2" = "Login success"; + +"no_data" = "No data"; +"no_data_description" = "There is no data."; diff --git a/locales/eo.strings b/locales/eo.strings new file mode 100644 index 000000000..6e5711b8b --- /dev/null +++ b/locales/eo.strings @@ -0,0 +1,97 @@ +"home" = "Domo"; +"log_in" = "Ensaluti"; +"welcome" = "Bonvenon"; +"email" = "Email"; +"password" = "Pasvorto"; +"registration" = "Registriĝo"; +"select_language" = "Elektu lingvon"; +"birth_date" = "Dato de naskiĝo"; +"registration_date" = "Date de aliĝilo"; +"this_is_you" = "ĝi estas Vi"; +"post_writes_m" = "skribis"; +"post_writes_f" = "skribis"; +"wall" = "Muro"; +"post" = "Enskribo"; +"write" = "Skribi"; +"publish" = "Posti"; +"edit_page" = "Redakti la paĝo"; +"name" = "Nomo"; +"surname" = "Familinomo"; +"gender" = "Sekso"; +"male" = "viro"; +"female" = "virino"; +"description" = "Priskribo"; +"save" = "Konservi"; +"main_information" = "Ĝenerala informo"; +"friends" = "Amikoj"; +"friends_add" = "Aldoni kiel amikon"; +"friends_delete" = "Forigi de amikoj"; +"friends_reject" = "Nuligi la peton"; +"friends_accept" = "Akcepti la peton"; +"nickname" = "Kromnomo"; +"online" = "Enreta"; +"delete" = "Forigi"; +"incoming_req" = "Alvenanta petoj"; +"outcoming_req" = "Venanta petoj"; +"req" = "La petoj"; +"you_have" = "Vi havas"; +"all_title" = "Ĉiuj"; +"your_comment" = "Via komento"; +"comments" = "Komentoj"; +"name_group" = "Nomo"; +"subscribe" = "Aboni"; +"unsubscribe" = "Malaboni"; +"join_community" = "Aliĝi al la grupo"; +"leave_community" = "Forlasi la grupon"; +"participants" = "Membroj"; +"groups" = "Grupoj"; +"create_group" = "Krei grupon"; +"create" = "Krei"; +"albums" = "Albumoj"; +"create_album" = "Krei albumon"; +"creating_album" = "Kreas albumon"; +"upload_photo" = "Alŝuti foton"; +"photo" = "Foto"; +"notes" = "Notoj"; +"name_note" = "Nomo"; +"text_note" = "Enhavo"; +"create_notes" = "Krei noton"; + +"edit_button" = "redaktu"; +"my_page" = "Mia Paĝo"; +"my_friends" = "Miaj Amikoj"; +"my_photos" = "Miaj Fotoj"; +"my_notes" = "Miaj Notoj"; +"my_groups" = "Miaj Grupoj"; + +"friends_one" = "%1 amiko"; +"friends_other" = "%1 amikoj"; +"wall_one" = "%1 enskribo"; +"wall_other" = "%1 enskriboj"; +"participants_one" = "%1 membro"; +"participants_other" = "%1 membroj"; +"notes_one" = "%1 noto"; +"notes_other" = "%1 notoj"; +"groups_one" = "%1 grupo"; +"groups_other" = "%1 grupoj"; + +"date_format_1" = "%1a de %2 %3"; +"date_format_2" = "%1a de %2 %3 je %4"; +"date_month_1" = "januaro"; +"date_month_2" = "februaro"; +"date_month_3" = "marto"; +"date_month_4" = "aprilo"; +"date_month_5" = "majo"; +"date_month_6" = "junio"; +"date_month_7" = "julio"; +"date_month_8" = "aŭgusto"; +"date_month_9" = "septembro"; +"date_month_10" = "oktobro"; +"date_month_11" = "novembro"; +"date_month_12" = "decembro"; +"error_1" = "Malĝusta konsulto"; +"error_2" = "Malĝusta retpoŝto aŭ pasvorto"; +"error_3" = "Ne rajtigita"; +"error_4" = "La uzanto ne ekzistas"; +"information_-1" = "Operacio sukcesa"; +"information_-2" = "Salutnomo sukcesis"; \ No newline at end of file diff --git a/locales/kz.strings b/locales/kz.strings new file mode 100644 index 000000000..9974f200b --- /dev/null +++ b/locales/kz.strings @@ -0,0 +1,236 @@ +"home" = "Басты бет"; +"log_in" = "Кіру"; +"welcome" = "Қош келдіңіз"; +"password" = "Пароль"; +"registration" = "Тіркеу"; +"select_language" = "Тілді таңдау"; +"edit" = "редакциялау"; +"birth_date" = "Туған күні"; +"registration_date" = "Тіркеу күні"; +"hometown" = "Туған қаласы"; +"this_is_you" = "бұл сіз"; +"post_writes_m" = "жазған"; +"post_writes_f" = "жазды"; +"wall" = "Қабырға"; +"post" = "Жазба"; +"write" = "Жазу"; +"publish" = "Жариялау"; +"edit_page" = "Бетті өңдеу"; +"edit_group" = "Топты өңдеу"; +"change_status" = "мәртебені өзгерту"; +"name" = "Аты"; +"surname" = "Тегі"; +"gender" = "Жынысы"; +"male" = "ерлер"; +"female" = "қыздар"; +"description" = "Сипаттамасы"; +"save" = "Сақтау"; +"main_information" = "Негізгі ақпарат"; +"friends" = "Достар"; +"friends_add" = "Достар қосу"; +"friends_delete" = "Өшіру достар"; +"friends_reject" = "Өтінімді болдырмау"; +"friends_accept" = "Қабылдауға өтінім"; +"send_message" = "Хабар жіберу"; +"nickname" = "Никнейм"; +"online" = "Онлайн"; +"was_online_m" = "желіде болды"; +"was_online_f" = "желіде болды"; +"delete" = "Өшіру"; +"comments" = "Комментарии"; +"share" = "Бөлісу"; +"comments_tip" = "Пікір қалдыратын бірінші болыңыз!"; +"incoming_req" = "Жазылушылар"; +"outcoming_req" = "Өтінімдер"; +"req" = "Өтінімдер"; +"you_have" = "Сізде бар"; +"all_title" = "Барлық"; +"your_comment" = "Пікір қалдыру"; +"comments" = "Комментарии"; +"name_group" = "Атауы"; +"subscribe" = "Подписаться"; +"unsubscribe" = "Отписаться"; +"join_community" = "Топқа кіру"; +"leave_community" = "Топтан шығу"; +"min_6_community" = "Атауы 1 таңбадан кем болмауы тиіс"; +"participants" = "Қатысушылар"; +"groups" = "Топтар"; +"create_group" = "Топты құру"; +"group_managers" = "Басшылық"; +"group_type" = "Топ түрі"; +"group_type_open" = "Бұл ашық топ оған кез келген адам кіре алады"; +"creator" = "Жасаушы"; +"create" = "Құру"; +"albums" = "Альбомы"; +"create_album" = "Құру альбом"; +"edit_album" = "Альбомды өңдеу"; +"creating_album" = "Альбомды жасау"; +"upload_photo" = "Фотосуретті жүктеу"; +"photo" = "Фотосурет"; +"notes" = "Жазбалар"; +"name_note" = "Атауы"; +"text_note" = "Мазмұны"; +"create_notes" = "Құру заметку"; +"acl_welcome" = "Топтық саясат редакторы"; +"status" = "Мәртебесі"; +"actions" = "Әрекеттер"; +"upload_button" = "Жүктеу"; +"feed" = "Жаңалықтар"; +"publish_post" = "Жазбаны қосу"; + +"edit_button" = "ред."; +"my_page" = "Менің Бетім"; +"my_friends" = "Менің Достар"; +"my_photos" = "Менің Суреттер"; +"my_videos" = "Менің Бейнежазбалар"; +"my_messages" = "Менің Хабарламалар"; +"my_notes" = "Менің Жазбалар"; +"my_groups" = "Менің Топтар"; +"my_feed" = "Менің Жаңалықтар"; +"my_settings" = "Менің Параметрлері"; + +"header_home" = "Басты бет"; +"header_groups" = "топтар"; +"header_donate" = "қолдау"; +"header_people" = "адамдар"; +"header_invite" = "шақыру"; +"header_help" = "көмек"; +"header_log_out" = "шығу"; +"header_search" = "Іздеу"; + + +"friends_one" = "%1 досым"; +"friends_few" = "%1 досым"; +"friends_many" = "%1 дос"; +"friends_other" = "%1 достар"; +"wall_one" = "%1 жазба"; +"wall_few" = "%1 жазба"; +"wall_many" = "%1 жазбалар"; +"wall_other" = "%1 жазба"; +"participants_one" = "%1 қатысушы"; +"participants_few" = "%1 қатысушы"; +"participants_many" = "%1 қатысушы"; +"participants_other" = "%1 қатысушы"; +"groups_one" = "%1 тобы"; +"groups_few" = "%1 тобы"; +"groups_many" = "%1 тобы"; +"groups_other" = "%1 тобы"; +"notes_one" = "%1 ескерту"; +"notes_few" = "%1 ескерту"; +"notes_many" = "%1 ескерту"; +"notes_other" = "%1 ескерту"; +"albums_one" = "%1 альбом"; +"albums_few" = "%1 альбома"; +"albums_many" = "%1 альбома"; +"albums_other" = "%1 альбомов"; + +"information" = "Ақпарат"; + +"relationship" = "Отбасы жағдайы"; + +"relationship_0" = "Таңдалмаған"; +"relationship_1" = "Үйленген жоқ"; +"relationship_2" = "Кездесулерді өткізу"; +"relationship_3" = "Ұнтақталған"; +"relationship_4" = "Үйленген"; +"relationship_5" = "Үйленген"; +"relationship_6" = "Оған ғашықпын"; +"relationship_7" = "Барлығы қиын"; +"relationship_8" = "В активном поиске"; + +"politViews" = "Саяси көзқарастар"; + +"politViews_0" = "Таңдалмаған"; +"politViews_1" = "Индифферентті"; +"politViews_2" = "Коммунистік"; +"politViews_3" = "Социалистік"; +"politViews_4" = "Қалыпты"; +"politViews_5" = "Либералды"; +"politViews_6" = "Консервативті"; +"politViews_7" = "Монархиялық"; +"politViews_8" = "Ультраконсервативные"; +"politViews_9" = "Либертариандық"; + +"contact_information" = "Байланыс ақпараты"; + +"email" = "Электрондық пошта"; +"telegram" = "Telegram"; +"city" = "Қаласы"; +"address" = "Мекен жайы"; + +"personal_information" = "Жеке ақпарат"; + +"interests" = "Мүдделері"; +"favorite_music" = "Сүйікті музыка"; +"favorite_films" = "Сүйікті фильмдер"; +"favorite_shows" = "Сүйікті ТВ-шоу"; +"favorite_books" = "Сүйікті кітаптар"; +"favorite_quotes" = "Сүйікті дәйексөздер"; +"information_about" = "Өзі туралы"; + +"main" = "Негізгі"; +"contacts" = "Контакты"; +"avatar" = "Аватар"; + +"profile_picture" = "Бет бейнесі"; + +"picture" = "Сурет"; + +"sort_randomly" = "Рандомно сұрыптау"; +"sort_up" = "Жасау күні бойынша жоғары сұрыптау"; +"sort_down" = "Төмен жасау күні бойынша сұрыптау"; + +"videos" = "Бейнежазбалар"; +"video" = "Бейнежазбал"; +"upload_video" = "Бейнені жүктеу"; +"video_uploaded" = "Загружено"; +"video_updated" = "Жаңартылды"; +"video_link_to_yt" = "Сілтеме YT"; + +"video_name" = "Атауы"; +"video_description" = "Сипаттамасы"; +"video_uploaded_by" = "Загрузил"; +"video_upload_date" = "Жүктеу күні"; + +"privacy" = "Құпиялылық"; +"interface" = "Сыртқы түрі"; + +"change_password" = "Парольді өзгерту"; +"old_password" = "Ескі пароль"; +"new_password" = "Жаңа пароль"; +"repeat_password" = "Парольді қайталаңыз"; + +"avatars_style" = "Аватарды көрсету"; +"style" = "Стилі"; + +"default" = "Әдетті"; +"cut" = "Кесу"; +"round_avatars" = "Дөңгелек аватар"; + + +"date_format_1" = "%1 %2 %3 г."; +"date_format_2" = "%1 %2 %3 г. в %4"; +"date_month_1" = "қаңтар"; +"date_month_2" = "ақпан"; +"date_month_3" = "наурыз"; +"date_month_4" = "сәуір"; +"date_month_5" = "мамыр"; +"date_month_6" = "маусым"; +"date_month_7" = "шілде"; +"date_month_8" = "тамыз"; +"date_month_9" = "қыркүйек"; +"date_month_10" = "қазан"; +"date_month_11" = "қараша"; +"date_month_12" = "желтоқсан"; + +"error_1" = "Қате сұрау"; +"error_2" = "Қате логин немесе пароль"; +"error_3" = "Авторизацияланған жоқ"; +"error_4" = "Пайдаланушы жоқ"; +"information_-1" = "Операция сәтті орындалды"; +"information_-2" = "Кіріс сәтті орындалды"; + +"interface_translation" = "Интерфейсті аудару"; +"translations" = "Аударымдар"; +"no_translation" = "АУДАРЫЛМАҒАН!!!"; +"languages" = "Тілі"; \ No newline at end of file diff --git a/locales/la.strings b/locales/la.strings new file mode 100644 index 000000000..bccf8711f --- /dev/null +++ b/locales/la.strings @@ -0,0 +1,252 @@ +"acl_welcome" = "Coetus consilium editor"; +"actions" = "Actio"; +"address" = "Oratio"; +"administrator" = "Curagendarius"; +"albums" = "Albums"; +"albums_few" = "$1 album"; +"albums_many" = "$1 albums"; +"albums_one" = "Unum album"; +"albums_other" = "$1 albums"; +"albums_zero" = "Quis album"; +"all_title" = "Totum"; +"archive" = "Lorem ipsum dolor sit"; +"avatar" = "Avatar"; +"avatars_style" = "Propono avatar"; +"birth_date" = "Natalis"; +"bug_tracker" = "Bug tracker"; +"change_password" = "Mutare password"; +"change_status" = "mutare status"; +"city" = "Urbs"; +"comments" = "Comments"; +"comments_tip" = "Esse primum ad leave a comment!"; +"contact_information" = "Contactus notitia"; +"contacts" = "Contactus"; +"create" = "Creare"; +"create_album" = "Ut creare an album"; +"create_group" = "Ut lobortis dolor"; +"create_notes" = "Mauris ut enim ad minim"; +"creating_album" = "Faciendum album"; +"creator" = "Effector"; +"cut" = "Putationis"; +"default" = "Per defaltam"; +"delete" = "Ad tollendam"; +"description" = "Description"; +"devote" = "Demoted"; +"edit" = "recensere / fontem recensere"; +"edit_album" = "Edit album"; +"edit_button" = "ed."; +"edit_group" = "Recensere / fontem recensere coetus"; +"edit_page" = "Edit page"; +"email" = "Email"; +"error_1" = "Recte petentibus"; +"error_2" = "Irritum aut nomen usoris password"; +"error_3" = "Non auctoritate"; +"error_4" = "User non esse"; +"favorite_books" = "Favorite books"; +"favorite_films" = "Ventus movies"; +"favorite_music" = "Ventus musica"; +"favorite_quotes" = "Ventus quotes"; +"favorite_shows" = "Ventus TV ostendo"; +"feed" = "Nuntius"; +"feedback" = "Respondet"; +"female" = "muliebris"; +"firends_zero" = "Nulla alia"; +"follower" = "Subscriber"; +"followers" = "Signatores"; +"followers_few" = "$1 a subscriber"; +"followers_many" = "$1 signatores"; +"followers_one" = "Unum subscriber"; +"followers_other" = "$1 signatores"; +"followers_zero" = "Uno subscriber"; +"footer_blog" = "blog"; +"footer_choose_language" = "lego lingua"; +"footer_developers" = "tincidunt"; +"footer_help" = "auxilium"; +"footer_privacy" = "privacy"; +"friends" = "Friends"; +"friends_accept" = "Accipere application"; +"friends_add" = "Adde amicis"; +"friends_delete" = "Aufero ex amicis"; +"friends_few" = "$1 singulis"; +"friends_many" = "$1 amicis"; +"friends_one" = "Unus amicus"; +"friends_other" = "$1 amicis"; +"friends_reject" = "Ad inrita ordo"; +"gender" = "Solum"; +"group_managers" = "Dux"; +"group_type" = "Coetus genus"; +"group_type_open" = "Hoc est a publicis coetus. Potest intrare quis."; +"groups" = "Coetus"; +"groups_few" = "$1 coetus"; +"groups_many" = "$1 coetibus"; +"groups_one" = "Unum coetus"; +"groups_other" = "$1 coetibus"; +"groups_zero" = "Quis coetus"; +"header_donate" = "support"; +"header_groups" = "coetus"; +"header_help" = "auxilium"; +"header_home" = "consectetur"; +"header_invite" = "duis"; +"header_log_out" = "ex"; +"header_people" = "homo"; +"header_search" = "Investigatio"; +"home" = "Consectetur"; +"hometown" = "Patria"; +"incoming_req" = "Signatores"; +"information" = "Informatio"; +"information_-1" = "Operatio peracta feliciter"; +"information_-2" = "Praesent sit amet"; +"information_about" = "De te"; +"interests" = "Commodis"; +"interface" = "Filum"; +"interface_translation" = "Translatio dapibus"; +"join_community" = "Ad coniungere coetus"; +"languages" = "Linguis"; +"leave_community" = "Relinquere coetus"; +"log_in" = "Ostium"; +"main" = "Consectetur"; +"main_information" = "Basic notitia"; +"male" = "masculinus"; +"meetings" = "Conventus"; +"meetings_few" = "$1 sessio"; +"meetings_many" = "$1 conventus"; +"meetings_one" = "Unus conventus"; +"meetings_other" = "$1 conventus"; +"meetings_zero" = "Nulla conventus"; +"min_6_community" = "Titulus debet esse saltem 1 moribus"; +"my_feed" = "Mea News"; +"my_feedback" = "Responsa Mea"; +"my_friends" = "Amici Mei"; +"my_groups" = "Mea Coetus"; +"my_messages" = "Verba Mea"; +"my_notes" = "Notis Meis"; +"my_page" = "Mea Page"; +"my_photos" = "Mea Imaginibus"; +"my_settings" = "Meus Occasus"; +"my_videos" = "Mea Videos"; +"name" = "Nomen"; +"name_group" = "Nomen"; +"name_note" = "Nomen"; +"new_password" = "New password"; +"nickname" = "Cognomen"; +"no_translation" = "NON EST TRANSLATUM!!!"; +"notes" = "Notes"; +"notes_few" = "$1 notes"; +"notes_many" = "$1 notes"; +"notes_one" = "Una nota"; +"notes_other" = "$1 notes"; +"notes_zero" = "Nulla notis"; +"notifications_appoint" = "$1 nesvacil te Director communitatis $2"; +"notifications_comment_under" = "$1 reliquit (La) review sub $2"; +"notifications_like" = "$1 sima $2запись$3 a $4"; +"notifications_post" = "$1 scriptum (La) $2запись$3 in pariete $4"; +"notifications_repost" = "$1 communis(-Las) tua $2записью$3 a $4"; +"notifications_under_note" = "vestra $3заметкой$4"; +"notifications_under_photo" = "vestra $3фото$4"; +"notifications_under_post" = "vestra $3записью$4 a $5"; +"notifications_under_video" = "vestra $3видео$4"; +"old_password" = "Vetus signum"; +"online" = "Online"; +"outcoming_req" = "Applications"; +"participants" = "Participes"; +"participants_few" = "$1 pars"; +"participants_many" = "$1 participes"; +"participants_one" = "Unum particeps"; +"participants_other" = "$1 participes"; +"participants_zero" = "Quis particeps"; +"password" = "Tessera"; +"personal_information" = "Alio notitia"; +"phone" = "Telephonum"; +"photo" = "Photographia"; +"picture" = "Imago"; +"politViews" = "Polit. views"; +"politViews_0" = "Non delectus"; +"politViews_1" = "Indifferens"; +"politViews_2" = "Communistarum"; +"politViews_3" = "Socialās"; +"politViews_4" = "Modica"; +"politViews_5" = "Liberalis"; +"politViews_6" = "Conservative"; +"politViews_7" = "Monarchical"; +"politViews_8" = "Ultra-conservative"; +"politViews_9" = "Libertarian"; +"post" = "Entry"; +"post_writes_f" = "scripsit"; +"post_writes_m" = "scripsit"; +"privacy" = "Privacy"; +"profile_picture" = "Imago page"; +"promote_to_admin" = "Ad promovere ad administrator"; +"publish" = "Ad auditum"; +"publish_post" = "Adde introitu"; +"registration" = "Reprehendo"; +"registration_date" = "Registration date"; +"relationship" = "Maritali status"; +"relationship_0" = "Non delectus"; +"relationship_1" = "Non nupta"; +"relationship_2" = "Dignum"; +"relationship_3" = "Versantur"; +"relationship_4" = "Uxorem"; +"relationship_5" = "In civili matrimonio"; +"relationship_6" = "Amor"; +"relationship_7" = "Praesent rhoncus"; +"relationship_8" = "In activa quaerere"; +"repeat_password" = "Repetere signum"; +"req" = "Applications"; +"role" = "Munus"; +"round_avatars" = "Per avatar"; +"save" = "Nisi"; +"search_button" = "Invenire"; +"search_for_groups" = "Quaerere coetus"; +"search_for_people" = "Populus quaerere"; +"select_language" = "Lego lingua"; +"send_message" = "Ad mittite"; +"share" = "Partem"; +"sort_down" = "Sort by date descendit"; +"sort_randomly" = "Exstat passim"; +"sort_up" = "Sort by date usque"; +"status" = "Status"; +"style" = "Still"; +"subscribe" = "Aliquam"; +"subscriptions" = "Subscriptione"; +"subscriptions_few" = "$1 subscriptione"; +"subscriptions_many" = "$1 subscriptiones"; +"subscriptions_one" = "Unum subscriptione"; +"subscriptions_other" = "$1 subscriptiones"; +"subscriptions_zero" = "Nulla subscriptiones"; +"surname" = "Cōgnōmen"; +"telegram" = "Telegram"; +"text_note" = "Contenta"; +"this_is_you" = "suus ' Vos"; +"translations" = "Translationes"; +"unread" = "Intactum,"; +"unsubscribe" = "Ad unsubscribe"; +"upload_button" = "Download"; +"upload_photo" = "Upload a photo"; +"upload_video" = "Download video"; +"video" = "Video"; +"video_description" = "Description"; +"video_link_to_yt" = "Donec YT"; +"video_name" = "Nomen"; +"video_updated" = "Amet"; +"video_upload_date" = "Upload date"; +"video_uploaded" = "Lorem"; +"video_uploaded_by" = "Lorem"; +"videos" = "Videos"; +"videos_few" = "$1 video"; +"videos_many" = "$1 movies"; +"videos_one" = "Unum video"; +"videos_other" = "$1 movies"; +"videos_zero" = "Nulla videos"; +"wall" = "Murus"; +"wall_few" = "$1 monumentis"; +"wall_many" = "$1 monumentis"; +"wall_one" = "tantum introitu"; +"wall_other" = "$1 monumentis"; +"wall_zero" = "nulla monumentis"; +"was_online" = "erat online"; +"was_online_f" = "erat online"; +"was_online_m" = "erat online"; +"welcome" = "Grata"; +"write" = "Exaro"; +"you_have" = "Vos"; +"your_comment" = "Comment"; \ No newline at end of file diff --git a/locales/ru.strings b/locales/ru.strings new file mode 100644 index 000000000..c89c96ec0 --- /dev/null +++ b/locales/ru.strings @@ -0,0 +1,435 @@ +"__locale" = "ru_UA.utf8;ru_RU.UTF-8;Rus"; + +%{ Check for https://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html %} + +%{ Main page %} + +"home" = "Главная"; +"welcome" = "Добро пожаловать"; + +%{ Login %} + +"log_in" = "Вход"; +"password" = "Пароль"; +"registration" = "Регистрация"; + +%{ Profile information %} + +"select_language" = "Выбрать язык"; +"edit" = "редактировать"; +"birth_date" = "День рождения"; +"registration_date" = "Дата регистрации"; +"hometown" = "Родной город"; +"this_is_you" = "это Вы"; +"edit_page" = "Редактировать страницу"; +"edit_group" = "Редактировать группу"; +"change_status" = "изменить статус"; +"name" = "Имя"; +"surname" = "Фамилия"; +"gender" = "Пол"; +"male" = "мужской"; +"female" = "женский"; +"description" = "Описание"; +"save" = "Сохранить"; +"main_information" = "Основная информация"; +"nickname" = "Никнейм"; +"online" = "Онлайн"; +"was_online" = "был в сети"; +"was_online_m" = "был в сети"; +"was_online_f" = "была в сети"; +"all_title" = "Все"; +"information" = "Информация"; +"status" = "Статус"; + +"relationship" = "Семейное положение"; + +"relationship_0" = "Не выбрано"; +"relationship_1" = "Не женат"; +"relationship_2" = "Встречаюсь"; +"relationship_3" = "Помолвлен"; +"relationship_4" = "Женат"; +"relationship_5" = "В гражданском браке"; +"relationship_6" = "Влюблен"; +"relationship_7" = "Всё сложно"; +"relationship_8" = "В активном поиске"; + +"politViews" = "Полит. взгляды"; + +"politViews_0" = "Не выбраны"; +"politViews_1" = "Индифферентные"; +"politViews_2" = "Коммунистические"; +"politViews_3" = "Социалистические"; +"politViews_4" = "Умеренные"; +"politViews_5" = "Либеральные"; +"politViews_6" = "Консервативные"; +"politViews_7" = "Монархические"; +"politViews_8" = "Ультраконсервативные"; +"politViews_9" = "Либертарианские"; + +"contact_information" = "Контактная информация"; + +"email" = "Электронная почта"; +"phone" = "Телефон"; +"telegram" = "Telegram"; +"city" = "Город"; +"address" = "Адрес"; + +"personal_information" = "Личная информация"; + +"interests" = "Интересы"; +"favorite_music" = "Любимая музыка"; +"favorite_films" = "Любимые фильмы"; +"favorite_shows" = "Любимые ТВ-шоу"; +"favorite_books" = "Любимые книги"; +"favorite_quotes" = "Любимые цитаты"; +"information_about" = "О себе"; + +%{ Wall %} + +"post_writes_m" = "написал"; +"post_writes_f" = "написала"; +"post_writes_g" = "опубликовали"; +"wall" = "Стена"; +"post" = "Запись"; +"write" = "Написать"; +"publish" = "Опубликовать"; +"delete" = "Удалить"; +"comments" = "Комментарии"; +"share" = "Поделиться"; +"comments_tip" = "Будьте первым, кто оставит комментарий!"; +"your_comment" = "Ваш комментарий"; +"comments" = "Комментарии"; +"shown" = "Показано"; +"x_out_of" = "$1 из"; +"wall_zero" = "нет записей"; +"wall_one" = "единственная запись"; +"wall_few" = "$1 записи"; +"wall_many" = "$1 записей"; +"wall_other" = "$1 записей"; + +"nsfw_warning" = "Данный пост может содержать 18+ контент"; + +%{ Friends %} + +"friends" = "Друзья"; +"followers" = "Подписчики"; +"follower" = "Подписчик"; +"login_as" = "Войти как"; +"ban_fast" = "Забанить"; +"adm_notify" = "Предупредить"; +"friends_add" = "Добавить в друзья"; +"friends_delete" = "Удалить из друзей"; +"friends_reject" = "Отменить заявку"; +"friends_accept" = "Принять заявку"; +"send_message" = "Отправить сообщение"; +"incoming_req" = "Подписчики"; +"outcoming_req" = "Заявки"; +"req" = "Заявки"; + +"firends_zero" = "Ни одного друга"; +"friends_one" = "$1 друг"; +"friends_few" = "$1 друга"; +"friends_many" = "$1 друзей"; +"friends_other" = "$1 друзей"; + +"followers_zero" = "Ни одного подписчика"; +"followers_one" = "$1 подписчик"; +"followers_few" = "$1 подписчика"; +"followers_many" = "$1 подписчиков"; +"followers_other" = "$1 подписчиков"; + +"subscriptions_zero" = "Ни одной подписки"; +"subscriptions_one" = "$1 подписка"; +"subscriptions_few" = "$1 подписки"; +"subscriptions_many" = "$1 подписок"; +"subscriptions_other" = "$1 подписок"; + +%{ Group %} + +"name_group" = "Название"; +"subscribe" = "Подписаться"; +"unsubscribe" = "Отписаться"; +"subscriptions" = "Подписки"; +"join_community" = "Вступить в группу"; +"leave_community" = "Выйти из группы"; +"min_6_community" = "Название должно быть не менее 6 символов"; +"participants" = "Участники"; +"groups" = "Группы"; +"meetings" = "Встречи"; +"create_group" = "Создать группу"; +"group_managers" = "Руководство"; +"group_type" = "Тип группы"; +"group_type_open" = "Это открытая группа. В неё может вступить любой желающий."; +"group_type_closed" = "Это закрытая группа. Для вступления необходимо подавать заявку."; +"creator" = "Создатель"; + +"role" = "Роль"; +"administrator" = "Администратор"; +"promote_to_admin" = "Повысить до администратора"; +"devote" = "Разжаловать"; + +"participants_zero" = "Ни одного участника"; +"participants_one" = "Один участник"; +"participants_few" = "$1 участника"; +"participants_many" = "$1 участников"; +"participants_other" = "$1 участников"; + +"groups_zero" = "Ни одной группы"; +"groups_one" = "Одна группа"; +"groups_few" = "$1 группы"; +"groups_many" = "$1 групп"; +"groups_other" = "$1 групп"; + +"meetings_zero" = "Ни одной встречи"; +"meetings_one" = "Одна встреча"; +"meetings_few" = "$1 встречи"; +"meetings_many" = "$1 встреч"; +"meetings_other" = "$1 встреч"; + +%{ Albums %} + +"create" = "Создать"; +"albums" = "Альбомы"; +"create_album" = "Создать альбом"; +"edit_album" = "Редактировать альбом"; +"creating_album" = "Создание альбома"; +"upload_photo" = "Загрузить фотографию"; +"photo" = "Фотография"; +"upload_button" = "Загрузить"; + +"albums_zero" = "Ни одного альбома"; +"albums_one" = "Один альбом"; +"albums_few" = "$1 альбома"; +"albums_many" = "$1 альбомов"; +"albums_other" = "$1 альбомов"; + +%{ Notes %} + +"notes" = "Заметки"; +"note" = "Заметка"; +"name_note" = "Название"; +"text_note" = "Содержание"; +"create_note" = "Создать заметку"; +"actions" = "Действия"; +"feed" = "Новости"; +"publish_post" = "Добавить запись"; + +"notes_zero" = "Ни одной заметки"; +"notes_one" = "Одна заметка"; +"notes_few" = "$1 заметки"; +"notes_many" = "$1 заметок"; +"notes_other" = "$1 заметок"; + +%{ Menus %} + +"edit_button" = "ред."; +"my_page" = "Моя Страница"; +"my_friends" = "Мои Друзья"; +"my_photos" = "Мои Фотографии"; +"my_videos" = "Мои Видеозаписи"; +"my_messages" = "Мои Сообщения"; +"my_notes" = "Мои Заметки"; +"my_groups" = "Мои Группы"; +"my_feed" = "Мои Новости"; +"my_feedback" = "Мои Ответы"; +"my_settings" = "Мои Настройки"; +"bug_tracker" = "Баг-трекер"; + +"menu_login" = "Вход"; +"menu_registration" = "Регистрация"; +"menu_help" = "Помощь"; + +"header_home" = "главная"; +"header_groups" = "группы"; +"header_donate" = "поддержать"; +"header_people" = "люди"; +"header_invite" = "пригласить"; +"header_help" = "помощь"; +"header_log_out" = "выйти"; +"header_search" = "Поиск"; + +"header_login" = "Вход"; +"header_registration" = "Регистрация"; +"header_help" = "Помощь"; + +"footer_blog" = "блог"; +"footer_help" = "помощь"; +"footer_developers" = "разработчикам"; +"footer_choose_language" = "выбрать язык"; +"footer_privacy" = "приватность"; + +"notes_zero" = "Ни одной заметки"; +"notes_one" = "Одна заметка"; +"notes_few" = "$1 заметки"; +"notes_many" = "$1 заметок"; +"notes_other" = "$1 заметок"; + +%{ Settings %} + +"main" = "Основное"; +"contacts" = "Контакты"; +"avatar" = "Аватар"; +"privacy" = "Приватность"; +"interface" = "Внешний вид"; + +"profile_picture" = "Изображение страницы"; + +"picture" = "Изображение"; + +"change_password" = "Изменить пароль"; +"old_password" = "Старый пароль"; +"new_password" = "Новый пароль"; +"repeat_password" = "Повторите пароль"; + +"avatars_style" = "Отображение аватар"; +"style" = "Стиль"; + +"default" = "По умолчанию"; +"cut" = "Обрезка"; +"round_avatars" = "Круглый аватар"; + +"search_for_groups" = "Поиск групп"; +"search_for_people" = "Поиск людей"; +"search_button" = "Найти"; +"privacy_setting_access_page" = "Кому в интернете видно мою страницу"; +"privacy_setting_read_info" = "Кому видно основную информацию моей страницы"; +"privacy_setting_see_groups" = "Кому видно мои группы и встречи"; +"privacy_setting_see_photos" = "Кому видно мои фотографии"; +"privacy_setting_see_videos" = "Кому видно мои видеозаписи"; +"privacy_setting_see_notes" = "Кому видно мои заметки"; +"privacy_setting_see_friends" = "Кому видно моих друзей"; +"privacy_setting_add_to_friends" = "Кто может называть меня другом"; +"privacy_setting_write_wall" = "Кто может писать у меня на стене"; +"privacy_value_anybody" = "Все желающие"; +"privacy_value_anybody_dative" = "Всем желающим"; +"privacy_value_users" = "Пользователям OpenVK"; +"privacy_value_friends" = "Друзья"; +"privacy_value_friends_dative" = "Друзьям"; +"privacy_value_only_me_and_super_capite" = "Я и Виталина Павленко"; +"privacy_value_only_me_and_super_capite_dative" = "Мне и Виталине Павленко"; +"privacy_value_super_capite" = "Виталине Павленко"; + +"your_email_address" = "Адрес Вашей электронной почты"; +"your_page_address" = "Адрес Вашей страницы"; +"page_address" = "Адрес страницы"; +"current_email_address" = "Текущий адрес"; +"page_id" = "ID страницы"; +"you_can_also" = "Вы также можете"; +"delete_your_page" = "удалить свою страницу"; +"delete_album" = "удалить альбом"; + +"ui_settings_interface" = "Интерфейс"; +"ui_settings_sidebar" = "Левое меню"; +"ui_settings_rating" = "Рейтинг"; +"ui_settings_rating_show" = "Показывать"; +"ui_settings_rating_hide" = "Скрывать"; + +%{ Sorting %} + +"sort_randomly" = "Сортировать рандомно"; +"sort_up" = "Сортировать по дате создания вверх"; +"sort_down" = "Сортировать по дате создания вниз"; + +%{ Videos %} + +"videos" = "Видеозаписи"; +"video" = "Видеозапись"; +"upload_video" = "Загрузить видео"; +"video_uploaded" = "Загружено"; +"video_updated" = "Обновлено"; +"video_link_to_yt" = "Ссылка на YT"; + +"video_name" = "Название"; +"video_description" = "Описание"; +"video_uploaded_by" = "Загрузил"; +"video_upload_date" = "Дата загрузки"; + +"videos_zero" = "Ни одной видеозаписи"; +"videos_one" = "Одна видеозапись"; +"videos_few" = "$1 видеозаписи"; +"videos_many" = "$1 видеозаписей"; +"videos_other" = "$1 видеозаписей"; + +%{ Notifications %} + +"feedback" = "Ответы"; +"unread" = "Непрочитанное"; +"archive" = "Архив"; + +"notifications_like" = "$1 оценил вашу $2запись$3 от $4"; +"notifications_repost" = "$1 поделился(-лась) вашей $2записью$3 от $4"; +"notifications_comment_under" = "$1 оставил(-ла) комментарий под $2"; +"notifications_under_note" = "вашей $3заметкой$4"; +"notifications_under_photo" = "вашим $3фото$4"; +"notifications_under_post" = "вашей $3записью$4 от $5"; +"notifications_under_video" = "вашим $3видео$4"; +"notifications_post" = "$1 написал(-ла) $2запись$3 на вашей стене: $4"; +"notifications_appoint" = "$1 назвачил вас руководителем сообщества $2"; + +"nt_liked_yours" = "понравился ваш"; +"nt_shared_yours" = "поделился(-ась) вашим"; +"nt_commented_yours" = "оставил(а) комментарий под"; +"nt_written_on_your_wall" = "написал(а) на вашей стене"; +"nt_made_you_admin" = "назначил(а) вас руководителем сообщества"; + +"nt_from" = "от"; +"nt_yours_adjective" = "вашим"; +"nt_yours_feminitive_adjective" = "вашей"; +"nt_post_nominative" = "пост"; +"nt_post_instrumental" = "постом"; +"nt_note_instrumental" = "заметкой"; +"nt_photo_instrumental" = "фотографией"; + +%{ Time %} + +"time_at_sp" = " в "; +"time_just_now" = "только что"; +"time_exactly_five_minutes_ago" = "ровно 5 минут назад"; +"time_minutes_ago" = "$1 минут назад"; +"time_today" = "сегодня"; +"time_yesterday" = "вчера"; + +"privacy_setting_access_page" = "Кому в интернете видно мою страницу"; +"privacy_setting_read_info" = "Кому видно основную информацию моей страницы"; +"privacy_setting_see_groups" = "Кому видно мои группы и встречи"; +"privacy_setting_see_photos" = "Кому видно мои фотографии"; +"privacy_setting_see_videos" = "Кому видно мои видеозаписи"; +"privacy_setting_see_notes" = "Кому видно мои заметки"; +"privacy_setting_see_friends" = "Кому видно моих друзей"; +"privacy_setting_add_to_friends" = "Кто может называть меня другом"; +"privacy_setting_write_wall" = "Кто может писать у меня на стене"; +"privacy_value_anybody" = "Все желающие"; +"privacy_value_anybody_dative" = "Всем желающим"; +"privacy_value_users" = "Пользователям OpenVK"; +"privacy_value_friends" = "Друзья"; +"privacy_value_friends_dative" = "Друзьям"; +"privacy_value_only_me_and_super_capite" = "Я и Виталина Павленко"; +"privacy_value_only_me_and_super_capite_dative" = "Мне и Виталине Павленко"; +"privacy_value_super_capite" = "Виталине Павленко"; + +"your_email_address" = "Адрес Вашей электронной почты"; +"your_page_address" = "Адрес Вашей страницы"; +"page_address" = "Адрес страницы"; +"current_email_address" = "Текущий адрес"; +"page_id" = "ID страницы"; +"you_can_also" = "Вы также можете"; +"delete_your_page" = "удалить свою страницу"; +"delete_album" = "удалить альбом"; + +"ui_settings_interface" = "Интерфейс"; +"ui_settings_sidebar" = "Левое меню"; +"ui_settings_rating" = "Рейтинг"; +"ui_settings_rating_show" = "Показывать"; +"ui_settings_rating_hide" = "Скрывать"; + +%{ Errors %} + +"error_1" = "Некорректный запрос"; +"error_2" = "Неверный логин или пароль"; +"error_3" = "Не авторизован"; +"error_4" = "Пользователь не существует"; +"information_-1" = "Операция выполнена успешно"; +"information_-2" = "Вход выполнен успешно"; + +"no_data" = "Нет данных"; +"no_data_description" = "В этом представлении отсутствуют данные."; diff --git a/locales/sr_cyr.strings b/locales/sr_cyr.strings new file mode 100644 index 000000000..d7446043c --- /dev/null +++ b/locales/sr_cyr.strings @@ -0,0 +1,430 @@ +"__locale" = "sr_CS.UTF-8;Srb"; + +%{ Check for https://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html %} + +%{ Main page %} + +"home" = "Поћетна"; +"welcome" = "Добро дошли"; + +%{ Login %} + +"log_in" = "Пријава"; +"password" = "Шифра"; +"registration" = "Регистрација"; + +%{ Profile information %} + +"select_language" = "Промена језика"; +"edit" = "уредити"; +"birth_date" = "Рођендан"; +"registration_date" = "Датум регистрације"; +"hometown" = "Родни град"; +"this_is_you" = "то сте Ви"; +"edit_page" = "Уредити страницу"; +"edit_group" = "Уредити групу"; +"change_status" = "променити статус"; +"name" = "Име"; +"surname" = "Презиме"; +"gender" = "Пол"; +"male" = "мушки"; +"female" = "женски"; +"description" = "Опис"; +"save" = "Саћувати"; +"main_information" = "Основна информација"; +"nickname" = "Корисничко име"; +"online" = "У мрежи"; +"was_online" = "био на мрежи"; +"was_online_m" = "био на мрежи"; +"was_online_f" = "била на мрежи"; +"all_title" = "Све"; +"information" = "Информација"; +"status" = "Статус"; + +"relationship" = "Породични статус"; + +"relationship_0" = "Немам"; +"relationship_1" = "Нисам ожењен"; +"relationship_2" = "Упознајем се"; +"relationship_3" = "Ангажован"; +"relationship_4" = "Ожењен"; +"relationship_5" = "У грађанском браку"; +"relationship_6" = "Заљубљен"; +"relationship_7" = "Све је тешко"; +"relationship_8" = "Налазим се у активном претраживању"; + +"politViews" = "Политички погледи"; + +"politViews_0" = "Немам"; +"politViews_1" = "Равнодушан"; +"politViews_2" = "Комунистички"; +"politViews_3" = "Социјалистички"; +"politViews_4" = "Умерени"; +"politViews_5" = "Либерални"; +"politViews_6" = "Конзервативни"; +"politViews_7" = "Монархистички"; +"politViews_8" = "Ултраконзервативне"; +"politViews_9" = "Либертаријанске"; + +"contact_information" = "Контакт информације"; + +"email" = "Електронска пошта"; +"phone" = "Број телефона"; +"telegram" = "Telegram"; +"city" = "Име града"; +"address" = "Адреса"; + +"personal_information" = "Персонална информација"; + +"interests" = "Интереси"; +"favorite_music" = "Омиљена музика"; +"favorite_films" = "Омиљени филмови"; +"favorite_shows" = "Омиљена ТВ емисија"; +"favorite_books" = "Омиљене књиге"; +"favorite_quotes" = "Омиљени цитати"; +"information_about" = "Лична информација"; + +%{ Wall %} + +"post_writes_m" = "је написао"; +"post_writes_f" = "је написала"; +"wall" = "Зид"; +"post" = "Запис"; +"write" = "Написати"; +"publish" = "Објавити"; +"delete" = "Обрисати"; +"comments" = "Коментарије"; +"share" = "Поделити се информацијом"; +"comments_tip" = "Ћете бити први, ко ће оставити коментар!"; +"your_comment" = "Ваш коментар"; +"comments" = "Коментари"; +"shown" = "Представљено"; +"x_out_of" = "$1 од"; +"wall_zero" = "нема записа"; +"wall_one" = "једина запис"; +"wall_few" = "$1 записа"; +"wall_many" = "$1 записа"; +"wall_other" = "$1 записа"; + +"nsfw_warning" = "Овај пост има садржај за особе од 18 година и више"; + +%{ Friends %} + +"friends" = "Пријатељи"; +"followers" = "Следбеници"; +"follower" = "Следбеник"; +"friends_add" = "Додати у пријатеље"; +"friends_delete" = "Избрисати од пријатеља"; +"friends_reject" = "Одјавити захтев"; +"friends_accept" = "Прихватити захтев"; +"send_message" = "Пошаљити поруку"; +"incoming_req" = "Претплатници"; +"outcoming_req" = "Захтеви"; +"req" = "Захтеви"; + +"firends_zero" = "Нема ниједног пријатеља"; +"friends_one" = "$1 пријатељ"; +"friends_few" = "$1 пријатеља"; +"friends_many" = "$1 пријатеља"; +"friends_other" = "$1 пријатеља"; + +"followers_zero" = "Нема ниједног следбеника"; +"followers_one" = "$1 следбеник"; +"followers_few" = "$1 следбеника"; +"followers_many" = "$1 следбеника"; +"followers_other" = "$1 следбеника"; + +"subscriptions_zero" = "Нема ниједне пријаве"; +"subscriptions_one" = "$1 пријава"; +"subscriptions_few" = "$1 пријава"; +"subscriptions_many" = "$1 пријава"; +"subscriptions_other" = "$1 пријава"; + +%{ Group %} + +"name_group" = "Назив"; +"subscribe" = "Пријавити се"; +"unsubscribe" = "Одјавити се"; +"subscriptions" = "Следбеници"; +"join_community" = "Ући у групу"; +"leave_community" = "Изаћи из групе"; +"min_6_community" = "Назив се мора да садржи 6 симбола"; +"participants" = "Учесници"; +"groups" = "Групи"; +"meetings" = "Састанци"; +"create_group" = "Створити групу"; +"group_managers" = "Вођство"; +"group_type" = "Типови група"; +"group_type_open" = "То је отворена група. Свака персона може се придружити приликом ње."; +"group_type_closed" = "То је затворена група. Морате се пријавити."; +"creator" = "Стваралац"; + +"role" = "Улога"; +"administrator" = "Администратор"; +"promote_to_admin" = "Промовисати до администратора"; +"devote" = "Ражаловати улогу"; + +"participants_zero" = "Нема учесника"; +"participants_one" = "Један учесник"; +"participants_few" = "$1 учесника"; +"participants_many" = "$1 учесника"; +"participants_other" = "$1 учесника"; + +"groups_zero" = "Нема групе"; +"groups_one" = "Једна група"; +"groups_few" = "$1 група"; +"groups_many" = "$1 група"; +"groups_other" = "$1 група"; + +"meetings_zero" = "Нема састанка"; +"meetings_one" = "Један састанак"; +"meetings_few" = "$1 састанка"; +"meetings_many" = "$1 састанка"; +"meetings_other" = "$1 састанка"; + +%{ Albums %} + +"create" = "Створити"; +"albums" = "Албуми"; +"create_album" = "Креирати албум"; +"edit_album" = "Уредити албум"; +"creating_album" = "Створење албума"; +"upload_photo" = "Отпремити фото"; +"photo" = "Фото"; +"upload_button" = "Отпремање"; + +"albums_zero" = "Нема албума"; +"albums_one" = "Један албум"; +"albums_few" = "$1 албума"; +"albums_many" = "$1 албума"; +"albums_other" = "$1 албума"; + +%{ Notes %} + +"notes" = "Белешци"; +"note" = "Белешка"; +"name_note" = "Назив"; +"text_note" = "Садржај"; +"create_note" = "Креирати белешку"; +"actions" = "Активности"; +"feed" = "Вести"; +"publish_post" = "Објавити пост"; + +"notes_zero" = "Нема белешка"; +"notes_one" = "Једна белешка"; +"notes_few" = "$1 белешка"; +"notes_many" = "$1 белешка"; +"notes_other" = "$1 белешка"; + +%{ Menus %} + +"edit_button" = "уред."; +"my_page" = "Моја Страница"; +"my_friends" = "Моји другови"; +"my_photos" = "Моје слике"; +"my_videos" = "Моје видео"; +"my_messages" = "Моји поруке"; +"my_notes" = "Моји белешци"; +"my_groups" = "Моји групи"; +"my_feed" = "Моји вести"; +"my_feedback" = "Моји одговаре"; +"my_settings" = "Моја подешавања"; +"bug_tracker" = "Праћење грешака"; + +"menu_login" = "Пријавити се"; +"menu_registration" = "Регистрација"; +"menu_help" = "Помоћ"; + +"header_home" = "насловна страна"; +"header_groups" = "групи"; +"header_donate" = "подршка"; +"header_people" = "људи"; +"header_invite" = "позвати"; +"header_help" = "помоћ"; +"header_log_out" = "одјавити се"; +"header_search" = "Истраживање"; + +"header_login" = "Пријава"; +"header_registration" = "Регистрација"; +"header_help" = "Помоћ"; + +"footer_blog" = "блог"; +"footer_help" = "помоћ"; +"footer_developers" = "обраћање програмерима"; +"footer_choose_language" = "изабрати језик"; +"footer_privacy" = "приватност"; + +"notes_zero" = "Нема белешка"; +"notes_one" = "Једна белешка"; +"notes_few" = "$1 белешка"; +"notes_many" = "$1 белешка"; +"notes_other" = "$1 белешци"; + +%{ Settings %} + +"main" = "Насловна страна"; +"contacts" = "Контакти"; +"avatar" = "Слика"; +"privacy" = "Приватност"; +"interface" = "Изглед"; + +"profile_picture" = "Слика профила"; + +"picture" = "Слика"; + +"change_password" = "Промена шифре"; +"old_password" = "Стара шифра"; +"new_password" = "Нова шифра"; +"repeat_password" = "Поновити шифру"; + +"avatars_style" = "Приказати аватар"; +"style" = "Стил"; + +"default" = "Уобичајено"; +"cut" = "Уређивање слике"; +"round_avatars" = "Округли аватар"; + +"search_for_groups" = "Истраживање група"; +"search_for_people" = "Истраживање људи"; +"search_button" = "Пронаћи"; +"privacy_setting_access_page" = "Ко може видјети моју страницу на Интернету"; +"privacy_setting_read_info" = "Ко може видјети основну информацију од моје странице"; +"privacy_setting_see_groups" = "Ко може видјети информацију о мојим груповима и састанку"; +"privacy_setting_see_photos" = "Ко може видјети моје слике"; +"privacy_setting_see_videos" = "Ко може погледати моје видео"; +"privacy_setting_see_notes" = "Ко може видјети моје билешке"; +"privacy_setting_see_friends" = "Ко може да упозна са мојим пријатељима"; +"privacy_setting_add_to_friends" = "Ко има могућност да дода ме у пријатеље"; +"privacy_setting_write_wall" = "Ко има могућност да пиши на мојем зиду"; +"privacy_value_anybody" = "Било ко"; +"privacy_value_anybody_dative" = "Било ко"; +"privacy_value_users" = "Корисници OpenVK"; +"privacy_value_friends" = "Пријатеље"; +"privacy_value_friends_dative" = "Пријатеље"; +"privacy_value_only_me_and_super_capite" = "Ја сам и Виталина Павленко"; +"privacy_value_only_me_and_super_capite_dative" = "За мене и Виталину Павленко"; +"privacy_value_super_capite" = "Виталини Павленко"; + +"your_email_address" = "Ваша e-mail адреса"; +"your_page_address" = "Адреса Ваше странице"; +"page_address" = "Адреса странице"; +"current_email_address" = "Тренутна e-mail адреса"; +"page_id" = "ID странице"; +"you_can_also" = "Ви такође имате могућност"; +"delete_your_page" = "да избришите своју страницу"; +"delete_album" = "да избришите албум"; + +"ui_settings_interface" = "Интерфејс"; +"ui_settings_sidebar" = "Леви мени"; +"ui_settings_rating" = "Рејтијнг"; +"ui_settings_rating_show" = "Показати"; +"ui_settings_rating_hide" = "Сакрити"; + +%{ Sorting %} + +"sort_randomly" = "Поредити насумнично"; +"sort_up" = "Поредити по датуму креирања према горе"; +"sort_down" = "Поредити по датуму креирања према доле"; + +%{ Videos %} + +"videos" = "Видео"; +"video" = "Видео"; +"upload_video" = "Отпремати своје видео"; +"video_uploaded" = "Отпремљено"; +"video_updated" = "Ажурирано"; +"video_link_to_yt" = "YT Адреса"; + +"video_name" = "Назив"; +"video_description" = "Опис"; +"video_uploaded_by" = "Видео је преузео"; +"video_upload_date" = "Датум преузимања"; + +"videos_zero" = "Нема ниједног видео"; +"videos_one" = "Једно видео"; +"videos_few" = "$1 видео"; +"videos_many" = "$1 видео"; +"videos_other" = "$1 видео"; + +%{ Notifications %} + +"feedback" = "Повратна веза"; +"unread" = "Непрочитано"; +"archive" = "Архива"; + +"notifications_like" = "$1 је проценио вашу $2запис$3 испод $4"; +"notifications_repost" = "$1 поделио(-лила) се вашим $2постом$3 испод $4"; +"notifications_comment_under" = "$1 оставио(-ла) коментар испод $2"; +"notifications_under_note" = "ваших $3белешка$4"; +"notifications_under_photo" = "вашег $3фото$4"; +"notifications_under_post" = "вашег $3поста$4 од $5"; +"notifications_under_video" = "вашег $3видео$4"; +"notifications_post" = "$1 написао(-ла) је $2нешто$3 на вашем зиду: $4"; +"notifications_appoint" = "$1 одредио је вас за вођу заједнице $2"; + +"nt_liked_yours" = "свиђало је ваше"; +"nt_shared_yours" = "поделио(-ила) је са вашим"; +"nt_commented_yours" = "написао(ла) је коментар испод"; +"nt_written_on_your_wall" = "написао(ла) је нешто на вашем зиду"; +"nt_made_you_admin" = "одредио(ла) је вас за вођу заједнице"; + +"nt_from" = "од"; +"nt_yours_adjective" = "вашег"; +"nt_yours_feminitive_adjective" = "вашег"; +"nt_post_nominative" = "пост"; +"nt_post_instrumental" = "постом"; +"nt_note_instrumental" = "белешком"; +"nt_photo_instrumental" = "фотографијом"; + +%{ Time %} + +"time_at_sp" = " у "; +"time_just_now" = "садашње вријеме"; +"time_exactly_five_minutes_ago" = "пре 5 минута"; +"time_minutes_ago" = "$пре 1 минути"; +"time_today" = "данас"; +"time_yesterday" = "јуће"; + +"privacy_setting_access_page" = "Ко може видјети моју страницу на Интернету"; +"privacy_setting_read_info" = "Ко може видјети основну информацију од моје странице"; +"privacy_setting_see_groups" = "Ко може видјети информацију о мојим груповима и састанку"; +"privacy_setting_see_photos" = "Ко може видјети моје слике"; +"privacy_setting_see_videos" = "Ко може погледати моје видео"; +"privacy_setting_see_notes" = "Ко може видјети моје билешке"; +"privacy_setting_see_friends" = "Ко може да упозна са мојим пријатељима"; +"privacy_setting_add_to_friends" = "Ко има могућност да дода ме у пријатеље"; +"privacy_setting_write_wall" = "Ко има могућност да пиши на мојем зиду"; +"privacy_value_anybody" = "Било ко"; +"privacy_value_anybody_dative" = "Било ко"; +"privacy_value_users" = "Корисници OpenVK"; +"privacy_value_friends" = "Пријатеље"; +"privacy_value_friends_dative" = "Пријатеље"; +"privacy_value_only_me_and_super_capite" = "Ја сам"; +"privacy_value_only_me_and_super_capite_dative" = "За мене"; + +"your_email_address" = "Адреса Ваше електронске поште"; +"your_page_address" = "Адреса Ваше странице"; +"page_address" = "Адреса странице"; +"current_email_address" = "Текућа адреса"; +"page_id" = "ID странице"; +"you_can_also" = "Ви такође можете"; +"delete_your_page" = "да избришите своју страницу"; +"delete_album" = "да избришите албум"; + +"ui_settings_interface" = "Интерфејс"; +"ui_settings_sidebar" = "Леви мени"; +"ui_settings_rating" = "Рејтијнг"; +"ui_settings_rating_show" = "Показати"; +"ui_settings_rating_hide" = "Сакрити"; + +%{ Errors %} + +"error_1" = "Није тачан захтев"; +"error_2" = "Корисничко име или лозинка нису тачни"; +"error_3" = "Није овлашћен"; +"error_4" = "Не постоји оног корисника"; +"information_-1" = "Операција је успешно завршена"; +"information_-2" = "Пријавили сте успешно"; + +"no_data" = "Нема податка"; +"no_data_description" = "Недостају подаци у овој одредби."; diff --git a/locales/sr_lat.strings b/locales/sr_lat.strings new file mode 100644 index 000000000..89b494dfe --- /dev/null +++ b/locales/sr_lat.strings @@ -0,0 +1,430 @@ +"__locale" = "sr_CS.UTF-8;Srb_Latin"; + +%{ Check for https://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html %} + +%{ Main page %} + +"home" = "Poćetna"; +"welcome" = "Dobro došli"; + +%{ Login %} + +"log_in" = "Prijava"; +"password" = "Šifra"; +"registration" = "Registracija"; + +%{ Profile information %} + +"select_language" = "Promena jezika"; +"edit" = "urediti"; +"birth_date" = "Rođendan"; +"registration_date" = "Datum registracije"; +"hometown" = "Rodni grad"; +"this_is_you" = "to ste Vi"; +"edit_page" = "Urediti stranicu"; +"edit_group" = "Urediti grupu"; +"change_status" = "promeniti status"; +"name" = "Ime"; +"surname" = "Prezime"; +"gender" = "Pol"; +"male" = "muški"; +"female" = "ženski"; +"description" = "Opis"; +"save" = "Saćuvati"; +"main_information" = "Osnovna informacija"; +"nickname" = "Korisnićko ime"; +"online" = "U mreži"; +"was_online" = "bio na mreži"; +"was_online_m" = "bio na mreži"; +"was_online_f" = "bila na mreži"; +"all_title" = "Sve"; +"information" = "Informacija"; +"status" = "Status"; + +"relationship" = "Porodićni status"; + +"relationship_0" = "Nemam"; +"relationship_1" = "Nisam oženjen"; +"relationship_2" = "Upoznajem se"; +"relationship_3" = "Angažovan"; +"relationship_4" = "Oženjen"; +"relationship_5" = "U građanskom braku"; +"relationship_6" = "Zaljubljen"; +"relationship_7" = "Sve je teško"; +"relationship_8" = "Nalazim se u aktivnom pretraživanju"; + +"politViews" = "Politički pogledi"; + +"politViews_0" = "Nemam"; +"politViews_1" = "Ravnodušan"; +"politViews_2" = "Komunistićki"; +"politViews_3" = "Socijalistićki"; +"politViews_4" = "Umereni"; +"politViews_5" = "Liberalni"; +"politViews_6" = "Konzervativne"; +"politViews_7" = "Monarhistićki"; +"politViews_8" = "Ultrakonzervativne"; +"politViews_9" = "Libertarijanske"; + +"contact_information" = "Kontakt informacije"; + +"email" = "Elektronska pošta"; +"phone" = "Broj telefona"; +"telegram" = "Telegram"; +"city" = "Ime grada"; +"address" = "Adresa"; + +"personal_information" = "Personalna informacija"; + +"interests" = "Interesi"; +"favorite_music" = "Omiljena muzika"; +"favorite_films" = "Omiljeni filmovi"; +"favorite_shows" = "Omiljena TV emisija"; +"favorite_books" = "Omiljene knjige"; +"favorite_quotes" = "Omiljeni citati"; +"information_about" = "Lična informacija"; + +%{ Wall %} + +"post_writes_m" = "je napisao"; +"post_writes_f" = "je napisala"; +"wall" = "Zid"; +"post" = "Zapis"; +"write" = "Napisati"; +"publish" = "Objaviti"; +"delete" = "Obrisati"; +"comments" = "Komentarije"; +"share" = "Podeliti se informacijom"; +"comments_tip" = "Ćete biti prvi, ko će ostaviti komentar!"; +"your_comment" = "Vaš komentar"; +"comments" = "Komentari"; +"shown" = "Predstavljeno"; +"x_out_of" = "$1 od"; +"wall_zero" = "nema zapisa"; +"wall_one" = "jedina zapis"; +"wall_few" = "$1 zapisa"; +"wall_many" = "$1 zapisa"; +"wall_other" = "$1 zapisa"; + +"nsfw_warning" = "Ova post ima sadržaj za osobe od 18 godina i više"; + +%{ Friends %} + +"friends" = "Prijatelji"; +"followers" = "Pretplatnici"; +"follower" = "Pretplatnik"; +"friends_add" = "Dodati u prijatelje"; +"friends_delete" = "Izbrisati od prijatelja"; +"friends_reject" = "Odjaviti zahtev"; +"friends_accept" = "Prihvatiti zahtev"; +"send_message" = "Pošaljiti poruku"; +"incoming_req" = "Pretplatnici"; +"outcoming_req" = "Zahtevi"; +"req" = "Zahtevi"; + +"firends_zero" = "Nema nijednog prijatelja"; +"friends_one" = "$1 prijatelj"; +"friends_few" = "$1 prijatelja"; +"friends_many" = "$1 prijatelja"; +"friends_other" = "$1 prijatelja"; + +"followers_zero" = "Nema nijednog sledbenika"; +"followers_one" = "$1 sledbenik"; +"followers_few" = "$1 sledbenika"; +"followers_many" = "$1 sledbenika"; +"followers_other" = "$1 sledbenika"; + +"subscriptions_zero" = "Nema nijedne prijave"; +"subscriptions_one" = "$1 prijava"; +"subscriptions_few" = "$1 prijava"; +"subscriptions_many" = "$1 prijava"; +"subscriptions_other" = "$1 prijava"; + +%{ Group %} + +"name_group" = "Naziv"; +"subscribe" = "Prijaviti se"; +"unsubscribe" = "Odjaviti se"; +"subscriptions" = "Sledbenici"; +"join_community" = "Ući u grupu"; +"leave_community" = "Izaći iz grupe"; +"min_6_community" = "Naziv se mora da sadr?i 6 simbola"; +"participants" = "Učesnici"; +"groups" = "Grupi"; +"meetings" = "Sastanci"; +"create_group" = "Stvoriti grupu"; +"group_managers" = "Vođstvo"; +"group_type" = "Tipovi grupa"; +"group_type_open" = "To je otvorena grupa. Svaka persona može se pridružiti prilikom nje."; +"group_type_closed" = "To je zatvorena grupa. Morate se prijaviti."; +"creator" = "Stvaralac"; + +"role" = "Uloga"; +"administrator" = "Administrator"; +"promote_to_admin" = "Promovisati do administatora"; +"devote" = "Ražalovati ulogu"; + +"participants_zero" = "Nema učesnika"; +"participants_one" = "Jedan učesnik"; +"participants_few" = "$1 učesnika"; +"participants_many" = "$1 učesnika"; +"participants_other" = "$1 učesnika"; + +"groups_zero" = "Nema grupe"; +"groups_one" = "Jedna grupa"; +"groups_few" = "$1 grupa"; +"groups_many" = "$1 grupa"; +"groups_other" = "$1 grupa"; + +"meetings_zero" = "Nema sastanka"; +"meetings_one" = "Jedan sastanak"; +"meetings_few" = "$1 sastanka"; +"meetings_many" = "$1 sastanka"; +"meetings_other" = "$1 sastanka"; + +%{ Albums %} + +"create" = "Stvoriti"; +"albums" = "Albumi"; +"create_album" = "Kreirati album"; +"edit_album" = "Urediti album"; +"creating_album" = "Stvorenje albuma"; +"upload_photo" = "Otpremiti foto"; +"photo" = "Foto"; +"upload_button" = "Otpremanje"; + +"albums_zero" = "Nema albuma"; +"albums_one" = "Jedan album"; +"albums_few" = "$1 albuma"; +"albums_many" = "$1 albuma"; +"albums_other" = "$1 albuma"; + +%{ Notes %} + +"notes" = "Belešci"; +"note" = "Beleška"; +"name_note" = "Naziv"; +"text_note" = "Sadržaj"; +"create_note" = "Kreirati belešku"; +"actions" = "Aktivnosti"; +"feed" = "Vesti"; +"publish_post" = "Objaviti post"; + +"notes_zero" = "Nema beleška"; +"notes_one" = "Jedna beleška"; +"notes_few" = "$1 beleška"; +"notes_many" = "$1 beleška"; +"notes_other" = "$1 beleška"; + +%{ Menus %} + +"edit_button" = "ured."; +"my_page" = "Moja Stranica"; +"my_friends" = "Moji drugovi"; +"my_photos" = "Moje slike"; +"my_videos" = "Moje video"; +"my_messages" = "Moji poruke"; +"my_notes" = "Moji belešci"; +"my_groups" = "Moji grupi"; +"my_feed" = "Moji vesti"; +"my_feedback" = "Moji odgovare"; +"my_settings" = "Moja podešavanja"; +"bug_tracker" = "Praćenje grešaka"; + +"menu_login" = "Prijaviti se"; +"menu_registration" = "Registracija"; +"menu_help" = "Pomoć"; + +"header_home" = "naslovna strana"; +"header_groups" = "grupi"; +"header_donate" = "podrška"; +"header_people" = "ljudi"; +"header_invite" = "pozvati"; +"header_help" = "pomoć"; +"header_log_out" = "odjaviti se"; +"header_search" = "Istraživanje"; + +"header_login" = "Prijava"; +"header_registration" = "Registracija"; +"header_help" = "Pomoć"; + +"footer_blog" = "blog"; +"footer_help" = "pomoć"; +"footer_developers" = "obraćanje programerima"; +"footer_choose_language" = "izabrati jezik"; +"footer_privacy" = "privatnost"; + +"notes_zero" = "Nema beleška"; +"notes_one" = "Jedna beleška"; +"notes_few" = "$1 beleška"; +"notes_many" = "$1 beleška"; +"notes_other" = "$1 belešci"; + +%{ Settings %} + +"main" = "Naslovna strana"; +"contacts" = "Kontakti"; +"avatar" = "Slika"; +"privacy" = "Privatnost"; +"interface" = "Izgled"; + +"profile_picture" = "Slika profila"; + +"picture" = "Slika"; + +"change_password" = "Promena šifre"; +"old_password" = "Stara šifra"; +"new_password" = "Nova šifra"; +"repeat_password" = "Ponoviti šifru"; + +"avatars_style" = "Prikazati avatar"; +"style" = "Stil"; + +"default" = "Uobićajeno"; +"cut" = "Uređivanje slike"; +"round_avatars" = "Okrugli avatar"; + +"search_for_groups" = "Istraživanje grupa"; +"search_for_people" = "Istraživanje ljudi"; +"search_button" = "Pronaći"; +"privacy_setting_access_page" = "Ko može videti moju stranicu na Internetu"; +"privacy_setting_read_info" = "Ko može videti osnovnu informaciju od moje stranice"; +"privacy_setting_see_groups" = "Ko može videti informaciju o mojim grupovima i sastanku"; +"privacy_setting_see_photos" = "Ko može videti moje slike"; +"privacy_setting_see_videos" = "Ko može pogledati moje video"; +"privacy_setting_see_notes" = "Ko može videti moje bile?ke"; +"privacy_setting_see_friends" = "Ko može da upozna sa mojim prijateljima"; +"privacy_setting_add_to_friends" = "Ko ima mogućnost da doda me u prijatelje"; +"privacy_setting_write_wall" = "Ko ima mogućnost da piši na mojem zidu"; +"privacy_value_anybody" = "Bilo ko"; +"privacy_value_anybody_dative" = "Bilo ko"; +"privacy_value_users" = "Korisnici OpenVK"; +"privacy_value_friends" = "Prijatelje"; +"privacy_value_friends_dative" = "Prijatelje"; +"privacy_value_only_me_and_super_capite" = "Ja sam i Vitalina Pavlenko"; +"privacy_value_only_me_and_super_capite_dative" = "Za mene i Vitalinu Pavlenko"; +"privacy_value_super_capite" = "Vitalini Pavlenko"; + +"your_email_address" = "Vaša e-mail adresa"; +"your_page_address" = "Adresa Vaše stranice"; +"page_address" = "Adresa stranice"; +"current_email_address" = "Sadašnja e-mail adresa"; +"page_id" = "ID stranice"; +"you_can_also" = "Vi takođe imate mogućnost"; +"delete_your_page" = "da izbrišite svoju stranicu"; +"delete_album" = "da izbrišite album"; + +"ui_settings_interface" = "Interfejs"; +"ui_settings_sidebar" = "Levi meni"; +"ui_settings_rating" = "Rejting"; +"ui_settings_rating_show" = "Pokazati"; +"ui_settings_rating_hide" = "Sakriti"; + +%{ Sorting %} + +"sort_randomly" = "Porediti nasumnično"; +"sort_up" = "Porediti po datumu kreiranja prema gore"; +"sort_down" = "Porediti po datumu kreiranja prema dole"; + +%{ Videos %} + +"videos" = "Video"; +"video" = "Video"; +"upload_video" = "Otpremati svoje video"; +"video_uploaded" = "Otpremljeno"; +"video_updated" = "Ažurirano"; +"video_link_to_yt" = "YT Adresa"; + +"video_name" = "Naziv"; +"video_description" = "Opis"; +"video_uploaded_by" = "Video je preuzeo"; +"video_upload_date" = "Datum preuzimanja"; + +"videos_zero" = "Nema nijednog video"; +"videos_one" = "Jedno video"; +"videos_few" = "$1 video"; +"videos_many" = "$1 video"; +"videos_other" = "$1 video"; + +%{ Notifications %} + +"feedback" = "Povratna veza"; +"unread" = "Nepročitano"; +"archive" = "Arhiva"; + +"notifications_like" = "$1 je procenio vašu $2zapis$3 ispod $4"; +"notifications_repost" = "$1 podelio(-lila) se vašim $2postom$3 ispod $4"; +"notifications_comment_under" = "$1 ostavio(-la) komentar ispod $2"; +"notifications_under_note" = "vaših $3beleška$4"; +"notifications_under_photo" = "vašeg $3foto$4"; +"notifications_under_post" = "vašeg $3posta$4 od $5"; +"notifications_under_video" = "vašeg $3video$4"; +"notifications_post" = "$1 napisao(-la) je $2nešto$3 na vašem zidu: $4"; +"notifications_appoint" = "$1 odredio je vas za vođu zajednice $2"; + +"nt_liked_yours" = "sviđalo je vaše"; +"nt_shared_yours" = "podelio(-ila) je sa vašim"; +"nt_commented_yours" = "napisao(la) je komentar ispod"; +"nt_written_on_your_wall" = "napisao(la) je nešto na vašem zidu"; +"nt_made_you_admin" = "odredio(la) je vas za vođu zajednice"; + +"nt_from" = "od"; +"nt_yours_adjective" = "vašeg"; +"nt_yours_feminitive_adjective" = "vašeg"; +"nt_post_nominative" = "post"; +"nt_post_instrumental" = "postom"; +"nt_note_instrumental" = "beleškom"; +"nt_photo_instrumental" = "fotografijom"; + +%{ Time %} + +"time_at_sp" = " u "; +"time_just_now" = "sadašnje vrijeme"; +"time_exactly_five_minutes_ago" = "pre 5 minuta"; +"time_minutes_ago" = "$pre 1 minuti"; +"time_today" = "danas"; +"time_yesterday" = "juće"; + +"privacy_setting_access_page" = "Ko može videti moju stranicu na Internetu"; +"privacy_setting_read_info" = "Ko može videti osnovnu informaciju od moje stranice"; +"privacy_setting_see_groups" = "Ko može videti informaciju o mojim grupovima i sastanku"; +"privacy_setting_see_photos" = "Ko može videti moje slike"; +"privacy_setting_see_videos" = "Ko može pogledati moje video"; +"privacy_setting_see_notes" = "Ko može videti moje bile?ke"; +"privacy_setting_see_friends" = "Ko može da upozna sa mojim prijateljima"; +"privacy_setting_add_to_friends" = "Ko ima mogućnost da doda me u prijatelje"; +"privacy_setting_write_wall" = "Ko ima mogućnost da piši na mojem zidu"; +"privacy_value_anybody" = "Bilo ko"; +"privacy_value_anybody_dative" = "Bilo ko"; +"privacy_value_users" = "Korisnici OpenVK"; +"privacy_value_friends" = "Prijatelje"; +"privacy_value_friends_dative" = "Prijatelje"; +"privacy_value_only_me_and_super_capite" = "Ja sam"; +"privacy_value_only_me_and_super_capite_dative" = "Za mene"; + +"your_email_address" = "Adresa Vaše elektronske pošte"; +"your_page_address" = "Adresa Vaše stranice"; +"page_address" = "Adresa stranice"; +"current_email_address" = "Tekuća adresa"; +"page_id" = "ID stranice"; +"you_can_also" = "Vi takođe možete"; +"delete_your_page" = "da izbrišite svoju stranicu"; +"delete_album" = "da izbrišite album"; + +"ui_settings_interface" = "Interfejs"; +"ui_settings_sidebar" = "Levi meni"; +"ui_settings_rating" = "Rejting"; +"ui_settings_rating_show" = "Pokazati"; +"ui_settings_rating_hide" = "Sakriti"; + +%{ Errors %} + +"error_1" = "Nije taćan zahtev"; +"error_2" = "Korisnićko ime ili lozinka nisu tačni"; +"error_3" = "Nije ovlašćen"; +"error_4" = "Ne postoji onog korisnika"; +"information_-1" = "Operacija je uspešno završena"; +"information_-2" = "Prijavili ste uspešno"; + +"no_data" = "Nema podatka"; +"no_data_description" = "Nedostaju podaci u ovoj odredbi."; diff --git a/locales/tr.strings b/locales/tr.strings new file mode 100644 index 000000000..96a00ea29 --- /dev/null +++ b/locales/tr.strings @@ -0,0 +1,378 @@ +"__locale" = "tr_TR.UTF-8;Tur"; + +%{ Check for https://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html %} + +%{ Main page %} + +"home" = "Ana ekran"; +"welcome" = "Hoşgeldin"; + +%{ Login %} + +"log_in" = "Giriş yap"; +"password" = "Parola"; +"registration" = "Kayıt"; + +%{ Profile information %} + +"select_language" = "Dil seç"; +"edit" = "dznl"; +"birth_date" = "Doğum tarihi"; +"registration_date" = "Kayıt tarihi"; +"hometown" = "Memleket"; +"this_is_you" = "bu Sensin"; +"edit_page" = "Sayfayı düzenle"; +"edit_group" = "Grubu düzenle"; +"change_status" = "durumu değiştir"; +"name" = "İsim"; +"surname" = "Soyisim"; +"gender" = "Cinsiyet"; +"male" = "bay"; +"female" = "bayan"; +"description" = "Açıklama"; +"save" = "Kaydet"; +"main_information" = "Ana bilgi"; +"nickname" = "Takma ad"; +"online" = "Çevrimiçi"; +"was_online" = "çevrimiçiydi"; +"was_online_m" = "çevrimiçiydi"; +%{ For male and femail %} + +"was_online_f" = "çevrimiçiydi"; +"all_title" = "Tümü"; +"information" = "Bilgi"; +"status" = "Durum"; + +"relationship" = "İlişki durumu"; + +"relationship_0" = "Seçilmedi"; +"relationship_1" = "Evli değil"; +"relationship_2" = "Çıkıyor"; +"relationship_3" = "Nişanlı"; +"relationship_4" = "Evli"; +"relationship_5" = "Medeni evli"; +"relationship_6" = "Aşık"; +"relationship_7" = "Her şey karışık"; +"relationship_8" = "Aktif arayışta"; + +"politViews" = "Politik grşlr"; + +"politViews_0" = "Seçilmedi"; +"politViews_1" = "Kayıtsız"; +"politViews_2" = "Komünist"; +"politViews_3" = "Sosyalist"; +"politViews_4" = "Ilımlı"; +"politViews_5" = "Liberal"; +"politViews_6" = "Muhafazakar"; +"politViews_7" = "Monarşik"; +"politViews_8" = "Ultra-Muhafazakar"; +"politViews_9" = "Özgürlükçü"; + +"contact_information" = "İletişim bilgileri"; + +"email" = "E-posta"; +"phone" = "Telefon"; +"telegram" = "Telegram"; +"city" = "Şehir"; +"address" = "Adres"; + +"personal_information" = "Kişisel bilgi"; + +"interests" = "İlgiler"; +"favorite_music" = "Favori müzik"; +"favorite_films" = "Favori filmler"; +"favorite_shows" = "Favori TV şovları"; +"favorite_books" = "Favori kitaplar"; +"favorite_quotes" = "Favori sözler"; +"information_about" = "Hakkında"; + +%{ Wall %} + +"post_writes_m" = "yazdı"; +"post_writes_f" = "yazdı"; +"wall" = "Duvar"; +"post" = "Gönderi"; +"write" = "Yaz"; +"publish" = "Yayınla"; +"delete" = "Sil"; +"comments" = "Yorumlar"; +"share" = "Paylaş"; +"comments_tip" = "Bu gönderiye yorum bırakan ilk kişi sen ol!"; +"your_comment" = "Yorumun"; +"comments" = "Yorumlar"; +"shown" = "Gösterildi"; +"x_out_of" = "$1 of"; +"wall_zero" = "gönderi yok"; +"wall_one" = "$1 gönderi"; +"wall_other" = "$1 gönderi"; +"feed" = "Haberler"; +"publish_post" = "Gönderi ekle"; + +"nsfw_warning" = "Bu gönderinin +18 içeriii olabilir"; + +%{ Friends %} + +"friends" = "Arkadaşlar"; +"followers" = "Takipçiler"; +"follower" = "Takipçi"; +"friends_add" = "Arkadaşlara ekle"; +"friends_delete" = "Arkadaşlardan kaldır"; +"friends_reject" = "İsteği reddet"; +"friends_accept" = "İsteği onayla"; +"send_message" = "Mesaj gönder"; +"incoming_req" = "Aboneler"; +"outcoming_req" = "İstekler"; +"req" = "İstekler"; + +"firends_zero" = "Arkadaş yok"; +"friends_one" = "$1 arkadaş"; +"friends_other" = "$1 arkadaş"; + +"followers_zero" = "Takipçi yok"; +"followers_one" = "$1 takipçi"; +"followers_other" = "$1 takipçi"; + +"subscriptions_zero" = "Abonelik yok"; +"subscriptions_one" = "$1 abonelik"; +"subscriptions_other" = "$1 abonelik"; + +%{ Group %} + +"name_group" = "Grup adı"; +"subscribe" = "Abone ol"; +"unsubscribe" = "Abonelikten çık"; +"subscriptions" = "Abonelikler"; +"join_community" = "Topluluğa katıl"; +"leave_community" = "Topluluktan ayrıl"; +"min_6_community" = "Grup adının 6 karakterden uzun olması gerekir."; +"participants" = "Katılımcılar"; +"groups" = "Gruplar"; +"meetings" = "Toplantılar"; +"create_group" = "Grup oluştur"; +"group_managers" = "Yöneticiler"; +"group_type" = "Grup türü"; +"group_type_open" = "Bu herkese açık bir gruptur, herkes girebilir."; +"group_type_closed" = "Bu herkese kapalı bir gruptur, girmek için izin istemelisin."; +"creator" = "Oluşturan"; + +"role" = "Rol"; +"administrator" = "Yönetici"; +"promote_to_admin" = "Yöneticiliğe yükselt"; +"devote" = "Yetkisini kaldır"; + + +"participants_zero" = "Katılımcı yok"; +"participants_one" = "$1 katılımcı"; +"participants_other" = "$1 katılımcı"; + +"groups_zero" = "Grup yok"; +"groups_one" = "$1 grup"; +"groups_other" = "$1 grup"; + +"meetings_zero" = "Toplantı yok"; +"meetings_one" = "$1 toplantı"; +"meetings_other" = "$1 toplantı"; + +%{ Albums %} + +"create" = "Oluştur"; +"albums" = "Albümler"; +"create_album" = "Albüm oluştur"; +"edit_album" = "Albümü düzenle"; +"creating_album" = "Albüm oluşturuluyor"; +"upload_photo" = "Fotoğraf yükle"; +"photo" = "Fotoğraf"; +"upload_button" = "Yükle"; + +"albums_zero" = "Albüm yok"; +"albums_one" = "$1 albüm"; +"albums_other" = "$1 albüm"; + +%{ Notes %} + +"notes" = "Notlar"; +"note" = "Not"; +"name_note" = "Başlık"; +"text_note" = "İçerik"; +"create_note" = "Not oluştur"; +"actions" = "İşlemler"; + +"notes_zero" = "Not yok"; +"notes_one" = "$1 not"; +"notes_other" = "$1 not"; + +%{ Menus %} + +%{ Note that is string need to fit into the "My Page" link %} + +"edit_button" = "dznl"; +"my_page" = "Sayfam"; +"my_friends" = "Arkadaşlarım"; +"my_photos" = "Fotoğraflarım"; +"my_videos" = "Videolarım"; +"my_messages" = "Mesajlarım"; +"my_notes" = "Notlarım"; +"my_groups" = "Gruplarım"; +"my_feed" = "Haber Kaynağım"; +"my_feedback" = "Geri bildirimim"; +"my_settings" = "Ayarlarım"; +"bug_tracker" = "Hata-tkpçsi"; + +"menu_login" = "Giriş yap"; +"menu_registration" = "Kayıt"; +"menu_help" = "Yardım"; + +"header_home" = "ana menü"; +"header_groups" = "gruplar"; +"header_donate" = "bağış yap"; +"header_people" = "kişiler"; +"header_invite" = "davet et"; +"header_help" = "yardım"; +"header_log_out" = "çıkış yap"; +"header_search" = "ara"; + +"header_login" = "giriş yap"; +"header_registration" = "kayıt"; +"header_help" = "yardım"; + +"footer_blog" = "blog"; +"footer_help" = "yardım"; +"footer_developers" = "geliştiriciler"; +"footer_choose_language" = "dil seç"; +"footer_privacy" = "gizlilik"; + +%{ Settings %} + +"main" = "Ana"; +"contacts" = "Kişiler"; +"avatar" = "Avatar"; +"privacy" = "Gizlilik"; +"interface" = "Arayüz"; + +"profile_picture" = "Profil resmi"; + +"picture" = "Resim"; + +"change_password" = "Parolayı değiştir"; +"old_password" = "Eski parola"; +"new_password" = "Yeni parola"; +"repeat_password" = "Parolayı tekrarla"; + +"avatars_style" = "Avatar tarzı"; +"style" = "Tarz"; + +"default" = "Varsayılan"; +"cut" = "Kes"; +"round_avatars" = "Avatarı yuvarla"; + +"search_for_groups" = "Grup ara"; +"search_for_people" = "Kişi ara"; +"search_button" = "Ara"; +"privacy_setting_access_page" = "Sayfamı kim görebilir"; +"privacy_setting_read_info" = "Sayfamın ana bilgisini kim görebilir"; +"privacy_setting_see_groups" = "Gruplarımı ve toplantılarımı kim görebilir"; +"privacy_setting_see_photos" = "Fotoğraflarımı kim görebilir"; +"privacy_setting_see_videos" = "Videolarımı kim görebilir"; +"privacy_setting_see_notes" = "Notlarımı kin görebilir"; +"privacy_setting_see_friends" = "Arkadaşlarımı kim görebilir"; +"privacy_setting_add_to_friends" = "Beni arkadaşlarına kim ekleyebilir"; +"privacy_setting_write_wall" = "Duvarımda kim gönderi yayınlayabilir"; +"privacy_value_anybody" = "Hiç kimse"; +"privacy_value_anybody_dative" = "Hiç kimse"; +"privacy_value_users" = "OpenVK kullanıcıları"; +"privacy_value_friends" = "Arkadaşlarım"; +"privacy_value_friends_dative" = "Arkadaşlarım"; +"privacy_value_only_me" = "Sadece ben"; +"privacy_value_only_me_dative" = "Sadece ben"; + +"your_email_address" = "E-posta adresin"; +"your_page_address" = "Adres sayfan"; +"page_address" = "Adres sayfası"; +"current_email_address" = "Mevcut e-posta adresin"; +"page_id" = "Sayfa ID'si"; +"you_can_also" = "Ayrıca"; +"delete_your_page" = "sayfanı silebilirsin"; +"delete_album" = "albümü silebilirsin"; + +"ui_settings_interface" = "Arayüz"; +"ui_settings_sidebar" = "Sol menü"; +"ui_settings_rating" = "Oylama"; +"ui_settings_rating_show" = "Göster"; +"ui_settings_rating_hide" = "Gizle"; + +%{ Sorting %} + +"sort_randomly" = "Rastgele sırala"; +"sort_up" = "ID'ye göre yukarı doğru sırayla"; +"sort_down" = "ID'ye göre aşağı doğru sırala"; + +%{ Videos %} + +"videos" = "Videolar"; +"video" = "Video"; +"upload_video" = "Video yükle"; +"video_uploaded" = "Yüklendi"; +"video_updated" = "Güncellendi"; +"video_link_to_yt" = "YT linki"; + +"video_name" = "Başlık"; +"video_description" = "Açıklama"; +"video_uploaded_by" = "Yükleyen"; +"video_upload_date" = "Yükleme tarihi"; + +"videos_zero" = "Video yok"; +"videos_one" = "$1 video"; +"videos_other" = "$1 video"; + +%{ Notifications %} + +"feedback" = "Geri bildirim"; +"unread" = "Okunmamış"; +"archive" = "Arşivle"; + +"notifications_like" = "$1, $4 üzerinden $2gönderini$3 beğendi"; +"notifications_repost" = "$1, $4 üzerinden $2gönderini$3 paylaştı"; +"notifications_comment_under" = "$1, $2 içinde bir yorum yaptı"; +"notifications_under_note" = "$3notun$4"; +"notifications_under_photo" = "$3fotoğrafın$4"; +"notifications_under_post" = "$5 üzerinden $3gönderin$4"; +"notifications_under_video" = "$3videon$4"; +"notifications_post" = "$1, duvarında $2bir gönderi$3 paylaştı: $4"; +"notifications_appoint" = "$1, seni $2 topluluğunda bir yönetici olarak atadı"; + +"nt_liked_yours" = "seninkini beğendi"; +"nt_shared_yours" = "seninkini paylaştı"; +"nt_commented_yours" = "seninkine yorum yaptı"; +"nt_written_on_your_wall" = "duvarında yazdı"; +"nt_made_you_admin" = "toplulukta yönetici yaptı"; + +"nt_from" = "üzerinden"; +"nt_yours_adjective" = "senin"; +"nt_yours_feminitive_adjective" = "senin"; +"nt_post_nominative" = "gönderi"; +"nt_post_instrumental" = "gönderi"; +"nt_note_instrumental" = "not"; +"nt_photo_instrumental" = "fotoğraf"; + +%{ Time %} + +"time_at_sp" = " saat "; +"time_just_now" = "daha şimdi"; +"time_exactly_five_minutes_ago" = "tam 5 dakika önce"; +"time_minutes_ago" = "$1 dakika önce"; +"time_today" = "bugün"; +"time_yesterday" = "dün"; + +%{ Errors %} + +"error_1" = "Yanlış sorgu"; +"error_2" = "Yanlış kullanıcı adı ve parola"; +"error_3" = "İzin verilmedi"; +"error_4" = "Böyle bir kullanıcı yok"; +"information_-1" = "Başarılı"; +"information_-2" = "Giriş başarılı"; + +"no_data" = "Veri yok"; +"no_data_description" = "Böyle bir veri yok."; + +%{ Translations by @WindowZytch - Çeviriler @WindowZytch'den %} \ No newline at end of file diff --git a/locales/ua.strings b/locales/ua.strings new file mode 100644 index 000000000..0fac39045 --- /dev/null +++ b/locales/ua.strings @@ -0,0 +1,213 @@ +"home" = "Головна"; +"log_in" = "Вхід"; +"welcome" = "Прошу"; +"password" = "Пароль"; +"registration" = "Зареєструватися"; +"select_language" = "Виберіть мову"; +"edit" = "Редагувати"; +"birth_date" = "День народження"; +"registration_date" = "Дата реєстрації"; +"hometown" = "Рідне місто"; +"this_is_you" = "Це ви"; +"post_writes_m" = "Написав"; +"post_writes_f" = "Написав"; +"wall" = "Стіни"; +"post" = "Запис"; +"write" = "Писати"; +"publish" = "Опублікувати"; +"edit_page" = "Редагувати сторінку"; +"edit_group" = "Редагування групи"; +"change_status" = "змінити стан"; +"name" = "Ім'я"; +"surname" = "Прізвище"; +"gender" = "Підлоги"; +"male" = "Чоловіки"; +"female" = "Жіночий"; +"description" = "Опис"; +"save" = "Зберегти"; +"main_information" = "Основи"; +"friends" = "Друзів"; +"friends_add" = "Додати в друзі"; +"friends_delete" = "Видалити з друзів"; +"friends_reject" = "Скасування програми"; +"friends_accept" = "Прийміть заявку"; +"send_message" = "Надіслати повідомлення"; +"nickname" = "Псевдонім"; +"online" = "Онлайн"; +"was_online_m" = "був на сайті"; +"was_online_f" = "був на сайті"; +"delete" = "Видалити"; +"comments" = "Коментарі"; +"share" = "Поділитися"; +"comments_tip" = "Будьте першим, щоб залишити коментар!"; +"incoming_req" = "Абонентів"; +"outcoming_req" = "Застосування"; +"req" = "Застосування"; +"you_have" = "У вас є"; +"all_title" = "Всі"; +"your_comment" = "Ваш коментар"; +"name_group" = "Ім'я"; +"subscribe" = "Підписатися"; +"unsubscribe" = "Відписатися"; +"join_community" = "Приєднуйтесь до групи"; +"leave_community" = "Вибратися з групи"; +"min_6_community" = "Ім'я має бути принаймні 1 символом"; +"participants" = "Учасники"; +"groups" = "Група"; +"create_group" = "Створити групу"; +"group_managers" = "Керівництво"; +"group_type" = "Тип групи"; +"group_type_open" = "Це відкрита Група. Будь-хто може приєднатися до нього."; +"creator" = "Творець"; +"create" = "Створити"; +"albums" = "Альбоми"; +"create_album" = "Створити альбом"; +"edit_album" = "Редагувати альбом"; +"creating_album" = "Створення альбому"; +"upload_photo" = "Завантажити фотографію"; +"photo" = "Фото"; +"notes" = "Нотатки"; +"name_note" = "Ім'я"; +"text_note" = "Вмісту"; +"create_notes" = "Створити нотатку"; +"acl_welcome" = "Редактор групової політики"; +"status" = "Статус"; +"actions" = "Дії"; +"upload_button" = "Завантажити"; +"feed" = "Новини"; +"publish_post" = "Додати запис"; +"edit_button" = "ред."; +"my_page" = "Моя сторінка"; +"my_friends" = "Мої друзі"; +"my_photos" = "Мої фотографії"; +"my_videos" = "Мої відео"; +"my_messages" = "Мої повідомлення"; +"my_notes" = "Мої нотатки"; +"my_groups" = "Мої групи"; +"my_feed" = "Мої Новини"; +"my_settings" = "Мої налаштування"; +"bug_tracker" = "Трекер помилок"; +"header_home" = "Головна"; +"header_groups" = "Група"; +"header_donate" = "Підтримати"; +"header_people" = "Люди"; +"header_invite" = "Запросити"; +"header_help" = "Допомога"; +"header_log_out" = "Вийти"; +"header_search" = "Пошук"; +"friends_one" = "%1 друг"; +"friends_few" = "%1 друг"; +"friends_many" = "%1 друг"; +"friends_other" = "%1 друзів"; +"wall_one" = "%1 запис"; +"wall_few" = "%1 запис"; +"wall_many" = "%1 записів"; +"wall_other" = "%1 записів"; +"participants_one" = "%1 учасник"; +"participants_few" = "%1 учасник"; +"participants_many" = "%1 учасник"; +"participants_other" = "%1 учасників"; +"groups_one" = "%1 Група"; +"groups_few" = "%1 Група"; +"groups_many" = "%1 Група"; +"groups_other" = "%1 Група"; +"notes_one" = "%1 Примітка"; +"notes_few" = "%1 примітки"; +"notes_many" = "%1 примітки"; +"notes_other" = "%1 примітки"; +"albums_one" = "%1 альбом"; +"albums_few" = "%1 альбом"; +"albums_many" = "%1 альбом"; +"albums_other" = "%1 альбомів"; +"information" = "Інформації"; +"relationship" = "Сімейний стан"; +"relationship_0" = "Не вибрано"; +"relationship_1" = "Неодружений"; +"relationship_2" = "Знайомства"; +"relationship_3" = "Займається"; +"relationship_4" = "Одружений"; +"relationship_5" = "У цивільному шлюбі"; +"relationship_6" = "У коханні"; +"relationship_7" = "Усе складно"; +"relationship_8" = "В активному пошуку"; +"politViews" = "Політичні погляди"; +"politViews_0" = "Не вибрано"; +"politViews_1" = "Відокремлені"; +"politViews_2" = "Комуністичні"; +"politViews_3" = "Соціалістичні"; +"politViews_4" = "Помірні"; +"politViews_5" = "Ліберальні"; +"politViews_6" = "Консервативні"; +"politViews_7" = "Монарїчні"; +"politViews_8" = "Ультраконсервативні"; +"politViews_9" = "Лібертаріанські"; +"contact_information" = "Контактна інформація"; +"email" = "Електронна пошта"; +"phone" = "Телефон"; +"telegram" = "Телеграма"; +"city" = "Місто"; +"address" = "Адреса"; +"personal_information" = "Особиста інформація"; +"interests" = "Інтереси"; +"favorite_music" = "Улюблену музику"; +"favorite_films" = "Улюблені фільми"; +"favorite_shows" = "Улюблені ТЕЛЕВІЗІЙНІ шоу"; +"favorite_books" = "Улюблені книги"; +"favorite_quotes" = "Улюблені цитати"; +"information_about" = "Про себе"; +"main" = "Основні"; +"contacts" = "Контакти"; +"avatar" = "Аватар"; +"profile_picture" = "Зображення сторінки"; +"picture" = "Зображення"; +"sort_randomly" = "Сортувати випадковим чином"; +"sort_up" = "Сортувати за датою створення"; +"sort_down" = "Відсортувати за датою створення"; +"videos" = "Відео"; +"video" = "Відео"; +"upload_video" = "Завантажити відео"; +"video_uploaded" = "Завантажені"; +"video_updated" = "Оновлено"; +"video_link_to_yt" = "Посилання на УТ"; +"video_name" = "Ім'я"; +"video_description" = "Опис"; +"video_uploaded_by" = "Завантажити"; +"video_upload_date" = "Дата завантаження"; +"privacy" = "Конфіденційності"; +"interface" = "Зовнішній вигляд"; +"change_password" = "Змінити пароль"; +"old_password" = "Старий пароль"; +"new_password" = "Новий пароль"; +"repeat_password" = "Повторіть свій пароль"; +"avatars_style" = "Відображення аватара"; +"style" = "Стиль"; +"default" = "За промовчанням"; +"cut" = "Обрізки"; +"round_avatars" = "Круглий Аватар"; +"search_for_groups" = "Пошукові групи"; +"search_for_people" = "Знаходження людей"; +"search_button" = "Знаходити"; +"date_format_1" = "%1 %2 %3 g."; +"date_format_2" = "%1 %2 %3 у %4"; +"date_month_1" = "Січня"; +"date_month_2" = "Лютого"; +"date_month_3" = "Березня"; +"date_month_4" = "Квітня"; +"date_month_5" = "Може"; +"date_month_6" = "Червня"; +"date_month_7" = "Липня"; +"date_month_8" = "Серпня"; +"date_month_9" = "Вересня"; +"date_month_10" = "Жовтня"; +"date_month_11" = "Листопада"; +"date_month_12" = "Грудня"; +"error_1" = "Неправильний запит"; +"error_2" = "Невірний логін або пароль"; +"error_3" = "Не авторизовано"; +"error_4" = "Користувач не існує"; +"information_-1" = "Операція пройшла успішно"; +"information_-2" = "Вхід був успішним"; +"interface_translation" = "Переклад інтерфейсу"; +"translations" = "Переклади"; +"no_translation" = "НЕ ПЕРЕКЛАДЕНО!!!"; +"languages" = "Мови"; \ No newline at end of file diff --git a/manifest.yml b/manifest.yml new file mode 100644 index 000000000..001d638c5 --- /dev/null +++ b/manifest.yml @@ -0,0 +1,6 @@ +name: "OpenVK 2 Electric Boogalo" +description: "Yet another OpenVK social network" +author: "OpenVK contributors" +version: "2.0" + +init: "bootstrap.php" \ No newline at end of file diff --git a/misc/install_err.phtml b/misc/install_err.phtml new file mode 100644 index 000000000..f6de4db28 --- /dev/null +++ b/misc/install_err.phtml @@ -0,0 +1,71 @@ + + + + OpenVK | Installation Error + + + + +
      +
      +

      Installation error

      +
      +
      +

      Detected problems

      +
        + +
      1. + +
      +
      +

      Notice: if you've just installed OpenVK, don't panic, you probably just need to install some dependencies via composer.

      +
      +
      + +