FALSE)); // Load common functions. form_load_include($form_state, 'inc', 'wysiwyg_filter'); $settings = $filter->settings; $settings += $defaults; // carry over settings for other formats $filterform = array(); // *** valid elements *** $valid_elements = $settings['valid_elements']; $valid_elements_rows = min(20, max(5, substr_count($valid_elements, "\n") + 2)); // show blacklisted elements in description $elements_blacklist = wysiwyg_filter_get_elements_blacklist(); foreach ($elements_blacklist as $i => $element) { $elements_blacklist[$i] = '<' . $element . '>'; } $filterform['valid_elements'] = array( '#type' => 'textarea', '#title' => t('HTML elements and attributes'), '#default_value' => $valid_elements, '#cols' => 60, '#rows' => $valid_elements_rows, '#description' => t('

This option allows you to specify which HTML elements and attributes are allowed in TinyMCE valid_elements format.

Syntax tips: Additional notes:', array( '@valid-elements' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Configuration/valid_elements', '%elements-blacklist' => implode(' ', $elements_blacklist), )), ); // *** allow comments *** $filterform['allow_comments'] = array( '#type' => 'radios', '#title' => t('HTML comments'), '#options' => array( 0 => t('Disabled'), 1 => t('Enabled'), ), '#default_value' => $settings['allow_comments'], '#description' => t('Use this option to allow HTML comments.'), ); // *** Style properties *** $filterform['styles'] = array( '#title' => t('Style properties'), '#description' => '

' . t('This section allows you to select which style properties can be used for HTML elements where the "style" attribute has been allowed. The WYSIWYG Filter will strip out style properties (and their values) not explicitly enabled here. On the other hand, for allowed style properties the WYSIWYG Filter will check their values for strict CSS syntax and strip out those that do not match.') . '

' . '

' . t('Additional matching rules should be specified from the "Advanced rules" section below for a few of these properties that may contain URLs in their values ("background", "background-image", "list-style" and "list-style-image"). Otherwise, these style properties will be ignored from user input.') . '

', ); $style_property_groups = wysiwyg_filter_get_style_property_groups(); $enabled_style_properties = array(); $i = 0; foreach ($style_property_groups as $group => $group_info) { $style_properties = $settings["style_$group"]; $enabled_style_properties += array_filter($style_properties); $filterform["style_$group"] = array( '#type' => 'checkboxes', '#title' => $group_info['title'], '#default_value' => $style_properties, '#options' => drupal_map_assoc(array_keys($group_info['properties'])), '#checkall' => TRUE, '#prefix' => '
', '#suffix' => '
'. (($i % 3) == 2?'
':''), ); $i++; } // *** Advanced rules *** $filterform['rules'] = array( '#title' => t('Advanced rules'), '#prefix' => '
', '#description' => '

' . t('Use the following options to configure additional rules for certain HTML element attributes. As a safety measure, these rules should be defined explicitly. Otherwise, the corresponding HTML element attributes will be ignored from user input.') . '

', ); $valid_elements_parsed = wysiwyg_filter_parse_valid_elements($settings['valid_elements']); foreach (wysiwyg_filter_get_advanced_rules() as $rule_key => $rule_info) { $field_name = "rule_$rule_key"; $field_bypass_name = "rule_bypass_$rule_key"; $default_value = wysiwyg_filter_array2csv($settings[$field_name]); $filterform[$field_name] = array( '#type' => 'textarea', '#title' => $rule_info['title'], '#default_value' => $default_value, '#cols' => 60, '#rows' => min(10, max(2, substr_count($default_value, "\n") + 2)), '#description' => $rule_info['description'], ); $filterform[$field_bypass_name] = array( '#type' => 'checkbox', '#title' => t('Bypass %rule', array('%rule' => $rule_info['title'])), '#default_value' => !empty($settings[$field_bypass_name]), '#description' => t('Bypassing this rule may lead to security vulnerabilities. Only grant this filter to trusted roles.'), ); // Display warning if the field is empty but the rule definition is not // complete. if (empty($settings[$field_bypass_name]) && empty($default_value) && !_wysiwyg_filter_is_rule_definition_complete($rule_info, $valid_elements_parsed, $enabled_style_properties)) { drupal_set_message($rule_info['required_by_message'], 'warning'); } } // *** Nofollow properties *** $filterform['nofollow'] = array( '#title' => t('Spam link deterrent settings'), '#description' => t('As a measure to reduce the effectiveness of spam links, it is often recommended to add rel="nofollow" to posted links leading to external sites. The WYSIWYG Filter can easily do this for you while HTML is being processed with almost no additional performance impact.'), ); $filterform['nofollow_policy'] = array( '#type' => 'radios', '#title' => t('Policy'), '#options' => array( 'disabled' => t('Disabled - Do not add rel="nofollow" to any link.'), 'whitelist' => t('Whitelist - Add rel="nofollow" to all links except those leading to domain names specified in the list below.'), 'whitelist_current' => t('Whitelist - Add rel="nofollow" to all links except those leading to current domain.'), 'blacklist' => t('Blacklist - Add rel="nofollow" to all links leading to domain names specified in the list below.'), ), '#default_value' => $settings['nofollow_policy'], '#description' => t('If you choose the whitelist option, be sure to add your own domain names to the list!'), ); $parts = parse_url($base_url); // Note that domains list is stored by our submit handler in array form where // dots have been escaped, so we need here to revert the process to get a clean // string for user input where dots are unescaped. $nofollow_domains = wysiwyg_filter_array2csv($settings['nofollow_domains']); $filterform['nofollow_domains'] = array( '#type' => 'textarea', '#title' => t('Domains list'), '#default_value' => $nofollow_domains, '#cols' => 60, '#rows' => min(10, max(5, substr_count($nofollow_domains, "\n") + 2)), '#description' => t('Enter a comma separated list of top level domain names. Note that all subdomains will also be included. Example: example.com will match example.com, www.example.com, etc.'), ); return $filterform; } /* * Implements hook_form_FORM_ID_alter * * add validate and submit handlers */ function wysiwyg_filter_form_filter_admin_format_form_alter(&$form, &$form_state, $form_id) { $form['#validate'][] = 'wysiwyg_filter_filter_wysiwyg_settings_validate'; // Add the submit callback to the beginning of the array because we need // to prepare data for system_settings_form_submit(). array_unshift($form['#submit'], 'wysiwyg_filter_filter_wysiwyg_settings_submit'); } /** * Validate if the given rule definition is complete. * * @param $rule_info * An array of information about the rule we are about to check. * @param $elements * The array of all valid elements enabled for the current filter. * @param $style_properties * The array of all style properties enabled for the current filter. * @return * TRUE if the rule definiton is complete, FALSE otherwise. * * @see wysiwyg_filter_parse_valid_elements() * @see wysiwyg_filter_get_advanced_rules() */ function _wysiwyg_filter_is_rule_definition_complete($rule_info, $elements, $style_properties) { foreach ($elements as $tag => $attributes) { if (isset($attributes[$rule_info['required_by']])) { // If this rule is not dependent on style properties, then we found it, // while we should not, so the rule is not complete. if (empty($rule_info['required_by_styles'])) { return FALSE; } // If this rule is dependent on style properties, then we need to check // if the related style properties exist. foreach ($rule_info['required_by_styles'] as $style_property) { if (isset($style_properties[$style_property])) { return FALSE; } } } } return TRUE; } /** * Validate filter settings form. * * @ingroup forms */ function wysiwyg_filter_filter_wysiwyg_settings_validate($form, &$form_state) { $values =& $form_state['values']['filters']['wysiwyg']['settings']; // Don't validate disabled filters. if (empty($form_state['values']['filters']['wysiwyg']['status'])) { return; } // *** validate valid_elements *** // Check elements against hardcoded backlist. $elements_blacklist = wysiwyg_filter_get_elements_blacklist(); $valid_elements = trim($values['valid_elements']); $valid_elements = wysiwyg_filter_parse_valid_elements($valid_elements); $forbidden_elements = array(); foreach (array_keys($valid_elements) as $element) { if (in_array($element, $elements_blacklist)) { $forbidden_elements[] = $element; } } if (!empty($forbidden_elements)) { form_set_error('valid_elements', t('The following elements cannot be allowed: %elements.', array('%elements' => implode(', ', $forbidden_elements)))); } // *** validate advanced rules *** foreach (wysiwyg_filter_get_advanced_rules() as $rule_key => $rule_info) { if (empty($settings["rule_bypass_$rule_key"])) { $field_name = "rule_$rule_key"; $expressions = array_filter(explode(',', preg_replace('#\s+#', ',', trim($values[$field_name])))); // form2db $errors = array(); foreach ($expressions as $expression) { if (preg_match('`[*?]\*|\*\?`', $expression)) { $errors[] = t('Invalid expression %expression. Please, do not use more than one consecutive asterisk (**) or one that is next to a question mark wildcard (?* or *?).', array('%expression' => $expression)); } if (!preg_match($rule_info['validate_regexp'], $expression)) { $errors[] = t('Invalid expression %expression. Please, check the syntax of the %field field.', array('%expression' => $expression, '%field' => $rule_info['title'])); } } if (!empty($errors)) { form_set_error($field_name, implode('
', $errors)); } } } // *** validate nofollow_domains *** $nofollow_domains = array_filter(explode(',', preg_replace('#\s+#', ',', $values['nofollow_domains']))); // form2db foreach ($nofollow_domains as $nofollow_domain) { if (!preg_match('#^([a-z0-9]([-a-z0-9]*)?\.)+([a-z]+)$#i', $nofollow_domain)) { form_set_error('nofollow_domains', t('Invalid domain %domain. Please, enter a comma separated list of valid domain names.', array('%domain' => $nofollow_domain))); } } } /** * Submit processing for the filter settings form. * * Parse filter options to help us save resources that would otherwiese * require time and precious cpu cycles at filter processing time. * * @ingroup forms */ function wysiwyg_filter_filter_wysiwyg_settings_submit($form, &$form_state) { $values =& $form_state['values']['filters']['wysiwyg']['settings']; // *** prepare valid_elements - just trim *** $values['valid_elements'] = trim($values['valid_elements']); // *** prepare rules - csv2array *** foreach (array_keys(wysiwyg_filter_get_advanced_rules()) as $rule_key) { $field_name = "rule_$rule_key"; $values[$field_name] = wysiwyg_filter_csv2array($values[$field_name]); } // *** prepare nofollow_domains - csv2array *** $values['nofollow_domains'] = wysiwyg_filter_csv2array($values['nofollow_domains']); } /* * CSV to Array * * @param atring $v * @param bool $space2comma - shall we convet whitespace to commas before processing? * @return array */ function wysiwyg_filter_csv2array($v, $space2comma = TRUE) { IF($space2comma) $v = preg_replace('#\s+#', ',', $v); return array_filter(explode(',', $v)); } /* * Array to CSV * * @param array $v * @return string */ function wysiwyg_filter_array2csv($v, $separator = ",\n") { return implode($separator, $v); }