summaryrefslogtreecommitdiff
path: root/kolab.org/www/drupal-7.26/modules/filter
diff options
context:
space:
mode:
Diffstat (limited to 'kolab.org/www/drupal-7.26/modules/filter')
-rw-r--r--kolab.org/www/drupal-7.26/modules/filter/filter.admin.inc408
-rw-r--r--kolab.org/www/drupal-7.26/modules/filter/filter.admin.js44
-rw-r--r--kolab.org/www/drupal-7.26/modules/filter/filter.api.php323
-rw-r--r--kolab.org/www/drupal-7.26/modules/filter/filter.css53
-rw-r--r--kolab.org/www/drupal-7.26/modules/filter/filter.info14
-rw-r--r--kolab.org/www/drupal-7.26/modules/filter/filter.install494
-rw-r--r--kolab.org/www/drupal-7.26/modules/filter/filter.js20
-rw-r--r--kolab.org/www/drupal-7.26/modules/filter/filter.module1786
-rw-r--r--kolab.org/www/drupal-7.26/modules/filter/filter.pages.inc92
-rw-r--r--kolab.org/www/drupal-7.26/modules/filter/filter.test1972
-rw-r--r--kolab.org/www/drupal-7.26/modules/filter/tests/filter.url-input.txt36
-rw-r--r--kolab.org/www/drupal-7.26/modules/filter/tests/filter.url-output.txt36
12 files changed, 5278 insertions, 0 deletions
diff --git a/kolab.org/www/drupal-7.26/modules/filter/filter.admin.inc b/kolab.org/www/drupal-7.26/modules/filter/filter.admin.inc
new file mode 100644
index 0000000..60284d9
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/filter/filter.admin.inc
@@ -0,0 +1,408 @@
+<?php
+
+/**
+ * @file
+ * Administrative page callbacks for the Filter module.
+ */
+
+/**
+ * Page callback: Form constructor for a form to list and reorder text formats.
+ *
+ * @ingroup forms
+ * @see filter_menu()
+ * @see filter_admin_overview_submit()
+ */
+function filter_admin_overview($form) {
+ // Overview of all formats.
+ $formats = filter_formats();
+ $fallback_format = filter_fallback_format();
+
+ $form['#tree'] = TRUE;
+ foreach ($formats as $id => $format) {
+ // Check whether this is the fallback text format. This format is available
+ // to all roles and cannot be disabled via the admin interface.
+ $form['formats'][$id]['#is_fallback'] = ($id == $fallback_format);
+ if ($form['formats'][$id]['#is_fallback']) {
+ $form['formats'][$id]['name'] = array('#markup' => drupal_placeholder($format->name));
+ $roles_markup = drupal_placeholder(t('All roles may use this format'));
+ }
+ else {
+ $form['formats'][$id]['name'] = array('#markup' => check_plain($format->name));
+ $roles = array_map('check_plain', filter_get_roles_by_format($format));
+ $roles_markup = $roles ? implode(', ', $roles) : t('No roles may use this format');
+ }
+ $form['formats'][$id]['roles'] = array('#markup' => $roles_markup);
+ $form['formats'][$id]['configure'] = array('#type' => 'link', '#title' => t('configure'), '#href' => 'admin/config/content/formats/' . $id);
+ $form['formats'][$id]['disable'] = array('#type' => 'link', '#title' => t('disable'), '#href' => 'admin/config/content/formats/' . $id . '/disable', '#access' => !$form['formats'][$id]['#is_fallback']);
+ $form['formats'][$id]['weight'] = array(
+ '#type' => 'weight',
+ '#title' => t('Weight for @title', array('@title' => $format->name)),
+ '#title_display' => 'invisible',
+ '#default_value' => $format->weight,
+ );
+ }
+ $form['actions'] = array('#type' => 'actions');
+ $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Save changes'));
+ return $form;
+}
+
+/**
+ * Form submission handler for filter_admin_overview().
+ */
+function filter_admin_overview_submit($form, &$form_state) {
+ foreach ($form_state['values']['formats'] as $id => $data) {
+ if (is_array($data) && isset($data['weight'])) {
+ // Only update if this is a form element with weight.
+ db_update('filter_format')
+ ->fields(array('weight' => $data['weight']))
+ ->condition('format', $id)
+ ->execute();
+ }
+ }
+ filter_formats_reset();
+ drupal_set_message(t('The text format ordering has been saved.'));
+}
+
+/**
+ * Returns HTML for the text format administration overview form.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - form: A render element representing the form.
+ *
+ * @ingroup themeable
+ */
+function theme_filter_admin_overview($variables) {
+ $form = $variables['form'];
+
+ $rows = array();
+ foreach (element_children($form['formats']) as $id) {
+ $form['formats'][$id]['weight']['#attributes']['class'] = array('text-format-order-weight');
+ $rows[] = array(
+ 'data' => array(
+ drupal_render($form['formats'][$id]['name']),
+ drupal_render($form['formats'][$id]['roles']),
+ drupal_render($form['formats'][$id]['weight']),
+ drupal_render($form['formats'][$id]['configure']),
+ drupal_render($form['formats'][$id]['disable']),
+ ),
+ 'class' => array('draggable'),
+ );
+ }
+ $header = array(t('Name'), t('Roles'), t('Weight'), array('data' => t('Operations'), 'colspan' => 2));
+ $output = theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('id' => 'text-format-order')));
+ $output .= drupal_render_children($form);
+
+ drupal_add_tabledrag('text-format-order', 'order', 'sibling', 'text-format-order-weight');
+
+ return $output;
+}
+
+/**
+ * Page callback: Displays the text format add/edit form.
+ *
+ * @param object|null $format
+ * (optional) An object representing a format, with the following properties:
+ * - format: A machine-readable name representing the ID of the text format
+ * to save. If this corresponds to an existing text format, that format
+ * will be updated; otherwise, a new format will be created.
+ * - name: The title of the text format.
+ * - cache: (optional) An integer indicating whether the text format is
+ * cacheable (1) or not (0). Defaults to 1.
+ * - status: (optional) An integer indicating whether the text format is
+ * enabled (1) or not (0). Defaults to 1.
+ * - weight: (optional) The weight of the text format, which controls its
+ * placement in text format lists. If omitted, the weight is set to 0.
+ * Defaults to NULL.
+ *
+ * @return
+ * A form array.
+ *
+ * @see filter_menu()
+ */
+function filter_admin_format_page($format = NULL) {
+ if (!isset($format->name)) {
+ drupal_set_title(t('Add text format'));
+ $format = (object) array(
+ 'format' => NULL,
+ 'name' => '',
+ );
+ }
+ return drupal_get_form('filter_admin_format_form', $format);
+}
+
+/**
+ * Form constructor for the text format add/edit form.
+ *
+ * @param $format
+ * A format object having the properties:
+ * - format: A machine-readable name representing the ID of the text format to
+ * save. If this corresponds to an existing text format, that format will be
+ * updated; otherwise, a new format will be created.
+ * - name: The title of the text format.
+ * - cache: An integer indicating whether the text format is cacheable (1) or
+ * not (0). Defaults to 1.
+ * - status: (optional) An integer indicating whether the text format is
+ * enabled (1) or not (0). Defaults to 1.
+ * - weight: (optional) The weight of the text format, which controls its
+ * placement in text format lists. If omitted, the weight is set to 0.
+ *
+ * @see filter_admin_format_form_validate()
+ * @see filter_admin_format_form_submit()
+ * @ingroup forms
+ */
+function filter_admin_format_form($form, &$form_state, $format) {
+ $is_fallback = ($format->format == filter_fallback_format());
+
+ $form['#format'] = $format;
+ $form['#tree'] = TRUE;
+ $form['#attached']['js'][] = drupal_get_path('module', 'filter') . '/filter.admin.js';
+ $form['#attached']['css'][] = drupal_get_path('module', 'filter') . '/filter.css';
+
+ $form['name'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Name'),
+ '#default_value' => $format->name,
+ '#required' => TRUE,
+ );
+ $form['format'] = array(
+ '#type' => 'machine_name',
+ '#required' => TRUE,
+ '#default_value' => $format->format,
+ '#maxlength' => 255,
+ '#machine_name' => array(
+ 'exists' => 'filter_format_exists',
+ ),
+ '#disabled' => !empty($format->format),
+ );
+
+ // Add user role access selection.
+ $form['roles'] = array(
+ '#type' => 'checkboxes',
+ '#title' => t('Roles'),
+ '#options' => array_map('check_plain', user_roles()),
+ '#disabled' => $is_fallback,
+ );
+ if ($is_fallback) {
+ $form['roles']['#description'] = t('All roles for this text format must be enabled and cannot be changed.');
+ }
+ if (!empty($format->format)) {
+ // If editing an existing text format, pre-select its current permissions.
+ $form['roles']['#default_value'] = array_keys(filter_get_roles_by_format($format));
+ }
+ elseif ($admin_role = variable_get('user_admin_role', 0)) {
+ // If adding a new text format and the site has an administrative role,
+ // pre-select that role so as to grant administrators access to the new
+ // text format permission by default.
+ $form['roles']['#default_value'] = array($admin_role);
+ }
+
+ // Retrieve available filters and load all configured filters for existing
+ // text formats.
+ $filter_info = filter_get_filters();
+ $filters = !empty($format->format) ? filter_list_format($format->format) : array();
+
+ // Prepare filters for form sections.
+ foreach ($filter_info as $name => $filter) {
+ // Create an empty filter object for new/unconfigured filters.
+ if (!isset($filters[$name])) {
+ $filters[$name] = new stdClass();
+ $filters[$name]->format = $format->format;
+ $filters[$name]->module = $filter['module'];
+ $filters[$name]->name = $name;
+ $filters[$name]->status = 0;
+ $filters[$name]->weight = $filter['weight'];
+ $filters[$name]->settings = array();
+ }
+ }
+ $form['#filters'] = $filters;
+
+ // Filter status.
+ $form['filters']['status'] = array(
+ '#type' => 'item',
+ '#title' => t('Enabled filters'),
+ '#prefix' => '<div id="filters-status-wrapper">',
+ '#suffix' => '</div>',
+ );
+ foreach ($filter_info as $name => $filter) {
+ $form['filters']['status'][$name] = array(
+ '#type' => 'checkbox',
+ '#title' => $filter['title'],
+ '#default_value' => $filters[$name]->status,
+ '#parents' => array('filters', $name, 'status'),
+ '#description' => $filter['description'],
+ '#weight' => $filter['weight'],
+ );
+ }
+
+ // Filter order (tabledrag).
+ $form['filters']['order'] = array(
+ '#type' => 'item',
+ '#title' => t('Filter processing order'),
+ '#theme' => 'filter_admin_format_filter_order',
+ );
+ foreach ($filter_info as $name => $filter) {
+ $form['filters']['order'][$name]['filter'] = array(
+ '#markup' => $filter['title'],
+ );
+ $form['filters']['order'][$name]['weight'] = array(
+ '#type' => 'weight',
+ '#title' => t('Weight for @title', array('@title' => $filter['title'])),
+ '#title_display' => 'invisible',
+ '#delta' => 50,
+ '#default_value' => $filters[$name]->weight,
+ '#parents' => array('filters', $name, 'weight'),
+ );
+ $form['filters']['order'][$name]['#weight'] = $filters[$name]->weight;
+ }
+
+ // Filter settings.
+ $form['filter_settings_title'] = array(
+ '#type' => 'item',
+ '#title' => t('Filter settings'),
+ );
+ $form['filter_settings'] = array(
+ '#type' => 'vertical_tabs',
+ );
+
+ foreach ($filter_info as $name => $filter) {
+ if (isset($filter['settings callback']) && function_exists($filter['settings callback'])) {
+ $function = $filter['settings callback'];
+ // Pass along stored filter settings and default settings, but also the
+ // format object and all filters to allow for complex implementations.
+ $defaults = (isset($filter['default settings']) ? $filter['default settings'] : array());
+ $settings_form = $function($form, $form_state, $filters[$name], $format, $defaults, $filters);
+ if (!empty($settings_form)) {
+ $form['filters']['settings'][$name] = array(
+ '#type' => 'fieldset',
+ '#title' => $filter['title'],
+ '#parents' => array('filters', $name, 'settings'),
+ '#weight' => $filter['weight'],
+ '#group' => 'filter_settings',
+ );
+ $form['filters']['settings'][$name] += $settings_form;
+ }
+ }
+ }
+
+ $form['actions'] = array('#type' => 'actions');
+ $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Save configuration'));
+
+ return $form;
+}
+
+/**
+ * Returns HTML for a text format's filter order form.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - element: A render element representing the form.
+ *
+ * @ingroup themeable
+ */
+function theme_filter_admin_format_filter_order($variables) {
+ $element = $variables['element'];
+
+ // Filter order (tabledrag).
+ $rows = array();
+ foreach (element_children($element, TRUE) as $name) {
+ $element[$name]['weight']['#attributes']['class'][] = 'filter-order-weight';
+ $rows[] = array(
+ 'data' => array(
+ drupal_render($element[$name]['filter']),
+ drupal_render($element[$name]['weight']),
+ ),
+ 'class' => array('draggable'),
+ );
+ }
+ $output = drupal_render_children($element);
+ $output .= theme('table', array('rows' => $rows, 'attributes' => array('id' => 'filter-order')));
+ drupal_add_tabledrag('filter-order', 'order', 'sibling', 'filter-order-weight', NULL, NULL, TRUE);
+
+ return $output;
+}
+
+/**
+ * Form validation handler for filter_admin_format_form().
+ *
+ * @see filter_admin_format_form_submit()
+ */
+function filter_admin_format_form_validate($form, &$form_state) {
+ $format_format = trim($form_state['values']['format']);
+ $format_name = trim($form_state['values']['name']);
+
+ // Ensure that the values to be saved later are exactly the ones validated.
+ form_set_value($form['format'], $format_format, $form_state);
+ form_set_value($form['name'], $format_name, $form_state);
+
+ $result = db_query("SELECT format FROM {filter_format} WHERE name = :name AND format <> :format", array(':name' => $format_name, ':format' => $format_format))->fetchField();
+ if ($result) {
+ form_set_error('name', t('Text format names must be unique. A format named %name already exists.', array('%name' => $format_name)));
+ }
+}
+
+/**
+ * Form submission handler for filter_admin_format_form().
+ *
+ * @see filter_admin_format_form_validate()
+ */
+function filter_admin_format_form_submit($form, &$form_state) {
+ // Remove unnecessary values.
+ form_state_values_clean($form_state);
+
+ // Add the submitted form values to the text format, and save it.
+ $format = $form['#format'];
+ foreach ($form_state['values'] as $key => $value) {
+ $format->$key = $value;
+ }
+ $status = filter_format_save($format);
+
+ // Save user permissions.
+ if ($permission = filter_permission_name($format)) {
+ foreach ($format->roles as $rid => $enabled) {
+ user_role_change_permissions($rid, array($permission => $enabled));
+ }
+ }
+
+ switch ($status) {
+ case SAVED_NEW:
+ drupal_set_message(t('Added text format %format.', array('%format' => $format->name)));
+ break;
+
+ case SAVED_UPDATED:
+ drupal_set_message(t('The text format %format has been updated.', array('%format' => $format->name)));
+ break;
+ }
+}
+
+/**
+ * Form constructor for the text format deletion confirmation form.
+ *
+ * @param $format
+ * An object representing a text format.
+ *
+ * @see filter_menu()
+ * @see filter_admin_disable_submit()
+ * @ingroup forms
+ */
+function filter_admin_disable($form, &$form_state, $format) {
+ $form['#format'] = $format;
+
+ return confirm_form($form,
+ t('Are you sure you want to disable the text format %format?', array('%format' => $format->name)),
+ 'admin/config/content/formats',
+ t('Disabled text formats are completely removed from the administrative interface, and any content stored with that format will not be displayed. This action cannot be undone.'),
+ t('Disable')
+ );
+}
+
+/**
+ * Form submission handler for filter_admin_disable().
+ */
+function filter_admin_disable_submit($form, &$form_state) {
+ $format = $form['#format'];
+ filter_format_disable($format);
+ drupal_set_message(t('Disabled text format %format.', array('%format' => $format->name)));
+
+ $form_state['redirect'] = 'admin/config/content/formats';
+}
diff --git a/kolab.org/www/drupal-7.26/modules/filter/filter.admin.js b/kolab.org/www/drupal-7.26/modules/filter/filter.admin.js
new file mode 100644
index 0000000..3bc6233
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/filter/filter.admin.js
@@ -0,0 +1,44 @@
+(function ($) {
+
+Drupal.behaviors.filterStatus = {
+ attach: function (context, settings) {
+ $('#filters-status-wrapper input.form-checkbox', context).once('filter-status', function () {
+ var $checkbox = $(this);
+ // Retrieve the tabledrag row belonging to this filter.
+ var $row = $('#' + $checkbox.attr('id').replace(/-status$/, '-weight'), context).closest('tr');
+ // Retrieve the vertical tab belonging to this filter.
+ var tab = $('#' + $checkbox.attr('id').replace(/-status$/, '-settings'), context).data('verticalTab');
+
+ // Bind click handler to this checkbox to conditionally show and hide the
+ // filter's tableDrag row and vertical tab pane.
+ $checkbox.bind('click.filterUpdate', function () {
+ if ($checkbox.is(':checked')) {
+ $row.show();
+ if (tab) {
+ tab.tabShow().updateSummary();
+ }
+ }
+ else {
+ $row.hide();
+ if (tab) {
+ tab.tabHide().updateSummary();
+ }
+ }
+ // Restripe table after toggling visibility of table row.
+ Drupal.tableDrag['filter-order'].restripeTable();
+ });
+
+ // Attach summary for configurable filters (only for screen-readers).
+ if (tab) {
+ tab.fieldset.drupalSetSummary(function (tabContext) {
+ return $checkbox.is(':checked') ? Drupal.t('Enabled') : Drupal.t('Disabled');
+ });
+ }
+
+ // Trigger our bound click handler to update elements to initial state.
+ $checkbox.triggerHandler('click.filterUpdate');
+ });
+ }
+};
+
+})(jQuery);
diff --git a/kolab.org/www/drupal-7.26/modules/filter/filter.api.php b/kolab.org/www/drupal-7.26/modules/filter/filter.api.php
new file mode 100644
index 0000000..2901eb9
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/filter/filter.api.php
@@ -0,0 +1,323 @@
+<?php
+
+/**
+ * @file
+ * Hooks provided by the Filter module.
+ */
+
+/**
+ * @addtogroup hooks
+ * @{
+ */
+
+/**
+ * Define content filters.
+ *
+ * User submitted content is passed through a group of filters before it is
+ * output in HTML, in order to remove insecure or unwanted parts, correct or
+ * enhance the formatting, transform special keywords, etc. A group of filters
+ * is referred to as a "text format". Administrators can create as many text
+ * formats as needed. Individual filters can be enabled and configured
+ * differently for each text format.
+ *
+ * This hook is invoked by filter_get_filters() and allows modules to register
+ * input filters they provide.
+ *
+ * Filtering is a two-step process. First, the content is 'prepared' by calling
+ * the 'prepare callback' function for every filter. The purpose of the 'prepare
+ * callback' is to escape HTML-like structures. For example, imagine a filter
+ * which allows the user to paste entire chunks of programming code without
+ * requiring manual escaping of special HTML characters like < or &. If the
+ * programming code were left untouched, then other filters could think it was
+ * HTML and change it. For many filters, the prepare step is not necessary.
+ *
+ * The second step is the actual processing step. The result from passing the
+ * text through all the filters' prepare steps gets passed to all the filters
+ * again, this time with the 'process callback' function. The process callbacks
+ * should then actually change the content: transform URLs into hyperlinks,
+ * convert smileys into images, etc.
+ *
+ * For performance reasons content is only filtered once; the result is stored
+ * in the cache table and retrieved from the cache the next time the same piece
+ * of content is displayed. If a filter's output is dynamic, it can override the
+ * cache mechanism, but obviously this should be used with caution: having one
+ * filter that does not support caching in a particular text format disables
+ * caching for the entire format, not just for one filter.
+ *
+ * Beware of the filter cache when developing your module: it is advised to set
+ * your filter to 'cache' => FALSE while developing, but be sure to remove that
+ * setting if it's not needed, when you are no longer in development mode.
+ *
+ * @return
+ * An associative array of filters, whose keys are internal filter names,
+ * which should be unique and therefore prefixed with the name of the module.
+ * Each value is an associative array describing the filter, with the
+ * following elements (all are optional except as noted):
+ * - title: (required) An administrative summary of what the filter does.
+ * - description: Additional administrative information about the filter's
+ * behavior, if needed for clarification.
+ * - settings callback: The name of a function that returns configuration form
+ * elements for the filter. See callback_filter_settings() for details.
+ * - default settings: An associative array containing default settings for
+ * the filter, to be applied when the filter has not been configured yet.
+ * - prepare callback: The name of a function that escapes the content before
+ * the actual filtering happens. See callback_filter_prepare() for
+ * details.
+ * - process callback: (required) The name the function that performs the
+ * actual filtering. See callback_filter_process() for details.
+ * - cache (default TRUE): Specifies whether the filtered text can be cached.
+ * Note that setting this to FALSE makes the entire text format not
+ * cacheable, which may have an impact on the site's overall performance.
+ * See filter_format_allowcache() for details.
+ * - tips callback: The name of a function that returns end-user-facing filter
+ * usage guidelines for the filter. See callback_filter_tips() for
+ * details.
+ * - weight: A default weight for the filter in new text formats.
+ *
+ * @see filter_example.module
+ * @see hook_filter_info_alter()
+ */
+function hook_filter_info() {
+ $filters['filter_html'] = array(
+ 'title' => t('Limit allowed HTML tags'),
+ 'description' => t('Allows you to restrict the HTML tags the user can use. It will also remove harmful content such as JavaScript events, JavaScript URLs and CSS styles from those tags that are not removed.'),
+ 'process callback' => '_filter_html',
+ 'settings callback' => '_filter_html_settings',
+ 'default settings' => array(
+ 'allowed_html' => '<a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>',
+ 'filter_html_help' => 1,
+ 'filter_html_nofollow' => 0,
+ ),
+ 'tips callback' => '_filter_html_tips',
+ );
+ $filters['filter_autop'] = array(
+ 'title' => t('Convert line breaks'),
+ 'description' => t('Converts line breaks into HTML (i.e. &lt;br&gt; and &lt;p&gt;) tags.'),
+ 'process callback' => '_filter_autop',
+ 'tips callback' => '_filter_autop_tips',
+ );
+ return $filters;
+}
+
+/**
+ * Perform alterations on filter definitions.
+ *
+ * @param $info
+ * Array of information on filters exposed by hook_filter_info()
+ * implementations.
+ */
+function hook_filter_info_alter(&$info) {
+ // Replace the PHP evaluator process callback with an improved
+ // PHP evaluator provided by a module.
+ $info['php_code']['process callback'] = 'my_module_php_evaluator';
+
+ // Alter the default settings of the URL filter provided by core.
+ $info['filter_url']['default settings'] = array(
+ 'filter_url_length' => 100,
+ );
+}
+
+/**
+ * @} End of "addtogroup hooks".
+ */
+
+/**
+ * Provide a settings form for filter settings.
+ *
+ * Callback for hook_filter_info().
+ *
+ * This callback function is used to provide a settings form for filter
+ * settings, for filters that need settings on a per-text-format basis. This
+ * function should return the form elements for the settings; the filter
+ * module will take care of saving the settings in the database.
+ *
+ * If the filter's behavior depends on an extensive list and/or external data
+ * (e.g. a list of smileys, a list of glossary terms), then the filter module
+ * can choose to provide a separate, global configuration page rather than
+ * per-text-format settings. In that case, the settings callback function
+ * should provide a link to the separate settings page.
+ *
+ * @param $form
+ * The prepopulated form array of the filter administration form.
+ * @param $form_state
+ * The state of the (entire) configuration form.
+ * @param $filter
+ * The filter object containing the current settings for the given format,
+ * in $filter->settings.
+ * @param $format
+ * The format object being configured.
+ * @param $defaults
+ * The default settings for the filter, as defined in 'default settings' in
+ * hook_filter_info(). These should be combined with $filter->settings to
+ * define the form element defaults.
+ * @param $filters
+ * The complete list of filter objects that are enabled for the given format.
+ *
+ * @return
+ * An array of form elements defining settings for the filter. Array keys
+ * should match the array keys in $filter->settings and $defaults.
+ *
+ * @ingroup callbacks
+ */
+function callback_filter_settings($form, &$form_state, $filter, $format, $defaults, $filters) {
+ $filter->settings += $defaults;
+
+ $elements = array();
+ $elements['nofollow'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Add rel="nofollow" to all links'),
+ '#default_value' => $filter->settings['nofollow'],
+ );
+ return $elements;
+}
+
+/**
+ * Provide prepared text with special characters escaped.
+ *
+ * Callback for hook_filter_info().
+ *
+ * See hook_filter_info() for a description of the filtering process. Filters
+ * should not use the 'prepare callback' step for anything other than escaping,
+ * because that would short-circuit the control the user has over the order in
+ * which filters are applied.
+ *
+ * @param $text
+ * The text string to be filtered.
+ * @param $filter
+ * The filter object containing settings for the given format.
+ * @param $format
+ * The text format object assigned to the text to be filtered.
+ * @param $langcode
+ * The language code of the text to be filtered.
+ * @param $cache
+ * A Boolean indicating whether the filtered text is going to be cached in
+ * {cache_filter}.
+ * @param $cache_id
+ * The ID of the filtered text in {cache_filter}, if $cache is TRUE.
+ *
+ * @return
+ * The prepared, escaped text.
+ *
+ * @ingroup callbacks
+ */
+function callback_filter_prepare($text, $filter, $format, $langcode, $cache, $cache_id) {
+ // Escape <code> and </code> tags.
+ $text = preg_replace('|<code>(.+?)</code>|se', "[codefilter_code]$1[/codefilter_code]", $text);
+ return $text;
+}
+
+/**
+ * Provide text filtered to conform to the supplied format.
+ *
+ * Callback for hook_filter_info().
+ *
+ * See hook_filter_info() for a description of the filtering process. This step
+ * is where the filter actually transforms the text.
+ *
+ * @param $text
+ * The text string to be filtered.
+ * @param $filter
+ * The filter object containing settings for the given format.
+ * @param $format
+ * The text format object assigned to the text to be filtered.
+ * @param $langcode
+ * The language code of the text to be filtered.
+ * @param $cache
+ * A Boolean indicating whether the filtered text is going to be cached in
+ * {cache_filter}.
+ * @param $cache_id
+ * The ID of the filtered text in {cache_filter}, if $cache is TRUE.
+ *
+ * @return
+ * The filtered text.
+ *
+ * @ingroup callbacks
+ */
+function callback_filter_process($text, $filter, $format, $langcode, $cache, $cache_id) {
+ $text = preg_replace('|\[codefilter_code\](.+?)\[/codefilter_code\]|se', "<pre>$1</pre>", $text);
+
+ return $text;
+}
+
+/**
+ * Return help text for a filter.
+ *
+ * Callback for hook_filter_info().
+ *
+ * A filter's tips should be informative and to the point. Short tips are
+ * preferably one-liners.
+ *
+ * @param $filter
+ * An object representing the filter.
+ * @param $format
+ * An object representing the text format the filter is contained in.
+ * @param $long
+ * Whether this callback should return a short tip to display in a form
+ * (FALSE), or whether a more elaborate filter tips should be returned for
+ * theme_filter_tips() (TRUE).
+ *
+ * @return
+ * Translated text to display as a tip.
+ *
+ * @ingroup callbacks
+ */
+function callback_filter_tips($filter, $format, $long) {
+ if ($long) {
+ return t('Lines and paragraphs are automatically recognized. The &lt;br /&gt; line break, &lt;p&gt; paragraph and &lt;/p&gt; close paragraph tags are inserted automatically. If paragraphs are not recognized simply add a couple blank lines.');
+ }
+ else {
+ return t('Lines and paragraphs break automatically.');
+ }
+}
+
+/**
+ * @addtogroup hooks
+ * @{
+ */
+
+/**
+ * Perform actions when a new text format has been created.
+ *
+ * @param $format
+ * The format object of the format being updated.
+ *
+ * @see hook_filter_format_update()
+ * @see hook_filter_format_disable()
+ */
+function hook_filter_format_insert($format) {
+ mymodule_cache_rebuild();
+}
+
+/**
+ * Perform actions when a text format has been updated.
+ *
+ * This hook allows modules to act when a text format has been updated in any
+ * way. For example, when filters have been reconfigured, disabled, or
+ * re-arranged in the text format.
+ *
+ * @param $format
+ * The format object of the format being updated.
+ *
+ * @see hook_filter_format_insert()
+ * @see hook_filter_format_disable()
+ */
+function hook_filter_format_update($format) {
+ mymodule_cache_rebuild();
+}
+
+/**
+ * Perform actions when a text format has been disabled.
+ *
+ * @param $format
+ * The format object of the format being disabled.
+ *
+ * @see hook_filter_format_insert()
+ * @see hook_filter_format_update()
+ */
+function hook_filter_format_disable($format) {
+ mymodule_cache_rebuild();
+}
+
+/**
+ * @} End of "addtogroup hooks".
+ */
diff --git a/kolab.org/www/drupal-7.26/modules/filter/filter.css b/kolab.org/www/drupal-7.26/modules/filter/filter.css
new file mode 100644
index 0000000..f731733
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/filter/filter.css
@@ -0,0 +1,53 @@
+
+.text-format-wrapper .form-item {
+ margin-bottom: 0;
+}
+.filter-wrapper {
+ border-top: 0;
+ margin: 0;
+ padding: 1.5em 0 1.5em;
+}
+.filter-wrapper .form-item {
+ float: left;
+ padding: 0 0 0.5em 1.5em;
+}
+.filter-wrapper .form-item label {
+ display: inline;
+}
+.filter-help {
+ float: right;
+ padding: 0 1.5em 0.5em;
+}
+.filter-help p {
+ margin: 0;
+}
+.filter-help a {
+ background: transparent url(../../misc/help.png) right center no-repeat;
+ padding: 0 20px;
+}
+.filter-guidelines {
+ clear: left;
+ padding: 0 1.5em;
+}
+.text-format-wrapper .description {
+ margin-top: 0.5em;
+}
+
+#filter-order tr .form-item {
+ padding: 0.5em 0 0 3em;
+ white-space: normal;
+}
+#filter-order tr .form-type-checkbox .description {
+ padding: 0 0 0 2.5em;
+}
+input#edit-filters-filter-html-settings-allowed-html {
+ width: 100%;
+}
+
+.tips {
+ margin-top: 0;
+ margin-bottom: 0;
+ padding-top: 0;
+ padding-bottom: 0;
+ font-size: 0.9em;
+}
diff --git a/kolab.org/www/drupal-7.26/modules/filter/filter.info b/kolab.org/www/drupal-7.26/modules/filter/filter.info
new file mode 100644
index 0000000..5c25bbf
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/filter/filter.info
@@ -0,0 +1,14 @@
+name = Filter
+description = Filters content in preparation for display.
+package = Core
+version = VERSION
+core = 7.x
+files[] = filter.test
+required = TRUE
+configure = admin/config/content/formats
+
+; Information added by Drupal.org packaging script on 2014-01-15
+version = "7.26"
+project = "drupal"
+datestamp = "1389815930"
+
diff --git a/kolab.org/www/drupal-7.26/modules/filter/filter.install b/kolab.org/www/drupal-7.26/modules/filter/filter.install
new file mode 100644
index 0000000..71ba97b
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/filter/filter.install
@@ -0,0 +1,494 @@
+<?php
+
+/**
+ * @file
+ * Install, update, and uninstall functions for the Filter module.
+ */
+
+/**
+ * Implements hook_schema().
+ */
+function filter_schema() {
+ $schema['filter'] = array(
+ 'description' => 'Table that maps filters (HTML corrector) to text formats (Filtered HTML).',
+ 'fields' => array(
+ 'format' => array(
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'description' => 'Foreign key: The {filter_format}.format to which this filter is assigned.',
+ ),
+ 'module' => array(
+ 'type' => 'varchar',
+ 'length' => 64,
+ 'not null' => TRUE,
+ 'default' => '',
+ 'description' => 'The origin module of the filter.',
+ ),
+ 'name' => array(
+ 'type' => 'varchar',
+ 'length' => 32,
+ 'not null' => TRUE,
+ 'default' => '',
+ 'description' => 'Name of the filter being referenced.',
+ ),
+ 'weight' => array(
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'description' => 'Weight of filter within format.',
+ ),
+ 'status' => array(
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'description' => 'Filter enabled status. (1 = enabled, 0 = disabled)',
+ ),
+ 'settings' => array(
+ 'type' => 'blob',
+ 'not null' => FALSE,
+ 'size' => 'big',
+ 'serialize' => TRUE,
+ 'description' => 'A serialized array of name value pairs that store the filter settings for the specific format.',
+ ),
+ ),
+ 'primary key' => array('format', 'name'),
+ 'indexes' => array(
+ 'list' => array('weight', 'module', 'name'),
+ ),
+ );
+ $schema['filter_format'] = array(
+ 'description' => 'Stores text formats: custom groupings of filters, such as Filtered HTML.',
+ 'fields' => array(
+ 'format' => array(
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'description' => 'Primary Key: Unique machine name of the format.',
+ ),
+ 'name' => array(
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ 'description' => 'Name of the text format (Filtered HTML).',
+ 'translatable' => TRUE,
+ ),
+ 'cache' => array(
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'size' => 'tiny',
+ 'description' => 'Flag to indicate whether format is cacheable. (1 = cacheable, 0 = not cacheable)',
+ ),
+ 'status' => array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 1,
+ 'size' => 'tiny',
+ 'description' => 'The status of the text format. (1 = enabled, 0 = disabled)',
+ ),
+ 'weight' => array(
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'description' => 'Weight of text format to use when listing.',
+ ),
+ ),
+ 'primary key' => array('format'),
+ 'unique keys' => array(
+ 'name' => array('name'),
+ ),
+ 'indexes' => array(
+ 'status_weight' => array('status', 'weight'),
+ ),
+ );
+
+ $schema['cache_filter'] = drupal_get_schema_unprocessed('system', 'cache');
+ $schema['cache_filter']['description'] = 'Cache table for the Filter module to store already filtered pieces of text, identified by text format and hash of the text.';
+
+ return $schema;
+}
+
+/**
+ * Implements hook_install().
+ */
+function filter_install() {
+ // All sites require at least one text format (the fallback format) that all
+ // users have access to, so add it here. We initialize it as a simple, safe
+ // plain text format with very basic formatting, but it can be modified by
+ // installation profiles to have other properties.
+ $plain_text_format = array(
+ 'format' => 'plain_text',
+ 'name' => 'Plain text',
+ 'weight' => 10,
+ 'filters' => array(
+ // Escape all HTML.
+ 'filter_html_escape' => array(
+ 'weight' => 0,
+ 'status' => 1,
+ ),
+ // URL filter.
+ 'filter_url' => array(
+ 'weight' => 1,
+ 'status' => 1,
+ ),
+ // Line break filter.
+ 'filter_autop' => array(
+ 'weight' => 2,
+ 'status' => 1,
+ ),
+ ),
+ );
+ $plain_text_format = (object) $plain_text_format;
+ filter_format_save($plain_text_format);
+
+ // Set the fallback format to plain text.
+ variable_set('filter_fallback_format', $plain_text_format->format);
+}
+
+/**
+ * Implements hook_update_dependencies().
+ */
+function filter_update_dependencies() {
+ // filter_update_7005() migrates role permissions and therefore must run
+ // after the {role} and {role_permission} tables are properly set up, which
+ // happens in user_update_7007().
+ $dependencies['filter'][7005] = array(
+ 'user' => 7007,
+ );
+
+ return $dependencies;
+}
+/**
+ * @addtogroup updates-6.x-to-7.x
+ * @{
+ */
+
+/**
+ * Upgrade the {filter_formats} table and rename it to {filter_format}.
+ */
+function filter_update_7000() {
+ db_rename_table('filter_formats', 'filter_format');
+
+ // Add the new {filter_format}.status and {filter_format}.weight column.
+ db_add_field('filter_format', 'status', array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 1,
+ 'size' => 'tiny',
+ 'description' => 'The status of the text format. (1 = enabled, 0 = disabled)',
+ ));
+ db_add_field('filter_format', 'weight', array(
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'description' => 'Weight of text format to use when listing.',
+ ), array(
+ 'indexes' => array(
+ 'status_weight' => array('status', 'weight'),
+ ),
+ ));
+}
+
+/**
+ * Break out "escape HTML filter" option to its own filter.
+ */
+function filter_update_7001() {
+ $result = db_query("SELECT format FROM {filter_format}")->fetchCol();
+ $insert = db_insert('filters')->fields(array('format', 'module', 'delta', 'weight'));
+
+ foreach ($result as $format_id) {
+ // Deprecated constants FILTER_HTML_STRIP = 1 and FILTER_HTML_ESCAPE = 2.
+ if (variable_get('filter_html_' . $format_id, 1) == 2) {
+ $insert->values(array(
+ 'format' => $format_id,
+ 'module' => 'filter',
+ 'delta' => 4,
+ 'weight' => 0,
+ ));
+ }
+ variable_del('filter_html_' . $format_id);
+ }
+
+ $insert->execute();
+}
+
+/**
+ * Upgrade the {filter} table for core filters.
+ */
+function filter_update_7003() {
+ // Duplicates the {filters} table since core cannot take care of the potential
+ // contributed module filters.
+ db_rename_table('filters', 'd6_upgrade_filter');
+ // Creates the Drupal 7 filter table.
+ $filter_table = array(
+ 'description' => 'Table that maps filters (HTML corrector) to text formats (Filtered HTML).',
+ 'fields' => array(
+ 'format' => array(
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'description' => 'Foreign key: The {filter_format}.format to which this filter is assigned.',
+ ),
+ 'module' => array(
+ 'type' => 'varchar',
+ 'length' => 64,
+ 'not null' => TRUE,
+ 'default' => '',
+ 'description' => 'The origin module of the filter.',
+ ),
+ 'name' => array(
+ 'type' => 'varchar',
+ 'length' => 32,
+ 'not null' => TRUE,
+ 'default' => '',
+ 'description' => 'Name of the filter being referenced.',
+ ),
+ 'weight' => array(
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'description' => 'Weight of filter within format.',
+ ),
+ 'status' => array(
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'description' => 'Filter enabled status. (1 = enabled, 0 = disabled)',
+ ),
+ 'settings' => array(
+ 'type' => 'blob',
+ 'not null' => FALSE,
+ 'size' => 'big',
+ 'serialize' => TRUE,
+ 'description' => 'A serialized array of name value pairs that store the filter settings for the specific format.',
+ ),
+ ),
+ 'primary key' => array('format', 'name'),
+ 'indexes' => array(
+ 'list' => array('weight', 'module', 'name'),
+ ),
+ );
+ db_create_table('filter', $filter_table);
+
+ // Get an array of the renamed filter deltas, organized by module.
+ $renamed_deltas = array(
+ 'filter' => array(
+ '0' => 'filter_html',
+ '1' => 'filter_autop',
+ '2' => 'filter_url',
+ '3' => 'filter_htmlcorrector',
+ '4' => 'filter_html_escape',
+ ),
+ 'php' => array(
+ '0' => 'php_code',
+ ),
+ );
+
+ // Loop through each filter and make changes to the core filter table by
+ // each record from the old to the new table.
+ foreach ($renamed_deltas as $module => $deltas) {
+ foreach ($deltas as $old_delta => $new_name) {
+ $query = db_select('d6_upgrade_filter')
+ ->fields('d6_upgrade_filter', array('format', 'weight'))
+ ->condition('module', $module)
+ ->condition('delta', $old_delta)
+ ->distinct();
+
+ foreach ($query->execute() as $record) {
+ // Port the filter settings.
+ $settings = array();
+ if ($new_name == 'filter_html') {
+ if ($setting = variable_get("allowed_html_{$record->format}", NULL)) {
+ $settings['allowed_html'] = $setting;
+ variable_del("allowed_html_{$record->format}");
+ }
+ if ($setting = variable_get("filter_html_help_{$record->format}", NULL)) {
+ $settings['filter_html_help'] = $setting;
+ variable_del("filter_html_help_{$record->format}");
+ }
+ if ($setting = variable_get("filter_html_nofollow_{$record->format}", NULL)) {
+ $settings['filter_html_nofollow'] = $setting;
+ variable_del("filter_html_nofollow_{$record->format}");
+ }
+ }
+ elseif ($new_name == 'filter_url') {
+ if ($setting = variable_get("filter_url_length_{$record->format}", NULL)) {
+ $settings['filter_url_length'] = $setting;
+ variable_del("filter_url_length_{$record->format}");
+ }
+ }
+
+ db_insert('filter')
+ ->fields(array(
+ 'format' => $record->format,
+ 'module' => $module,
+ 'name' => $new_name,
+ 'weight' => $record->weight,
+ 'settings' => serialize($settings),
+ 'status' => 1,
+ ))
+ ->execute();
+ }
+ db_delete('d6_upgrade_filter')
+ ->condition('module', $module)
+ ->condition('delta', $old_delta)
+ ->execute();
+ }
+ }
+}
+
+/**
+ * Integrate text formats with the user permissions system.
+ *
+ * This function converts text format role assignments to use the new text
+ * format permissions introduced in Drupal 7, creates a fallback (plain text)
+ * format that is available to all users, and explicitly sets the text format
+ * in cases that used to rely on a single site-wide default.
+ */
+function filter_update_7005() {
+ // Move role data from the filter system to the user permission system.
+ $all_roles = array_keys(user_roles());
+ $default_format = variable_get('filter_default_format', 1);
+ $result = db_query("SELECT * FROM {filter_format}");
+ foreach ($result as $format) {
+ // We need to assign the default format to all roles (regardless of what
+ // was stored in the database) to preserve the behavior of the site at the
+ // moment of the upgrade.
+ $format_roles = ($format->format == $default_format ? $all_roles : explode(',', $format->roles));
+ foreach ($format_roles as $format_role) {
+ if (in_array($format_role, $all_roles)) {
+ _update_7000_user_role_grant_permissions($format_role, array('use text format ' . $format->format), 'filter');
+ }
+ }
+ }
+
+ // Drop the roles field from the {filter_format} table.
+ db_drop_field('filter_format', 'roles');
+
+ // Add a fallback text format which outputs plain text and appears last on
+ // the list for all users. Generate a unique name for it, starting with
+ // "Plain text".
+ $start_name = 'Plain text';
+ $format_name = $start_name;
+ while ($format = db_query('SELECT format FROM {filter_format} WHERE name = :name', array(':name' => $format_name))->fetchField()) {
+ $id = empty($id) ? 2 : $id + 1;
+ $format_name = $start_name . ' ' . $id;
+ }
+
+ // Insert the filter format.
+ $format_id = db_insert('filter_format')
+ ->fields(array(
+ 'name' => $format_name,
+ 'cache' => 1,
+ 'weight' => 1,
+ 'status' => 1,
+ ))
+ ->execute();
+
+ // This format should output plain text, so we escape all HTML and apply the
+ // line break and URL filters only.
+ db_insert('filter')
+ ->fields(array(
+ 'format',
+ 'name',
+ 'weight',
+ 'status',
+ 'module',
+ 'settings',
+ ))
+ ->values(array(
+ 'format' => $format_id,
+ 'name' => 'filter_html_escape',
+ 'weight' => 0,
+ 'status' => 1,
+ 'module' => 'filter',
+ 'settings' => serialize(array()),
+ ))
+ ->values(array(
+ 'format' => $format_id,
+ 'name' => 'filter_url',
+ 'weight' => 1,
+ 'status' => 1,
+ 'module' => 'filter',
+ 'settings' => serialize(array()),
+ ))
+ ->values(array(
+ 'format' => $format_id,
+ 'name' => 'filter_autop',
+ 'weight' => 2,
+ 'status' => 1,
+ 'module' => 'filter',
+ 'settings' => serialize(array()),
+ ))
+ ->execute();
+
+ variable_set('filter_fallback_format', $format_id);
+ drupal_set_message('A new <em>Plain text</em> format has been created which will be available to all users. You can configure this text format on the <a href="' . url('admin/config/content/formats/' . $format) . '">text format configuration page</a>.');
+
+ // Move the former site-wide default text format to the top of the list, so
+ // that it continues to be the default text format for all users.
+ db_update('filter_format')
+ ->fields(array('weight' => -1))
+ ->condition('format', $default_format)
+ ->execute();
+
+ // We do not delete the 'filter_default_format' variable, since other modules
+ // need it in their update functions; for an example, see user_update_7010().
+ // @todo This variable can be deleted in Drupal 8.
+}
+
+/**
+ * Grant usage of all text formats to user roles having the 'administer filters' permission.
+ */
+function filter_update_7008() {
+ // Build the list of permissions to grant.
+ $permissions = array();
+ foreach (db_query('SELECT format FROM {filter_format}')->fetchCol() as $format_id) {
+ if ($format_id != variable_get('filter_fallback_format')) {
+ $permissions[] = 'use text format ' . $format_id;
+ }
+ }
+ // Grant text format permissions to all roles that can 'administer filters'.
+ // Albeit anonymous users *should not* have the permission, we cannot presume
+ // that they do not or must not.
+ if ($roles = user_roles(FALSE, 'administer filters')) {
+ foreach ($roles as $rid => $name) {
+ _update_7000_user_role_grant_permissions($rid, $permissions, 'filter');
+ }
+ }
+}
+
+/**
+ * Converts fields that store serialized variables from text to blob.
+ */
+function filter_update_7009() {
+ $schema = system_schema_cache_7054();
+ db_drop_table('cache_filter');
+ db_create_table('cache_filter', $schema);
+}
+
+/**
+ * Change {filter_format}.format and {filter}.format into varchar.
+ */
+function filter_update_7010() {
+ db_change_field('filter_format', 'format', 'format', array(
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'description' => 'Primary Key: Unique machine name of the format.',
+ ));
+ db_change_field('filter', 'format', 'format', array(
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'description' => 'Foreign key: The {filter_format}.format to which this filter is assigned.',
+ ));
+}
+
+/**
+ * @} End of "addtogroup updates-6.x-to-7.x".
+ */
diff --git a/kolab.org/www/drupal-7.26/modules/filter/filter.js b/kolab.org/www/drupal-7.26/modules/filter/filter.js
new file mode 100644
index 0000000..c286159
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/filter/filter.js
@@ -0,0 +1,20 @@
+(function ($) {
+
+/**
+ * Automatically display the guidelines of the selected text format.
+ */
+Drupal.behaviors.filterGuidelines = {
+ attach: function (context) {
+ $('.filter-guidelines', context).once('filter-guidelines')
+ .find(':header').hide()
+ .closest('.filter-wrapper').find('select.filter-list')
+ .bind('change', function () {
+ $(this).closest('.filter-wrapper')
+ .find('.filter-guidelines-item').hide()
+ .siblings('.filter-guidelines-' + this.value).show();
+ })
+ .change();
+ }
+};
+
+})(jQuery);
diff --git a/kolab.org/www/drupal-7.26/modules/filter/filter.module b/kolab.org/www/drupal-7.26/modules/filter/filter.module
new file mode 100644
index 0000000..2afe901
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/filter/filter.module
@@ -0,0 +1,1786 @@
+<?php
+
+/**
+ * @file
+ * Framework for handling the filtering of content.
+ */
+
+/**
+ * Implements hook_help().
+ */
+function filter_help($path, $arg) {
+ switch ($path) {
+ case 'admin/help#filter':
+ $output = '';
+ $output .= '<h3>' . t('About') . '</h3>';
+ $output .= '<p>' . t('The Filter module allows administrators to configure text formats. A text format defines the HTML tags, codes, and other input allowed in content and comments, and is a key feature in guarding against potentially damaging input from malicious users. For more information, see the online handbook entry for <a href="@filter">Filter module</a>.', array('@filter' => 'http://drupal.org/documentation/modules/filter/')) . '</p>';
+ $output .= '<h3>' . t('Uses') . '</h3>';
+ $output .= '<dl>';
+ $output .= '<dt>' . t('Configuring text formats') . '</dt>';
+ $output .= '<dd>' . t('Configure text formats on the <a href="@formats">Text formats page</a>. <strong>Improper text format configuration is a security risk</strong>. To ensure security, untrusted users should only have access to text formats that restrict them to either plain text or a safe set of HTML tags, since certain HTML tags can allow embedding malicious links or scripts in text. More trusted registered users may be granted permission to use less restrictive text formats in order to create rich content.', array('@formats' => url('admin/config/content/formats'))) . '</dd>';
+ $output .= '<dt>' . t('Applying filters to text') . '</dt>';
+ $output .= '<dd>' . t('Each text format uses filters to manipulate text, and most formats apply several different filters to text in a specific order. Each filter is designed for a specific purpose, and generally either adds, removes, or transforms elements within user-entered text before it is displayed. A filter does not change the actual content, but instead, modifies it temporarily before it is displayed. One filter may remove unapproved HTML tags, while another automatically adds HTML to make URLs display as clickable links.') . '</dd>';
+ $output .= '<dt>' . t('Defining text formats') . '</dt>';
+ $output .= '<dd>' . t('One format is included by default: <em>Plain text</em> (which removes all HTML tags). Additional formats may be created by your installation profile when you install Drupal, and more can be created by an administrator on the <a href="@text-formats">Text formats page</a>.', array('@text-formats' => url('admin/config/content/formats'))) . '</dd>';
+ $output .= '<dt>' . t('Choosing a text format') . '</dt>';
+ $output .= '<dd>' . t('Users with access to more than one text format can use the <em>Text format</em> fieldset to choose between available text formats when creating or editing multi-line content. Administrators can define the text formats available to each user role, and control the order of formats listed in the <em>Text format</em> fieldset on the <a href="@text-formats">Text formats page</a>.', array('@text-formats' => url('admin/config/content/formats'))) . '</dd>';
+ $output .= '</dl>';
+ return $output;
+
+ case 'admin/config/content/formats':
+ $output = '<p>' . t('Text formats define the HTML tags, code, and other formatting that can be used when entering text. <strong>Improper text format configuration is a security risk</strong>. Learn more on the <a href="@filterhelp">Filter module help page</a>.', array('@filterhelp' => url('admin/help/filter'))) . '</p>';
+ $output .= '<p>' . t('Text formats are presented on content editing pages in the order defined on this page. The first format available to a user will be selected by default.') . '</p>';
+ return $output;
+
+ case 'admin/config/content/formats/%':
+ $output = '<p>' . t('A text format contains filters that change the user input, for example stripping out malicious HTML or making URLs clickable. Filters are executed from top to bottom and the order is important, since one filter may prevent another filter from doing its job. For example, when URLs are converted into links before disallowed HTML tags are removed, all links may be removed. When this happens, the order of filters may need to be re-arranged.') . '</p>';
+ return $output;
+ }
+}
+
+/**
+ * Implements hook_theme().
+ */
+function filter_theme() {
+ return array(
+ 'filter_admin_overview' => array(
+ 'render element' => 'form',
+ 'file' => 'filter.admin.inc',
+ ),
+ 'filter_admin_format_filter_order' => array(
+ 'render element' => 'element',
+ 'file' => 'filter.admin.inc',
+ ),
+ 'filter_tips' => array(
+ 'variables' => array('tips' => NULL, 'long' => FALSE),
+ 'file' => 'filter.pages.inc',
+ ),
+ 'text_format_wrapper' => array(
+ 'render element' => 'element',
+ ),
+ 'filter_tips_more_info' => array(
+ 'variables' => array(),
+ ),
+ 'filter_guidelines' => array(
+ 'variables' => array('format' => NULL),
+ ),
+ );
+}
+
+/**
+ * Implements hook_element_info().
+ *
+ * @see filter_process_format()
+ * @see text_format_wrapper()
+ */
+function filter_element_info() {
+ $type['text_format'] = array(
+ '#process' => array('filter_process_format'),
+ '#base_type' => 'textarea',
+ '#theme_wrappers' => array('text_format_wrapper'),
+ );
+ return $type;
+}
+
+/**
+ * Implements hook_menu().
+ */
+function filter_menu() {
+ $items['filter/tips'] = array(
+ 'title' => 'Compose tips',
+ 'page callback' => 'filter_tips_long',
+ 'access callback' => TRUE,
+ 'type' => MENU_SUGGESTED_ITEM,
+ 'file' => 'filter.pages.inc',
+ );
+ $items['admin/config/content/formats'] = array(
+ 'title' => 'Text formats',
+ 'description' => 'Configure how content input by users is filtered, including allowed HTML tags. Also allows enabling of module-provided filters.',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('filter_admin_overview'),
+ 'access arguments' => array('administer filters'),
+ 'file' => 'filter.admin.inc',
+ );
+ $items['admin/config/content/formats/list'] = array(
+ 'title' => 'List',
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ );
+ $items['admin/config/content/formats/add'] = array(
+ 'title' => 'Add text format',
+ 'page callback' => 'filter_admin_format_page',
+ 'access arguments' => array('administer filters'),
+ 'type' => MENU_LOCAL_ACTION,
+ 'weight' => 1,
+ 'file' => 'filter.admin.inc',
+ );
+ $items['admin/config/content/formats/%filter_format'] = array(
+ 'title callback' => 'filter_admin_format_title',
+ 'title arguments' => array(4),
+ 'page callback' => 'filter_admin_format_page',
+ 'page arguments' => array(4),
+ 'access arguments' => array('administer filters'),
+ 'file' => 'filter.admin.inc',
+ );
+ $items['admin/config/content/formats/%filter_format/disable'] = array(
+ 'title' => 'Disable text format',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('filter_admin_disable', 4),
+ 'access callback' => '_filter_disable_format_access',
+ 'access arguments' => array(4),
+ 'file' => 'filter.admin.inc',
+ );
+ return $items;
+}
+
+/**
+ * Access callback: Checks access for disabling text formats.
+ *
+ * @param $format
+ * A text format object.
+ *
+ * @return
+ * TRUE if the text format can be disabled by the current user, FALSE
+ * otherwise.
+ *
+ * @see filter_menu()
+ */
+function _filter_disable_format_access($format) {
+ // The fallback format can never be disabled.
+ return user_access('administer filters') && ($format->format != filter_fallback_format());
+}
+
+/**
+ * Loads a text format object from the database.
+ *
+ * @param $format_id
+ * The format ID.
+ *
+ * @return
+ * A fully-populated text format object, if the requested format exists and
+ * is enabled. If the format does not exist, or exists in the database but
+ * has been marked as disabled, FALSE is returned.
+ *
+ * @see filter_format_exists()
+ */
+function filter_format_load($format_id) {
+ $formats = filter_formats();
+ return isset($formats[$format_id]) ? $formats[$format_id] : FALSE;
+}
+
+/**
+ * Saves a text format object to the database.
+ *
+ * @param $format
+ * A format object having the properties:
+ * - format: A machine-readable name representing the ID of the text format
+ * to save. If this corresponds to an existing text format, that format
+ * will be updated; otherwise, a new format will be created.
+ * - name: The title of the text format.
+ * - status: (optional) An integer indicating whether the text format is
+ * enabled (1) or not (0). Defaults to 1.
+ * - weight: (optional) The weight of the text format, which controls its
+ * placement in text format lists. If omitted, the weight is set to 0.
+ * - filters: (optional) An associative, multi-dimensional array of filters
+ * assigned to the text format, keyed by the name of each filter and using
+ * the properties:
+ * - weight: (optional) The weight of the filter in the text format. If
+ * omitted, either the currently stored weight is retained (if there is
+ * one), or the filter is assigned a weight of 10, which will usually
+ * put it at the bottom of the list.
+ * - status: (optional) A boolean indicating whether the filter is
+ * enabled in the text format. If omitted, the filter will be disabled.
+ * - settings: (optional) An array of configured settings for the filter.
+ * See hook_filter_info() for details.
+ *
+ * @return
+ * SAVED_NEW or SAVED_UPDATED.
+ */
+function filter_format_save($format) {
+ $format->name = trim($format->name);
+ $format->cache = _filter_format_is_cacheable($format);
+ if (!isset($format->status)) {
+ $format->status = 1;
+ }
+ if (!isset($format->weight)) {
+ $format->weight = 0;
+ }
+
+ // Insert or update the text format.
+ $return = db_merge('filter_format')
+ ->key(array('format' => $format->format))
+ ->fields(array(
+ 'name' => $format->name,
+ 'cache' => (int) $format->cache,
+ 'status' => (int) $format->status,
+ 'weight' => (int) $format->weight,
+ ))
+ ->execute();
+
+ // Programmatic saves may not contain any filters.
+ if (!isset($format->filters)) {
+ $format->filters = array();
+ }
+ $filter_info = filter_get_filters();
+ foreach ($filter_info as $name => $filter) {
+ // If the format does not specify an explicit weight for a filter, assign
+ // a default weight, either defined in hook_filter_info(), or the default of
+ // 0 by filter_get_filters()
+ if (!isset($format->filters[$name]['weight'])) {
+ $format->filters[$name]['weight'] = $filter['weight'];
+ }
+ $format->filters[$name]['status'] = isset($format->filters[$name]['status']) ? $format->filters[$name]['status'] : 0;
+ $format->filters[$name]['module'] = $filter['module'];
+
+ // If settings were passed, only ensure default settings.
+ if (isset($format->filters[$name]['settings'])) {
+ if (isset($filter['default settings'])) {
+ $format->filters[$name]['settings'] = array_merge($filter['default settings'], $format->filters[$name]['settings']);
+ }
+ }
+ // Otherwise, use default settings or fall back to an empty array.
+ else {
+ $format->filters[$name]['settings'] = isset($filter['default settings']) ? $filter['default settings'] : array();
+ }
+
+ $fields = array();
+ $fields['weight'] = $format->filters[$name]['weight'];
+ $fields['status'] = $format->filters[$name]['status'];
+ $fields['module'] = $format->filters[$name]['module'];
+ $fields['settings'] = serialize($format->filters[$name]['settings']);
+
+ db_merge('filter')
+ ->key(array(
+ 'format' => $format->format,
+ 'name' => $name,
+ ))
+ ->fields($fields)
+ ->execute();
+ }
+
+ if ($return == SAVED_NEW) {
+ module_invoke_all('filter_format_insert', $format);
+ }
+ else {
+ module_invoke_all('filter_format_update', $format);
+ // Explicitly indicate that the format was updated. We need to do this
+ // since if the filters were updated but the format object itself was not,
+ // the merge query above would not return an indication that anything had
+ // changed.
+ $return = SAVED_UPDATED;
+
+ // Clear the filter cache whenever a text format is updated.
+ cache_clear_all($format->format . ':', 'cache_filter', TRUE);
+ }
+
+ filter_formats_reset();
+
+ return $return;
+}
+
+/**
+ * Disables a text format.
+ *
+ * There is no core facility to re-enable a disabled format. It is not deleted
+ * to keep information for contrib and to make sure the format ID is never
+ * reused. As there might be content using the disabled format, this would lead
+ * to data corruption.
+ *
+ * @param $format
+ * The text format object to be disabled.
+ */
+function filter_format_disable($format) {
+ db_update('filter_format')
+ ->fields(array('status' => 0))
+ ->condition('format', $format->format)
+ ->execute();
+
+ // Allow modules to react on text format deletion.
+ module_invoke_all('filter_format_disable', $format);
+
+ // Clear the filter cache whenever a text format is disabled.
+ filter_formats_reset();
+ cache_clear_all($format->format . ':', 'cache_filter', TRUE);
+}
+
+/**
+ * Determines if a text format exists.
+ *
+ * @param $format_id
+ * The ID of the text format to check.
+ *
+ * @return
+ * TRUE if the text format exists, FALSE otherwise. Note that for disabled
+ * formats filter_format_exists() will return TRUE while filter_format_load()
+ * will return FALSE.
+ *
+ * @see filter_format_load()
+ */
+function filter_format_exists($format_id) {
+ return (bool) db_query_range('SELECT 1 FROM {filter_format} WHERE format = :format', 0, 1, array(':format' => $format_id))->fetchField();
+}
+
+/**
+ * Displays a text format form title.
+ *
+ * @param object $format
+ * A format object.
+ *
+ * @return string
+ * The name of the format.
+ *
+ * @see filter_menu()
+ */
+function filter_admin_format_title($format) {
+ return $format->name;
+}
+
+/**
+ * Implements hook_permission().
+ */
+function filter_permission() {
+ $perms['administer filters'] = array(
+ 'title' => t('Administer text formats and filters'),
+ 'restrict access' => TRUE,
+ );
+
+ // Generate permissions for each text format. Warn the administrator that any
+ // of them are potentially unsafe.
+ foreach (filter_formats() as $format) {
+ $permission = filter_permission_name($format);
+ if (!empty($permission)) {
+ // Only link to the text format configuration page if the user who is
+ // viewing this will have access to that page.
+ $format_name_replacement = user_access('administer filters') ? l($format->name, 'admin/config/content/formats/' . $format->format) : drupal_placeholder($format->name);
+ $perms[$permission] = array(
+ 'title' => t("Use the !text_format text format", array('!text_format' => $format_name_replacement,)),
+ 'description' => drupal_placeholder(t('Warning: This permission may have security implications depending on how the text format is configured.')),
+ );
+ }
+ }
+ return $perms;
+}
+
+/**
+ * Returns the machine-readable permission name for a provided text format.
+ *
+ * @param $format
+ * An object representing a text format.
+ *
+ * @return
+ * The machine-readable permission name, or FALSE if the provided text format
+ * is malformed or is the fallback format (which is available to all users).
+ */
+function filter_permission_name($format) {
+ if (isset($format->format) && $format->format != filter_fallback_format()) {
+ return 'use text format ' . $format->format;
+ }
+ return FALSE;
+}
+
+/**
+ * Implements hook_modules_enabled().
+ */
+function filter_modules_enabled($modules) {
+ // Reset the static cache of module-provided filters, in case any of the
+ // newly enabled modules defines a new filter or alters existing ones.
+ drupal_static_reset('filter_get_filters');
+}
+
+/**
+ * Implements hook_modules_disabled().
+ */
+function filter_modules_disabled($modules) {
+ // Reset the static cache of module-provided filters, in case any of the
+ // newly disabled modules defined or altered any filters.
+ drupal_static_reset('filter_get_filters');
+}
+
+/**
+ * Retrieves a list of text formats, ordered by weight.
+ *
+ * @param $account
+ * (optional) If provided, only those formats that are allowed for this user
+ * account will be returned. All formats will be returned otherwise. Defaults
+ * to NULL.
+ *
+ * @return
+ * An array of text format objects, keyed by the format ID and ordered by
+ * weight.
+ *
+ * @see filter_formats_reset()
+ */
+function filter_formats($account = NULL) {
+ global $language;
+ $formats = &drupal_static(__FUNCTION__, array());
+
+ // All available formats are cached for performance.
+ if (!isset($formats['all'])) {
+ if ($cache = cache_get("filter_formats:{$language->language}")) {
+ $formats['all'] = $cache->data;
+ }
+ else {
+ $formats['all'] = db_select('filter_format', 'ff')
+ ->addTag('translatable')
+ ->fields('ff')
+ ->condition('status', 1)
+ ->orderBy('weight')
+ ->execute()
+ ->fetchAllAssoc('format');
+
+ cache_set("filter_formats:{$language->language}", $formats['all']);
+ }
+ }
+
+ // Build a list of user-specific formats.
+ if (isset($account) && !isset($formats['user'][$account->uid])) {
+ $formats['user'][$account->uid] = array();
+ foreach ($formats['all'] as $format) {
+ if (filter_access($format, $account)) {
+ $formats['user'][$account->uid][$format->format] = $format;
+ }
+ }
+ }
+
+ return isset($account) ? $formats['user'][$account->uid] : $formats['all'];
+}
+
+/**
+ * Resets the text format caches.
+ *
+ * @see filter_formats()
+ */
+function filter_formats_reset() {
+ cache_clear_all('filter_formats', 'cache', TRUE);
+ cache_clear_all('filter_list_format', 'cache', TRUE);
+ drupal_static_reset('filter_list_format');
+ drupal_static_reset('filter_formats');
+}
+
+/**
+ * Retrieves a list of roles that are allowed to use a given text format.
+ *
+ * @param $format
+ * An object representing the text format.
+ *
+ * @return
+ * An array of role names, keyed by role ID.
+ */
+function filter_get_roles_by_format($format) {
+ // Handle the fallback format upfront (all roles have access to this format).
+ if ($format->format == filter_fallback_format()) {
+ return user_roles();
+ }
+ // Do not list any roles if the permission does not exist.
+ $permission = filter_permission_name($format);
+ return !empty($permission) ? user_roles(FALSE, $permission) : array();
+}
+
+/**
+ * Retrieves a list of text formats that are allowed for a given role.
+ *
+ * @param $rid
+ * The user role ID to retrieve text formats for.
+ *
+ * @return
+ * An array of text format objects that are allowed for the role, keyed by
+ * the text format ID and ordered by weight.
+ */
+function filter_get_formats_by_role($rid) {
+ $formats = array();
+ foreach (filter_formats() as $format) {
+ $roles = filter_get_roles_by_format($format);
+ if (isset($roles[$rid])) {
+ $formats[$format->format] = $format;
+ }
+ }
+ return $formats;
+}
+
+/**
+ * Returns the ID of the default text format for a particular user.
+ *
+ * The default text format is the first available format that the user is
+ * allowed to access, when the formats are ordered by weight. It should
+ * generally be used as a default choice when presenting the user with a list
+ * of possible text formats (for example, in a node creation form).
+ *
+ * Conversely, when existing content that does not have an assigned text format
+ * needs to be filtered for display, the default text format is the wrong
+ * choice, because it is not guaranteed to be consistent from user to user, and
+ * some trusted users may have an unsafe text format set by default, which
+ * should not be used on text of unknown origin. Instead, the fallback format
+ * returned by filter_fallback_format() should be used, since that is intended
+ * to be a safe, consistent format that is always available to all users.
+ *
+ * @param $account
+ * (optional) The user account to check. Defaults to the currently logged-in
+ * user. Defaults to NULL.
+ *
+ * @return
+ * The ID of the user's default text format.
+ *
+ * @see filter_fallback_format()
+ */
+function filter_default_format($account = NULL) {
+ global $user;
+ if (!isset($account)) {
+ $account = $user;
+ }
+ // Get a list of formats for this user, ordered by weight. The first one
+ // available is the user's default format.
+ $formats = filter_formats($account);
+ $format = reset($formats);
+ return $format->format;
+}
+
+/**
+ * Returns the ID of the fallback text format that all users have access to.
+ *
+ * The fallback text format is a regular text format in every respect, except
+ * it does not participate in the filter permission system and cannot be
+ * disabled. It needs to exist because any user who has permission to create
+ * formatted content must always have at least one text format they can use.
+ *
+ * Because the fallback format is available to all users, it should always be
+ * configured securely. For example, when the Filter module is installed, this
+ * format is initialized to output plain text. Installation profiles and site
+ * administrators have the freedom to configure it further.
+ *
+ * Note that the fallback format is completely distinct from the default format,
+ * which differs per user and is simply the first format which that user has
+ * access to. The default and fallback formats are only guaranteed to be the
+ * same for users who do not have access to any other format; otherwise, the
+ * fallback format's weight determines its placement with respect to the user's
+ * other formats.
+ *
+ * Any modules implementing a format deletion functionality must not delete this
+ * format.
+ *
+ * @return
+ * The ID of the fallback text format.
+ *
+ * @see hook_filter_format_disable()
+ * @see filter_default_format()
+ */
+function filter_fallback_format() {
+ // This variable is automatically set in the database for all installations
+ // of Drupal. In the event that it gets disabled or deleted somehow, there
+ // is no safe default to return, since we do not want to risk making an
+ // existing (and potentially unsafe) text format on the site automatically
+ // available to all users. Returning NULL at least guarantees that this
+ // cannot happen.
+ return variable_get('filter_fallback_format');
+}
+
+/**
+ * Returns the title of the fallback text format.
+ *
+ * @return string
+ * The title of the fallback text format.
+ */
+function filter_fallback_format_title() {
+ $fallback_format = filter_format_load(filter_fallback_format());
+ return filter_admin_format_title($fallback_format);
+}
+
+/**
+ * Returns a list of all filters provided by modules.
+ *
+ * @return array
+ * An array of filter formats.
+ */
+function filter_get_filters() {
+ $filters = &drupal_static(__FUNCTION__, array());
+
+ if (empty($filters)) {
+ foreach (module_implements('filter_info') as $module) {
+ $info = module_invoke($module, 'filter_info');
+ if (isset($info) && is_array($info)) {
+ // Assign the name of the module implementing the filters and ensure
+ // default values.
+ foreach (array_keys($info) as $name) {
+ $info[$name]['module'] = $module;
+ $info[$name] += array(
+ 'description' => '',
+ 'weight' => 0,
+ );
+ }
+ $filters = array_merge($filters, $info);
+ }
+ }
+ // Allow modules to alter filter definitions.
+ drupal_alter('filter_info', $filters);
+
+ uasort($filters, '_filter_list_cmp');
+ }
+
+ return $filters;
+}
+
+/**
+ * Sorts an array of filters by filter name.
+ *
+ * Callback for uasort() within filter_get_filters().
+ */
+function _filter_list_cmp($a, $b) {
+ return strcmp($a['title'], $b['title']);
+}
+
+/**
+ * Checks if the text in a certain text format is allowed to be cached.
+ *
+ * This function can be used to check whether the result of the filtering
+ * process can be cached. A text format may allow caching depending on the
+ * filters enabled.
+ *
+ * @param $format_id
+ * The text format ID to check.
+ *
+ * @return
+ * TRUE if the given text format allows caching, FALSE otherwise.
+ */
+function filter_format_allowcache($format_id) {
+ $format = filter_format_load($format_id);
+ return !empty($format->cache);
+}
+
+/**
+ * Helper function to determine whether the output of a given text format can be cached.
+ *
+ * The output of a given text format can be cached when all enabled filters in
+ * the text format allow caching.
+ *
+ * @param $format
+ * The text format object to check.
+ *
+ * @return
+ * TRUE if all the filters enabled in the given text format allow caching,
+ * FALSE otherwise.
+ *
+ * @see filter_format_save()
+ */
+function _filter_format_is_cacheable($format) {
+ if (empty($format->filters)) {
+ return TRUE;
+ }
+ $filter_info = filter_get_filters();
+ foreach ($format->filters as $name => $filter) {
+ // By default, 'cache' is TRUE for all filters unless specified otherwise.
+ if (!empty($filter['status']) && isset($filter_info[$name]['cache']) && !$filter_info[$name]['cache']) {
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+/**
+ * Retrieves a list of filters for a given text format.
+ *
+ * Note that this function returns all associated filters regardless of whether
+ * they are enabled or disabled. All functions working with the filter
+ * information outside of filter administration should test for $filter->status
+ * before performing actions with the filter.
+ *
+ * @param $format_id
+ * The format ID to retrieve filters for.
+ *
+ * @return
+ * An array of filter objects associated to the given text format, keyed by
+ * filter name.
+ */
+function filter_list_format($format_id) {
+ $filters = &drupal_static(__FUNCTION__, array());
+ $filter_info = filter_get_filters();
+
+ if (!isset($filters['all'])) {
+ if ($cache = cache_get('filter_list_format')) {
+ $filters['all'] = $cache->data;
+ }
+ else {
+ $result = db_query('SELECT * FROM {filter} ORDER BY weight, module, name');
+ foreach ($result as $record) {
+ $filters['all'][$record->format][$record->name] = $record;
+ }
+ cache_set('filter_list_format', $filters['all']);
+ }
+ }
+
+ if (!isset($filters[$format_id])) {
+ $format_filters = array();
+ $filter_map = isset($filters['all'][$format_id]) ? $filters['all'][$format_id] : array();
+ foreach ($filter_map as $name => $filter) {
+ if (isset($filter_info[$name])) {
+ $filter->title = $filter_info[$name]['title'];
+ // Unpack stored filter settings.
+ $filter->settings = (isset($filter->settings) ? unserialize($filter->settings) : array());
+ // Merge in default settings.
+ if (isset($filter_info[$name]['default settings'])) {
+ $filter->settings += $filter_info[$name]['default settings'];
+ }
+
+ $format_filters[$name] = $filter;
+ }
+ }
+ $filters[$format_id] = $format_filters;
+ }
+
+ return isset($filters[$format_id]) ? $filters[$format_id] : array();
+}
+
+/**
+ * Runs all the enabled filters on a piece of text.
+ *
+ * Note: Because filters can inject JavaScript or execute PHP code, security is
+ * vital here. When a user supplies a text format, you should validate it using
+ * filter_access() before accepting/using it. This is normally done in the
+ * validation stage of the Form API. You should for example never make a preview
+ * of content in a disallowed format.
+ *
+ * @param $text
+ * The text to be filtered.
+ * @param $format_id
+ * (optional) The format ID of the text to be filtered. If no format is
+ * assigned, the fallback format will be used. Defaults to NULL.
+ * @param $langcode
+ * (optional) The language code of the text to be filtered, e.g. 'en' for
+ * English. This allows filters to be language aware so language specific
+ * text replacement can be implemented. Defaults to an empty string.
+ * @param $cache
+ * (optional) A Boolean indicating whether to cache the filtered output in the
+ * {cache_filter} table. The caller may set this to FALSE when the output is
+ * already cached elsewhere to avoid duplicate cache lookups and storage.
+ * Defaults to FALSE.
+ *
+ * @return
+ * The filtered text.
+ *
+ * @ingroup sanitization
+ */
+function check_markup($text, $format_id = NULL, $langcode = '', $cache = FALSE) {
+ if (!isset($format_id)) {
+ $format_id = filter_fallback_format();
+ }
+ // If the requested text format does not exist, the text cannot be filtered.
+ if (!$format = filter_format_load($format_id)) {
+ watchdog('filter', 'Missing text format: %format.', array('%format' => $format_id), WATCHDOG_ALERT);
+ return '';
+ }
+
+ // Check for a cached version of this piece of text.
+ $cache = $cache && !empty($format->cache);
+ $cache_id = '';
+ if ($cache) {
+ $cache_id = $format->format . ':' . $langcode . ':' . hash('sha256', $text);
+ if ($cached = cache_get($cache_id, 'cache_filter')) {
+ return $cached->data;
+ }
+ }
+
+ // Convert all Windows and Mac newlines to a single newline, so filters only
+ // need to deal with one possibility.
+ $text = str_replace(array("\r\n", "\r"), "\n", $text);
+
+ // Get a complete list of filters, ordered properly.
+ $filters = filter_list_format($format->format);
+ $filter_info = filter_get_filters();
+
+ // Give filters the chance to escape HTML-like data such as code or formulas.
+ foreach ($filters as $name => $filter) {
+ if ($filter->status && isset($filter_info[$name]['prepare callback']) && function_exists($filter_info[$name]['prepare callback'])) {
+ $function = $filter_info[$name]['prepare callback'];
+ $text = $function($text, $filter, $format, $langcode, $cache, $cache_id);
+ }
+ }
+
+ // Perform filtering.
+ foreach ($filters as $name => $filter) {
+ if ($filter->status && isset($filter_info[$name]['process callback']) && function_exists($filter_info[$name]['process callback'])) {
+ $function = $filter_info[$name]['process callback'];
+ $text = $function($text, $filter, $format, $langcode, $cache, $cache_id);
+ }
+ }
+
+ // Cache the filtered text. This cache is infinitely valid. It becomes
+ // obsolete when $text changes (which leads to a new $cache_id). It is
+ // automatically flushed when the text format is updated.
+ // @see filter_format_save()
+ if ($cache) {
+ cache_set($cache_id, $text, 'cache_filter');
+ }
+
+ return $text;
+}
+
+/**
+ * Expands an element into a base element with text format selector attached.
+ *
+ * The form element will be expanded into two separate form elements, one
+ * holding the original element, and the other holding the text format selector:
+ * - value: Holds the original element, having its #type changed to the value of
+ * #base_type or 'textarea' by default.
+ * - format: Holds the text format fieldset and the text format selection, using
+ * the text format id specified in #format or the user's default format by
+ * default, if NULL.
+ *
+ * The resulting value for the element will be an array holding the value and
+ * the format. For example, the value for the body element will be:
+ * @code
+ * $form_state['values']['body']['value'] = 'foo';
+ * $form_state['values']['body']['format'] = 'foo';
+ * @endcode
+ *
+ * @param $element
+ * The form element to process. Properties used:
+ * - #base_type: The form element #type to use for the 'value' element.
+ * 'textarea' by default.
+ * - #format: (optional) The text format ID to preselect. If NULL or not set,
+ * the default format for the current user will be used.
+ *
+ * @return
+ * The expanded element.
+ */
+function filter_process_format($element) {
+ global $user;
+
+ // Ensure that children appear as subkeys of this element.
+ $element['#tree'] = TRUE;
+ $blacklist = array(
+ // Make form_builder() regenerate child properties.
+ '#parents',
+ '#id',
+ '#name',
+ // Do not copy this #process function to prevent form_builder() from
+ // recursing infinitely.
+ '#process',
+ // Description is handled by theme_text_format_wrapper().
+ '#description',
+ // Ensure proper ordering of children.
+ '#weight',
+ // Properties already processed for the parent element.
+ '#prefix',
+ '#suffix',
+ '#attached',
+ '#processed',
+ '#theme_wrappers',
+ );
+ // Move this element into sub-element 'value'.
+ unset($element['value']);
+ foreach (element_properties($element) as $key) {
+ if (!in_array($key, $blacklist)) {
+ $element['value'][$key] = $element[$key];
+ }
+ }
+
+ $element['value']['#type'] = $element['#base_type'];
+ $element['value'] += element_info($element['#base_type']);
+
+ // Turn original element into a text format wrapper.
+ $path = drupal_get_path('module', 'filter');
+ $element['#attached']['js'][] = $path . '/filter.js';
+ $element['#attached']['css'][] = $path . '/filter.css';
+
+ // Setup child container for the text format widget.
+ $element['format'] = array(
+ '#type' => 'fieldset',
+ '#attributes' => array('class' => array('filter-wrapper')),
+ );
+
+ // Prepare text format guidelines.
+ $element['format']['guidelines'] = array(
+ '#type' => 'container',
+ '#attributes' => array('class' => array('filter-guidelines')),
+ '#weight' => 20,
+ );
+ // Get a list of formats that the current user has access to.
+ $formats = filter_formats($user);
+ foreach ($formats as $format) {
+ $options[$format->format] = $format->name;
+ $element['format']['guidelines'][$format->format] = array(
+ '#theme' => 'filter_guidelines',
+ '#format' => $format,
+ );
+ }
+
+ // Use the default format for this user if none was selected.
+ if (!isset($element['#format'])) {
+ $element['#format'] = filter_default_format($user);
+ }
+
+ $element['format']['format'] = array(
+ '#type' => 'select',
+ '#title' => t('Text format'),
+ '#options' => $options,
+ '#default_value' => $element['#format'],
+ '#access' => count($formats) > 1,
+ '#weight' => 10,
+ '#attributes' => array('class' => array('filter-list')),
+ '#parents' => array_merge($element['#parents'], array('format')),
+ );
+
+ $element['format']['help'] = array(
+ '#type' => 'container',
+ '#theme' => 'filter_tips_more_info',
+ '#attributes' => array('class' => array('filter-help')),
+ '#weight' => 0,
+ );
+
+ $all_formats = filter_formats();
+ $format_exists = isset($all_formats[$element['#format']]);
+ $user_has_access = isset($formats[$element['#format']]);
+ $user_is_admin = user_access('administer filters');
+
+ // If the stored format does not exist, administrators have to assign a new
+ // format.
+ if (!$format_exists && $user_is_admin) {
+ $element['format']['format']['#required'] = TRUE;
+ $element['format']['format']['#default_value'] = NULL;
+ // Force access to the format selector (it may have been denied above if
+ // the user only has access to a single format).
+ $element['format']['format']['#access'] = TRUE;
+ }
+ // Disable this widget, if the user is not allowed to use the stored format,
+ // or if the stored format does not exist. The 'administer filters' permission
+ // only grants access to the filter administration, not to all formats.
+ elseif (!$user_has_access || !$format_exists) {
+ // Overload default values into #value to make them unalterable.
+ $element['value']['#value'] = $element['value']['#default_value'];
+ $element['format']['format']['#value'] = $element['format']['format']['#default_value'];
+
+ // Prepend #pre_render callback to replace field value with user notice
+ // prior to rendering.
+ $element['value'] += array('#pre_render' => array());
+ array_unshift($element['value']['#pre_render'], 'filter_form_access_denied');
+
+ // Cosmetic adjustments.
+ if (isset($element['value']['#rows'])) {
+ $element['value']['#rows'] = 3;
+ }
+ $element['value']['#disabled'] = TRUE;
+ $element['value']['#resizable'] = FALSE;
+
+ // Hide the text format selector and any other child element (such as text
+ // field's summary).
+ foreach (element_children($element) as $key) {
+ if ($key != 'value') {
+ $element[$key]['#access'] = FALSE;
+ }
+ }
+ }
+
+ return $element;
+}
+
+/**
+ * Render API callback: Hides the field value of 'text_format' elements.
+ *
+ * To not break form processing and previews if a user does not have access to a
+ * stored text format, the expanded form elements in filter_process_format() are
+ * forced to take over the stored #default_values for 'value' and 'format'.
+ * However, to prevent the unfiltered, original #value from being displayed to
+ * the user, we replace it with a friendly notice here.
+ *
+ * @see filter_process_format()
+ */
+function filter_form_access_denied($element) {
+ $element['#value'] = t('This field has been disabled because you do not have sufficient permissions to edit it.');
+ return $element;
+}
+
+/**
+ * Returns HTML for a text format-enabled form element.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - element: A render element containing #children and #description.
+ *
+ * @ingroup themeable
+ */
+function theme_text_format_wrapper($variables) {
+ $element = $variables['element'];
+ $output = '<div class="text-format-wrapper">';
+ $output .= $element['#children'];
+ if (!empty($element['#description'])) {
+ $output .= '<div class="description">' . $element['#description'] . '</div>';
+ }
+ $output .= "</div>\n";
+
+ return $output;
+}
+
+/**
+ * Checks if a user has access to a particular text format.
+ *
+ * @param $format
+ * An object representing the text format.
+ * @param $account
+ * (optional) The user account to check access for; if omitted, the currently
+ * logged-in user is used. Defaults to NULL.
+ *
+ * @return
+ * Boolean TRUE if the user is allowed to access the given format.
+ */
+function filter_access($format, $account = NULL) {
+ global $user;
+ if (!isset($account)) {
+ $account = $user;
+ }
+ // Handle special cases up front. All users have access to the fallback
+ // format.
+ if ($format->format == filter_fallback_format()) {
+ return TRUE;
+ }
+ // Check the permission if one exists; otherwise, we have a non-existent
+ // format so we return FALSE.
+ $permission = filter_permission_name($format);
+ return !empty($permission) && user_access($permission, $account);
+}
+
+/**
+ * Retrieves the filter tips.
+ *
+ * @param $format_id
+ * The ID of the text format for which to retrieve tips, or -1 to return tips
+ * for all formats accessible to the current user.
+ * @param $long
+ * (optional) Boolean indicating whether the long form of tips should be
+ * returned. Defaults to FALSE.
+ *
+ * @return
+ * An associative array of filtering tips, keyed by filter name. Each
+ * filtering tip is an associative array with elements:
+ * - tip: Tip text.
+ * - id: Filter ID.
+ */
+function _filter_tips($format_id, $long = FALSE) {
+ global $user;
+
+ $formats = filter_formats($user);
+ $filter_info = filter_get_filters();
+
+ $tips = array();
+
+ // If only listing one format, extract it from the $formats array.
+ if ($format_id != -1) {
+ $formats = array($formats[$format_id]);
+ }
+
+ foreach ($formats as $format) {
+ $filters = filter_list_format($format->format);
+ $tips[$format->name] = array();
+ foreach ($filters as $name => $filter) {
+ if ($filter->status && isset($filter_info[$name]['tips callback']) && function_exists($filter_info[$name]['tips callback'])) {
+ $tip = $filter_info[$name]['tips callback']($filter, $format, $long);
+ if (isset($tip)) {
+ $tips[$format->name][$name] = array('tip' => $tip, 'id' => $name);
+ }
+ }
+ }
+ }
+
+ return $tips;
+}
+
+/**
+ * Parses an HTML snippet and returns it as a DOM object.
+ *
+ * This function loads the body part of a partial (X)HTML document and returns
+ * a full DOMDocument object that represents this document. You can use
+ * filter_dom_serialize() to serialize this DOMDocument back to a XHTML
+ * snippet.
+ *
+ * @param $text
+ * The partial (X)HTML snippet to load. Invalid mark-up will be corrected on
+ * import.
+ * @return
+ * A DOMDocument that represents the loaded (X)HTML snippet.
+ */
+function filter_dom_load($text) {
+ $dom_document = new DOMDocument();
+ // Ignore warnings during HTML soup loading.
+ @$dom_document->loadHTML('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /></head><body>' . $text . '</body></html>');
+
+ return $dom_document;
+}
+
+/**
+ * Converts a DOM object back to an HTML snippet.
+ *
+ * The function serializes the body part of a DOMDocument back to an XHTML
+ * snippet. The resulting XHTML snippet will be properly formatted to be
+ * compatible with HTML user agents.
+ *
+ * @param $dom_document
+ * A DOMDocument object to serialize, only the tags below
+ * the first <body> node will be converted.
+ *
+ * @return
+ * A valid (X)HTML snippet, as a string.
+ */
+function filter_dom_serialize($dom_document) {
+ $body_node = $dom_document->getElementsByTagName('body')->item(0);
+ $body_content = '';
+
+ foreach ($body_node->getElementsByTagName('script') as $node) {
+ filter_dom_serialize_escape_cdata_element($dom_document, $node);
+ }
+
+ foreach ($body_node->getElementsByTagName('style') as $node) {
+ filter_dom_serialize_escape_cdata_element($dom_document, $node, '/*', '*/');
+ }
+
+ foreach ($body_node->childNodes as $child_node) {
+ $body_content .= $dom_document->saveXML($child_node);
+ }
+ return preg_replace('|<([^> ]*)/>|i', '<$1 />', $body_content);
+}
+
+/**
+ * Adds comments around the <!CDATA section in a dom element.
+ *
+ * DOMDocument::loadHTML in filter_dom_load() makes CDATA sections from the
+ * contents of inline script and style tags. This can cause HTML 4 browsers to
+ * throw exceptions.
+ *
+ * This function attempts to solve the problem by creating a DocumentFragment
+ * and imitating the behavior in drupal_get_js(), commenting the CDATA tag.
+ *
+ * @param $dom_document
+ * The DOMDocument containing the $dom_element.
+ * @param $dom_element
+ * The element potentially containing a CDATA node.
+ * @param $comment_start
+ * (optional) A string to use as a comment start marker to escape the CDATA
+ * declaration. Defaults to '//'.
+ * @param $comment_end
+ * (optional) A string to use as a comment end marker to escape the CDATA
+ * declaration. Defaults to an empty string.
+ */
+function filter_dom_serialize_escape_cdata_element($dom_document, $dom_element, $comment_start = '//', $comment_end = '') {
+ foreach ($dom_element->childNodes as $node) {
+ if (get_class($node) == 'DOMCdataSection') {
+ // See drupal_get_js(). This code is more or less duplicated there.
+ $embed_prefix = "\n<!--{$comment_start}--><![CDATA[{$comment_start} ><!--{$comment_end}\n";
+ $embed_suffix = "\n{$comment_start}--><!]]>{$comment_end}\n";
+
+ // Prevent invalid cdata escaping as this would throw a DOM error.
+ // This is the same behavior as found in libxml2.
+ // Related W3C standard: http://www.w3.org/TR/REC-xml/#dt-cdsection
+ // Fix explanation: http://en.wikipedia.org/wiki/CDATA#Nesting
+ $data = str_replace(']]>', ']]]]><![CDATA[>', $node->data);
+
+ $fragment = $dom_document->createDocumentFragment();
+ $fragment->appendXML($embed_prefix . $data . $embed_suffix);
+ $dom_element->appendChild($fragment);
+ $dom_element->removeChild($node);
+ }
+ }
+}
+
+/**
+ * Returns HTML for a link to the more extensive filter tips.
+ *
+ * @ingroup themeable
+ */
+function theme_filter_tips_more_info() {
+ return '<p>' . l(t('More information about text formats'), 'filter/tips', array('attributes' => array('target' => '_blank'))) . '</p>';
+}
+
+/**
+ * Returns HTML for guidelines for a text format.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - format: An object representing a text format.
+ *
+ * @ingroup themeable
+ */
+function theme_filter_guidelines($variables) {
+ $format = $variables['format'];
+ $attributes['class'][] = 'filter-guidelines-item';
+ $attributes['class'][] = 'filter-guidelines-' . $format->format;
+ $output = '<div' . drupal_attributes($attributes) . '>';
+ $output .= '<h3>' . check_plain($format->name) . '</h3>';
+ $output .= theme('filter_tips', array('tips' => _filter_tips($format->format, FALSE)));
+ $output .= '</div>';
+ return $output;
+}
+
+/**
+ * @defgroup standard_filters Standard filters
+ * @{
+ * Filters implemented by the Filter module.
+ */
+
+/**
+ * Implements hook_filter_info().
+ */
+function filter_filter_info() {
+ $filters['filter_html'] = array(
+ 'title' => t('Limit allowed HTML tags'),
+ 'process callback' => '_filter_html',
+ 'settings callback' => '_filter_html_settings',
+ 'default settings' => array(
+ 'allowed_html' => '<a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>',
+ 'filter_html_help' => 1,
+ 'filter_html_nofollow' => 0,
+ ),
+ 'tips callback' => '_filter_html_tips',
+ 'weight' => -10,
+ );
+ $filters['filter_autop'] = array(
+ 'title' => t('Convert line breaks into HTML (i.e. <code>&lt;br&gt;</code> and <code>&lt;p&gt;</code>)'),
+ 'process callback' => '_filter_autop',
+ 'tips callback' => '_filter_autop_tips',
+ );
+ $filters['filter_url'] = array(
+ 'title' => t('Convert URLs into links'),
+ 'process callback' => '_filter_url',
+ 'settings callback' => '_filter_url_settings',
+ 'default settings' => array(
+ 'filter_url_length' => 72,
+ ),
+ 'tips callback' => '_filter_url_tips',
+ );
+ $filters['filter_htmlcorrector'] = array(
+ 'title' => t('Correct faulty and chopped off HTML'),
+ 'process callback' => '_filter_htmlcorrector',
+ 'weight' => 10,
+ );
+ $filters['filter_html_escape'] = array(
+ 'title' => t('Display any HTML as plain text'),
+ 'process callback' => '_filter_html_escape',
+ 'tips callback' => '_filter_html_escape_tips',
+ 'weight' => -10,
+ );
+ return $filters;
+}
+
+/**
+ * Implements callback_filter_settings().
+ *
+ * Filter settings callback for the HTML content filter.
+ */
+function _filter_html_settings($form, &$form_state, $filter, $format, $defaults) {
+ $filter->settings += $defaults;
+
+ $settings['allowed_html'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Allowed HTML tags'),
+ '#default_value' => $filter->settings['allowed_html'],
+ '#maxlength' => 1024,
+ '#description' => t('A list of HTML tags that can be used. JavaScript event attributes, JavaScript URLs, and CSS are always stripped.'),
+ );
+ $settings['filter_html_help'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Display basic HTML help in long filter tips'),
+ '#default_value' => $filter->settings['filter_html_help'],
+ );
+ $settings['filter_html_nofollow'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Add rel="nofollow" to all links'),
+ '#default_value' => $filter->settings['filter_html_nofollow'],
+ );
+ return $settings;
+}
+
+/**
+ * Implements callback_filter_process().
+ *
+ * Provides filtering of input into accepted HTML.
+ */
+function _filter_html($text, $filter) {
+ $allowed_tags = preg_split('/\s+|<|>/', $filter->settings['allowed_html'], -1, PREG_SPLIT_NO_EMPTY);
+ $text = filter_xss($text, $allowed_tags);
+
+ if ($filter->settings['filter_html_nofollow']) {
+ $html_dom = filter_dom_load($text);
+ $links = $html_dom->getElementsByTagName('a');
+ foreach ($links as $link) {
+ $link->setAttribute('rel', 'nofollow');
+ }
+ $text = filter_dom_serialize($html_dom);
+ }
+
+ return trim($text);
+}
+
+/**
+ * Implements callback_filter_tips().
+ *
+ * Provides help for the HTML filter.
+ *
+ * @see filter_filter_info()
+ */
+function _filter_html_tips($filter, $format, $long = FALSE) {
+ global $base_url;
+
+ if (!($allowed_html = $filter->settings['allowed_html'])) {
+ return;
+ }
+ $output = t('Allowed HTML tags: @tags', array('@tags' => $allowed_html));
+ if (!$long) {
+ return $output;
+ }
+
+ $output = '<p>' . $output . '</p>';
+ if (!$filter->settings['filter_html_help']) {
+ return $output;
+ }
+
+ $output .= '<p>' . t('This site allows HTML content. While learning all of HTML may feel intimidating, learning how to use a very small number of the most basic HTML "tags" is very easy. This table provides examples for each tag that is enabled on this site.') . '</p>';
+ $output .= '<p>' . t('For more information see W3C\'s <a href="@html-specifications">HTML Specifications</a> or use your favorite search engine to find other sites that explain HTML.', array('@html-specifications' => 'http://www.w3.org/TR/html/')) . '</p>';
+ $tips = array(
+ 'a' => array(t('Anchors are used to make links to other pages.'), '<a href="' . $base_url . '">' . check_plain(variable_get('site_name', 'Drupal')) . '</a>'),
+ 'br' => array(t('By default line break tags are automatically added, so use this tag to add additional ones. Use of this tag is different because it is not used with an open/close pair like all the others. Use the extra " /" inside the tag to maintain XHTML 1.0 compatibility'), t('Text with <br />line break')),
+ 'p' => array(t('By default paragraph tags are automatically added, so use this tag to add additional ones.'), '<p>' . t('Paragraph one.') . '</p> <p>' . t('Paragraph two.') . '</p>'),
+ 'strong' => array(t('Strong', array(), array('context' => 'Font weight')), '<strong>' . t('Strong', array(), array('context' => 'Font weight')) . '</strong>'),
+ 'em' => array(t('Emphasized'), '<em>' . t('Emphasized') . '</em>'),
+ 'cite' => array(t('Cited'), '<cite>' . t('Cited') . '</cite>'),
+ 'code' => array(t('Coded text used to show programming source code'), '<code>' . t('Coded') . '</code>'),
+ 'b' => array(t('Bolded'), '<b>' . t('Bolded') . '</b>'),
+ 'u' => array(t('Underlined'), '<u>' . t('Underlined') . '</u>'),
+ 'i' => array(t('Italicized'), '<i>' . t('Italicized') . '</i>'),
+ 'sup' => array(t('Superscripted'), t('<sup>Super</sup>scripted')),
+ 'sub' => array(t('Subscripted'), t('<sub>Sub</sub>scripted')),
+ 'pre' => array(t('Preformatted'), '<pre>' . t('Preformatted') . '</pre>'),
+ 'abbr' => array(t('Abbreviation'), t('<abbr title="Abbreviation">Abbrev.</abbr>')),
+ 'acronym' => array(t('Acronym'), t('<acronym title="Three-Letter Acronym">TLA</acronym>')),
+ 'blockquote' => array(t('Block quoted'), '<blockquote>' . t('Block quoted') . '</blockquote>'),
+ 'q' => array(t('Quoted inline'), '<q>' . t('Quoted inline') . '</q>'),
+ // Assumes and describes tr, td, th.
+ 'table' => array(t('Table'), '<table> <tr><th>' . t('Table header') . '</th></tr> <tr><td>' . t('Table cell') . '</td></tr> </table>'),
+ 'tr' => NULL, 'td' => NULL, 'th' => NULL,
+ 'del' => array(t('Deleted'), '<del>' . t('Deleted') . '</del>'),
+ 'ins' => array(t('Inserted'), '<ins>' . t('Inserted') . '</ins>'),
+ // Assumes and describes li.
+ 'ol' => array(t('Ordered list - use the &lt;li&gt; to begin each list item'), '<ol> <li>' . t('First item') . '</li> <li>' . t('Second item') . '</li> </ol>'),
+ 'ul' => array(t('Unordered list - use the &lt;li&gt; to begin each list item'), '<ul> <li>' . t('First item') . '</li> <li>' . t('Second item') . '</li> </ul>'),
+ 'li' => NULL,
+ // Assumes and describes dt and dd.
+ 'dl' => array(t('Definition lists are similar to other HTML lists. &lt;dl&gt; begins the definition list, &lt;dt&gt; begins the definition term and &lt;dd&gt; begins the definition description.'), '<dl> <dt>' . t('First term') . '</dt> <dd>' . t('First definition') . '</dd> <dt>' . t('Second term') . '</dt> <dd>' . t('Second definition') . '</dd> </dl>'),
+ 'dt' => NULL, 'dd' => NULL,
+ 'h1' => array(t('Heading'), '<h1>' . t('Title') . '</h1>'),
+ 'h2' => array(t('Heading'), '<h2>' . t('Subtitle') . '</h2>'),
+ 'h3' => array(t('Heading'), '<h3>' . t('Subtitle three') . '</h3>'),
+ 'h4' => array(t('Heading'), '<h4>' . t('Subtitle four') . '</h4>'),
+ 'h5' => array(t('Heading'), '<h5>' . t('Subtitle five') . '</h5>'),
+ 'h6' => array(t('Heading'), '<h6>' . t('Subtitle six') . '</h6>')
+ );
+ $header = array(t('Tag Description'), t('You Type'), t('You Get'));
+ preg_match_all('/<([a-z0-9]+)[^a-z0-9]/i', $allowed_html, $out);
+ foreach ($out[1] as $tag) {
+ if (!empty($tips[$tag])) {
+ $rows[] = array(
+ array('data' => $tips[$tag][0], 'class' => array('description')),
+ array('data' => '<code>' . check_plain($tips[$tag][1]) . '</code>', 'class' => array('type')),
+ array('data' => $tips[$tag][1], 'class' => array('get'))
+ );
+ }
+ else {
+ $rows[] = array(
+ array('data' => t('No help provided for tag %tag.', array('%tag' => $tag)), 'class' => array('description'), 'colspan' => 3),
+ );
+ }
+ }
+ $output .= theme('table', array('header' => $header, 'rows' => $rows));
+
+ $output .= '<p>' . t('Most unusual characters can be directly entered without any problems.') . '</p>';
+ $output .= '<p>' . t('If you do encounter problems, try using HTML character entities. A common example looks like &amp;amp; for an ampersand &amp; character. For a full list of entities see HTML\'s <a href="@html-entities">entities</a> page. Some of the available characters include:', array('@html-entities' => 'http://www.w3.org/TR/html4/sgml/entities.html')) . '</p>';
+
+ $entities = array(
+ array(t('Ampersand'), '&amp;'),
+ array(t('Greater than'), '&gt;'),
+ array(t('Less than'), '&lt;'),
+ array(t('Quotation mark'), '&quot;'),
+ );
+ $header = array(t('Character Description'), t('You Type'), t('You Get'));
+ unset($rows);
+ foreach ($entities as $entity) {
+ $rows[] = array(
+ array('data' => $entity[0], 'class' => array('description')),
+ array('data' => '<code>' . check_plain($entity[1]) . '</code>', 'class' => array('type')),
+ array('data' => $entity[1], 'class' => array('get'))
+ );
+ }
+ $output .= theme('table', array('header' => $header, 'rows' => $rows));
+ return $output;
+}
+
+/**
+ * Implements callback_filter_settings().
+ *
+ * Provides settings for the URL filter.
+ *
+ * @see filter_filter_info()
+ */
+function _filter_url_settings($form, &$form_state, $filter, $format, $defaults) {
+ $filter->settings += $defaults;
+
+ $settings['filter_url_length'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Maximum link text length'),
+ '#default_value' => $filter->settings['filter_url_length'],
+ '#size' => 5,
+ '#maxlength' => 4,
+ '#field_suffix' => t('characters'),
+ '#description' => t('URLs longer than this number of characters will be truncated to prevent long strings that break formatting. The link itself will be retained; just the text portion of the link will be truncated.'),
+ '#element_validate' => array('element_validate_integer_positive'),
+ );
+ return $settings;
+}
+
+/**
+ * Implements callback_filter_process().
+ *
+ * Converts text into hyperlinks automatically.
+ *
+ * This filter identifies and makes clickable three types of "links".
+ * - URLs like http://example.com.
+ * - E-mail addresses like name@example.com.
+ * - Web addresses without the "http://" protocol defined, like www.example.com.
+ * Each type must be processed separately, as there is no one regular
+ * expression that could possibly match all of the cases in one pass.
+ */
+function _filter_url($text, $filter) {
+ // Tags to skip and not recurse into.
+ $ignore_tags = 'a|script|style|code|pre';
+
+ // Pass length to regexp callback.
+ _filter_url_trim(NULL, $filter->settings['filter_url_length']);
+
+ // Create an array which contains the regexps for each type of link.
+ // The key to the regexp is the name of a function that is used as
+ // callback function to process matches of the regexp. The callback function
+ // is to return the replacement for the match. The array is used and
+ // matching/replacement done below inside some loops.
+ $tasks = array();
+
+ // Prepare protocols pattern for absolute URLs.
+ // check_url() will replace any bad protocols with HTTP, so we need to support
+ // the identical list. While '//' is technically optional for MAILTO only,
+ // we cannot cleanly differ between protocols here without hard-coding MAILTO,
+ // so '//' is optional for all protocols.
+ // @see filter_xss_bad_protocol()
+ $protocols = variable_get('filter_allowed_protocols', array('ftp', 'http', 'https', 'irc', 'mailto', 'news', 'nntp', 'rtsp', 'sftp', 'ssh', 'tel', 'telnet', 'webcal'));
+ $protocols = implode(':(?://)?|', $protocols) . ':(?://)?';
+
+ // Prepare domain name pattern.
+ // The ICANN seems to be on track towards accepting more diverse top level
+ // domains, so this pattern has been "future-proofed" to allow for TLDs
+ // of length 2-64.
+ $domain = '(?:[A-Za-z0-9._+-]+\.)?[A-Za-z]{2,64}\b';
+ $ip = '(?:[0-9]{1,3}\.){3}[0-9]{1,3}';
+ $auth = '[a-zA-Z0-9:%_+*~#?&=.,/;-]+@';
+ $trail = '[a-zA-Z0-9:%_+*~#&\[\]=/;?!\.,-]*[a-zA-Z0-9:%_+*~#&\[\]=/;-]';
+
+ // Prepare pattern for optional trailing punctuation.
+ // Even these characters could have a valid meaning for the URL, such usage is
+ // rare compared to using a URL at the end of or within a sentence, so these
+ // trailing characters are optionally excluded.
+ $punctuation = '[\.,?!]*?';
+
+ // Match absolute URLs.
+ $url_pattern = "(?:$auth)?(?:$domain|$ip)/?(?:$trail)?";
+ $pattern = "`((?:$protocols)(?:$url_pattern))($punctuation)`";
+ $tasks['_filter_url_parse_full_links'] = $pattern;
+
+ // Match e-mail addresses.
+ $url_pattern = "[A-Za-z0-9._-]{1,254}@(?:$domain)";
+ $pattern = "`($url_pattern)`";
+ $tasks['_filter_url_parse_email_links'] = $pattern;
+
+ // Match www domains.
+ $url_pattern = "www\.(?:$domain)/?(?:$trail)?";
+ $pattern = "`($url_pattern)($punctuation)`";
+ $tasks['_filter_url_parse_partial_links'] = $pattern;
+
+ // Each type of URL needs to be processed separately. The text is joined and
+ // re-split after each task, since all injected HTML tags must be correctly
+ // protected before the next task.
+ foreach ($tasks as $task => $pattern) {
+ // HTML comments need to be handled separately, as they may contain HTML
+ // markup, especially a '>'. Therefore, remove all comment contents and add
+ // them back later.
+ _filter_url_escape_comments('', TRUE);
+ $text = preg_replace_callback('`<!--(.*?)-->`s', '_filter_url_escape_comments', $text);
+
+ // Split at all tags; ensures that no tags or attributes are processed.
+ $chunks = preg_split('/(<.+?>)/is', $text, -1, PREG_SPLIT_DELIM_CAPTURE);
+ // PHP ensures that the array consists of alternating delimiters and
+ // literals, and begins and ends with a literal (inserting NULL as
+ // required). Therefore, the first chunk is always text:
+ $chunk_type = 'text';
+ // If a tag of $ignore_tags is found, it is stored in $open_tag and only
+ // removed when the closing tag is found. Until the closing tag is found,
+ // no replacements are made.
+ $open_tag = '';
+
+ for ($i = 0; $i < count($chunks); $i++) {
+ if ($chunk_type == 'text') {
+ // Only process this text if there are no unclosed $ignore_tags.
+ if ($open_tag == '') {
+ // If there is a match, inject a link into this chunk via the callback
+ // function contained in $task.
+ $chunks[$i] = preg_replace_callback($pattern, $task, $chunks[$i]);
+ }
+ // Text chunk is done, so next chunk must be a tag.
+ $chunk_type = 'tag';
+ }
+ else {
+ // Only process this tag if there are no unclosed $ignore_tags.
+ if ($open_tag == '') {
+ // Check whether this tag is contained in $ignore_tags.
+ if (preg_match("`<($ignore_tags)(?:\s|>)`i", $chunks[$i], $matches)) {
+ $open_tag = $matches[1];
+ }
+ }
+ // Otherwise, check whether this is the closing tag for $open_tag.
+ else {
+ if (preg_match("`<\/$open_tag>`i", $chunks[$i], $matches)) {
+ $open_tag = '';
+ }
+ }
+ // Tag chunk is done, so next chunk must be text.
+ $chunk_type = 'text';
+ }
+ }
+
+ $text = implode($chunks);
+ // Revert back to the original comment contents
+ _filter_url_escape_comments('', FALSE);
+ $text = preg_replace_callback('`<!--(.*?)-->`', '_filter_url_escape_comments', $text);
+ }
+
+ return $text;
+}
+
+/**
+ * Makes links out of absolute URLs.
+ *
+ * Callback for preg_replace_callback() within _filter_url().
+ */
+function _filter_url_parse_full_links($match) {
+ // The $i:th parenthesis in the regexp contains the URL.
+ $i = 1;
+
+ $match[$i] = decode_entities($match[$i]);
+ $caption = check_plain(_filter_url_trim($match[$i]));
+ $match[$i] = check_plain($match[$i]);
+ return '<a href="' . $match[$i] . '">' . $caption . '</a>' . $match[$i + 1];
+}
+
+/**
+ * Makes links out of e-mail addresses.
+ *
+ * Callback for preg_replace_callback() within _filter_url().
+ */
+function _filter_url_parse_email_links($match) {
+ // The $i:th parenthesis in the regexp contains the URL.
+ $i = 0;
+
+ $match[$i] = decode_entities($match[$i]);
+ $caption = check_plain(_filter_url_trim($match[$i]));
+ $match[$i] = check_plain($match[$i]);
+ return '<a href="mailto:' . $match[$i] . '">' . $caption . '</a>';
+}
+
+/**
+ * Makes links out of domain names starting with "www."
+ *
+ * Callback for preg_replace_callback() within _filter_url().
+ */
+function _filter_url_parse_partial_links($match) {
+ // The $i:th parenthesis in the regexp contains the URL.
+ $i = 1;
+
+ $match[$i] = decode_entities($match[$i]);
+ $caption = check_plain(_filter_url_trim($match[$i]));
+ $match[$i] = check_plain($match[$i]);
+ return '<a href="http://' . $match[$i] . '">' . $caption . '</a>' . $match[$i + 1];
+}
+
+/**
+ * Escapes the contents of HTML comments.
+ *
+ * Callback for preg_replace_callback() within _filter_url().
+ *
+ * @param $match
+ * An array containing matches to replace from preg_replace_callback(),
+ * whereas $match[1] is expected to contain the content to be filtered.
+ * @param $escape
+ * (optional) A Boolean indicating whether to escape (TRUE) or unescape
+ * comments (FALSE). Defaults to NULL, indicating neither. If TRUE, statically
+ * cached $comments are reset.
+ */
+function _filter_url_escape_comments($match, $escape = NULL) {
+ static $mode, $comments = array();
+
+ if (isset($escape)) {
+ $mode = $escape;
+ if ($escape){
+ $comments = array();
+ }
+ return;
+ }
+
+ // Replace all HTML coments with a '<!-- [hash] -->' placeholder.
+ if ($mode) {
+ $content = $match[1];
+ $hash = md5($content);
+ $comments[$hash] = $content;
+ return "<!-- $hash -->";
+ }
+ // Or replace placeholders with actual comment contents.
+ else {
+ $hash = $match[1];
+ $hash = trim($hash);
+ $content = $comments[$hash];
+ return "<!--$content-->";
+ }
+}
+
+/**
+ * Shortens long URLs to http://www.example.com/long/url...
+ */
+function _filter_url_trim($text, $length = NULL) {
+ static $_length;
+ if ($length !== NULL) {
+ $_length = $length;
+ }
+
+ // Use +3 for '...' string length.
+ if ($_length && strlen($text) > $_length + 3) {
+ $text = substr($text, 0, $_length) . '...';
+ }
+
+ return $text;
+}
+
+/**
+ * Implements callback_filter_tips().
+ *
+ * Provides help for the URL filter.
+ *
+ * @see filter_filter_info()
+ */
+function _filter_url_tips($filter, $format, $long = FALSE) {
+ return t('Web page addresses and e-mail addresses turn into links automatically.');
+}
+
+/**
+ * Implements callback_filter_process().
+ *
+ * Scans the input and makes sure that HTML tags are properly closed.
+ */
+function _filter_htmlcorrector($text) {
+ return filter_dom_serialize(filter_dom_load($text));
+}
+
+/**
+ * Implements callback_filter_process().
+ *
+ * Converts line breaks into <p> and <br> in an intelligent fashion.
+ *
+ * Based on: http://photomatt.net/scripts/autop
+ */
+function _filter_autop($text) {
+ // All block level tags
+ $block = '(?:table|thead|tfoot|caption|colgroup|tbody|tr|td|th|div|dl|dd|dt|ul|ol|li|pre|select|form|blockquote|address|p|h[1-6]|hr)';
+
+ // Split at opening and closing PRE, SCRIPT, STYLE, OBJECT, IFRAME tags
+ // and comments. We don't apply any processing to the contents of these tags
+ // to avoid messing up code. We look for matched pairs and allow basic
+ // nesting. For example:
+ // "processed <pre> ignored <script> ignored </script> ignored </pre> processed"
+ $chunks = preg_split('@(<!--.*?-->|</?(?:pre|script|style|object|iframe|!--)[^>]*>)@i', $text, -1, PREG_SPLIT_DELIM_CAPTURE);
+ // Note: PHP ensures the array consists of alternating delimiters and literals
+ // and begins and ends with a literal (inserting NULL as required).
+ $ignore = FALSE;
+ $ignoretag = '';
+ $output = '';
+ foreach ($chunks as $i => $chunk) {
+ if ($i % 2) {
+ $comment = (substr($chunk, 0, 4) == '<!--');
+ if ($comment) {
+ // Nothing to do, this is a comment.
+ $output .= $chunk;
+ continue;
+ }
+ // Opening or closing tag?
+ $open = ($chunk[1] != '/');
+ list($tag) = preg_split('/[ >]/', substr($chunk, 2 - $open), 2);
+ if (!$ignore) {
+ if ($open) {
+ $ignore = TRUE;
+ $ignoretag = $tag;
+ }
+ }
+ // Only allow a matching tag to close it.
+ elseif (!$open && $ignoretag == $tag) {
+ $ignore = FALSE;
+ $ignoretag = '';
+ }
+ }
+ elseif (!$ignore) {
+ $chunk = preg_replace('|\n*$|', '', $chunk) . "\n\n"; // just to make things a little easier, pad the end
+ $chunk = preg_replace('|<br />\s*<br />|', "\n\n", $chunk);
+ $chunk = preg_replace('!(<' . $block . '[^>]*>)!', "\n$1", $chunk); // Space things out a little
+ $chunk = preg_replace('!(</' . $block . '>)!', "$1\n\n", $chunk); // Space things out a little
+ $chunk = preg_replace("/\n\n+/", "\n\n", $chunk); // take care of duplicates
+ $chunk = preg_replace('/^\n|\n\s*\n$/', '', $chunk);
+ $chunk = '<p>' . preg_replace('/\n\s*\n\n?(.)/', "</p>\n<p>$1", $chunk) . "</p>\n"; // make paragraphs, including one at the end
+ $chunk = preg_replace("|<p>(<li.+?)</p>|", "$1", $chunk); // problem with nested lists
+ $chunk = preg_replace('|<p><blockquote([^>]*)>|i', "<blockquote$1><p>", $chunk);
+ $chunk = str_replace('</blockquote></p>', '</p></blockquote>', $chunk);
+ $chunk = preg_replace('|<p>\s*</p>\n?|', '', $chunk); // under certain strange conditions it could create a P of entirely whitespace
+ $chunk = preg_replace('!<p>\s*(</?' . $block . '[^>]*>)!', "$1", $chunk);
+ $chunk = preg_replace('!(</?' . $block . '[^>]*>)\s*</p>!', "$1", $chunk);
+ $chunk = preg_replace('|(?<!<br />)\s*\n|', "<br />\n", $chunk); // make line breaks
+ $chunk = preg_replace('!(</?' . $block . '[^>]*>)\s*<br />!', "$1", $chunk);
+ $chunk = preg_replace('!<br />(\s*</?(?:p|li|div|th|pre|td|ul|ol)>)!', '$1', $chunk);
+ $chunk = preg_replace('/&([^#])(?![A-Za-z0-9]{1,8};)/', '&amp;$1', $chunk);
+ }
+ $output .= $chunk;
+ }
+ return $output;
+}
+
+/**
+ * Implements callback_filter_tips().
+ *
+ * Provides help for the auto-paragraph filter.
+ *
+ * @see filter_filter_info()
+ */
+function _filter_autop_tips($filter, $format, $long = FALSE) {
+ if ($long) {
+ return t('Lines and paragraphs are automatically recognized. The &lt;br /&gt; line break, &lt;p&gt; paragraph and &lt;/p&gt; close paragraph tags are inserted automatically. If paragraphs are not recognized simply add a couple blank lines.');
+ }
+ else {
+ return t('Lines and paragraphs break automatically.');
+ }
+}
+
+/**
+ * Implements callback_filter_process().
+ *
+ * Escapes all HTML tags, so they will be visible instead of being effective.
+ */
+function _filter_html_escape($text) {
+ return trim(check_plain($text));
+}
+
+/**
+ * Implements callback_filter_tips().
+ *
+ * Provides help for the HTML escaping filter.
+ *
+ * @see filter_filter_info()
+ */
+function _filter_html_escape_tips($filter, $format, $long = FALSE) {
+ return t('No HTML tags allowed.');
+}
+
+/**
+ * @} End of "Standard filters".
+ */
diff --git a/kolab.org/www/drupal-7.26/modules/filter/filter.pages.inc b/kolab.org/www/drupal-7.26/modules/filter/filter.pages.inc
new file mode 100644
index 0000000..50f8117
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/filter/filter.pages.inc
@@ -0,0 +1,92 @@
+<?php
+
+/**
+ * @file
+ * User page callbacks for the Filter module.
+ */
+
+/**
+ * Page callback: Displays a page with long filter tips.
+ *
+ * @return string
+ * An HTML-formatted string.
+ *
+ * @see filter_menu()
+ * @see theme_filter_tips()
+ */
+function filter_tips_long() {
+ $format_id = arg(2);
+ if ($format_id) {
+ $output = theme('filter_tips', array('tips' => _filter_tips($format_id, TRUE), 'long' => TRUE));
+ }
+ else {
+ $output = theme('filter_tips', array('tips' => _filter_tips(-1, TRUE), 'long' => TRUE));
+ }
+ return $output;
+}
+
+/**
+ * Returns HTML for a set of filter tips.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - tips: An array containing descriptions and a CSS ID in the form of
+ * 'module-name/filter-id' (only used when $long is TRUE) for each
+ * filter in one or more text formats. Example:
+ * @code
+ * array(
+ * 'Full HTML' => array(
+ * 0 => array(
+ * 'tip' => 'Web page addresses and e-mail addresses turn into links automatically.',
+ * 'id' => 'filter/2',
+ * ),
+ * ),
+ * );
+ * @endcode
+ * - long: (optional) Whether the passed-in filter tips contain extended
+ * explanations, i.e. intended to be output on the path 'filter/tips'
+ * (TRUE), or are in a short format, i.e. suitable to be displayed below a
+ * form element. Defaults to FALSE.
+ *
+ * @see _filter_tips()
+ * @ingroup themeable
+ */
+function theme_filter_tips($variables) {
+ $tips = $variables['tips'];
+ $long = $variables['long'];
+ $output = '';
+
+ $multiple = count($tips) > 1;
+ if ($multiple) {
+ $output = '<h2>' . t('Text Formats') . '</h2>';
+ }
+
+ if (count($tips)) {
+ if ($multiple) {
+ $output .= '<div class="compose-tips">';
+ }
+ foreach ($tips as $name => $tiplist) {
+ if ($multiple) {
+ $output .= '<div class="filter-type filter-' . drupal_html_class($name) . '">';
+ $output .= '<h3>' . $name . '</h3>';
+ }
+
+ if (count($tiplist) > 0) {
+ $output .= '<ul class="tips">';
+ foreach ($tiplist as $tip) {
+ $output .= '<li' . ($long ? ' id="filter-' . str_replace("/", "-", $tip['id']) . '">' : '>') . $tip['tip'] . '</li>';
+ }
+ $output .= '</ul>';
+ }
+
+ if ($multiple) {
+ $output .= '</div>';
+ }
+ }
+ if ($multiple) {
+ $output .= '</div>';
+ }
+ }
+
+ return $output;
+}
diff --git a/kolab.org/www/drupal-7.26/modules/filter/filter.test b/kolab.org/www/drupal-7.26/modules/filter/filter.test
new file mode 100644
index 0000000..cc0295b
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/filter/filter.test
@@ -0,0 +1,1972 @@
+<?php
+
+/**
+ * @file
+ * Tests for filter.module.
+ */
+
+/**
+ * Tests for text format and filter CRUD operations.
+ */
+class FilterCRUDTestCase extends DrupalWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Filter CRUD operations',
+ 'description' => 'Test creation, loading, updating, deleting of text formats and filters.',
+ 'group' => 'Filter',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('filter_test');
+ }
+
+ /**
+ * Tests CRUD operations for text formats and filters.
+ */
+ function testTextFormatCRUD() {
+ // Add a text format with minimum data only.
+ $format = new stdClass();
+ $format->format = 'empty_format';
+ $format->name = 'Empty format';
+ filter_format_save($format);
+ $this->verifyTextFormat($format);
+ $this->verifyFilters($format);
+
+ // Add another text format specifying all possible properties.
+ $format = new stdClass();
+ $format->format = 'custom_format';
+ $format->name = 'Custom format';
+ $format->filters = array(
+ 'filter_url' => array(
+ 'status' => 1,
+ 'settings' => array(
+ 'filter_url_length' => 30,
+ ),
+ ),
+ );
+ filter_format_save($format);
+ $this->verifyTextFormat($format);
+ $this->verifyFilters($format);
+
+ // Alter some text format properties and save again.
+ $format->name = 'Altered format';
+ $format->filters['filter_url']['status'] = 0;
+ $format->filters['filter_autop']['status'] = 1;
+ filter_format_save($format);
+ $this->verifyTextFormat($format);
+ $this->verifyFilters($format);
+
+ // Add a uncacheable filter and save again.
+ $format->filters['filter_test_uncacheable']['status'] = 1;
+ filter_format_save($format);
+ $this->verifyTextFormat($format);
+ $this->verifyFilters($format);
+
+ // Disable the text format.
+ filter_format_disable($format);
+
+ $db_format = db_query("SELECT * FROM {filter_format} WHERE format = :format", array(':format' => $format->format))->fetchObject();
+ $this->assertFalse($db_format->status, 'Database: Disabled text format is marked as disabled.');
+ $formats = filter_formats();
+ $this->assertTrue(!isset($formats[$format->format]), 'filter_formats: Disabled text format no longer exists.');
+ }
+
+ /**
+ * Verifies that a text format is properly stored.
+ */
+ function verifyTextFormat($format) {
+ $t_args = array('%format' => $format->name);
+ // Verify text format database record.
+ $db_format = db_select('filter_format', 'ff')
+ ->fields('ff')
+ ->condition('format', $format->format)
+ ->execute()
+ ->fetchObject();
+ $this->assertEqual($db_format->format, $format->format, format_string('Database: Proper format id for text format %format.', $t_args));
+ $this->assertEqual($db_format->name, $format->name, format_string('Database: Proper title for text format %format.', $t_args));
+ $this->assertEqual($db_format->cache, $format->cache, format_string('Database: Proper cache indicator for text format %format.', $t_args));
+ $this->assertEqual($db_format->weight, $format->weight, format_string('Database: Proper weight for text format %format.', $t_args));
+
+ // Verify filter_format_load().
+ $filter_format = filter_format_load($format->format);
+ $this->assertEqual($filter_format->format, $format->format, format_string('filter_format_load: Proper format id for text format %format.', $t_args));
+ $this->assertEqual($filter_format->name, $format->name, format_string('filter_format_load: Proper title for text format %format.', $t_args));
+ $this->assertEqual($filter_format->cache, $format->cache, format_string('filter_format_load: Proper cache indicator for text format %format.', $t_args));
+ $this->assertEqual($filter_format->weight, $format->weight, format_string('filter_format_load: Proper weight for text format %format.', $t_args));
+
+ // Verify the 'cache' text format property according to enabled filters.
+ $filter_info = filter_get_filters();
+ $filters = filter_list_format($filter_format->format);
+ $cacheable = TRUE;
+ foreach ($filters as $name => $filter) {
+ // If this filter is not cacheable, update $cacheable accordingly, so we
+ // can verify $format->cache after iterating over all filters.
+ if ($filter->status && isset($filter_info[$name]['cache']) && !$filter_info[$name]['cache']) {
+ $cacheable = FALSE;
+ break;
+ }
+ }
+ $this->assertEqual($filter_format->cache, $cacheable, 'Text format contains proper cache property.');
+ }
+
+ /**
+ * Verifies that filters are properly stored for a text format.
+ */
+ function verifyFilters($format) {
+ // Verify filter database records.
+ $filters = db_query("SELECT * FROM {filter} WHERE format = :format", array(':format' => $format->format))->fetchAllAssoc('name');
+ $format_filters = $format->filters;
+ foreach ($filters as $name => $filter) {
+ $t_args = array('%format' => $format->name, '%filter' => $name);
+
+ // Verify that filter status is properly stored.
+ $this->assertEqual($filter->status, $format_filters[$name]['status'], format_string('Database: Proper status for %filter in text format %format.', $t_args));
+
+ // Verify that filter settings were properly stored.
+ $this->assertEqual(unserialize($filter->settings), isset($format_filters[$name]['settings']) ? $format_filters[$name]['settings'] : array(), format_string('Database: Proper filter settings for %filter in text format %format.', $t_args));
+
+ // Verify that each filter has a module name assigned.
+ $this->assertTrue(!empty($filter->module), format_string('Database: Proper module name for %filter in text format %format.', $t_args));
+
+ // Remove the filter from the copy of saved $format to check whether all
+ // filters have been processed later.
+ unset($format_filters[$name]);
+ }
+ // Verify that all filters have been processed.
+ $this->assertTrue(empty($format_filters), 'Database contains values for all filters in the saved format.');
+
+ // Verify filter_list_format().
+ $filters = filter_list_format($format->format);
+ $format_filters = $format->filters;
+ foreach ($filters as $name => $filter) {
+ $t_args = array('%format' => $format->name, '%filter' => $name);
+
+ // Verify that filter status is properly stored.
+ $this->assertEqual($filter->status, $format_filters[$name]['status'], format_string('filter_list_format: Proper status for %filter in text format %format.', $t_args));
+
+ // Verify that filter settings were properly stored.
+ $this->assertEqual($filter->settings, isset($format_filters[$name]['settings']) ? $format_filters[$name]['settings'] : array(), format_string('filter_list_format: Proper filter settings for %filter in text format %format.', $t_args));
+
+ // Verify that each filter has a module name assigned.
+ $this->assertTrue(!empty($filter->module), format_string('filter_list_format: Proper module name for %filter in text format %format.', $t_args));
+
+ // Remove the filter from the copy of saved $format to check whether all
+ // filters have been processed later.
+ unset($format_filters[$name]);
+ }
+ // Verify that all filters have been processed.
+ $this->assertTrue(empty($format_filters), 'filter_list_format: Loaded filters contain values for all filters in the saved format.');
+ }
+}
+
+/**
+ * Tests the administrative functionality of the Filter module.
+ */
+class FilterAdminTestCase extends DrupalWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Filter administration functionality',
+ 'description' => 'Thoroughly test the administrative interface of the filter module.',
+ 'group' => 'Filter',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+
+ // Create users.
+ $filtered_html_format = filter_format_load('filtered_html');
+ $full_html_format = filter_format_load('full_html');
+ $this->admin_user = $this->drupalCreateUser(array(
+ 'administer filters',
+ filter_permission_name($filtered_html_format),
+ filter_permission_name($full_html_format),
+ ));
+
+ $this->web_user = $this->drupalCreateUser(array('create page content', 'edit own page content'));
+ $this->drupalLogin($this->admin_user);
+ }
+
+ /**
+ * Tests the format administration functionality.
+ */
+ function testFormatAdmin() {
+ // Add text format.
+ $this->drupalGet('admin/config/content/formats');
+ $this->clickLink('Add text format');
+ $format_id = drupal_strtolower($this->randomName());
+ $name = $this->randomName();
+ $edit = array(
+ 'format' => $format_id,
+ 'name' => $name,
+ );
+ $this->drupalPost(NULL, $edit, t('Save configuration'));
+
+ // Verify default weight of the text format.
+ $this->drupalGet('admin/config/content/formats');
+ $this->assertFieldByName("formats[$format_id][weight]", 0, 'Text format weight was saved.');
+
+ // Change the weight of the text format.
+ $edit = array(
+ "formats[$format_id][weight]" => 5,
+ );
+ $this->drupalPost('admin/config/content/formats', $edit, t('Save changes'));
+ $this->assertFieldByName("formats[$format_id][weight]", 5, 'Text format weight was saved.');
+
+ // Edit text format.
+ $this->drupalGet('admin/config/content/formats');
+ $this->assertLinkByHref('admin/config/content/formats/' . $format_id);
+ $this->drupalGet('admin/config/content/formats/' . $format_id);
+ $this->drupalPost(NULL, array(), t('Save configuration'));
+
+ // Verify that the custom weight of the text format has been retained.
+ $this->drupalGet('admin/config/content/formats');
+ $this->assertFieldByName("formats[$format_id][weight]", 5, 'Text format weight was retained.');
+
+ // Disable text format.
+ $this->assertLinkByHref('admin/config/content/formats/' . $format_id . '/disable');
+ $this->drupalGet('admin/config/content/formats/' . $format_id . '/disable');
+ $this->drupalPost(NULL, array(), t('Disable'));
+
+ // Verify that disabled text format no longer exists.
+ $this->drupalGet('admin/config/content/formats/' . $format_id);
+ $this->assertResponse(404, 'Disabled text format no longer exists.');
+
+ // Attempt to create a format of the same machine name as the disabled
+ // format but with a different human readable name.
+ $edit = array(
+ 'format' => $format_id,
+ 'name' => 'New format',
+ );
+ $this->drupalPost('admin/config/content/formats/add', $edit, t('Save configuration'));
+ $this->assertText('The machine-readable name is already in use. It must be unique.');
+
+ // Attempt to create a format of the same human readable name as the
+ // disabled format but with a different machine name.
+ $edit = array(
+ 'format' => 'new_format',
+ 'name' => $name,
+ );
+ $this->drupalPost('admin/config/content/formats/add', $edit, t('Save configuration'));
+ $this->assertRaw(t('Text format names must be unique. A format named %name already exists.', array(
+ '%name' => $name,
+ )));
+ }
+
+ /**
+ * Tests filter administration functionality.
+ */
+ function testFilterAdmin() {
+ // URL filter.
+ $first_filter = 'filter_url';
+ // Line filter.
+ $second_filter = 'filter_autop';
+
+ $filtered = 'filtered_html';
+ $full = 'full_html';
+ $plain = 'plain_text';
+
+ // Check that the fallback format exists and cannot be disabled.
+ $this->assertTrue($plain == filter_fallback_format(), 'The fallback format is set to plain text.');
+ $this->drupalGet('admin/config/content/formats');
+ $this->assertNoRaw('admin/config/content/formats/' . $plain . '/disable', 'Disable link for the fallback format not found.');
+ $this->drupalGet('admin/config/content/formats/' . $plain . '/disable');
+ $this->assertResponse(403, 'The fallback format cannot be disabled.');
+
+ // Verify access permissions to Full HTML format.
+ $this->assertTrue(filter_access(filter_format_load($full), $this->admin_user), 'Admin user may use Full HTML.');
+ $this->assertFalse(filter_access(filter_format_load($full), $this->web_user), 'Web user may not use Full HTML.');
+
+ // Add an additional tag.
+ $edit = array();
+ $edit['filters[filter_html][settings][allowed_html]'] = '<a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <quote>';
+ $this->drupalPost('admin/config/content/formats/' . $filtered, $edit, t('Save configuration'));
+ $this->assertFieldByName('filters[filter_html][settings][allowed_html]', $edit['filters[filter_html][settings][allowed_html]'], 'Allowed HTML tag added.');
+
+ $result = db_query('SELECT * FROM {cache_filter}')->fetchObject();
+ $this->assertFalse($result, 'Cache cleared.');
+
+ $elements = $this->xpath('//select[@name=:first]/following::select[@name=:second]', array(
+ ':first' => 'filters[' . $first_filter . '][weight]',
+ ':second' => 'filters[' . $second_filter . '][weight]',
+ ));
+ $this->assertTrue(!empty($elements), 'Order confirmed in admin interface.');
+
+ // Reorder filters.
+ $edit = array();
+ $edit['filters[' . $second_filter . '][weight]'] = 1;
+ $edit['filters[' . $first_filter . '][weight]'] = 2;
+ $this->drupalPost(NULL, $edit, t('Save configuration'));
+ $this->assertFieldByName('filters[' . $second_filter . '][weight]', 1, 'Order saved successfully.');
+ $this->assertFieldByName('filters[' . $first_filter . '][weight]', 2, 'Order saved successfully.');
+
+ $elements = $this->xpath('//select[@name=:first]/following::select[@name=:second]', array(
+ ':first' => 'filters[' . $second_filter . '][weight]',
+ ':second' => 'filters[' . $first_filter . '][weight]',
+ ));
+ $this->assertTrue(!empty($elements), 'Reorder confirmed in admin interface.');
+
+ $result = db_query('SELECT * FROM {filter} WHERE format = :format ORDER BY weight ASC', array(':format' => $filtered));
+ $filters = array();
+ foreach ($result as $filter) {
+ if ($filter->name == $second_filter || $filter->name == $first_filter) {
+ $filters[] = $filter;
+ }
+ }
+ $this->assertTrue(($filters[0]->name == $second_filter && $filters[1]->name == $first_filter), 'Order confirmed in database.');
+
+ // Add format.
+ $edit = array();
+ $edit['format'] = drupal_strtolower($this->randomName());
+ $edit['name'] = $this->randomName();
+ $edit['roles[' . DRUPAL_AUTHENTICATED_RID . ']'] = 1;
+ $edit['filters[' . $second_filter . '][status]'] = TRUE;
+ $edit['filters[' . $first_filter . '][status]'] = TRUE;
+ $this->drupalPost('admin/config/content/formats/add', $edit, t('Save configuration'));
+ $this->assertRaw(t('Added text format %format.', array('%format' => $edit['name'])), 'New filter created.');
+
+ drupal_static_reset('filter_formats');
+ $format = filter_format_load($edit['format']);
+ $this->assertNotNull($format, 'Format found in database.');
+
+ $this->assertFieldByName('roles[' . DRUPAL_AUTHENTICATED_RID . ']', '', 'Role found.');
+ $this->assertFieldByName('filters[' . $second_filter . '][status]', '', 'Line break filter found.');
+ $this->assertFieldByName('filters[' . $first_filter . '][status]', '', 'Url filter found.');
+
+ // Disable new filter.
+ $this->drupalPost('admin/config/content/formats/' . $format->format . '/disable', array(), t('Disable'));
+ $this->assertRaw(t('Disabled text format %format.', array('%format' => $edit['name'])), 'Format successfully disabled.');
+
+ // Allow authenticated users on full HTML.
+ $format = filter_format_load($full);
+ $edit = array();
+ $edit['roles[' . DRUPAL_ANONYMOUS_RID . ']'] = 0;
+ $edit['roles[' . DRUPAL_AUTHENTICATED_RID . ']'] = 1;
+ $this->drupalPost('admin/config/content/formats/' . $full, $edit, t('Save configuration'));
+ $this->assertRaw(t('The text format %format has been updated.', array('%format' => $format->name)), 'Full HTML format successfully updated.');
+
+ // Switch user.
+ $this->drupalLogout();
+ $this->drupalLogin($this->web_user);
+
+ $this->drupalGet('node/add/page');
+ $this->assertRaw('<option value="' . $full . '">Full HTML</option>', 'Full HTML filter accessible.');
+
+ // Use filtered HTML and see if it removes tags that are not allowed.
+ $body = '<em>' . $this->randomName() . '</em>';
+ $extra_text = 'text';
+ $text = $body . '<random>' . $extra_text . '</random>';
+
+ $edit = array();
+ $langcode = LANGUAGE_NONE;
+ $edit["title"] = $this->randomName();
+ $edit["body[$langcode][0][value]"] = $text;
+ $edit["body[$langcode][0][format]"] = $filtered;
+ $this->drupalPost('node/add/page', $edit, t('Save'));
+ $this->assertRaw(t('Basic page %title has been created.', array('%title' => $edit["title"])), 'Filtered node created.');
+
+ $node = $this->drupalGetNodeByTitle($edit["title"]);
+ $this->assertTrue($node, 'Node found in database.');
+
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertRaw($body . $extra_text, 'Filter removed invalid tag.');
+
+ // Use plain text and see if it escapes all tags, whether allowed or not.
+ $edit = array();
+ $edit["body[$langcode][0][format]"] = $plain;
+ $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertText(check_plain($text), 'The "Plain text" text format escapes all HTML tags.');
+
+ // Switch user.
+ $this->drupalLogout();
+ $this->drupalLogin($this->admin_user);
+
+ // Clean up.
+ // Allowed tags.
+ $edit = array();
+ $edit['filters[filter_html][settings][allowed_html]'] = '<a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>';
+ $this->drupalPost('admin/config/content/formats/' . $filtered, $edit, t('Save configuration'));
+ $this->assertFieldByName('filters[filter_html][settings][allowed_html]', $edit['filters[filter_html][settings][allowed_html]'], 'Changes reverted.');
+
+ // Full HTML.
+ $edit = array();
+ $edit['roles[' . DRUPAL_AUTHENTICATED_RID . ']'] = FALSE;
+ $this->drupalPost('admin/config/content/formats/' . $full, $edit, t('Save configuration'));
+ $this->assertRaw(t('The text format %format has been updated.', array('%format' => $format->name)), 'Full HTML format successfully reverted.');
+ $this->assertFieldByName('roles[' . DRUPAL_AUTHENTICATED_RID . ']', $edit['roles[' . DRUPAL_AUTHENTICATED_RID . ']'], 'Changes reverted.');
+
+ // Filter order.
+ $edit = array();
+ $edit['filters[' . $second_filter . '][weight]'] = 2;
+ $edit['filters[' . $first_filter . '][weight]'] = 1;
+ $this->drupalPost('admin/config/content/formats/' . $filtered, $edit, t('Save configuration'));
+ $this->assertFieldByName('filters[' . $second_filter . '][weight]', $edit['filters[' . $second_filter . '][weight]'], 'Changes reverted.');
+ $this->assertFieldByName('filters[' . $first_filter . '][weight]', $edit['filters[' . $first_filter . '][weight]'], 'Changes reverted.');
+ }
+
+ /**
+ * Tests the URL filter settings form is properly validated.
+ */
+ function testUrlFilterAdmin() {
+ // The form does not save with an invalid filter URL length.
+ $edit = array(
+ 'filters[filter_url][settings][filter_url_length]' => $this->randomName(4),
+ );
+ $this->drupalPost('admin/config/content/formats/filtered_html', $edit, t('Save configuration'));
+ $this->assertNoRaw(t('The text format %format has been updated.', array('%format' => 'Filtered HTML')));
+ }
+}
+
+/**
+ * Tests the filter format access functionality in the Filter module.
+ */
+class FilterFormatAccessTestCase extends DrupalWebTestCase {
+ /**
+ * A user with administrative permissions.
+ *
+ * @var object
+ */
+ protected $admin_user;
+
+ /**
+ * A user with 'administer filters' permission.
+ *
+ * @var object
+ */
+ protected $filter_admin_user;
+
+ /**
+ * A user with permission to create and edit own content.
+ *
+ * @var object
+ */
+ protected $web_user;
+
+ /**
+ * An object representing an allowed text format.
+ *
+ * @var object
+ */
+ protected $allowed_format;
+
+ /**
+ * An object representing a disallowed text format.
+ *
+ * @var object
+ */
+ protected $disallowed_format;
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Filter format access',
+ 'description' => 'Tests access to text formats.',
+ 'group' => 'Filter',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+
+ // Create a user who can administer text formats, but does not have
+ // specific permission to use any of them.
+ $this->filter_admin_user = $this->drupalCreateUser(array(
+ 'administer filters',
+ 'create page content',
+ 'edit any page content',
+ ));
+
+ // Create two text formats.
+ $this->drupalLogin($this->filter_admin_user);
+ $formats = array();
+ for ($i = 0; $i < 2; $i++) {
+ $edit = array(
+ 'format' => drupal_strtolower($this->randomName()),
+ 'name' => $this->randomName(),
+ );
+ $this->drupalPost('admin/config/content/formats/add', $edit, t('Save configuration'));
+ $this->resetFilterCaches();
+ $formats[] = filter_format_load($edit['format']);
+ }
+ list($this->allowed_format, $this->disallowed_format) = $formats;
+ $this->drupalLogout();
+
+ // Create a regular user with access to one of the formats.
+ $this->web_user = $this->drupalCreateUser(array(
+ 'create page content',
+ 'edit any page content',
+ filter_permission_name($this->allowed_format),
+ ));
+
+ // Create an administrative user who has access to use both formats.
+ $this->admin_user = $this->drupalCreateUser(array(
+ 'administer filters',
+ 'create page content',
+ 'edit any page content',
+ filter_permission_name($this->allowed_format),
+ filter_permission_name($this->disallowed_format),
+ ));
+ }
+
+ /**
+ * Tests the Filter format access permissions functionality.
+ */
+ function testFormatPermissions() {
+ // Make sure that a regular user only has access to the text format they
+ // were granted access to, as well to the fallback format.
+ $this->assertTrue(filter_access($this->allowed_format, $this->web_user), 'A regular user has access to a text format they were granted access to.');
+ $this->assertFalse(filter_access($this->disallowed_format, $this->web_user), 'A regular user does not have access to a text format they were not granted access to.');
+ $this->assertTrue(filter_access(filter_format_load(filter_fallback_format()), $this->web_user), 'A regular user has access to the fallback format.');
+
+ // Perform similar checks as above, but now against the entire list of
+ // available formats for this user.
+ $this->assertTrue(in_array($this->allowed_format->format, array_keys(filter_formats($this->web_user))), 'The allowed format appears in the list of available formats for a regular user.');
+ $this->assertFalse(in_array($this->disallowed_format->format, array_keys(filter_formats($this->web_user))), 'The disallowed format does not appear in the list of available formats for a regular user.');
+ $this->assertTrue(in_array(filter_fallback_format(), array_keys(filter_formats($this->web_user))), 'The fallback format appears in the list of available formats for a regular user.');
+
+ // Make sure that a regular user only has permission to use the format
+ // they were granted access to.
+ $this->assertTrue(user_access(filter_permission_name($this->allowed_format), $this->web_user), 'A regular user has permission to use the allowed text format.');
+ $this->assertFalse(user_access(filter_permission_name($this->disallowed_format), $this->web_user), 'A regular user does not have permission to use the disallowed text format.');
+
+ // Make sure that the allowed format appears on the node form and that
+ // the disallowed format does not.
+ $this->drupalLogin($this->web_user);
+ $this->drupalGet('node/add/page');
+ $langcode = LANGUAGE_NONE;
+ $elements = $this->xpath('//select[@name=:name]/option', array(
+ ':name' => "body[$langcode][0][format]",
+ ':option' => $this->allowed_format->format,
+ ));
+ $options = array();
+ foreach ($elements as $element) {
+ $options[(string) $element['value']] = $element;
+ }
+ $this->assertTrue(isset($options[$this->allowed_format->format]), 'The allowed text format appears as an option when adding a new node.');
+ $this->assertFalse(isset($options[$this->disallowed_format->format]), 'The disallowed text format does not appear as an option when adding a new node.');
+ $this->assertTrue(isset($options[filter_fallback_format()]), 'The fallback format appears as an option when adding a new node.');
+ }
+
+ /**
+ * Tests if text format is available to a role.
+ */
+ function testFormatRoles() {
+ // Get the role ID assigned to the regular user; it must be the maximum.
+ $rid = max(array_keys($this->web_user->roles));
+
+ // Check that this role appears in the list of roles that have access to an
+ // allowed text format, but does not appear in the list of roles that have
+ // access to a disallowed text format.
+ $this->assertTrue(in_array($rid, array_keys(filter_get_roles_by_format($this->allowed_format))), 'A role which has access to a text format appears in the list of roles that have access to that format.');
+ $this->assertFalse(in_array($rid, array_keys(filter_get_roles_by_format($this->disallowed_format))), 'A role which does not have access to a text format does not appear in the list of roles that have access to that format.');
+
+ // Check that the correct text format appears in the list of formats
+ // available to that role.
+ $this->assertTrue(in_array($this->allowed_format->format, array_keys(filter_get_formats_by_role($rid))), 'A text format which a role has access to appears in the list of formats available to that role.');
+ $this->assertFalse(in_array($this->disallowed_format->format, array_keys(filter_get_formats_by_role($rid))), 'A text format which a role does not have access to does not appear in the list of formats available to that role.');
+
+ // Check that the fallback format is always allowed.
+ $this->assertEqual(filter_get_roles_by_format(filter_format_load(filter_fallback_format())), user_roles(), 'All roles have access to the fallback format.');
+ $this->assertTrue(in_array(filter_fallback_format(), array_keys(filter_get_formats_by_role($rid))), 'The fallback format appears in the list of allowed formats for any role.');
+ }
+
+ /**
+ * Tests editing a page using a disallowed text format.
+ *
+ * Verifies that regular users and administrators are able to edit a page, but
+ * not allowed to change the fields which use an inaccessible text format.
+ * Also verifies that fields which use a text format that does not exist can
+ * be edited by administrators only, but that the administrator is forced to
+ * choose a new format before saving the page.
+ */
+ function testFormatWidgetPermissions() {
+ $langcode = LANGUAGE_NONE;
+ $title_key = "title";
+ $body_value_key = "body[$langcode][0][value]";
+ $body_format_key = "body[$langcode][0][format]";
+
+ // Create node to edit.
+ $this->drupalLogin($this->admin_user);
+ $edit = array();
+ $edit['title'] = $this->randomName(8);
+ $edit[$body_value_key] = $this->randomName(16);
+ $edit[$body_format_key] = $this->disallowed_format->format;
+ $this->drupalPost('node/add/page', $edit, t('Save'));
+ $node = $this->drupalGetNodeByTitle($edit['title']);
+
+ // Try to edit with a less privileged user.
+ $this->drupalLogin($this->web_user);
+ $this->drupalGet('node/' . $node->nid);
+ $this->clickLink(t('Edit'));
+
+ // Verify that body field is read-only and contains replacement value.
+ $this->assertFieldByXPath("//textarea[@name='$body_value_key' and @disabled='disabled']", t('This field has been disabled because you do not have sufficient permissions to edit it.'), 'Text format access denied message found.');
+
+ // Verify that title can be changed, but preview displays original body.
+ $new_edit = array();
+ $new_edit['title'] = $this->randomName(8);
+ $this->drupalPost(NULL, $new_edit, t('Preview'));
+ $this->assertText($edit[$body_value_key], 'Old body found in preview.');
+
+ // Save and verify that only the title was changed.
+ $this->drupalPost(NULL, $new_edit, t('Save'));
+ $this->assertNoText($edit['title'], 'Old title not found.');
+ $this->assertText($new_edit['title'], 'New title found.');
+ $this->assertText($edit[$body_value_key], 'Old body found.');
+
+ // Check that even an administrator with "administer filters" permission
+ // cannot edit the body field if they do not have specific permission to
+ // use its stored format. (This must be disallowed so that the
+ // administrator is never forced to switch the text format to something
+ // else.)
+ $this->drupalLogin($this->filter_admin_user);
+ $this->drupalGet('node/' . $node->nid . '/edit');
+ $this->assertFieldByXPath("//textarea[@name='$body_value_key' and @disabled='disabled']", t('This field has been disabled because you do not have sufficient permissions to edit it.'), 'Text format access denied message found.');
+
+ // Disable the text format used above.
+ filter_format_disable($this->disallowed_format);
+ $this->resetFilterCaches();
+
+ // Log back in as the less privileged user and verify that the body field
+ // is still disabled, since the less privileged user should not be able to
+ // edit content that does not have an assigned format.
+ $this->drupalLogin($this->web_user);
+ $this->drupalGet('node/' . $node->nid . '/edit');
+ $this->assertFieldByXPath("//textarea[@name='$body_value_key' and @disabled='disabled']", t('This field has been disabled because you do not have sufficient permissions to edit it.'), 'Text format access denied message found.');
+
+ // Log back in as the filter administrator and verify that the body field
+ // can be edited.
+ $this->drupalLogin($this->filter_admin_user);
+ $this->drupalGet('node/' . $node->nid . '/edit');
+ $this->assertNoFieldByXPath("//textarea[@name='$body_value_key' and @disabled='disabled']", NULL, 'Text format access denied message not found.');
+ $this->assertFieldByXPath("//select[@name='$body_format_key']", NULL, 'Text format selector found.');
+
+ // Verify that trying to save the node without selecting a new text format
+ // produces an error message, and does not result in the node being saved.
+ $old_title = $new_edit['title'];
+ $new_title = $this->randomName(8);
+ $edit = array('title' => $new_title);
+ $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
+ $this->assertText(t('!name field is required.', array('!name' => t('Text format'))), 'Error message is displayed.');
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertText($old_title, 'Old title found.');
+ $this->assertNoText($new_title, 'New title not found.');
+
+ // Now select a new text format and make sure the node can be saved.
+ $edit[$body_format_key] = filter_fallback_format();
+ $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
+ $this->assertUrl('node/' . $node->nid);
+ $this->assertText($new_title, 'New title found.');
+ $this->assertNoText($old_title, 'Old title not found.');
+
+ // Switch the text format to a new one, then disable that format and all
+ // other formats on the site (leaving only the fallback format).
+ $this->drupalLogin($this->admin_user);
+ $edit = array($body_format_key => $this->allowed_format->format);
+ $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
+ $this->assertUrl('node/' . $node->nid);
+ foreach (filter_formats() as $format) {
+ if ($format->format != filter_fallback_format()) {
+ filter_format_disable($format);
+ }
+ }
+
+ // Since there is now only one available text format, the widget for
+ // selecting a text format would normally not display when the content is
+ // edited. However, we need to verify that the filter administrator still
+ // is forced to make a conscious choice to reassign the text to a different
+ // format.
+ $this->drupalLogin($this->filter_admin_user);
+ $old_title = $new_title;
+ $new_title = $this->randomName(8);
+ $edit = array('title' => $new_title);
+ $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
+ $this->assertText(t('!name field is required.', array('!name' => t('Text format'))), 'Error message is displayed.');
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertText($old_title, 'Old title found.');
+ $this->assertNoText($new_title, 'New title not found.');
+ $edit[$body_format_key] = filter_fallback_format();
+ $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
+ $this->assertUrl('node/' . $node->nid);
+ $this->assertText($new_title, 'New title found.');
+ $this->assertNoText($old_title, 'Old title not found.');
+ }
+
+ /**
+ * Rebuilds text format and permission caches in the thread running the tests.
+ */
+ protected function resetFilterCaches() {
+ filter_formats_reset();
+ $this->checkPermissions(array(), TRUE);
+ }
+}
+
+/**
+ * Tests the default filter functionality in the Filter module.
+ */
+class FilterDefaultFormatTestCase extends DrupalWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Default text format functionality',
+ 'description' => 'Test the default text formats for different users.',
+ 'group' => 'Filter',
+ );
+ }
+
+ /**
+ * Tests if the default text format is accessible to users.
+ */
+ function testDefaultTextFormats() {
+ // Create two text formats, and two users. The first user has access to
+ // both formats, but the second user only has access to the second one.
+ $admin_user = $this->drupalCreateUser(array('administer filters'));
+ $this->drupalLogin($admin_user);
+ $formats = array();
+ for ($i = 0; $i < 2; $i++) {
+ $edit = array(
+ 'format' => drupal_strtolower($this->randomName()),
+ 'name' => $this->randomName(),
+ );
+ $this->drupalPost('admin/config/content/formats/add', $edit, t('Save configuration'));
+ $this->resetFilterCaches();
+ $formats[] = filter_format_load($edit['format']);
+ }
+ list($first_format, $second_format) = $formats;
+ $first_user = $this->drupalCreateUser(array(filter_permission_name($first_format), filter_permission_name($second_format)));
+ $second_user = $this->drupalCreateUser(array(filter_permission_name($second_format)));
+
+ // Adjust the weights so that the first and second formats (in that order)
+ // are the two lowest weighted formats available to any user.
+ $minimum_weight = db_query("SELECT MIN(weight) FROM {filter_format}")->fetchField();
+ $edit = array();
+ $edit['formats[' . $first_format->format . '][weight]'] = $minimum_weight - 2;
+ $edit['formats[' . $second_format->format . '][weight]'] = $minimum_weight - 1;
+ $this->drupalPost('admin/config/content/formats', $edit, t('Save changes'));
+ $this->resetFilterCaches();
+
+ // Check that each user's default format is the lowest weighted format that
+ // the user has access to.
+ $this->assertEqual(filter_default_format($first_user), $first_format->format, "The first user's default format is the lowest weighted format that the user has access to.");
+ $this->assertEqual(filter_default_format($second_user), $second_format->format, "The second user's default format is the lowest weighted format that the user has access to, and is different than the first user's.");
+
+ // Reorder the two formats, and check that both users now have the same
+ // default.
+ $edit = array();
+ $edit['formats[' . $second_format->format . '][weight]'] = $minimum_weight - 3;
+ $this->drupalPost('admin/config/content/formats', $edit, t('Save changes'));
+ $this->resetFilterCaches();
+ $this->assertEqual(filter_default_format($first_user), filter_default_format($second_user), 'After the formats are reordered, both users have the same default format.');
+ }
+
+ /**
+ * Rebuilds text format and permission caches in the thread running the tests.
+ */
+ protected function resetFilterCaches() {
+ filter_formats_reset();
+ $this->checkPermissions(array(), TRUE);
+ }
+}
+
+/**
+ * Tests the behavior of check_markup() when it is called without text format.
+ */
+class FilterNoFormatTestCase extends DrupalWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Unassigned text format functionality',
+ 'description' => 'Test the behavior of check_markup() when it is called without a text format.',
+ 'group' => 'Filter',
+ );
+ }
+
+ /**
+ * Tests text without format.
+ *
+ * Tests if text with no format is filtered the same way as text in the
+ * fallback format.
+ */
+ function testCheckMarkupNoFormat() {
+ // Create some text. Include some HTML and line breaks, so we get a good
+ // test of the filtering that is applied to it.
+ $text = "<strong>" . $this->randomName(32) . "</strong>\n\n<div>" . $this->randomName(32) . "</div>";
+
+ // Make sure that when this text is run through check_markup() with no text
+ // format, it is filtered as though it is in the fallback format.
+ $this->assertEqual(check_markup($text), check_markup($text, filter_fallback_format()), 'Text with no format is filtered the same as text in the fallback format.');
+ }
+}
+
+/**
+ * Security tests for missing/vanished text formats or filters.
+ */
+class FilterSecurityTestCase extends DrupalWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Security',
+ 'description' => 'Test the behavior of check_markup() when a filter or text format vanishes.',
+ 'group' => 'Filter',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('php', 'filter_test');
+ $this->admin_user = $this->drupalCreateUser(array('administer modules', 'administer filters', 'administer site configuration'));
+ $this->drupalLogin($this->admin_user);
+ }
+
+ /**
+ * Tests removal of filtered content when an active filter is disabled.
+ *
+ * Tests that filtered content is emptied when an actively used filter module
+ * is disabled.
+ */
+ function testDisableFilterModule() {
+ // Create a new node.
+ $node = $this->drupalCreateNode(array('promote' => 1));
+ $body_raw = $node->body[LANGUAGE_NONE][0]['value'];
+ $format_id = $node->body[LANGUAGE_NONE][0]['format'];
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertText($body_raw, 'Node body found.');
+
+ // Enable the filter_test_replace filter.
+ $edit = array(
+ 'filters[filter_test_replace][status]' => 1,
+ );
+ $this->drupalPost('admin/config/content/formats/' . $format_id, $edit, t('Save configuration'));
+
+ // Verify that filter_test_replace filter replaced the content.
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertNoText($body_raw, 'Node body not found.');
+ $this->assertText('Filter: Testing filter', 'Testing filter output found.');
+
+ // Disable the text format entirely.
+ $this->drupalPost('admin/config/content/formats/' . $format_id . '/disable', array(), t('Disable'));
+
+ // Verify that the content is empty, because the text format does not exist.
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertNoText($body_raw, 'Node body not found.');
+ }
+}
+
+/**
+ * Unit tests for core filters.
+ */
+class FilterUnitTestCase extends DrupalUnitTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Filter module filters',
+ 'description' => 'Tests Filter module filters individually.',
+ 'group' => 'Filter',
+ );
+ }
+
+ /**
+ * Tests the line break filter.
+ */
+ function testLineBreakFilter() {
+ // Setup dummy filter object.
+ $filter = new stdClass();
+ $filter->callback = '_filter_autop';
+
+ // Since the line break filter naturally needs plenty of newlines in test
+ // strings and expectations, we're using "\n" instead of regular newlines
+ // here.
+ $tests = array(
+ // Single line breaks should be changed to <br /> tags, while paragraphs
+ // separated with double line breaks should be enclosed with <p></p> tags.
+ "aaa\nbbb\n\nccc" => array(
+ "<p>aaa<br />\nbbb</p>\n<p>ccc</p>" => TRUE,
+ ),
+ // Skip contents of certain block tags entirely.
+ "<script>aaa\nbbb\n\nccc</script>
+<style>aaa\nbbb\n\nccc</style>
+<pre>aaa\nbbb\n\nccc</pre>
+<object>aaa\nbbb\n\nccc</object>
+<iframe>aaa\nbbb\n\nccc</iframe>
+" => array(
+ "<script>aaa\nbbb\n\nccc</script>" => TRUE,
+ "<style>aaa\nbbb\n\nccc</style>" => TRUE,
+ "<pre>aaa\nbbb\n\nccc</pre>" => TRUE,
+ "<object>aaa\nbbb\n\nccc</object>" => TRUE,
+ "<iframe>aaa\nbbb\n\nccc</iframe>" => TRUE,
+ ),
+ // Skip comments entirely.
+ "One. <!-- comment --> Two.\n<!--\nThree.\n-->\n" => array(
+ '<!-- comment -->' => TRUE,
+ "<!--\nThree.\n-->" => TRUE,
+ ),
+ // Resulting HTML should produce matching paragraph tags.
+ '<p><div> </div></p>' => array(
+ "<p>\n<div> </div>\n</p>" => TRUE,
+ ),
+ '<div><p> </p></div>' => array(
+ "<div>\n</div>" => TRUE,
+ ),
+ '<blockquote><pre>aaa</pre></blockquote>' => array(
+ "<blockquote><pre>aaa</pre></blockquote>" => TRUE,
+ ),
+ "<pre>aaa\nbbb\nccc</pre>\nddd\neee" => array(
+ "<pre>aaa\nbbb\nccc</pre>" => TRUE,
+ "<p>ddd<br />\neee</p>" => TRUE,
+ ),
+ // Comments remain unchanged and subsequent lines/paragraphs are
+ // transformed normally.
+ "aaa<!--comment-->\n\nbbb\n\nccc\n\nddd<!--comment\nwith linebreak-->\n\neee\n\nfff" => array(
+ "<p>aaa</p>\n<!--comment--><p>\nbbb</p>\n<p>ccc</p>\n<p>ddd</p>" => TRUE,
+ "<!--comment\nwith linebreak--><p>\neee</p>\n<p>fff</p>" => TRUE,
+ ),
+ // Check that a comment in a PRE will result that the text after
+ // the comment, but still in PRE, is not transformed.
+ "<pre>aaa\nbbb<!-- comment -->\n\nccc</pre>\nddd" => array(
+ "<pre>aaa\nbbb<!-- comment -->\n\nccc</pre>" => TRUE,
+ ),
+ // Bug 810824, paragraphs were appearing around iframe tags.
+ "<iframe>aaa</iframe>\n\n" => array(
+ "<p><iframe>aaa</iframe></p>" => FALSE,
+ ),
+ );
+ $this->assertFilteredString($filter, $tests);
+
+ // Very long string hitting PCRE limits.
+ $limit = max(ini_get('pcre.backtrack_limit'), ini_get('pcre.recursion_limit'));
+ $source = $this->randomName($limit);
+ $result = _filter_autop($source);
+ $success = $this->assertEqual($result, '<p>' . $source . "</p>\n", 'Line break filter can process very long strings.');
+ if (!$success) {
+ $this->verbose("\n" . $source . "\n<hr />\n" . $result);
+ }
+ }
+
+ /**
+ * Tests limiting allowed tags and XSS prevention.
+ *
+ * XSS tests assume that script is disallowed by default and src is allowed
+ * by default, but on* and style attributes are disallowed.
+ *
+ * Script injection vectors mostly adopted from http://ha.ckers.org/xss.html.
+ *
+ * Relevant CVEs:
+ * - CVE-2002-1806, ~CVE-2005-0682, ~CVE-2005-2106, CVE-2005-3973,
+ * CVE-2006-1226 (= rev. 1.112?), CVE-2008-0273, CVE-2008-3740.
+ */
+ function testFilterXSS() {
+ // Tag stripping, different ways to work around removal of HTML tags.
+ $f = filter_xss('<script>alert(0)</script>');
+ $this->assertNoNormalized($f, 'script', 'HTML tag stripping -- simple script without special characters.');
+
+ $f = filter_xss('<script src="http://www.example.com" />');
+ $this->assertNoNormalized($f, 'script', 'HTML tag stripping -- empty script with source.');
+
+ $f = filter_xss('<ScRipt sRc=http://www.example.com/>');
+ $this->assertNoNormalized($f, 'script', 'HTML tag stripping evasion -- varying case.');
+
+ $f = filter_xss("<script\nsrc\n=\nhttp://www.example.com/\n>");
+ $this->assertNoNormalized($f, 'script', 'HTML tag stripping evasion -- multiline tag.');
+
+ $f = filter_xss('<script/a src=http://www.example.com/a.js></script>');
+ $this->assertNoNormalized($f, 'script', 'HTML tag stripping evasion -- non whitespace character after tag name.');
+
+ $f = filter_xss('<script/src=http://www.example.com/a.js></script>');
+ $this->assertNoNormalized($f, 'script', 'HTML tag stripping evasion -- no space between tag and attribute.');
+
+ // Null between < and tag name works at least with IE6.
+ $f = filter_xss("<\0scr\0ipt>alert(0)</script>");
+ $this->assertNoNormalized($f, 'ipt', 'HTML tag stripping evasion -- breaking HTML with nulls.');
+
+ $f = filter_xss("<scrscriptipt src=http://www.example.com/a.js>");
+ $this->assertNoNormalized($f, 'script', 'HTML tag stripping evasion -- filter just removing "script".');
+
+ $f = filter_xss('<<script>alert(0);//<</script>');
+ $this->assertNoNormalized($f, 'script', 'HTML tag stripping evasion -- double opening brackets.');
+
+ $f = filter_xss('<script src=http://www.example.com/a.js?<b>');
+ $this->assertNoNormalized($f, 'script', 'HTML tag stripping evasion -- no closing tag.');
+
+ // DRUPAL-SA-2008-047: This doesn't seem exploitable, but the filter should
+ // work consistently.
+ $f = filter_xss('<script>>');
+ $this->assertNoNormalized($f, 'script', 'HTML tag stripping evasion -- double closing tag.');
+
+ $f = filter_xss('<script src=//www.example.com/.a>');
+ $this->assertNoNormalized($f, 'script', 'HTML tag stripping evasion -- no scheme or ending slash.');
+
+ $f = filter_xss('<script src=http://www.example.com/.a');
+ $this->assertNoNormalized($f, 'script', 'HTML tag stripping evasion -- no closing bracket.');
+
+ $f = filter_xss('<script src=http://www.example.com/ <');
+ $this->assertNoNormalized($f, 'script', 'HTML tag stripping evasion -- opening instead of closing bracket.');
+
+ $f = filter_xss('<nosuchtag attribute="newScriptInjectionVector">');
+ $this->assertNoNormalized($f, 'nosuchtag', 'HTML tag stripping evasion -- unknown tag.');
+
+ $f = filter_xss('<?xml:namespace ns="urn:schemas-microsoft-com:time">');
+ $this->assertTrue(stripos($f, '<?xml') === FALSE, 'HTML tag stripping evasion -- starting with a question sign (processing instructions).');
+
+ $f = filter_xss('<t:set attributeName="innerHTML" to="&lt;script defer&gt;alert(0)&lt;/script&gt;">');
+ $this->assertNoNormalized($f, 't:set', 'HTML tag stripping evasion -- colon in the tag name (namespaces\' tricks).');
+
+ $f = filter_xss('<img """><script>alert(0)</script>', array('img'));
+ $this->assertNoNormalized($f, 'script', 'HTML tag stripping evasion -- a malformed image tag.');
+
+ $f = filter_xss('<blockquote><script>alert(0)</script></blockquote>', array('blockquote'));
+ $this->assertNoNormalized($f, 'script', 'HTML tag stripping evasion -- script in a blockqoute.');
+
+ $f = filter_xss("<!--[if true]><script>alert(0)</script><![endif]-->");
+ $this->assertNoNormalized($f, 'script', 'HTML tag stripping evasion -- script within a comment.');
+
+ // Dangerous attributes removal.
+ $f = filter_xss('<p onmouseover="http://www.example.com/">', array('p'));
+ $this->assertNoNormalized($f, 'onmouseover', 'HTML filter attributes removal -- events, no evasion.');
+
+ $f = filter_xss('<li style="list-style-image: url(javascript:alert(0))">', array('li'));
+ $this->assertNoNormalized($f, 'style', 'HTML filter attributes removal -- style, no evasion.');
+
+ $f = filter_xss('<img onerror =alert(0)>', array('img'));
+ $this->assertNoNormalized($f, 'onerror', 'HTML filter attributes removal evasion -- spaces before equals sign.');
+
+ $f = filter_xss('<img onabort!#$%&()*~+-_.,:;?@[/|\]^`=alert(0)>', array('img'));
+ $this->assertNoNormalized($f, 'onabort', 'HTML filter attributes removal evasion -- non alphanumeric characters before equals sign.');
+
+ $f = filter_xss('<img oNmediAError=alert(0)>', array('img'));
+ $this->assertNoNormalized($f, 'onmediaerror', 'HTML filter attributes removal evasion -- varying case.');
+
+ // Works at least with IE6.
+ $f = filter_xss("<img o\0nfocus\0=alert(0)>", array('img'));
+ $this->assertNoNormalized($f, 'focus', 'HTML filter attributes removal evasion -- breaking with nulls.');
+
+ // Only whitelisted scheme names allowed in attributes.
+ $f = filter_xss('<img src="javascript:alert(0)">', array('img'));
+ $this->assertNoNormalized($f, 'javascript', 'HTML scheme clearing -- no evasion.');
+
+ $f = filter_xss('<img src=javascript:alert(0)>', array('img'));
+ $this->assertNoNormalized($f, 'javascript', 'HTML scheme clearing evasion -- no quotes.');
+
+ // A bit like CVE-2006-0070.
+ $f = filter_xss('<img src="javascript:confirm(0)">', array('img'));
+ $this->assertNoNormalized($f, 'javascript', 'HTML scheme clearing evasion -- no alert ;)');
+
+ $f = filter_xss('<img src=`javascript:alert(0)`>', array('img'));
+ $this->assertNoNormalized($f, 'javascript', 'HTML scheme clearing evasion -- grave accents.');
+
+ $f = filter_xss('<img dynsrc="javascript:alert(0)">', array('img'));
+ $this->assertNoNormalized($f, 'javascript', 'HTML scheme clearing -- rare attribute.');
+
+ $f = filter_xss('<table background="javascript:alert(0)">', array('table'));
+ $this->assertNoNormalized($f, 'javascript', 'HTML scheme clearing -- another tag.');
+
+ $f = filter_xss('<base href="javascript:alert(0);//">', array('base'));
+ $this->assertNoNormalized($f, 'javascript', 'HTML scheme clearing -- one more attribute and tag.');
+
+ $f = filter_xss('<img src="jaVaSCriPt:alert(0)">', array('img'));
+ $this->assertNoNormalized($f, 'javascript', 'HTML scheme clearing evasion -- varying case.');
+
+ $f = filter_xss('<img src=&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;&#108;&#101;&#114;&#116;&#40;&#48;&#41;>', array('img'));
+ $this->assertNoNormalized($f, 'javascript', 'HTML scheme clearing evasion -- UTF-8 decimal encoding.');
+
+ $f = filter_xss('<img src=&#00000106&#0000097&#00000118&#0000097&#00000115&#0000099&#00000114&#00000105&#00000112&#00000116&#0000058&#0000097&#00000108&#00000101&#00000114&#00000116&#0000040&#0000048&#0000041>', array('img'));
+ $this->assertNoNormalized($f, 'javascript', 'HTML scheme clearing evasion -- long UTF-8 encoding.');
+
+ $f = filter_xss('<img src=&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A&#x61&#x6C&#x65&#x72&#x74&#x28&#x30&#x29>', array('img'));
+ $this->assertNoNormalized($f, 'javascript', 'HTML scheme clearing evasion -- UTF-8 hex encoding.');
+
+ $f = filter_xss("<img src=\"jav\tascript:alert(0)\">", array('img'));
+ $this->assertNoNormalized($f, 'script', 'HTML scheme clearing evasion -- an embedded tab.');
+
+ $f = filter_xss('<img src="jav&#x09;ascript:alert(0)">', array('img'));
+ $this->assertNoNormalized($f, 'script', 'HTML scheme clearing evasion -- an encoded, embedded tab.');
+
+ $f = filter_xss('<img src="jav&#x000000A;ascript:alert(0)">', array('img'));
+ $this->assertNoNormalized($f, 'script', 'HTML scheme clearing evasion -- an encoded, embedded newline.');
+
+ // With &#xD; this test would fail, but the entity gets turned into
+ // &amp;#xD;, so it's OK.
+ $f = filter_xss('<img src="jav&#x0D;ascript:alert(0)">', array('img'));
+ $this->assertNoNormalized($f, 'script', 'HTML scheme clearing evasion -- an encoded, embedded carriage return.');
+
+ $f = filter_xss("<img src=\"\n\n\nj\na\nva\ns\ncript:alert(0)\">", array('img'));
+ $this->assertNoNormalized($f, 'cript', 'HTML scheme clearing evasion -- broken into many lines.');
+
+ $f = filter_xss("<img src=\"jav\0a\0\0cript:alert(0)\">", array('img'));
+ $this->assertNoNormalized($f, 'cript', 'HTML scheme clearing evasion -- embedded nulls.');
+
+ $f = filter_xss('<img src=" &#14; javascript:alert(0)">', array('img'));
+ $this->assertNoNormalized($f, 'javascript', 'HTML scheme clearing evasion -- spaces and metacharacters before scheme.');
+
+ $f = filter_xss('<img src="vbscript:msgbox(0)">', array('img'));
+ $this->assertNoNormalized($f, 'vbscript', 'HTML scheme clearing evasion -- another scheme.');
+
+ $f = filter_xss('<img src="nosuchscheme:notice(0)">', array('img'));
+ $this->assertNoNormalized($f, 'nosuchscheme', 'HTML scheme clearing evasion -- unknown scheme.');
+
+ // Netscape 4.x javascript entities.
+ $f = filter_xss('<br size="&{alert(0)}">', array('br'));
+ $this->assertNoNormalized($f, 'alert', 'Netscape 4.x javascript entities.');
+
+ // DRUPAL-SA-2008-006: Invalid UTF-8, these only work as reflected XSS with
+ // Internet Explorer 6.
+ $f = filter_xss("<p arg=\"\xe0\">\" style=\"background-image: url(javascript:alert(0));\"\xe0<p>", array('p'));
+ $this->assertNoNormalized($f, 'style', 'HTML filter -- invalid UTF-8.');
+
+ $f = filter_xss("\xc0aaa");
+ $this->assertEqual($f, '', 'HTML filter -- overlong UTF-8 sequences.');
+
+ $f = filter_xss("Who&#039;s Online");
+ $this->assertNormalized($f, "who's online", 'HTML filter -- html entity number');
+
+ $f = filter_xss("Who&amp;#039;s Online");
+ $this->assertNormalized($f, "who&#039;s online", 'HTML filter -- encoded html entity number');
+
+ $f = filter_xss("Who&amp;amp;#039; Online");
+ $this->assertNormalized($f, "who&amp;#039; online", 'HTML filter -- double encoded html entity number');
+ }
+
+ /**
+ * Tests filter settings, defaults, access restrictions and similar.
+ *
+ * @todo This is for functions like filter_filter and check_markup, whose
+ * functionality is not completely focused on filtering. Some ideas:
+ * restricting formats according to user permissions, proper cache
+ * handling, defaults -- allowed tags/attributes/protocols.
+ *
+ * @todo It is possible to add script, iframe etc. to allowed tags, but this
+ * makes HTML filter completely ineffective.
+ *
+ * @todo Class, id, name and xmlns should be added to disallowed attributes,
+ * or better a whitelist approach should be used for that too.
+ */
+ function testHtmlFilter() {
+ // Setup dummy filter object.
+ $filter = new stdClass();
+ $filter->settings = array(
+ 'allowed_html' => '<a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>',
+ 'filter_html_help' => 1,
+ 'filter_html_nofollow' => 0,
+ );
+
+ // HTML filter is not able to secure some tags, these should never be
+ // allowed.
+ $f = _filter_html('<script />', $filter);
+ $this->assertNoNormalized($f, 'script', 'HTML filter should always remove script tags.');
+
+ $f = _filter_html('<iframe />', $filter);
+ $this->assertNoNormalized($f, 'iframe', 'HTML filter should always remove iframe tags.');
+
+ $f = _filter_html('<object />', $filter);
+ $this->assertNoNormalized($f, 'object', 'HTML filter should always remove object tags.');
+
+ $f = _filter_html('<style />', $filter);
+ $this->assertNoNormalized($f, 'style', 'HTML filter should always remove style tags.');
+
+ // Some tags make CSRF attacks easier, let the user take the risk herself.
+ $f = _filter_html('<img />', $filter);
+ $this->assertNoNormalized($f, 'img', 'HTML filter should remove img tags on default.');
+
+ $f = _filter_html('<input />', $filter);
+ $this->assertNoNormalized($f, 'img', 'HTML filter should remove input tags on default.');
+
+ // Filtering content of some attributes is infeasible, these shouldn't be
+ // allowed too.
+ $f = _filter_html('<p style="display: none;" />', $filter);
+ $this->assertNoNormalized($f, 'style', 'HTML filter should remove style attribute on default.');
+
+ $f = _filter_html('<p onerror="alert(0);" />', $filter);
+ $this->assertNoNormalized($f, 'onerror', 'HTML filter should remove on* attributes on default.');
+
+ $f = _filter_html('<code onerror>&nbsp;</code>', $filter);
+ $this->assertNoNormalized($f, 'onerror', 'HTML filter should remove empty on* attributes on default.');
+ }
+
+ /**
+ * Tests the spam deterrent.
+ */
+ function testNoFollowFilter() {
+ // Setup dummy filter object.
+ $filter = new stdClass();
+ $filter->settings = array(
+ 'allowed_html' => '<a>',
+ 'filter_html_help' => 1,
+ 'filter_html_nofollow' => 1,
+ );
+
+ // Test if the rel="nofollow" attribute is added, even if we try to prevent
+ // it.
+ $f = _filter_html('<a href="http://www.example.com/">text</a>', $filter);
+ $this->assertNormalized($f, 'rel="nofollow"', 'Spam deterrent -- no evasion.');
+
+ $f = _filter_html('<A href="http://www.example.com/">text</a>', $filter);
+ $this->assertNormalized($f, 'rel="nofollow"', 'Spam deterrent evasion -- capital A.');
+
+ $f = _filter_html("<a/href=\"http://www.example.com/\">text</a>", $filter);
+ $this->assertNormalized($f, 'rel="nofollow"', 'Spam deterrent evasion -- non whitespace character after tag name.');
+
+ $f = _filter_html("<\0a\0 href=\"http://www.example.com/\">text</a>", $filter);
+ $this->assertNormalized($f, 'rel="nofollow"', 'Spam deterrent evasion -- some nulls.');
+
+ $f = _filter_html('<a href="http://www.example.com/" rel="follow">text</a>', $filter);
+ $this->assertNoNormalized($f, 'rel="follow"', 'Spam deterrent evasion -- with rel set - rel="follow" removed.');
+ $this->assertNormalized($f, 'rel="nofollow"', 'Spam deterrent evasion -- with rel set - rel="nofollow" added.');
+ }
+
+ /**
+ * Tests the loose, admin HTML filter.
+ */
+ function testFilterXSSAdmin() {
+ // DRUPAL-SA-2008-044
+ $f = filter_xss_admin('<object />');
+ $this->assertNoNormalized($f, 'object', 'Admin HTML filter -- should not allow object tag.');
+
+ $f = filter_xss_admin('<script />');
+ $this->assertNoNormalized($f, 'script', 'Admin HTML filter -- should not allow script tag.');
+
+ $f = filter_xss_admin('<style /><iframe /><frame /><frameset /><meta /><link /><embed /><applet /><param /><layer />');
+ $this->assertEqual($f, '', 'Admin HTML filter -- should never allow some tags.');
+ }
+
+ /**
+ * Tests the HTML escaping filter.
+ *
+ * check_plain() is not tested here.
+ */
+ function testHtmlEscapeFilter() {
+ // Setup dummy filter object.
+ $filter = new stdClass();
+ $filter->callback = '_filter_html_escape';
+
+ $tests = array(
+ " One. <!-- \"comment\" --> Two'.\n<p>Three.</p>\n " => array(
+ "One. &lt;!-- &quot;comment&quot; --&gt; Two&#039;.\n&lt;p&gt;Three.&lt;/p&gt;" => TRUE,
+ ' One.' => FALSE,
+ "</p>\n " => FALSE,
+ ),
+ );
+ $this->assertFilteredString($filter, $tests);
+ }
+
+ /**
+ * Tests the URL filter.
+ */
+ function testUrlFilter() {
+ // Setup dummy filter object.
+ $filter = new stdClass();
+ $filter->callback = '_filter_url';
+ $filter->settings = array(
+ 'filter_url_length' => 496,
+ );
+ // @todo Possible categories:
+ // - absolute, mail, partial
+ // - characters/encoding, surrounding markup, security
+
+ // Create a e-mail that is too long.
+ $long_email = str_repeat('a', 254) . '@example.com';
+ $too_long_email = str_repeat('b', 255) . '@example.com';
+
+
+ // Filter selection/pattern matching.
+ $tests = array(
+ // HTTP URLs.
+ '
+http://example.com or www.example.com
+' => array(
+ '<a href="http://example.com">http://example.com</a>' => TRUE,
+ '<a href="http://www.example.com">www.example.com</a>' => TRUE,
+ ),
+ // MAILTO URLs.
+ '
+person@example.com or mailto:person2@example.com or ' . $long_email . ' but not ' . $too_long_email . '
+' => array(
+ '<a href="mailto:person@example.com">person@example.com</a>' => TRUE,
+ '<a href="mailto:person2@example.com">mailto:person2@example.com</a>' => TRUE,
+ '<a href="mailto:' . $long_email . '">' . $long_email . '</a>' => TRUE,
+ '<a href="mailto:' . $too_long_email . '">' . $too_long_email . '</a>' => FALSE,
+ ),
+ // URI parts and special characters.
+ '
+http://trailingslash.com/ or www.trailingslash.com/
+http://host.com/some/path?query=foo&bar[baz]=beer#fragment or www.host.com/some/path?query=foo&bar[baz]=beer#fragment
+http://twitter.com/#!/example/status/22376963142324226
+ftp://user:pass@ftp.example.com/~home/dir1
+sftp://user@nonstandardport:222/dir
+ssh://192.168.0.100/srv/git/drupal.git
+' => array(
+ '<a href="http://trailingslash.com/">http://trailingslash.com/</a>' => TRUE,
+ '<a href="http://www.trailingslash.com/">www.trailingslash.com/</a>' => TRUE,
+ '<a href="http://host.com/some/path?query=foo&amp;bar[baz]=beer#fragment">http://host.com/some/path?query=foo&amp;bar[baz]=beer#fragment</a>' => TRUE,
+ '<a href="http://www.host.com/some/path?query=foo&amp;bar[baz]=beer#fragment">www.host.com/some/path?query=foo&amp;bar[baz]=beer#fragment</a>' => TRUE,
+ '<a href="http://twitter.com/#!/example/status/22376963142324226">http://twitter.com/#!/example/status/22376963142324226</a>' => TRUE,
+ '<a href="ftp://user:pass@ftp.example.com/~home/dir1">ftp://user:pass@ftp.example.com/~home/dir1</a>' => TRUE,
+ '<a href="sftp://user@nonstandardport:222/dir">sftp://user@nonstandardport:222/dir</a>' => TRUE,
+ '<a href="ssh://192.168.0.100/srv/git/drupal.git">ssh://192.168.0.100/srv/git/drupal.git</a>' => TRUE,
+ ),
+ // Encoding.
+ '
+http://ampersand.com/?a=1&b=2
+http://encoded.com/?a=1&amp;b=2
+' => array(
+ '<a href="http://ampersand.com/?a=1&amp;b=2">http://ampersand.com/?a=1&amp;b=2</a>' => TRUE,
+ '<a href="http://encoded.com/?a=1&amp;b=2">http://encoded.com/?a=1&amp;b=2</a>' => TRUE,
+ ),
+ // Domain name length.
+ '
+www.ex.ex or www.example.example or www.toolongdomainexampledomainexampledomainexampledomainexampledomain or
+me@me.tv
+' => array(
+ '<a href="http://www.ex.ex">www.ex.ex</a>' => TRUE,
+ '<a href="http://www.example.example">www.example.example</a>' => TRUE,
+ 'http://www.toolong' => FALSE,
+ '<a href="mailto:me@me.tv">me@me.tv</a>' => TRUE,
+ ),
+ // Absolute URL protocols.
+ // The list to test is found in the beginning of _filter_url() at
+ // $protocols = variable_get('filter_allowed_protocols'... (approx line 1325).
+ '
+https://example.com,
+ftp://ftp.example.com,
+news://example.net,
+telnet://example,
+irc://example.host,
+ssh://odd.geek,
+sftp://secure.host?,
+webcal://calendar,
+rtsp://127.0.0.1,
+not foo://disallowed.com.
+' => array(
+ 'href="https://example.com"' => TRUE,
+ 'href="ftp://ftp.example.com"' => TRUE,
+ 'href="news://example.net"' => TRUE,
+ 'href="telnet://example"' => TRUE,
+ 'href="irc://example.host"' => TRUE,
+ 'href="ssh://odd.geek"' => TRUE,
+ 'href="sftp://secure.host"' => TRUE,
+ 'href="webcal://calendar"' => TRUE,
+ 'href="rtsp://127.0.0.1"' => TRUE,
+ 'href="foo://disallowed.com"' => FALSE,
+ 'not foo://disallowed.com.' => TRUE,
+ ),
+ );
+ $this->assertFilteredString($filter, $tests);
+
+ // Surrounding text/punctuation.
+ $tests = array(
+ '
+Partial URL with trailing period www.partial.com.
+E-mail with trailing comma person@example.com,
+Absolute URL with trailing question http://www.absolute.com?
+Query string with trailing exclamation www.query.com/index.php?a=!
+Partial URL with 3 trailing www.partial.periods...
+E-mail with 3 trailing exclamations@example.com!!!
+Absolute URL and query string with 2 different punctuation characters (http://www.example.com/q=abc).
+' => array(
+ 'period <a href="http://www.partial.com">www.partial.com</a>.' => TRUE,
+ 'comma <a href="mailto:person@example.com">person@example.com</a>,' => TRUE,
+ 'question <a href="http://www.absolute.com">http://www.absolute.com</a>?' => TRUE,
+ 'exclamation <a href="http://www.query.com/index.php?a=">www.query.com/index.php?a=</a>!' => TRUE,
+ 'trailing <a href="http://www.partial.periods">www.partial.periods</a>...' => TRUE,
+ 'trailing <a href="mailto:exclamations@example.com">exclamations@example.com</a>!!!' => TRUE,
+ 'characters (<a href="http://www.example.com/q=abc">http://www.example.com/q=abc</a>).' => TRUE,
+ ),
+ '
+(www.parenthesis.com/dir?a=1&b=2#a)
+' => array(
+ '(<a href="http://www.parenthesis.com/dir?a=1&amp;b=2#a">www.parenthesis.com/dir?a=1&amp;b=2#a</a>)' => TRUE,
+ ),
+ );
+ $this->assertFilteredString($filter, $tests);
+
+ // Surrounding markup.
+ $tests = array(
+ '
+<p xmlns="www.namespace.com" />
+<p xmlns="http://namespace.com">
+An <a href="http://example.com" title="Read more at www.example.info...">anchor</a>.
+</p>
+' => array(
+ '<p xmlns="www.namespace.com" />' => TRUE,
+ '<p xmlns="http://namespace.com">' => TRUE,
+ 'href="http://www.namespace.com"' => FALSE,
+ 'href="http://namespace.com"' => FALSE,
+ 'An <a href="http://example.com" title="Read more at www.example.info...">anchor</a>.' => TRUE,
+ ),
+ '
+Not <a href="foo">www.relative.com</a> or <a href="http://absolute.com">www.absolute.com</a>
+but <strong>http://www.strong.net</strong> or <em>www.emphasis.info</em>
+' => array(
+ '<a href="foo">www.relative.com</a>' => TRUE,
+ 'href="http://www.relative.com"' => FALSE,
+ '<a href="http://absolute.com">www.absolute.com</a>' => TRUE,
+ '<strong><a href="http://www.strong.net">http://www.strong.net</a></strong>' => TRUE,
+ '<em><a href="http://www.emphasis.info">www.emphasis.info</a></em>' => TRUE,
+ ),
+ '
+Test <code>using www.example.com the code tag</code>.
+' => array(
+ 'href' => FALSE,
+ 'http' => FALSE,
+ ),
+ '
+Intro.
+<blockquote>
+Quoted text linking to www.example.com, written by person@example.com, originating from http://origin.example.com. <code>@see www.usage.example.com or <em>www.example.info</em> bla bla</code>.
+</blockquote>
+
+Outro.
+' => array(
+ 'href="http://www.example.com"' => TRUE,
+ 'href="mailto:person@example.com"' => TRUE,
+ 'href="http://origin.example.com"' => TRUE,
+ 'http://www.usage.example.com' => FALSE,
+ 'http://www.example.info' => FALSE,
+ 'Intro.' => TRUE,
+ 'Outro.' => TRUE,
+ ),
+ '
+Unknown tag <x>containing x and www.example.com</x>? And a tag <pooh>beginning with p and containing www.example.pooh with p?</pooh>
+' => array(
+ 'href="http://www.example.com"' => TRUE,
+ 'href="http://www.example.pooh"' => TRUE,
+ ),
+ '
+<p>Test &lt;br/&gt;: This is a www.example17.com example <strong>with</strong> various http://www.example18.com tags. *<br/>
+ It is important www.example19.com to *<br/>test different URLs and http://www.example20.com in the same paragraph. *<br>
+HTML www.example21.com soup by person@example22.com can litererally http://www.example23.com contain *img*<img> anything. Just a www.example24.com with http://www.example25.com thrown in. www.example26.com from person@example27.com with extra http://www.example28.com.
+' => array(
+ 'href="http://www.example17.com"' => TRUE,
+ 'href="http://www.example18.com"' => TRUE,
+ 'href="http://www.example19.com"' => TRUE,
+ 'href="http://www.example20.com"' => TRUE,
+ 'href="http://www.example21.com"' => TRUE,
+ 'href="mailto:person@example22.com"' => TRUE,
+ 'href="http://www.example23.com"' => TRUE,
+ 'href="http://www.example24.com"' => TRUE,
+ 'href="http://www.example25.com"' => TRUE,
+ 'href="http://www.example26.com"' => TRUE,
+ 'href="mailto:person@example27.com"' => TRUE,
+ 'href="http://www.example28.com"' => TRUE,
+ ),
+ '
+<script>
+<!--
+ // @see www.example.com
+ var exampleurl = "http://example.net";
+-->
+<!--//--><![CDATA[//><!--
+ // @see www.example.com
+ var exampleurl = "http://example.net";
+//--><!]]>
+</script>
+' => array(
+ 'href="http://www.example.com"' => FALSE,
+ 'href="http://example.net"' => FALSE,
+ ),
+ '
+<style>body {
+ background: url(http://example.com/pixel.gif);
+}</style>
+' => array(
+ 'href' => FALSE,
+ ),
+ '
+<!-- Skip any URLs like www.example.com in comments -->
+' => array(
+ 'href' => FALSE,
+ ),
+ '
+<!-- Skip any URLs like
+www.example.com with a newline in comments -->
+' => array(
+ 'href' => FALSE,
+ ),
+ '
+<!-- Skip any URLs like www.comment.com in comments. <p>Also ignore http://commented.out/markup.</p> -->
+' => array(
+ 'href' => FALSE,
+ ),
+ '
+<dl>
+<dt>www.example.com</dt>
+<dd>http://example.com</dd>
+<dd>person@example.com</dd>
+<dt>Check www.example.net</dt>
+<dd>Some text around http://www.example.info by person@example.info?</dd>
+</dl>
+' => array(
+ 'href="http://www.example.com"' => TRUE,
+ 'href="http://example.com"' => TRUE,
+ 'href="mailto:person@example.com"' => TRUE,
+ 'href="http://www.example.net"' => TRUE,
+ 'href="http://www.example.info"' => TRUE,
+ 'href="mailto:person@example.info"' => TRUE,
+ ),
+ '
+<div>www.div.com</div>
+<ul>
+<li>http://listitem.com</li>
+<li class="odd">www.class.listitem.com</li>
+</ul>
+' => array(
+ '<div><a href="http://www.div.com">www.div.com</a></div>' => TRUE,
+ '<li><a href="http://listitem.com">http://listitem.com</a></li>' => TRUE,
+ '<li class="odd"><a href="http://www.class.listitem.com">www.class.listitem.com</a></li>' => TRUE,
+ ),
+ );
+ $this->assertFilteredString($filter, $tests);
+
+ // URL trimming.
+ $filter->settings['filter_url_length'] = 20;
+ $tests = array(
+ 'www.trimmed.com/d/ff.ext?a=1&b=2#a1' => array(
+ '<a href="http://www.trimmed.com/d/ff.ext?a=1&amp;b=2#a1">www.trimmed.com/d/ff...</a>' => TRUE,
+ ),
+ );
+ $this->assertFilteredString($filter, $tests);
+ }
+
+ /**
+ * Asserts multiple filter output expectations for multiple input strings.
+ *
+ * @param $filter
+ * A input filter object.
+ * @param $tests
+ * An associative array, whereas each key is an arbitrary input string and
+ * each value is again an associative array whose keys are filter output
+ * strings and whose values are Booleans indicating whether the output is
+ * expected or not.
+ *
+ * For example:
+ * @code
+ * $tests = array(
+ * 'Input string' => array(
+ * '<p>Input string</p>' => TRUE,
+ * 'Input string<br' => FALSE,
+ * ),
+ * );
+ * @endcode
+ */
+ function assertFilteredString($filter, $tests) {
+ foreach ($tests as $source => $tasks) {
+ $function = $filter->callback;
+ $result = $function($source, $filter);
+ foreach ($tasks as $value => $is_expected) {
+ // Not using assertIdentical, since combination with strpos() is hard to grok.
+ if ($is_expected) {
+ $success = $this->assertTrue(strpos($result, $value) !== FALSE, format_string('@source: @value found.', array(
+ '@source' => var_export($source, TRUE),
+ '@value' => var_export($value, TRUE),
+ )));
+ }
+ else {
+ $success = $this->assertTrue(strpos($result, $value) === FALSE, format_string('@source: @value not found.', array(
+ '@source' => var_export($source, TRUE),
+ '@value' => var_export($value, TRUE),
+ )));
+ }
+ if (!$success) {
+ $this->verbose('Source:<pre>' . check_plain(var_export($source, TRUE)) . '</pre>'
+ . '<hr />' . 'Result:<pre>' . check_plain(var_export($result, TRUE)) . '</pre>'
+ . '<hr />' . ($is_expected ? 'Expected:' : 'Not expected:')
+ . '<pre>' . check_plain(var_export($value, TRUE)) . '</pre>'
+ );
+ }
+ }
+ }
+ }
+
+ /**
+ * Tests URL filter on longer content.
+ *
+ * Filters based on regular expressions should also be tested with a more
+ * complex content than just isolated test lines.
+ * The most common errors are:
+ * - accidental '*' (greedy) match instead of '*?' (minimal) match.
+ * - only matching first occurrence instead of all.
+ * - newlines not matching '.*'.
+ *
+ * This test covers:
+ * - Document with multiple newlines and paragraphs (two newlines).
+ * - Mix of several HTML tags, invalid non-HTML tags, tags to ignore and HTML
+ * comments.
+ * - Empty HTML tags (BR, IMG).
+ * - Mix of absolute and partial URLs, and e-mail addresses in one content.
+ */
+ function testUrlFilterContent() {
+ // Setup dummy filter object.
+ $filter = new stdClass();
+ $filter->settings = array(
+ 'filter_url_length' => 496,
+ );
+ $path = drupal_get_path('module', 'filter') . '/tests';
+
+ $input = file_get_contents($path . '/filter.url-input.txt');
+ $expected = file_get_contents($path . '/filter.url-output.txt');
+ $result = _filter_url($input, $filter);
+ $this->assertIdentical($result, $expected, 'Complex HTML document was correctly processed.');
+ }
+
+ /**
+ * Tests the HTML corrector filter.
+ *
+ * @todo This test could really use some validity checking function.
+ */
+ function testHtmlCorrectorFilter() {
+ // Tag closing.
+ $f = _filter_htmlcorrector('<p>text');
+ $this->assertEqual($f, '<p>text</p>', 'HTML corrector -- tag closing at the end of input.');
+
+ $f = _filter_htmlcorrector('<p>text<p><p>text');
+ $this->assertEqual($f, '<p>text</p><p></p><p>text</p>', 'HTML corrector -- tag closing.');
+
+ $f = _filter_htmlcorrector("<ul><li>e1<li>e2");
+ $this->assertEqual($f, "<ul><li>e1</li><li>e2</li></ul>", 'HTML corrector -- unclosed list tags.');
+
+ $f = _filter_htmlcorrector('<div id="d">content');
+ $this->assertEqual($f, '<div id="d">content</div>', 'HTML corrector -- unclosed tag with attribute.');
+
+ // XHTML slash for empty elements.
+ $f = _filter_htmlcorrector('<hr><br>');
+ $this->assertEqual($f, '<hr /><br />', 'HTML corrector -- XHTML closing slash.');
+
+ $f = _filter_htmlcorrector('<P>test</P>');
+ $this->assertEqual($f, '<p>test</p>', 'HTML corrector -- Convert uppercased tags to proper lowercased ones.');
+
+ $f = _filter_htmlcorrector('<P>test</p>');
+ $this->assertEqual($f, '<p>test</p>', 'HTML corrector -- Convert uppercased tags to proper lowercased ones.');
+
+ $f = _filter_htmlcorrector('test<hr />');
+ $this->assertEqual($f, 'test<hr />', 'HTML corrector -- Let proper XHTML pass through.');
+
+ $f = _filter_htmlcorrector('test<hr/>');
+ $this->assertEqual($f, 'test<hr />', 'HTML corrector -- Let proper XHTML pass through, but ensure there is a single space before the closing slash.');
+
+ $f = _filter_htmlcorrector('test<hr />');
+ $this->assertEqual($f, 'test<hr />', 'HTML corrector -- Let proper XHTML pass through, but ensure there are not too many spaces before the closing slash.');
+
+ $f = _filter_htmlcorrector('<span class="test" />');
+ $this->assertEqual($f, '<span class="test"></span>', 'HTML corrector -- Convert XHTML that is properly formed but that would not be compatible with typical HTML user agents.');
+
+ $f = _filter_htmlcorrector('test1<br class="test">test2');
+ $this->assertEqual($f, 'test1<br class="test" />test2', 'HTML corrector -- Automatically close single tags.');
+
+ $f = _filter_htmlcorrector('line1<hr>line2');
+ $this->assertEqual($f, 'line1<hr />line2', 'HTML corrector -- Automatically close single tags.');
+
+ $f = _filter_htmlcorrector('line1<HR>line2');
+ $this->assertEqual($f, 'line1<hr />line2', 'HTML corrector -- Automatically close single tags.');
+
+ $f = _filter_htmlcorrector('<img src="http://example.com/test.jpg">test</img>');
+ $this->assertEqual($f, '<img src="http://example.com/test.jpg" />test', 'HTML corrector -- Automatically close single tags.');
+
+ $f = _filter_htmlcorrector('<br></br>');
+ $this->assertEqual($f, '<br />', "HTML corrector -- Transform empty tags to a single closed tag if the tag's content model is EMPTY.");
+
+ $f = _filter_htmlcorrector('<div></div>');
+ $this->assertEqual($f, '<div></div>', "HTML corrector -- Do not transform empty tags to a single closed tag if the tag's content model is not EMPTY.");
+
+ $f = _filter_htmlcorrector('<p>line1<br/><hr/>line2</p>');
+ $this->assertEqual($f, '<p>line1<br /></p><hr />line2', 'HTML corrector -- Move non-inline elements outside of inline containers.');
+
+ $f = _filter_htmlcorrector('<p>line1<div>line2</div></p>');
+ $this->assertEqual($f, '<p>line1</p><div>line2</div>', 'HTML corrector -- Move non-inline elements outside of inline containers.');
+
+ $f = _filter_htmlcorrector('<p>test<p>test</p>\n');
+ $this->assertEqual($f, '<p>test</p><p>test</p>\n', 'HTML corrector -- Auto-close improperly nested tags.');
+
+ $f = _filter_htmlcorrector('<p>Line1<br><STRONG>bold stuff</b>');
+ $this->assertEqual($f, '<p>Line1<br /><strong>bold stuff</strong></p>', 'HTML corrector -- Properly close unclosed tags, and remove useless closing tags.');
+
+ $f = _filter_htmlcorrector('test <!-- this is a comment -->');
+ $this->assertEqual($f, 'test <!-- this is a comment -->', 'HTML corrector -- Do not touch HTML comments.');
+
+ $f = _filter_htmlcorrector('test <!--this is a comment-->');
+ $this->assertEqual($f, 'test <!--this is a comment-->', 'HTML corrector -- Do not touch HTML comments.');
+
+ $f = _filter_htmlcorrector('test <!-- comment <p>another
+ <strong>multiple</strong> line
+ comment</p> -->');
+ $this->assertEqual($f, 'test <!-- comment <p>another
+ <strong>multiple</strong> line
+ comment</p> -->', 'HTML corrector -- Do not touch HTML comments.');
+
+ $f = _filter_htmlcorrector('test <!-- comment <p>another comment</p> -->');
+ $this->assertEqual($f, 'test <!-- comment <p>another comment</p> -->', 'HTML corrector -- Do not touch HTML comments.');
+
+ $f = _filter_htmlcorrector('test <!--break-->');
+ $this->assertEqual($f, 'test <!--break-->', 'HTML corrector -- Do not touch HTML comments.');
+
+ $f = _filter_htmlcorrector('<p>test\n</p>\n');
+ $this->assertEqual($f, '<p>test\n</p>\n', 'HTML corrector -- New-lines are accepted and kept as-is.');
+
+ $f = _filter_htmlcorrector('<p>دروبال');
+ $this->assertEqual($f, '<p>دروبال</p>', 'HTML corrector -- Encoding is correctly kept.');
+
+ $f = _filter_htmlcorrector('<script type="text/javascript">alert("test")</script>');
+ $this->assertEqual($f, '<script type="text/javascript">
+<!--//--><![CDATA[// ><!--
+alert("test")
+//--><!]]>
+</script>', 'HTML corrector -- CDATA added to script element');
+
+ $f = _filter_htmlcorrector('<p><script type="text/javascript">alert("test")</script></p>');
+ $this->assertEqual($f, '<p><script type="text/javascript">
+<!--//--><![CDATA[// ><!--
+alert("test")
+//--><!]]>
+</script></p>', 'HTML corrector -- CDATA added to a nested script element');
+
+ $f = _filter_htmlcorrector('<p><style> /* Styling */ body {color:red}</style></p>');
+ $this->assertEqual($f, '<p><style>
+<!--/*--><![CDATA[/* ><!--*/
+ /* Styling */ body {color:red}
+/*--><!]]>*/
+</style></p>', 'HTML corrector -- CDATA added to a style element.');
+
+ $filtered_data = _filter_htmlcorrector('<p><style>
+/*<![CDATA[*/
+/* Styling */
+body {color:red}
+/*]]>*/
+</style></p>');
+ $this->assertEqual($filtered_data, '<p><style>
+<!--/*--><![CDATA[/* ><!--*/
+
+/*<![CDATA[*/
+/* Styling */
+body {color:red}
+/*]]]]><![CDATA[>*/
+
+/*--><!]]>*/
+</style></p>',
+ format_string('HTML corrector -- Existing cdata section @pattern_name properly escaped', array('@pattern_name' => '/*<![CDATA[*/'))
+ );
+
+ $filtered_data = _filter_htmlcorrector('<p><style>
+ <!--/*--><![CDATA[/* ><!--*/
+ /* Styling */
+ body {color:red}
+ /*--><!]]>*/
+</style></p>');
+ $this->assertEqual($filtered_data, '<p><style>
+<!--/*--><![CDATA[/* ><!--*/
+
+ <!--/*--><![CDATA[/* ><!--*/
+ /* Styling */
+ body {color:red}
+ /*--><!]]]]><![CDATA[>*/
+
+/*--><!]]>*/
+</style></p>',
+ format_string('HTML corrector -- Existing cdata section @pattern_name properly escaped', array('@pattern_name' => '<!--/*--><![CDATA[/* ><!--*/'))
+ );
+
+ $filtered_data = _filter_htmlcorrector('<p><script type="text/javascript">
+<!--//--><![CDATA[// ><!--
+ alert("test");
+//--><!]]>
+</script></p>');
+ $this->assertEqual($filtered_data, '<p><script type="text/javascript">
+<!--//--><![CDATA[// ><!--
+
+<!--//--><![CDATA[// ><!--
+ alert("test");
+//--><!]]]]><![CDATA[>
+
+//--><!]]>
+</script></p>',
+ format_string('HTML corrector -- Existing cdata section @pattern_name properly escaped', array('@pattern_name' => '<!--//--><![CDATA[// ><!--'))
+ );
+
+ $filtered_data = _filter_htmlcorrector('<p><script type="text/javascript">
+// <![CDATA[
+ alert("test");
+// ]]>
+</script></p>');
+ $this->assertEqual($filtered_data, '<p><script type="text/javascript">
+<!--//--><![CDATA[// ><!--
+
+// <![CDATA[
+ alert("test");
+// ]]]]><![CDATA[>
+
+//--><!]]>
+</script></p>',
+ format_string('HTML corrector -- Existing cdata section @pattern_name properly escaped', array('@pattern_name' => '// <![CDATA['))
+ );
+
+ }
+
+ /**
+ * Asserts that a text transformed to lowercase with HTML entities decoded does contains a given string.
+ *
+ * Otherwise fails the test with a given message, similar to all the
+ * SimpleTest assert* functions.
+ *
+ * Note that this does not remove nulls, new lines and other characters that
+ * could be used to obscure a tag or an attribute name.
+ *
+ * @param $haystack
+ * Text to look in.
+ * @param $needle
+ * Lowercase, plain text to look for.
+ * @param $message
+ * (optional) Message to display if failed. Defaults to an empty string.
+ * @param $group
+ * (optional) The group this message belongs to. Defaults to 'Other'.
+ * @return
+ * TRUE on pass, FALSE on fail.
+ */
+ function assertNormalized($haystack, $needle, $message = '', $group = 'Other') {
+ return $this->assertTrue(strpos(strtolower(decode_entities($haystack)), $needle) !== FALSE, $message, $group);
+ }
+
+ /**
+ * Asserts that text transformed to lowercase with HTML entities decoded does not contain a given string.
+ *
+ * Otherwise fails the test with a given message, similar to all the
+ * SimpleTest assert* functions.
+ *
+ * Note that this does not remove nulls, new lines, and other character that
+ * could be used to obscure a tag or an attribute name.
+ *
+ * @param $haystack
+ * Text to look in.
+ * @param $needle
+ * Lowercase, plain text to look for.
+ * @param $message
+ * (optional) Message to display if failed. Defaults to an empty string.
+ * @param $group
+ * (optional) The group this message belongs to. Defaults to 'Other'.
+ * @return
+ * TRUE on pass, FALSE on fail.
+ */
+ function assertNoNormalized($haystack, $needle, $message = '', $group = 'Other') {
+ return $this->assertTrue(strpos(strtolower(decode_entities($haystack)), $needle) === FALSE, $message, $group);
+ }
+}
+
+/**
+ * Tests for Filter's hook invocations.
+ */
+class FilterHooksTestCase extends DrupalWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Filter format hooks',
+ 'description' => 'Test hooks for text formats insert/update/disable.',
+ 'group' => 'Filter',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('block', 'filter_test');
+ $admin_user = $this->drupalCreateUser(array('administer filters', 'administer blocks'));
+ $this->drupalLogin($admin_user);
+ }
+
+ /**
+ * Tests hooks on format management.
+ *
+ * Tests that hooks run correctly on creating, editing, and deleting a text
+ * format.
+ */
+ function testFilterHooks() {
+ // Add a text format.
+ $name = $this->randomName();
+ $edit = array();
+ $edit['format'] = drupal_strtolower($this->randomName());
+ $edit['name'] = $name;
+ $edit['roles[' . DRUPAL_ANONYMOUS_RID . ']'] = 1;
+ $this->drupalPost('admin/config/content/formats/add', $edit, t('Save configuration'));
+ $this->assertRaw(t('Added text format %format.', array('%format' => $name)), 'New format created.');
+ $this->assertText('hook_filter_format_insert invoked.', 'hook_filter_format_insert was invoked.');
+
+ $format_id = $edit['format'];
+
+ // Update text format.
+ $edit = array();
+ $edit['roles[' . DRUPAL_AUTHENTICATED_RID . ']'] = 1;
+ $this->drupalPost('admin/config/content/formats/' . $format_id, $edit, t('Save configuration'));
+ $this->assertRaw(t('The text format %format has been updated.', array('%format' => $name)), 'Format successfully updated.');
+ $this->assertText('hook_filter_format_update invoked.', 'hook_filter_format_update() was invoked.');
+
+ // Add a new custom block.
+ $custom_block = array();
+ $custom_block['info'] = $this->randomName(8);
+ $custom_block['title'] = $this->randomName(8);
+ $custom_block['body[value]'] = $this->randomName(32);
+ // Use the format created.
+ $custom_block['body[format]'] = $format_id;
+ $this->drupalPost('admin/structure/block/add', $custom_block, t('Save block'));
+ $this->assertText(t('The block has been created.'), 'New block successfully created.');
+
+ // Verify the new block is in the database.
+ $bid = db_query("SELECT bid FROM {block_custom} WHERE info = :info", array(':info' => $custom_block['info']))->fetchField();
+ $this->assertNotNull($bid, 'New block found in database');
+
+ // Disable the text format.
+ $this->drupalPost('admin/config/content/formats/' . $format_id . '/disable', array(), t('Disable'));
+ $this->assertRaw(t('Disabled text format %format.', array('%format' => $name)), 'Format successfully disabled.');
+ $this->assertText('hook_filter_format_disable invoked.', 'hook_filter_format_disable() was invoked.');
+ }
+}
+
+/**
+ * Tests filter settings.
+ */
+class FilterSettingsTestCase extends DrupalWebTestCase {
+ /**
+ * The installation profile to use with this test class.
+ *
+ * @var string
+ */
+ protected $profile = 'testing';
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Filter settings',
+ 'description' => 'Tests filter settings.',
+ 'group' => 'Filter',
+ );
+ }
+
+ /**
+ * Tests explicit and implicit default settings for filters.
+ */
+ function testFilterDefaults() {
+ $filter_info = filter_filter_info();
+ $filters = array_fill_keys(array_keys($filter_info), array());
+
+ // Create text format using filter default settings.
+ $filter_defaults_format = (object) array(
+ 'format' => 'filter_defaults',
+ 'name' => 'Filter defaults',
+ 'filters' => $filters,
+ );
+ filter_format_save($filter_defaults_format);
+
+ // Verify that default weights defined in hook_filter_info() were applied.
+ $saved_settings = array();
+ foreach ($filter_defaults_format->filters as $name => $settings) {
+ $expected_weight = (isset($filter_info[$name]['weight']) ? $filter_info[$name]['weight'] : 0);
+ $this->assertEqual($settings['weight'], $expected_weight, format_string('@name filter weight %saved equals %default', array(
+ '@name' => $name,
+ '%saved' => $settings['weight'],
+ '%default' => $expected_weight,
+ )));
+ $saved_settings[$name]['weight'] = $expected_weight;
+ }
+
+ // Re-save the text format.
+ filter_format_save($filter_defaults_format);
+ // Reload it from scratch.
+ filter_formats_reset();
+ $filter_defaults_format = filter_format_load($filter_defaults_format->format);
+ $filter_defaults_format->filters = filter_list_format($filter_defaults_format->format);
+
+ // Verify that saved filter settings have not been changed.
+ foreach ($filter_defaults_format->filters as $name => $settings) {
+ $this->assertEqual($settings->weight, $saved_settings[$name]['weight'], format_string('@name filter weight %saved equals %previous', array(
+ '@name' => $name,
+ '%saved' => $settings->weight,
+ '%previous' => $saved_settings[$name]['weight'],
+ )));
+ }
+ }
+}
diff --git a/kolab.org/www/drupal-7.26/modules/filter/tests/filter.url-input.txt b/kolab.org/www/drupal-7.26/modules/filter/tests/filter.url-input.txt
new file mode 100644
index 0000000..7b33af5
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/filter/tests/filter.url-input.txt
@@ -0,0 +1,36 @@
+This is just a www.test.com. paragraph with person@test.com. some http://www.test.com. urls thrown in and also <code>using www.test.com the code tag</code>.
+
+<blockquote>
+This is just a www.test.com. paragraph with person@test.com. some http://www.test.com. urls thrown in and also <code>using www.test.com the code tag</code>.
+</blockquote>
+
+<code>Testing code tag http://www.test.com abc</code>
+
+http://www.test.com
+www.test.com
+person@test.com
+<code>www.test.com</code>
+
+What about tags that don't exist <x>like x say www.test.com</x>? And what about tag <pooh>beginning www.test.com with p?</pooh>
+
+Test &lt;br/&gt;: This is just a www.test.com. paragraph <strong>with</strong> some http://www.test.com urls thrown in. *<br/> This is just a www.test.com paragraph *<br/> with some http://www.test.com urls thrown in. *<br/>This is just a www.test.com paragraph person@test.com with some http://www.test.com urls *img*<img/> thrown in. This is just a www.test.com paragraph with some http://www.test.com urls thrown in. This is just a www.test.com paragraph person@test.com with some http://www.test.com urls thrown in.
+
+This is just a www.test.com paragraph <strong>with</strong> some http://www.test.com urls thrown in. <br /> This is just a www.test.com paragraph with some http://www.test.com urls thrown in. This is just a www.test.com paragraph person@test.com with some http://www.test.com urls thrown in. This is just a www.test.com paragraph with some http://www.test.com urls thrown in. This is just a www.test.com paragraph person@test.com with some http://www.test.com urls thrown in.
+
+The old URL filter has problems with <a title="kind of link www.example.com with text" href="http://www.example.com">this kind of link</a> with www address as part of text in title. www.test.com
+
+<!-- This url www.test.com is inside a comment -->
+
+<dl>
+<dt>www.test.com</dt>
+<dd>http://www.test.com</dd>
+<dd>person@test.com</dd>
+<dt>check www.test.com</dt>
+<dd>this with some text around: http://www.test.com not so easy person@test.com now?</dd>
+</dl>
+
+<!-- <p>This url http://www.test.com is
+ inside a comment containing newlines and
+<em>html</em> tags.</p> -->
+
+This is the end! \ No newline at end of file
diff --git a/kolab.org/www/drupal-7.26/modules/filter/tests/filter.url-output.txt b/kolab.org/www/drupal-7.26/modules/filter/tests/filter.url-output.txt
new file mode 100644
index 0000000..9cc5073
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/filter/tests/filter.url-output.txt
@@ -0,0 +1,36 @@
+This is just a <a href="http://www.test.com">www.test.com</a>. paragraph with <a href="mailto:person@test.com">person@test.com</a>. some <a href="http://www.test.com">http://www.test.com</a>. urls thrown in and also <code>using www.test.com the code tag</code>.
+
+<blockquote>
+This is just a <a href="http://www.test.com">www.test.com</a>. paragraph with <a href="mailto:person@test.com">person@test.com</a>. some <a href="http://www.test.com">http://www.test.com</a>. urls thrown in and also <code>using www.test.com the code tag</code>.
+</blockquote>
+
+<code>Testing code tag http://www.test.com abc</code>
+
+<a href="http://www.test.com">http://www.test.com</a>
+<a href="http://www.test.com">www.test.com</a>
+<a href="mailto:person@test.com">person@test.com</a>
+<code>www.test.com</code>
+
+What about tags that don't exist <x>like x say <a href="http://www.test.com">www.test.com</a></x>? And what about tag <pooh>beginning <a href="http://www.test.com">www.test.com</a> with p?</pooh>
+
+Test &lt;br/&gt;: This is just a <a href="http://www.test.com">www.test.com</a>. paragraph <strong>with</strong> some <a href="http://www.test.com">http://www.test.com</a> urls thrown in. *<br/> This is just a <a href="http://www.test.com">www.test.com</a> paragraph *<br/> with some <a href="http://www.test.com">http://www.test.com</a> urls thrown in. *<br/>This is just a <a href="http://www.test.com">www.test.com</a> paragraph <a href="mailto:person@test.com">person@test.com</a> with some <a href="http://www.test.com">http://www.test.com</a> urls *img*<img/> thrown in. This is just a <a href="http://www.test.com">www.test.com</a> paragraph with some <a href="http://www.test.com">http://www.test.com</a> urls thrown in. This is just a <a href="http://www.test.com">www.test.com</a> paragraph <a href="mailto:person@test.com">person@test.com</a> with some <a href="http://www.test.com">http://www.test.com</a> urls thrown in.
+
+This is just a <a href="http://www.test.com">www.test.com</a> paragraph <strong>with</strong> some <a href="http://www.test.com">http://www.test.com</a> urls thrown in. <br /> This is just a <a href="http://www.test.com">www.test.com</a> paragraph with some <a href="http://www.test.com">http://www.test.com</a> urls thrown in. This is just a <a href="http://www.test.com">www.test.com</a> paragraph <a href="mailto:person@test.com">person@test.com</a> with some <a href="http://www.test.com">http://www.test.com</a> urls thrown in. This is just a <a href="http://www.test.com">www.test.com</a> paragraph with some <a href="http://www.test.com">http://www.test.com</a> urls thrown in. This is just a <a href="http://www.test.com">www.test.com</a> paragraph <a href="mailto:person@test.com">person@test.com</a> with some <a href="http://www.test.com">http://www.test.com</a> urls thrown in.
+
+The old URL filter has problems with <a title="kind of link www.example.com with text" href="http://www.example.com">this kind of link</a> with www address as part of text in title. <a href="http://www.test.com">www.test.com</a>
+
+<!-- This url www.test.com is inside a comment -->
+
+<dl>
+<dt><a href="http://www.test.com">www.test.com</a></dt>
+<dd><a href="http://www.test.com">http://www.test.com</a></dd>
+<dd><a href="mailto:person@test.com">person@test.com</a></dd>
+<dt>check <a href="http://www.test.com">www.test.com</a></dt>
+<dd>this with some text around: <a href="http://www.test.com">http://www.test.com</a> not so easy <a href="mailto:person@test.com">person@test.com</a> now?</dd>
+</dl>
+
+<!-- <p>This url http://www.test.com is
+ inside a comment containing newlines and
+<em>html</em> tags.</p> -->
+
+This is the end! \ No newline at end of file