wysiwyg_filter.inc 25 KB

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