array( 'title' => t('Rules for Class Names'), 'description' => t('Enter a comma separated list of rules for Class Names. Whitespaces and line-breaks are ignored. Class Names should start with an upper or lower case letter "a to z" and can be followed by one or more upper or lower case letters "a to z", digits "0 to 9", hyphens "-" and/or underscores "_". The asterisk character "*" can be used in rules to represent any number of characters from the second position of the rule. Example: "userclass*, my-font-*" are valid rules for Class Names, whereas "*class" is invalid.'), 'validate_regexp' => '`^[a-zA-Z][-_a-zA-Z0-9?*]*$`', 'asterisk_expansion' => '[-_a-zA-Z0-9]*', 'required_by' => 'class', 'required_by_message' => t('The class attribute is used in your HTML elements and attributes rules. You should specify the Rules for Class Names field in the "Advanced rules" section below. Leaving it unspecified will result in all class attributes filtered out.'), ), 'valid_ids' => array( 'title' => t('Rules for Element IDs'), 'description' => t('Enter a comma separated list of rules for Element IDs. Whitespaces and line-breaks are ignored. Element IDs should start with an upper or lower case letter "a to z" and can be followed by one or more upper or lower case letters "a to z", digits "0 to 9", hyphens "-" and/or underscores "_". The asterisk character "*" can be used in rules to represent any number of characters from the second position of the rule. Example: "foo*" is a valid rule for Element IDs, whereas "*bar" is invalid.'), 'validate_regexp' => '`^[a-zA-Z][-_a-zA-Z0-9?*]*$`', 'asterisk_expansion' => '[-_a-zA-Z0-9]*', 'required_by' => 'id', 'required_by_message' => t('The id attribute is used in your HTML elements and attributes rules. You should specify the Rules for Element IDs field in the "Advanced rules" section below. Leaving it unspecified will result in all id attributes filtered out.'), ), 'style_urls' => array( 'title' => t('Rules for URLs used within inline styles'), 'description' => t('Enter a comma separated list of rules for URLs used within inline styles. Whitespaces and line-breaks are ignored. These rules affect the following style properties: "background", "background-image", "list-style" and "list-style-image". Each rule represents a single path or URL. The asterisk character "*" can be used to represent any number of characters. Examples: use "/*" for local URLs only, use "/images/*" for one particular directory, use "http://www.example.com/*" for URLs of an external site, use "@base-path*, @base-url*" for URLs of your own site.', array('@base-path' => $base_path, '@base-url' => $base_url)), 'validate_regexp' => '`^.*$`', 'asterisk_expansion' => '.*', 'required_by' => 'style', 'required_by_styles' => array('background', 'background-image', 'list-style', 'list-style-image'), 'required_by_message' => t('The style attribute is used in your HTML elements and attributes rules, and you have enabled one of the following style properties: "background", "background-image", "list-style" or "list-style-image". You should specify the Rules for URLs used within inline styles field in the "Advanced rules" section below. Leaving it unspecified will result in all URLs used within inline styles filtered out.'), ), ); } /** * Obtain a string with default valid_elements. */ function wysiwyg_filter_default_valid_elements() { return << $option_value) { $attributes[$attribute][$option_type] = $option_value; } } } // Obtain list of element names/synonyms (separated by /). // Consider synonyms as different elements with same exact attributes. foreach ($elements as $element) { if ($element == '@') { // These attributes should be enabled for all elements. foreach ($attributes as $attribute => $attribute_options) { if (!isset($common_attributes[$attribute])) { $common_attributes[$attribute] = array(); } foreach ($attribute_options as $option_type => $option_value) { $common_attributes[$attribute][$option_type] = $option_value; } } } else { // Ignore element name prefixes (+ - #) that are allowed for the // TynyMCE valid_elements parameter, but for the sake of simplicity, // our server side filter ignores them. if (strpos('+-#', $element[0]) !== FALSE) { $element = substr($element, 1); } if (!isset($parsed_elements[$element])) { $parsed_elements[$element] = array(); } if (!isset($parsed_elements[$element]['*'])) { foreach ($attributes as $attribute => $attribute_options) { if ($attribute == '*') { $parsed_elements[$element] = array('*' => array()); break; } if (!isset($parsed_elements[$element][$attribute])) { $parsed_elements[$element][$attribute] = array(); } foreach ($attribute_options as $option_type => $option_value) { $parsed_elements[$element][$attribute][$option_type] = $option_value; } } } } } } } // Append commonly allowed attributes to each allowed element. if (!empty($common_attributes)) { foreach ($parsed_elements as $element => &$attributes) { // Do not append common attributes when all are allowed. if (isset($parsed_elements[$element]['*'])) { continue; } foreach ($common_attributes as $attribute => $attribute_options) { if (!isset($attributes[$attribute])) { $attributes[$attribute] = array(); } foreach ($attribute_options as $option_type => $option_value) { $attributes[$attribute][$option_type] = $option_value; } } } } // Sort HTML elements alphabetically (for filter tips). ksort($parsed_elements); return $parsed_elements; } /** * Obtain list of style properties along their syntax rules. * * Style property information in compiled in logical groups, so it's * easier to build the filter settings form. * * Note that regular expression quantifiers are limited by number * of digits/characters. This is to prevent users from posting * big numbers/strings in property values that could cause browsers * to crash due to overflows, or any other kind of issue. Note that * users will still be able to break page layouts when using certain * combinations of numbers and units (ie. 100em, etc.), but nothing * more than that, hopefully. * This kind of issues may also happen when validating HTML attributes * where values are just checked for bad protocols. This is the same * exact measure taken by Drupal's filter_xss(), which has been a * friend of us for a long time. It's a matter of balance between * performance, code complexity, etc. * * All regular expressions are aimed to be delimited by `^ and $`. * * @return array * * @see wysiwyg_filter_get_style_properties() * @see wysiwyg_filter_settings_filter() */ function wysiwyg_filter_get_style_property_groups() { $regexp_integer = '[-]?[0-9]{1,3}'; $regexp_number = '[-]?(?:[0-9]{0,3}|[0-9]{0,3}\.[0-9]{1,4})'; $regexp_length = $regexp_number . '(?:px|pt|em|ex|in|cm|mm|pc)?'; $regexp_percent = '[-]?[12]?[0-9]{1,2}%'; $regexp_color = '#[a-fA-F0-9]{3}|#[a-fA-F0-9]{6}|rgb\(\s*[0-9]{0,3}%?(?:\s*,\s*[0-9]{0,3}%?){2}\s*\)|[a-zA-Z]+'; $regexp_bgcolor = $regexp_color . '|transparent'; $regexp_border_width = $regexp_length . '|thin|medium|thick'; $regexp_border_style = 'none|hidden|dotted|dashed|solid|double|groove|ridge|inset|outset'; $regexp_uri = 'url\(\s*[\'"]?(?:[^)]|(?<=\\\\)\\))+[\'"]?\s*\)'; $regexp_shape = 'rect\(\s*(?:auto|' . $regexp_length . ')(?:\s+(?:auto|' . $regexp_length . ')){3}\s*\)'; $regexp_list_style_type = 'none|disc|circle|square|decimal(?:-leading-zero)?|lower-(?:alpha|greek|latin|roman)|upper-(?:alpha|latin|roman)|hebrew|armenian|georgian|cjk-ideographic|hiragana(?:-iroha)?|katakana(?:-iroha)?'; // Note that shorthand properties such as background and font are built // below, so we can reuse regular expression definitions. $groups = array( 'color' => array( 'title' => t('Color and background properties'), 'properties' => array( 'color' => '(?:' . $regexp_color . ')', 'background' => '', // See this property expanded below. 'background-color' => '(?:' . $regexp_bgcolor . ')', 'background-image' => '(?:none|' . $regexp_uri . ')', 'background-repeat' => '(?:no-repeat|repeat(?:-x|-y)?)', 'background-attachment' => '(?:scroll|fixed)', 'background-position' => '(?:(?:(?:top|center|bottom|left|right)(?:\s+(?:top|center|bottom|left|right))?)|(?:(?:' . $regexp_length . '|' . $regexp_percent . ')(?:\s+(?:' . $regexp_length . '|' . $regexp_percent . '))?))', ), ), 'font' => array( 'title' => t('Font properties'), 'properties' => array( 'font' => '', // See this property expanded below. 'font-family' => '(?:[-_a-zA-Z0-9"\' ]*(?:\s*,\s*[-_a-zA-Z0-9"\' ]*)*)', 'font-size' => '(?:(?:x-|xx-)?(?:small|large)(?:er)?|medium|' . $regexp_length . '|' . $regexp_percent . ')', 'font-size-adjust' => '(?:none|' . $regexp_number . ')', 'font-stretch' => '(?:normal|wider|narrower|(?:ultra-|extra-|semi-)?(?:condensed|expanded))', 'font-style' => '(?:normal|italic|oblique)', 'font-variant' => '(?:normal|small-caps)', 'font-weight' => '(?:normal|bold|bolder|lighter|[1-9]00)', ), ), 'text' => array( 'title' => t('Text properties'), 'properties' => array( 'text-align' => '(?:left|right|center|justify)', 'text-decoration' => '(?:none|underline|overline|line-through|blink)', 'text-indent' => '(?:' . $regexp_length . '|' . $regexp_percent . ')', 'text-transform' => '(?:none|capitalize|(?:upper|lower)case)', 'letter-spacing' => '(?:normal|' . $regexp_length . ')', 'word-spacing' => '(?:normal|' . $regexp_length . ')', 'white-space' => '(?:normal|pre|nowrap)', 'direction' => '(?:ltr|rtl)', 'unicode-bidi' => '(?:normal|embed|bidi-override)', ), ), 'box' => array( 'title' => t('Box properties'), 'properties' => array( 'margin' => '(?:auto|' . $regexp_length . '|' . $regexp_percent . ')(?:\s+(?:auto|' . $regexp_length . '|' . $regexp_percent . ')){0,3}', 'margin-top' => '(?:auto|' . $regexp_length . '|' . $regexp_percent . ')', 'margin-right' => '(?:auto|' . $regexp_length . '|' . $regexp_percent . ')', 'margin-bottom' => '(?:auto|' . $regexp_length . '|' . $regexp_percent . ')', 'margin-left' => '(?:auto|' . $regexp_length . '|' . $regexp_percent . ')', 'padding' => '(?:' . $regexp_length . '|' . $regexp_percent . ')(?:\s+(?:' . $regexp_length . '|' . $regexp_percent . ')){0,3}', 'padding-top' => '(?:' . $regexp_length . '|' . $regexp_percent . ')', 'padding-right' => '(?:' . $regexp_length . '|' . $regexp_percent . ')', 'padding-bottom' => '(?:' . $regexp_length . '|' . $regexp_percent . ')', 'padding-left' => '(?:' . $regexp_length . '|' . $regexp_percent . ')', ), ), 'border-1' => array( 'title' => t('Border properties (1)'), 'properties' => array( 'border' => '(?:(?:' . $regexp_border_width . ')?(?:\s*(?:' . $regexp_border_style . ')?(?:\s*(?:' . $regexp_bgcolor . ')?)))', 'border-top' => '(?:(?:' . $regexp_border_width . ')?(?:\s*(?:' . $regexp_border_style . ')?(?:\s*(?:' . $regexp_bgcolor . ')?)))', 'border-right' => '(?:(?:' . $regexp_border_width . ')?(?:\s*(?:' . $regexp_border_style . ')?(?:\s*(?:' . $regexp_bgcolor . ')?)))', 'border-bottom' => '(?:(?:' . $regexp_border_width . ')?(?:\s*(?:' . $regexp_border_style . ')?(?:\s*(?:' . $regexp_bgcolor . ')?)))', 'border-left' => '(?:(?:' . $regexp_border_width . ')?(?:\s*(?:' . $regexp_border_style . ')?(?:\s*(?:' . $regexp_bgcolor . ')?)))', 'border-width' => '(?:' . $regexp_border_width . ')(?:\s+(?:' . $regexp_border_width . ')){0,3}', 'border-top-width' => '(?:' . $regexp_border_width . ')', 'border-right-width' => '(?:' . $regexp_border_width . ')', 'border-bottom-width' => '(?:' . $regexp_border_width . ')', 'border-left-width' => '(?:' . $regexp_border_width . ')', ), ), 'border-2' => array( 'title' => t('Border properties (2)'), 'properties' => array( 'border-color' => '(?:' . $regexp_bgcolor . ')(?:\s+(?:' . $regexp_bgcolor . ')){0,3}', 'border-top-color' => '(?:' . $regexp_bgcolor . ')', 'border-right-color' => '(?:' . $regexp_bgcolor . ')', 'border-bottom-color' => '(?:' . $regexp_bgcolor . ')', 'border-left-color' => '(?:' . $regexp_bgcolor . ')', 'border-style' => '(?:' . $regexp_border_style . ')(?:\s+(?:' . $regexp_border_style . ')){0,3}', 'border-top-style' => '(?:' . $regexp_border_style . ')', 'border-right-style' => '(?:' . $regexp_border_style . ')', 'border-bottom-style' => '(?:' . $regexp_border_style . ')', 'border-left-style' => '(?:' . $regexp_border_style . ')', ), ), 'dimension' => array( 'title' => t('Dimension properties'), 'properties' => array( 'height' => '(?:auto|' . $regexp_length . '|' . $regexp_percent . ')', 'line-height' => '(?:normal|' . $regexp_number . '|' . $regexp_length . '|' . $regexp_percent . ')', 'max-height' => '(?:none|' . $regexp_length . '|' . $regexp_percent . ')', 'max-width' => '(?:none|' . $regexp_length . '|' . $regexp_percent . ')', 'min-height' => '(?:' . $regexp_length . '|' . $regexp_percent . ')', 'min-width' => '(?:' . $regexp_length . '|' . $regexp_percent . ')', 'width' => '(?:auto|' . $regexp_length . '|' . $regexp_percent . ')', ), ), 'positioning' => array( 'title' => t('Positioning properties'), 'properties' => array( 'bottom' => '(?:auto|' . $regexp_length . '|' . $regexp_percent . ')', 'clip' => '(?:auto|' . $regexp_shape . ')', 'left' => '(?:auto|' . $regexp_length . '|' . $regexp_percent . ')', 'overflow' => '(?:visible|hidden|scroll|auto)', 'right' => '(?:auto|' . $regexp_length . '|' . $regexp_percent . ')', 'top' => '(?:auto|' . $regexp_length . '|' . $regexp_percent . ')', 'vertical-align' => '(?:baseline|sub|super|middle|(?:text-)?(?:top|bottom)|' . $regexp_length . '|' . $regexp_percent . ')', 'z-index' => '(?:auto|' . $regexp_integer . ')', ), ), 'layout' => array( 'title' => t('Layout properties'), 'properties' => array( 'clear' => '(?:left|right|both|none)', 'display' => '(?:none|inline|block|list-item|run-in|compact|marker|table-(?:(?:row|header|group|column)-group|row|column|cell|caption)|(?:inline-)?table)', 'float' => '(?:left|right|none)', 'position' => '(?:static|relative|absolute|fixed)', 'visibility' => '(?:visible|hidden|collapse)', ), ), 'list' => array( 'title' => t('List properties'), 'properties' => array( 'list-style' => '(?:(?:' . $regexp_list_style_type . ')?(?:\s*(?:(?:in|out)side)?(?:\s*(?:none|' . $regexp_uri . ')?)))', 'list-style-image' => '(?:none|' . $regexp_uri . ')', 'list-style-position' => '(?:inside|outside)', 'list-style-type' => '(?:' . $regexp_list_style_type . ')', ), ), 'table' => array( 'title' => t('Table properties'), 'properties' => array( 'border-collapse' => '(?:collapse|separate)', 'border-spacing' => '(?:' . $regexp_length . '(?:\s+' . $regexp_length . ')?)', 'caption-side' => '(?:top|bottom|left|right)', 'empty-cells' => '(?:show|hide)', 'table-layout' => '(?:auto|fixed)', ), ), 'user' => array( 'title' => t('User interface properties'), 'properties' => array( 'cursor' => '(?:auto|crosshair|default|pointer|move|(?:e|ne|nw|n|se|sw|s|w)-resize|text|wait|help)', 'outline' => '(?:(?:' . $regexp_color . '|invert)?(?:\s*(?:' . $regexp_border_style . ')?(?:\s*(?:' . $regexp_border_width . ')?)))', 'outline-width' => '(?:' . $regexp_border_width . ')', 'outline-style' => '(?:' . $regexp_border_style . ')', 'outline-color' => '(?:' . $regexp_color . '|invert)', 'zoom' => '(?:normal|' . $regexp_number . '|' . $regexp_percent . ')', ), ), ); // 'background' property. $regexp = '(?:' . $groups['color']['properties']['background-color'] . '|' . $groups['color']['properties']['background-image'] . '|' . $groups['color']['properties']['background-repeat'] . '|' . $groups['color']['properties']['background-attachment'] . '|' . $groups['color']['properties']['background-position'] . ')'; $groups['color']['properties']['background'] = '(?:' . $regexp . ')(?:(?:\s+' . $regexp . ')+)'; // 'font' property. $regexp = '(?:' . $groups['font']['properties']['font-style'] . '|' . $groups['font']['properties']['font-variant'] . '|' . $groups['font']['properties']['font-weight'] . '|' . '(?:' . $groups['font']['properties']['font-size'] . ')(?:\s*/\s*' . $groups['dimension']['properties']['line-height'] . ')?|' . $groups['font']['properties']['font-family'] . ')'; $groups['font']['properties']['font'] = '(?:' . $regexp . ')(?:(?:\s+' . $regexp . ')+)'; return $groups; } /** * Get filter options. * * @param int $format_name * Input format identifier. * @param int $settings * Filter settings. * @return array */ function wysiwyg_filter_get_filter_options($format_name, $settings) { $filter_options = array( 'valid_elements' => wysiwyg_filter_parse_valid_elements($settings['valid_elements']), 'allow_comments' => $settings['allow_comments'], 'style_properties' => wysiwyg_filter_get_style_properties($format_name, $settings), 'nofollow_policy' => $settings['nofollow_policy'], 'nofollow_domains' => $settings['nofollow_domains'], ); foreach (wysiwyg_filter_get_advanced_rules() as $rule_key => $rule_info) { $filter_options[$rule_key] = array(); foreach ($settings["rule_$rule_key"] as $rule) { $filter_options[$rule_key][] = '`^' . str_replace("\xFF", $rule_info['asterisk_expansion'], preg_quote(str_replace('*', "\xFF", $rule), '`')) . '$`'; } } return $filter_options; } /** * Get allowed style properties. * * @param int $format_name * Input format identifier. * @param int $settings * Filter settings. * @return array */ function wysiwyg_filter_get_style_properties($format_name, $settings) { static $style_properties = array(); if (!isset($style_properties[$format_name])) { $style_properties[$format_name] = array(); foreach (wysiwyg_filter_get_style_property_groups() as $group => $group_info) { $allowed_styles = array_filter($settings["style_$group"]); foreach ($group_info['properties'] as $property => $regexp) { if (isset($allowed_styles[$property])) { $style_properties[$format_name][$property] = '`^' . $regexp . '$`'; } } } // Sort style properties alphabetically (for filter tips). ksort($style_properties[$format_name]); } return $style_properties[$format_name]; }