wysiwyg_filter.inc 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532
  1. <?php
  2. /**
  3. * @file
  4. * Common functions for the WYSIWYG Filter module.
  5. */
  6. /**
  7. * Helper function to get information about fields that implement
  8. * advanced rules.
  9. *
  10. * @see wysiwyg_filter_get_filter_options()
  11. * @see wysiwyg_filter_settings_filter()
  12. * @see wysiwyg_filter_settings_filter_validate()
  13. * @see wysiwyg_filter_settings_filter_submit()
  14. */
  15. function wysiwyg_filter_get_advanced_rules() {
  16. global $base_url, $base_path;
  17. return array(
  18. 'valid_classes' => array(
  19. 'title' => t('Rules for Class Names'),
  20. 'description' => t('Enter a comma separated list of rules for <em>Class Names</em>. Whitespaces and line-breaks are ignored. <em>Class Names</em> should start with an upper or lower case letter &quot;a to z&quot; and can be followed by one or more upper or lower case letters &quot;a to z&quot;, digits &quot;0 to 9&quot;, hyphens &quot;-&quot; and/or underscores &quot;_&quot;. The asterisk character &quot;*&quot; can be used in rules to represent any number of characters from the second position of the rule. Example: &quot;userclass*, my-font-*&quot; are valid rules for <em>Class Names</em>, whereas &quot;*class&quot; is invalid.'),
  21. 'validate_regexp' => '`^[a-zA-Z][-_a-zA-Z0-9?*]*$`',
  22. 'asterisk_expansion' => '[-_a-zA-Z0-9]*',
  23. 'required_by' => 'class',
  24. 'required_by_message' => t('The <strong>class</strong> attribute is used in your <em>HTML elements and attributes</em> rules. You should specify the <em>Rules for Class Names</em> field in the &quot;Advanced rules&quot; section below. Leaving it unspecified will result in all class attributes filtered out.'),
  25. ),
  26. 'valid_ids' => array(
  27. 'title' => t('Rules for Element IDs'),
  28. 'description' => t('Enter a comma separated list of rules for <em>Element IDs</em>. Whitespaces and line-breaks are ignored. <em>Element IDs</em> should start with an upper or lower case letter &quot;a to z&quot; and can be followed by one or more upper or lower case letters &quot;a to z&quot;, digits &quot;0 to 9&quot;, hyphens &quot;-&quot; and/or underscores &quot;_&quot;. The asterisk character &quot;*&quot; can be used in rules to represent any number of characters from the second position of the rule. Example: &quot;foo*&quot; is a valid rule for <em>Element IDs</em>, whereas &quot;*bar&quot; is invalid.'),
  29. 'validate_regexp' => '`^[a-zA-Z][-_a-zA-Z0-9?*]*$`',
  30. 'asterisk_expansion' => '[-_a-zA-Z0-9]*',
  31. 'required_by' => 'id',
  32. 'required_by_message' => t('The <strong>id</strong> attribute is used in your <em>HTML elements and attributes</em> rules. You should specify the <em>Rules for Element IDs</em> field in the &quot;Advanced rules&quot; section below. Leaving it unspecified will result in all id attributes filtered out.'),
  33. ),
  34. 'style_urls' => array(
  35. 'title' => t('Rules for URLs used within inline styles'),
  36. 'description' => t('Enter a comma separated list of rules for <em>URLs used within inline styles</em>. Whitespaces and line-breaks are ignored. These rules affect the following style properties: &quot;background&quot;, &quot;background-image&quot;, &quot;list-style&quot; and &quot;list-style-image&quot;. Each rule represents a single path or URL. The asterisk character &quot;*&quot; can be used to represent any number of characters. Examples: use &quot;/*&quot; for local URLs only, use &quot;/images/*&quot; for one particular directory, use &quot;http://www.example.com/*&quot; for URLs of an external site, use &quot;@base-path*, @base-url*&quot; for URLs of your own site.', array('@base-path' => $base_path, '@base-url' => $base_url)),
  37. 'validate_regexp' => '`^.*$`',
  38. 'asterisk_expansion' => '.*',
  39. 'required_by' => 'style',
  40. 'required_by_styles' => array('background', 'background-image', 'list-style', 'list-style-image'),
  41. 'required_by_message' => t('The <strong>style</strong> attribute is used in your <em>HTML elements and attributes</em> rules, and you have enabled one of the following style properties: &quot;background&quot;, &quot;background-image&quot;, &quot;list-style&quot; or &quot;list-style-image&quot;. You should specify the <em>Rules for URLs used within inline styles</em> field in the &quot;Advanced rules&quot; section below. Leaving it unspecified will result in all URLs used within inline styles filtered out.'),
  42. ),
  43. );
  44. }
  45. /**
  46. * Obtain a string with default valid_elements.
  47. */
  48. function wysiwyg_filter_default_valid_elements() {
  49. return <<<EOT
  50. a[!href|target<_blank|title],
  51. div[align<center?justify?left?right],
  52. p[align<center?justify?left?right],
  53. br,span,em,strong,cite,code,blockquote,ul,ol,li,dl,dt,dd
  54. EOT;
  55. }
  56. /**
  57. * Get HTML elements blacklist.
  58. */
  59. function wysiwyg_filter_get_elements_blacklist() {
  60. $blacklist = array(
  61. 'applet',
  62. 'area',
  63. 'base',
  64. 'basefont',
  65. 'body',
  66. 'button',
  67. 'embed',
  68. 'form',
  69. 'frame',
  70. 'frameset',
  71. 'head',
  72. 'html',
  73. 'iframe',
  74. 'input',
  75. 'isindex',
  76. 'label',
  77. 'link',
  78. 'map',
  79. 'meta',
  80. 'noframes',
  81. 'noscript',
  82. 'object',
  83. 'optgroup',
  84. 'option',
  85. 'param',
  86. 'script',
  87. 'select',
  88. 'style',
  89. 'textarea',
  90. 'title',
  91. );
  92. drupal_alter('wysiwyg_filter_elements_blacklist', $blacklist);
  93. return $blacklist;
  94. }
  95. /**
  96. * Parse valid_elements string in TinyMCE format.
  97. *
  98. * @link http://wiki.moxiecode.com/index.php/TinyMCE:Configuration/valid_elements
  99. *
  100. * @param string $valid_elements
  101. *
  102. * @return array
  103. * Information about allowed HTML elements and attributes.
  104. * Each HTML element contains a whitelist of attributes or '*' meaning all
  105. * attributes are allowed for that element.
  106. * Each attribute contains an array with one or more of the following items
  107. * of information:
  108. * - required boolean TRUE when attribute is required.
  109. * - default string Default value that will be applied when only the
  110. * attribute name is specified.
  111. * - forced string Value that will be applied when the attribute is
  112. * present in the parsed HTML stream.
  113. * - values array Whitelist of attribute values.
  114. * This information is used by the WYSIWYG Filter itself to filter out
  115. * disallowed HTML elements and attributes.
  116. *
  117. * @see wysiwyg_filter_process()
  118. */
  119. function wysiwyg_filter_parse_valid_elements($valid_elements) {
  120. // Remove whitespaces and split valid elements from a comma separate list of items into an array.
  121. $valid_elements = array_map('drupal_strtolower', array_filter(explode(',', preg_replace('#\s+#', '', $valid_elements))));
  122. $parsed_elements = array();
  123. $common_attributes = array();
  124. foreach ($valid_elements as $valid_element) {
  125. // Extract the element name and its allowed attributes list
  126. // including special characters that will be parsed later.
  127. if (preg_match('`^(@|[#+-]{0,1}[a-z0-9/]+)(\[([^]]*)\])*$`', $valid_element, $matches)) {
  128. // Element names can be specified by the special character "@" (used
  129. // to allow a common set of attributes for all valid HTML elements)
  130. // or a list of names separated by the special character "/".
  131. $elements = array_unique(array_filter(explode('/', $matches[1])));
  132. // Parse allowed attributes list (empty list means no attributes are allowed).
  133. $attributes = array();
  134. if (!empty($matches[3])) {
  135. // More than one attribute can be specified in the list (separator: "|").
  136. foreach (array_filter(explode('|', $matches[3])) as $attribute) {
  137. // Split item into attribute name and (optional) attribute options.
  138. $attribute_options = array();
  139. if (preg_match('`^([-a-z]+)([=:<].*)$`', $attribute, $match)) {
  140. $attribute = $match[1];
  141. // Parse attribute options.
  142. if (strpos('=:<', $match[2][0]) !== FALSE) {
  143. $operator = $match[2][0];
  144. if ($operator == '=') {
  145. // Default value for the attribute (applied when present without explicit value).
  146. $attribute_options['default'] = substr($match[2], 1);
  147. }
  148. else if ($operator == ':') {
  149. // Forced value for the attribute (applied when present regardless of the specified value).
  150. $attribute_options['forced'] = substr($match[2], 1);
  151. }
  152. else if ($operator == '<') {
  153. // This attribute accepts only the specified list of values (separator: "?").
  154. $attribute_options['values'] = array_unique(array_filter(explode('?', substr($match[2], 1))));
  155. }
  156. }
  157. }
  158. // Are all attributes allowed for this element?
  159. if ($attribute == '*') {
  160. $attributes['*'] = array();
  161. continue;
  162. }
  163. if (substr($attribute, 0, 1) == '!') {
  164. // If this attribute is not present in parsed HTML, then
  165. // the whole HTML element will be stripped out.
  166. $attribute = substr($attribute, 1);
  167. $attribute_options['required'] = TRUE;
  168. }
  169. // Ignore malformed attribute names.
  170. if (!preg_match('`^[a-z][-a-z]*$`', $attribute)) {
  171. continue;
  172. }
  173. // Attributes related to DOM events (on*) are not allowed here.
  174. if (substr($attribute, 0, 2) == 'on') {
  175. continue;
  176. }
  177. // Collect attribute options.
  178. if (!isset($attributes[$attribute])) {
  179. $attributes[$attribute] = array();
  180. }
  181. foreach ($attribute_options as $option_type => $option_value) {
  182. $attributes[$attribute][$option_type] = $option_value;
  183. }
  184. }
  185. }
  186. // Obtain list of element names/synonyms (separated by /).
  187. // Consider synonyms as different elements with same exact attributes.
  188. foreach ($elements as $element) {
  189. if ($element == '@') {
  190. // These attributes should be enabled for all elements.
  191. foreach ($attributes as $attribute => $attribute_options) {
  192. if (!isset($common_attributes[$attribute])) {
  193. $common_attributes[$attribute] = array();
  194. }
  195. foreach ($attribute_options as $option_type => $option_value) {
  196. $common_attributes[$attribute][$option_type] = $option_value;
  197. }
  198. }
  199. }
  200. else {
  201. // Ignore element name prefixes (+ - #) that are allowed for the
  202. // TynyMCE valid_elements parameter, but for the sake of simplicity,
  203. // our server side filter ignores them.
  204. if (strpos('+-#', $element[0]) !== FALSE) {
  205. $element = substr($element, 1);
  206. }
  207. if (!isset($parsed_elements[$element])) {
  208. $parsed_elements[$element] = array();
  209. }
  210. if (!isset($parsed_elements[$element]['*'])) {
  211. foreach ($attributes as $attribute => $attribute_options) {
  212. if ($attribute == '*') {
  213. $parsed_elements[$element] = array('*' => array());
  214. break;
  215. }
  216. if (!isset($parsed_elements[$element][$attribute])) {
  217. $parsed_elements[$element][$attribute] = array();
  218. }
  219. foreach ($attribute_options as $option_type => $option_value) {
  220. $parsed_elements[$element][$attribute][$option_type] = $option_value;
  221. }
  222. }
  223. }
  224. }
  225. }
  226. }
  227. }
  228. // Append commonly allowed attributes to each allowed element.
  229. if (!empty($common_attributes)) {
  230. foreach ($parsed_elements as $element => &$attributes) {
  231. // Do not append common attributes when all are allowed.
  232. if (isset($parsed_elements[$element]['*'])) {
  233. continue;
  234. }
  235. foreach ($common_attributes as $attribute => $attribute_options) {
  236. if (!isset($attributes[$attribute])) {
  237. $attributes[$attribute] = array();
  238. }
  239. foreach ($attribute_options as $option_type => $option_value) {
  240. $attributes[$attribute][$option_type] = $option_value;
  241. }
  242. }
  243. }
  244. }
  245. // Sort HTML elements alphabetically (for filter tips).
  246. ksort($parsed_elements);
  247. return $parsed_elements;
  248. }
  249. /**
  250. * Obtain list of style properties along their syntax rules.
  251. *
  252. * Style property information in compiled in logical groups, so it's
  253. * easier to build the filter settings form.
  254. *
  255. * Note that regular expression quantifiers are limited by number
  256. * of digits/characters. This is to prevent users from posting
  257. * big numbers/strings in property values that could cause browsers
  258. * to crash due to overflows, or any other kind of issue. Note that
  259. * users will still be able to break page layouts when using certain
  260. * combinations of numbers and units (ie. 100em, etc.), but nothing
  261. * more than that, hopefully.
  262. * This kind of issues may also happen when validating HTML attributes
  263. * where values are just checked for bad protocols. This is the same
  264. * exact measure taken by Drupal's filter_xss(), which has been a
  265. * friend of us for a long time. It's a matter of balance between
  266. * performance, code complexity, etc.
  267. *
  268. * All regular expressions are aimed to be delimited by `^ and $`.
  269. *
  270. * @return array
  271. *
  272. * @see wysiwyg_filter_get_style_properties()
  273. * @see wysiwyg_filter_settings_filter()
  274. */
  275. function wysiwyg_filter_get_style_property_groups() {
  276. $regexp_integer = '[-]?[0-9]{1,3}';
  277. $regexp_number = '[-]?(?:[0-9]{0,3}|[0-9]{0,3}\.[0-9]{1,4})';
  278. $regexp_length = $regexp_number . '(?:px|pt|em|ex|in|cm|mm|pc)?';
  279. $regexp_percent = '[-]?[12]?[0-9]{1,2}%';
  280. $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]+';
  281. $regexp_bgcolor = $regexp_color . '|transparent';
  282. $regexp_border_width = $regexp_length . '|thin|medium|thick';
  283. $regexp_border_style = 'none|hidden|dotted|dashed|solid|double|groove|ridge|inset|outset';
  284. $regexp_uri = 'url\(\s*[\'"]?(?:[^)]|(?<=\\\\)\\))+[\'"]?\s*\)';
  285. $regexp_shape = 'rect\(\s*(?:auto|' . $regexp_length . ')(?:\s+(?:auto|' . $regexp_length . ')){3}\s*\)';
  286. $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)?';
  287. // Note that shorthand properties such as background and font are built
  288. // below, so we can reuse regular expression definitions.
  289. $groups = array(
  290. 'color' => array(
  291. 'title' => t('Color and background properties'),
  292. 'properties' => array(
  293. 'color' => '(?:' . $regexp_color . ')',
  294. 'background' => '', // See this property expanded below.
  295. 'background-color' => '(?:' . $regexp_bgcolor . ')',
  296. 'background-image' => '(?:none|' . $regexp_uri . ')',
  297. 'background-repeat' => '(?:no-repeat|repeat(?:-x|-y)?)',
  298. 'background-attachment' => '(?:scroll|fixed)',
  299. 'background-position' => '(?:(?:(?:top|center|bottom|left|right)(?:\s+(?:top|center|bottom|left|right))?)|(?:(?:' . $regexp_length . '|' . $regexp_percent . ')(?:\s+(?:' . $regexp_length . '|' . $regexp_percent . '))?))',
  300. ),
  301. ),
  302. 'font' => array(
  303. 'title' => t('Font properties'),
  304. 'properties' => array(
  305. 'font' => '', // See this property expanded below.
  306. 'font-family' => '(?:[-_a-zA-Z0-9"\' ]*(?:\s*,\s*[-_a-zA-Z0-9"\' ]*)*)',
  307. 'font-size' => '(?:(?:x-|xx-)?(?:small|large)(?:er)?|medium|' . $regexp_length . '|' . $regexp_percent . ')',
  308. 'font-size-adjust' => '(?:none|' . $regexp_number . ')',
  309. 'font-stretch' => '(?:normal|wider|narrower|(?:ultra-|extra-|semi-)?(?:condensed|expanded))',
  310. 'font-style' => '(?:normal|italic|oblique)',
  311. 'font-variant' => '(?:normal|small-caps)',
  312. 'font-weight' => '(?:normal|bold|bolder|lighter|[1-9]00)',
  313. ),
  314. ),
  315. 'text' => array(
  316. 'title' => t('Text properties'),
  317. 'properties' => array(
  318. 'text-align' => '(?:left|right|center|justify)',
  319. 'text-decoration' => '(?:none|underline|overline|line-through|blink)',
  320. 'text-indent' => '(?:' . $regexp_length . '|' . $regexp_percent . ')',
  321. 'text-transform' => '(?:none|capitalize|(?:upper|lower)case)',
  322. 'letter-spacing' => '(?:normal|' . $regexp_length . ')',
  323. 'word-spacing' => '(?:normal|' . $regexp_length . ')',
  324. 'white-space' => '(?:normal|pre|nowrap)',
  325. 'direction' => '(?:ltr|rtl)',
  326. 'unicode-bidi' => '(?:normal|embed|bidi-override)',
  327. ),
  328. ),
  329. 'box' => array(
  330. 'title' => t('Box properties'),
  331. 'properties' => array(
  332. 'margin' => '(?:auto|' . $regexp_length . '|' . $regexp_percent . ')(?:\s+(?:auto|' . $regexp_length . '|' . $regexp_percent . ')){0,3}',
  333. 'margin-top' => '(?:auto|' . $regexp_length . '|' . $regexp_percent . ')',
  334. 'margin-right' => '(?:auto|' . $regexp_length . '|' . $regexp_percent . ')',
  335. 'margin-bottom' => '(?:auto|' . $regexp_length . '|' . $regexp_percent . ')',
  336. 'margin-left' => '(?:auto|' . $regexp_length . '|' . $regexp_percent . ')',
  337. 'padding' => '(?:' . $regexp_length . '|' . $regexp_percent . ')(?:\s+(?:' . $regexp_length . '|' . $regexp_percent . ')){0,3}',
  338. 'padding-top' => '(?:' . $regexp_length . '|' . $regexp_percent . ')',
  339. 'padding-right' => '(?:' . $regexp_length . '|' . $regexp_percent . ')',
  340. 'padding-bottom' => '(?:' . $regexp_length . '|' . $regexp_percent . ')',
  341. 'padding-left' => '(?:' . $regexp_length . '|' . $regexp_percent . ')',
  342. ),
  343. ),
  344. 'border-1' => array(
  345. 'title' => t('Border properties (1)'),
  346. 'properties' => array(
  347. 'border' => '(?:(?:' . $regexp_border_width . ')?(?:\s*(?:' . $regexp_border_style . ')?(?:\s*(?:' . $regexp_bgcolor . ')?)))',
  348. 'border-top' => '(?:(?:' . $regexp_border_width . ')?(?:\s*(?:' . $regexp_border_style . ')?(?:\s*(?:' . $regexp_bgcolor . ')?)))',
  349. 'border-right' => '(?:(?:' . $regexp_border_width . ')?(?:\s*(?:' . $regexp_border_style . ')?(?:\s*(?:' . $regexp_bgcolor . ')?)))',
  350. 'border-bottom' => '(?:(?:' . $regexp_border_width . ')?(?:\s*(?:' . $regexp_border_style . ')?(?:\s*(?:' . $regexp_bgcolor . ')?)))',
  351. 'border-left' => '(?:(?:' . $regexp_border_width . ')?(?:\s*(?:' . $regexp_border_style . ')?(?:\s*(?:' . $regexp_bgcolor . ')?)))',
  352. 'border-width' => '(?:' . $regexp_border_width . ')(?:\s+(?:' . $regexp_border_width . ')){0,3}',
  353. 'border-top-width' => '(?:' . $regexp_border_width . ')',
  354. 'border-right-width' => '(?:' . $regexp_border_width . ')',
  355. 'border-bottom-width' => '(?:' . $regexp_border_width . ')',
  356. 'border-left-width' => '(?:' . $regexp_border_width . ')',
  357. ),
  358. ),
  359. 'border-2' => array(
  360. 'title' => t('Border properties (2)'),
  361. 'properties' => array(
  362. 'border-color' => '(?:' . $regexp_bgcolor . ')(?:\s+(?:' . $regexp_bgcolor . ')){0,3}',
  363. 'border-top-color' => '(?:' . $regexp_bgcolor . ')',
  364. 'border-right-color' => '(?:' . $regexp_bgcolor . ')',
  365. 'border-bottom-color' => '(?:' . $regexp_bgcolor . ')',
  366. 'border-left-color' => '(?:' . $regexp_bgcolor . ')',
  367. 'border-style' => '(?:' . $regexp_border_style . ')(?:\s+(?:' . $regexp_border_style . ')){0,3}',
  368. 'border-top-style' => '(?:' . $regexp_border_style . ')',
  369. 'border-right-style' => '(?:' . $regexp_border_style . ')',
  370. 'border-bottom-style' => '(?:' . $regexp_border_style . ')',
  371. 'border-left-style' => '(?:' . $regexp_border_style . ')',
  372. ),
  373. ),
  374. 'dimension' => array(
  375. 'title' => t('Dimension properties'),
  376. 'properties' => array(
  377. 'height' => '(?:auto|' . $regexp_length . '|' . $regexp_percent . ')',
  378. 'line-height' => '(?:normal|' . $regexp_number . '|' . $regexp_length . '|' . $regexp_percent . ')',
  379. 'max-height' => '(?:none|' . $regexp_length . '|' . $regexp_percent . ')',
  380. 'max-width' => '(?:none|' . $regexp_length . '|' . $regexp_percent . ')',
  381. 'min-height' => '(?:' . $regexp_length . '|' . $regexp_percent . ')',
  382. 'min-width' => '(?:' . $regexp_length . '|' . $regexp_percent . ')',
  383. 'width' => '(?:auto|' . $regexp_length . '|' . $regexp_percent . ')',
  384. ),
  385. ),
  386. 'positioning' => array(
  387. 'title' => t('Positioning properties'),
  388. 'properties' => array(
  389. 'bottom' => '(?:auto|' . $regexp_length . '|' . $regexp_percent . ')',
  390. 'clip' => '(?:auto|' . $regexp_shape . ')',
  391. 'left' => '(?:auto|' . $regexp_length . '|' . $regexp_percent . ')',
  392. 'overflow' => '(?:visible|hidden|scroll|auto)',
  393. 'right' => '(?:auto|' . $regexp_length . '|' . $regexp_percent . ')',
  394. 'top' => '(?:auto|' . $regexp_length . '|' . $regexp_percent . ')',
  395. 'vertical-align' => '(?:baseline|sub|super|middle|(?:text-)?(?:top|bottom)|' . $regexp_length . '|' . $regexp_percent . ')',
  396. 'z-index' => '(?:auto|' . $regexp_integer . ')',
  397. ),
  398. ),
  399. 'layout' => array(
  400. 'title' => t('Layout properties'),
  401. 'properties' => array(
  402. 'clear' => '(?:left|right|both|none)',
  403. 'display' => '(?:none|inline|block|list-item|run-in|compact|marker|table-(?:(?:row|header|group|column)-group|row|column|cell|caption)|(?:inline-)?table)',
  404. 'float' => '(?:left|right|none)',
  405. 'position' => '(?:static|relative|absolute|fixed)',
  406. 'visibility' => '(?:visible|hidden|collapse)',
  407. ),
  408. ),
  409. 'list' => array(
  410. 'title' => t('List properties'),
  411. 'properties' => array(
  412. 'list-style' => '(?:(?:' . $regexp_list_style_type . ')?(?:\s*(?:(?:in|out)side)?(?:\s*(?:none|' . $regexp_uri . ')?)))',
  413. 'list-style-image' => '(?:none|' . $regexp_uri . ')',
  414. 'list-style-position' => '(?:inside|outside)',
  415. 'list-style-type' => '(?:' . $regexp_list_style_type . ')',
  416. ),
  417. ),
  418. 'table' => array(
  419. 'title' => t('Table properties'),
  420. 'properties' => array(
  421. 'border-collapse' => '(?:collapse|separate)',
  422. 'border-spacing' => '(?:' . $regexp_length . '(?:\s+' . $regexp_length . ')?)',
  423. 'caption-side' => '(?:top|bottom|left|right)',
  424. 'empty-cells' => '(?:show|hide)',
  425. 'table-layout' => '(?:auto|fixed)',
  426. ),
  427. ),
  428. 'user' => array(
  429. 'title' => t('User interface properties'),
  430. 'properties' => array(
  431. 'cursor' => '(?:auto|crosshair|default|pointer|move|(?:e|ne|nw|n|se|sw|s|w)-resize|text|wait|help)',
  432. 'outline' => '(?:(?:' . $regexp_color . '|invert)?(?:\s*(?:' . $regexp_border_style . ')?(?:\s*(?:' . $regexp_border_width . ')?)))',
  433. 'outline-width' => '(?:' . $regexp_border_width . ')',
  434. 'outline-style' => '(?:' . $regexp_border_style . ')',
  435. 'outline-color' => '(?:' . $regexp_color . '|invert)',
  436. 'zoom' => '(?:normal|' . $regexp_number . '|' . $regexp_percent . ')',
  437. ),
  438. ),
  439. );
  440. // 'background' property.
  441. $regexp = '(?:' .
  442. $groups['color']['properties']['background-color'] . '|' .
  443. $groups['color']['properties']['background-image'] . '|' .
  444. $groups['color']['properties']['background-repeat'] . '|' .
  445. $groups['color']['properties']['background-attachment'] . '|' .
  446. $groups['color']['properties']['background-position'] . ')';
  447. $groups['color']['properties']['background'] = '(?:' . $regexp . ')(?:(?:\s+' . $regexp . ')+)';
  448. // 'font' property.
  449. $regexp = '(?:' .
  450. $groups['font']['properties']['font-style'] . '|' .
  451. $groups['font']['properties']['font-variant'] . '|' .
  452. $groups['font']['properties']['font-weight'] . '|' .
  453. '(?:' . $groups['font']['properties']['font-size'] . ')(?:\s*/\s*' . $groups['dimension']['properties']['line-height'] . ')?|' .
  454. $groups['font']['properties']['font-family'] . ')';
  455. $groups['font']['properties']['font'] = '(?:' . $regexp . ')(?:(?:\s+' . $regexp . ')+)';
  456. return $groups;
  457. }
  458. /**
  459. * Get filter options.
  460. *
  461. * @param int $format_name
  462. * Input format identifier.
  463. * @param int $settings
  464. * Filter settings.
  465. * @return array
  466. */
  467. function wysiwyg_filter_get_filter_options($format_name, $settings) {
  468. $filter_options = array(
  469. 'valid_elements' => wysiwyg_filter_parse_valid_elements($settings['valid_elements']),
  470. 'allow_comments' => $settings['allow_comments'],
  471. 'style_properties' => wysiwyg_filter_get_style_properties($format_name, $settings),
  472. 'nofollow_policy' => $settings['nofollow_policy'],
  473. 'nofollow_domains' => $settings['nofollow_domains'],
  474. );
  475. foreach (wysiwyg_filter_get_advanced_rules() as $rule_key => $rule_info) {
  476. $filter_options[$rule_key] = array();
  477. foreach ($settings["rule_$rule_key"] as $rule) {
  478. $filter_options[$rule_key][] = '`^' . str_replace("\xFF", $rule_info['asterisk_expansion'], preg_quote(str_replace('*', "\xFF", $rule), '`')) . '$`';
  479. }
  480. }
  481. return $filter_options;
  482. }
  483. /**
  484. * Get allowed style properties.
  485. *
  486. * @param int $format_name
  487. * Input format identifier.
  488. * @param int $settings
  489. * Filter settings.
  490. * @return array
  491. */
  492. function wysiwyg_filter_get_style_properties($format_name, $settings) {
  493. static $style_properties = array();
  494. if (!isset($style_properties[$format_name])) {
  495. $style_properties[$format_name] = array();
  496. foreach (wysiwyg_filter_get_style_property_groups() as $group => $group_info) {
  497. $allowed_styles = array_filter($settings["style_$group"]);
  498. foreach ($group_info['properties'] as $property => $regexp) {
  499. if (isset($allowed_styles[$property])) {
  500. $style_properties[$format_name][$property] = '`^' . $regexp . '$`';
  501. }
  502. }
  503. }
  504. // Sort style properties alphabetically (for filter tips).
  505. ksort($style_properties[$format_name]);
  506. }
  507. return $style_properties[$format_name];
  508. }