diff --git a/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorTest.php b/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorTest.php index 98d15c2..30bd960 100644 --- a/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorTest.php +++ b/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorTest.php @@ -107,7 +107,7 @@ function testGetJSSettings() { // Change the allowed HTML tags; the "format_tags" setting for CKEditor // should automatically be updated as well. $format = entity_load('filter_format', 'filtered_html'); - $format->filters['filter_html']['settings']['allowed_html'] .= '
'; + $format->filters('filter_html')->settings['allowed_html'] .= '
'; $format->save(); $expected_config['format_tags'] = 'p;h3;h4;h5;h6;pre'; $this->assertEqual($expected_config, $this->ckeditor->getJSSettings($editor), 'Generated JS settings are correct for customized configuration.'); diff --git a/core/modules/filter/config/filter.format.plain_text.yml b/core/modules/filter/config/filter.format.plain_text.yml index a7f72cd..8ce9548 100644 --- a/core/modules/filter/config/filter.format.plain_text.yml +++ b/core/modules/filter/config/filter.format.plain_text.yml @@ -15,12 +15,19 @@ filters: filter_html_escape: module: filter status: '1' + weight: '-10' + settings: { } # Convert URLs into links. filter_url: module: filter status: '1' + weight: '0' + settings: + filter_url_length: '72' # Convert linebreaks into paragraphs. filter_autop: module: filter status: '1' + weight: '0' + settings: { } langcode: und diff --git a/core/modules/filter/filter.admin.inc b/core/modules/filter/filter.admin.inc index aa3018c..a4493a9 100644 --- a/core/modules/filter/filter.admin.inc +++ b/core/modules/filter/filter.admin.inc @@ -197,25 +197,9 @@ function filter_admin_format_form($form, &$form_state, $format) { $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; + // Create filter plugin instances for all available filters, including both + // enabled/configured ones as well as new and not yet unconfigured ones. + $filters = $format->filters()->sort(); // Filter status. $form['filters']['status'] = array( @@ -228,17 +212,6 @@ function filter_admin_format_form($form, &$form_state, $format) { // @see http://drupal.org/node/1829202 '#input' => FALSE, ); - 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' => 'table', @@ -252,48 +225,54 @@ function filter_admin_format_form($form, &$form_state, $format) { '#input' => FALSE, '#theme_wrappers' => array('form_element'), ); - foreach ($filter_info as $name => $filter) { + // Filter settings. + $form['filter_settings'] = array( + '#type' => 'vertical_tabs', + '#title' => t('Filter settings'), + ); + + foreach ($filters as $name => $filter) { + $form['filters']['status'][$name] = array( + '#type' => 'checkbox', + '#title' => $filter->getLabel(), + '#default_value' => $filter->status, + '#parents' => array('filters', $name, 'status'), + '#description' => $filter->getDescription(), + '#weight' => $filter->weight, + ); + $form['filters']['order'][$name]['#attributes']['class'][] = 'draggable'; - $form['filters']['order'][$name]['#weight'] = $filters[$name]->weight; + $form['filters']['order'][$name]['#weight'] = $filter->weight; $form['filters']['order'][$name]['filter'] = array( - '#markup' => $filter['title'], + '#markup' => $filter->getLabel(), ); $form['filters']['order'][$name]['weight'] = array( '#type' => 'weight', - '#title' => t('Weight for @title', array('@title' => $filter['title'])), + '#title' => t('Weight for @title', array('@title' => $filter->getLabel())), '#title_display' => 'invisible', '#delta' => 50, - '#default_value' => $filters[$name]->weight, + '#default_value' => $filter->weight, '#parents' => array('filters', $name, 'weight'), '#attributes' => array('class' => array('filter-order-weight')), ); - } - // Make sure filters are in the correct order, since filter_get_filters() - // doesn't return sorted filters. - uasort($form['filters']['order'], 'element_sort'); - // Filter settings. - $form['filter_settings'] = array( - '#type' => 'vertical_tabs', - '#title' => t('Filter settings'), - ); - - foreach ($filter_info as $name => $filter) { - if (isset($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. - $settings_form = $function($form, $form_state, $filters[$name], $format, $filter['default settings'], $filters); - if (!empty($settings_form)) { - $form['filters']['settings'][$name] = array( - '#type' => 'details', - '#title' => $filter['title'], - '#parents' => array('filters', $name, 'settings'), - '#weight' => $filter['weight'], - '#group' => 'filter_settings', - ); - $form['filters']['settings'][$name] += $settings_form; - } + // Retrieve the settings form of the filter plugin. The plugin should not be + // aware of the text format. Therefore, it only receives a set of minimal + // base properties to allow advanced implementations to work. + $settings_form = array( + '#parents' => array('filters', $name, 'settings'), + '#tree' => TRUE, + ); + $settings_form = $filter->settingsForm($settings_form, $form_state); + if (!empty($settings_form)) { + $form['filters']['settings'][$name] = array( + '#type' => 'details', + '#title' => $filter->getLabel(), + '#weight' => $filter->weight, + '#parents' => array('filters', $name, 'settings'), + '#group' => 'filter_settings', + ); + $form['filters']['settings'][$name] += $settings_form; } } @@ -337,7 +316,14 @@ function filter_admin_format_form_submit($form, &$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->set($key, $value); + if ($key != 'filters') { + $format->set($key, $value); + } + else { + foreach ($value as $instance_id => $config) { + $format->setFilterConfig($instance_id, $config); + } + } } $status = $format->save(); diff --git a/core/modules/filter/filter.api.php b/core/modules/filter/filter.api.php index 49311c8..8d6aa01 100644 --- a/core/modules/filter/filter.api.php +++ b/core/modules/filter/filter.api.php @@ -11,283 +11,19 @@ */ /** - * 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. - * - type: (required) A classification of the filter's purpose. This is one - * of the following: - * - FILTER_TYPE_HTML_RESTRICTOR: HTML tag and attribute restricting - * filters. - * - FILTER_TYPE_MARKUP_LANGUAGE: Non-HTML markup language filters that - * generate HTML. - * - FILTER_TYPE_TRANSFORM_IRREVERSIBLE: Irreversible transformation - * filters. - * - FILTER_TYPE_TRANSFORM_REVERSIBLE: Reversible transformation filters. - * - 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 hook_filter_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 hook_filter_FILTER_prepare() for - * details. - * - process callback: (required) The name the function that performs the - * actual filtering. See hook_filter_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 hook_filter_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'), - 'type' => FILTER_TYPE_HTML_RESTRICTOR, - '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' => '
- ', - 'filter_html_help' => 1, - 'filter_html_nofollow' => 0, - ), - 'tips callback' => '_filter_html_tips', - ); - $filters['filter_autop'] = array( - 'title' => t('Convert line breaks'), - 'type' => FILTER_TYPE_MARKUP_LANGUAGE, - 'description' => t('Converts line breaks into HTML (i.e. <br> and <p>) 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. + * Array of information on filters exposed by filter plugins. */ 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( + $info['filter_url']['default_settings'] = array( 'filter_url_length' => 100, ); } /** - * @} End of "addtogroup hooks". - */ - -/** - * Settings callback for hook_filter_info(). - * - * Note: This is not really a hook. The function name is manually specified via - * 'settings callback' in hook_filter_info(), with this recommended callback - * name pattern. It is called from filter_admin_format_form(). - * - * 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. - */ -function hook_filter_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; -} - -/** - * Prepare callback for hook_filter_info(). - * - * Note: This is not really a hook. The function name is manually specified via - * 'prepare callback' in hook_filter_info(), with this recommended callback - * name pattern. It is called from check_markup(). - * - * 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. - */ -function hook_filter_FILTER_prepare($text, $filter, $format, $langcode, $cache, $cache_id) { - // Escape
and
tags. - $text = preg_replace('|(.+?)
|se', "[codefilter_code]$1[/codefilter_code]", $text); - return $text; -} - -/** - * Process callback for hook_filter_info(). - * - * Note: This is not really a hook. The function name is manually specified via - * 'process callback' in hook_filter_info(), with this recommended callback - * name pattern. It is called from check_markup(). - * - * 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. - */ -function hook_filter_FILTER_process($text, $filter, $format, $langcode, $cache, $cache_id) { - $text = preg_replace('|\[codefilter_code\](.+?)\[/codefilter_code\]|se', "$1", $text); - - return $text; -} - -/** - * Tips callback for hook_filter_info(). - * - * Note: This is not really a hook. The function name is manually specified via - * 'tips callback' in hook_filter_info(), with this recommended callback - * name pattern. It is called from _filter_tips(). - * - * 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. - */ -function hook_filter_FILTER_tips($filter, $format, $long) { - if ($long) { - return t('Lines and paragraphs are automatically recognized. The <br /> line break, <p> paragraph and </p> 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 text format has been disabled. * * @param $format diff --git a/core/modules/filter/filter.module b/core/modules/filter/filter.module index 57ff8c2..06d3fff 100644 --- a/core/modules/filter/filter.module +++ b/core/modules/filter/filter.module @@ -11,21 +11,29 @@ /** * Non-HTML markup language filters that generate HTML. + * + * @todo Move into \Drupal\filter\Plugin\Filter\FilterInterface */ const FILTER_TYPE_MARKUP_LANGUAGE = 0; /** * HTML tag and attribute restricting filters. + * + * @todo Move into \Drupal\filter\Plugin\Filter\FilterInterface */ const FILTER_TYPE_HTML_RESTRICTOR = 1; /** * Reversible transformation filters. + * + * @todo Move into \Drupal\filter\Plugin\Filter\FilterInterface */ const FILTER_TYPE_TRANSFORM_REVERSIBLE = 2; /** * Irreversible transformation filters. + * + * @todo Move into \Drupal\filter\Plugin\Filter\FilterInterface */ const FILTER_TYPE_TRANSFORM_IRREVERSIBLE = 3; @@ -166,6 +174,8 @@ function filter_menu() { * has been marked as disabled, FALSE is returned. * * @see filter_format_exists() + * + * @todo Use entity_load(). */ function filter_format_load($format_id) { $formats = filter_formats(); @@ -251,24 +261,6 @@ function filter_permission_name($format) { } /** - * 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 @@ -422,19 +414,10 @@ function filter_get_filter_types_by_format($format_id) { $filter_types = array(); $filters = filter_list_format($format_id); - - // Ignore filters that are disabled. - $filters = array_filter($filters, function($filter) { - return $filter->status; - }); - - $filters_info = filter_get_filters(); foreach ($filters as $filter) { - if (!isset($filters_info[$filter->name]['type'])) { - throw new Exception(t('Filter %filter has no type specified.', array ('%filter' => $filter->name))); + if ($filter->status) { + $filter_types[] = $filter->getType(); } - - $filter_types[] = $filters_info[$filter->name]['type']; } return array_unique($filter_types); @@ -491,50 +474,6 @@ function filter_fallback_format_title() { } /** - * 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, - 'default settings' => array(), - ); - } - $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 @@ -553,35 +492,6 @@ function filter_format_allowcache($format_id) { } /** - * Determines 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 Drupal\filter\Plugin\Core\Entity\FilterFormat::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 @@ -595,48 +505,33 @@ function _filter_format_is_cacheable($format) { * @return * An array of filter objects associated to the given text format, keyed by * filter name. + * + * @todo Change this function to only return enabled filters. Code that needs to + * access disabled filters is not regular runtime code and thus can work with + * the FilterFormat::filters(). */ 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 { + $filters['all'] = array(); $filter_formats = filter_formats(); foreach ($filter_formats as $filter_format) { - foreach ($filter_format->filters as $filter_name => $filter) { - $filter['name'] = $filter_name; - $filters['all'][$filter_format->format][$filter_name] = $filter; - } - uasort($filters['all'][$filter_format->format], 'Drupal\filter\Plugin\Core\Entity\FilterFormat::sortFilters'); - // Convert filters into objects. - // @todo Retain filters as arrays or convert to plugins. - foreach ($filters['all'][$filter_format->format] as $filter_name => $filter) { - $filters['all'][$filter_format->format][$filter_name] = (object) $filter; - } + // This loop must not instantiate the actual filter plugins, since the + // filter bag would be duplicated for each filter plugin instance upon + // unserialization of the cache item. + $filters['all'][$filter_format->id()] = $filter_format->filters(); } 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']; - - $filter->settings = isset($filter->settings) ? $filter->settings : array(); - // Merge in default settings. - $filter->settings += $filter_info[$name]['default settings']; - - $format_filters[$name] = $filter; - } - } - $filters[$format_id] = $format_filters; + if (!isset($filters[$format_id]) && isset($filters['all'][$format_id])) { + $filters[$format_id] = $filters['all'][$format_id]; } return isset($filters[$format_id]) ? $filters[$format_id] : array(); @@ -712,36 +607,33 @@ function check_markup($text, $format_id = NULL, $langcode = '', $cache = FALSE, // 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) { + foreach ($filters as $filter) { // If necessary, skip filters of a certain type. - if (in_array($filter_info[$name]['type'], $filter_types_to_skip)) { + if (in_array($filter->getType(), $filter_types_to_skip)) { continue; } - if (!empty($filter->status) && isset($filter_info[$name]['prepare callback'])) { - $function = $filter_info[$name]['prepare callback']; - $text = $function($text, $filter, $format, $langcode, $cache, $cache_id); + if ($filter->status) { + $text = $filter->prepare($text, $langcode, $cache, $cache_id); } } // Perform filtering. - foreach ($filters as $name => $filter) { + foreach ($filters as $filter) { // If necessary, skip filters of a certain type. - if (in_array($filter_info[$name]['type'], $filter_types_to_skip)) { + if (in_array($filter->getType(), $filter_types_to_skip)) { continue; } - if (!empty($filter->status) && isset($filter_info[$name]['process callback'])) { - $function = $filter_info[$name]['process callback']; - $text = $function($text, $filter, $format, $langcode, $cache, $cache_id); + if ($filter->status) { + $text = $filter->process($text, $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 Drupal\filter\Plugin\Core\Entity\FilterFormat::save() + // @see \Drupal\filter\Plugin\Core\Entity\FilterFormat::save() if ($cache) { cache('filter')->set($cache_id, $text, CacheBackendInterface::CACHE_PERMANENT, array('filter_format' => $format->format)); } @@ -1003,7 +895,6 @@ function _filter_tips($format_id, $long = FALSE) { global $user; $formats = filter_formats($user); - $filter_info = filter_get_filters(); $tips = array(); @@ -1014,10 +905,9 @@ function _filter_tips($format_id, $long = FALSE) { foreach ($formats as $format) { $filters = filter_list_format($format->format); - $tips[$format->name] = array(); foreach ($filters as $name => $filter) { - if (!empty($filter->status) && isset($filter_info[$name]['tips callback'])) { - $tip = $filter_info[$name]['tips callback']($filter, $format, $long); + if ($filter->status) { + $tip = $filter->tips($long); if (isset($tip)) { $tips[$format->name][$name] = array('tip' => $tip, 'id' => $name); } @@ -1161,93 +1051,6 @@ function theme_filter_guidelines($variables) { */ /** - * Implements hook_filter_info(). - */ -function filter_filter_info() { - $filters['filter_html'] = array( - 'title' => t('Limit allowed HTML tags'), - 'type' => FILTER_TYPE_HTML_RESTRICTOR, - 'process callback' => '_filter_html', - 'settings callback' => '_filter_html_settings', - 'default settings' => array( - 'allowed_html' => 'and
', - '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.
<br>
and<p>
)'), - 'type' => FILTER_TYPE_MARKUP_LANGUAGE, - 'process callback' => '_filter_autop', - 'tips callback' => '_filter_autop_tips', - ); - $filters['filter_url'] = array( - 'title' => t('Convert URLs into links'), - 'type' => FILTER_TYPE_MARKUP_LANGUAGE, - 'process callback' => '_filter_url', - 'settings callback' => '_filter_url_settings', - 'default settings' => array( - 'filter_url_length' => 72, - ), - 'tips callback' => '_filter_url_tips', - ); - $filters['filter_html_image_secure'] = array( - 'title' => t('Restrict images to this site'), - 'type' => FILTER_TYPE_HTML_RESTRICTOR, - 'description' => t('Disallows usage of <img> tag sources that are not hosted on this site by replacing them with a placeholder image.'), - 'process callback' => '_filter_html_image_secure_process', - 'tips callback' => '_filter_html_image_secure_tips', - // Supposed to run after other filters and before HTML corrector by default. - 'weight' => 9, - ); - $filters['filter_htmlcorrector'] = array( - 'title' => t('Correct faulty and chopped off HTML'), - 'type' => FILTER_TYPE_HTML_RESTRICTOR, - 'process callback' => '_filter_htmlcorrector', - 'weight' => 10, - ); - $filters['filter_html_escape'] = array( - 'title' => t('Display any HTML as plain text'), - 'type' => FILTER_TYPE_HTML_RESTRICTOR, - 'process callback' => '_filter_html_escape', - 'tips callback' => '_filter_html_escape_tips', - 'weight' => -10, - ); - return $filters; -} - -/** - * Filter settings callback for the HTML content filter. - * - * See hook_filter_FILTER_settings() for documentation of parameters and return - * value. - */ -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; -} - -/** * Provides filtering of input into accepted HTML. */ function _filter_html($text, $filter) { @@ -1267,125 +1070,6 @@ function _filter_html($text, $filter) { } /** - * Filter tips callback: 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 = '' . $output . '
'; - if (!$filter->settings['filter_html_help']) { - return $output; - } - - $output .= '' . 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.') . '
'; - $output .= '' . t('For more information see W3C\'s HTML Specifications or use your favorite search engine to find other sites that explain HTML.', array('@html-specifications' => 'http://www.w3.org/TR/html/')) . '
'; - $tips = array( - 'a' => array(t('Anchors are used to make links to other pages.'), '' . check_plain(config('system.site')->get('name')) . ''), - '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
line break')), - 'p' => array(t('By default paragraph tags are automatically added, so use this tag to add additional ones.'), '' . t('Paragraph one.') . '
' . t('Paragraph two.') . '
'), - 'strong' => array(t('Strong', array(), array('context' => 'Font weight')), '' . t('Strong', array(), array('context' => 'Font weight')) . ''), - 'em' => array(t('Emphasized'), '' . t('Emphasized') . ''), - 'cite' => array(t('Cited'), '' . t('Cited') . ''), - 'code' => array(t('Coded text used to show programming source code'), '' . t('Coded') . '
'), - 'b' => array(t('Bolded'), '' . t('Bolded') . ''), - 'u' => array(t('Underlined'), '' . t('Underlined') . ''), - 'i' => array(t('Italicized'), '' . t('Italicized') . ''), - 'sup' => array(t('Superscripted'), t('Superscripted')), - 'sub' => array(t('Subscripted'), t('Subscripted')), - 'pre' => array(t('Preformatted'), '' . t('Preformatted') . ''), - 'abbr' => array(t('Abbreviation'), t('Abbrev.')), - 'acronym' => array(t('Acronym'), t('TLA')), - 'blockquote' => array(t('Block quoted'), '' . t('Block quoted') . ''), - 'q' => array(t('Quoted inline'), '' . t('Quoted inline') . ''), - // Assumes and describes tr, td, th. - 'table' => array(t('Table'), ''), - 'tr' => NULL, 'td' => NULL, 'th' => NULL, - 'del' => array(t('Deleted'), '
' . t('Table header') . ' ' . t('Table cell') . ' ' . t('Deleted') . ''), - 'ins' => array(t('Inserted'), '' . t('Inserted') . ''), - // Assumes and describes li. - 'ol' => array(t('Ordered list - use the <li> to begin each list item'), ''), - 'ul' => array(t('Unordered list - use the <li> to begin each list item'), '
- ' . t('First item') . '
- ' . t('Second item') . '
'), - 'li' => NULL, - // Assumes and describes dt and dd. - 'dl' => array(t('Definition lists are similar to other HTML lists. <dl> begins the definition list, <dt> begins the definition term and <dd> begins the definition description.'), '
- ' . t('First item') . '
- ' . t('Second item') . '
'), - 'dt' => NULL, 'dd' => NULL, - 'h1' => array(t('Heading'), '
- ' . t('First term') . '
- ' . t('First definition') . '
- ' . t('Second term') . '
- ' . t('Second definition') . '
' . t('Title') . '
'), - 'h2' => array(t('Heading'), '' . t('Subtitle') . '
'), - 'h3' => array(t('Heading'), '' . t('Subtitle three') . '
'), - 'h4' => array(t('Heading'), '' . t('Subtitle four') . '
'), - 'h5' => array(t('Heading'), '' . t('Subtitle five') . '
'), - 'h6' => array(t('Heading'), '' . t('Subtitle six') . '
') - ); - $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' => '' . check_plain($tips[$tag][1]) . '
', '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 .= '' . t('Most unusual characters can be directly entered without any problems.') . '
'; - $output .= '' . t('If you do encounter problems, try using HTML character entities. A common example looks like & for an ampersand & character. For a full list of entities see HTML\'s entities page. Some of the available characters include:', array('@html-entities' => 'http://www.w3.org/TR/html4/sgml/entities.html')) . '
'; - - $entities = array( - array(t('Ampersand'), '&'), - array(t('Greater than'), '>'), - array(t('Less than'), '<'), - array(t('Quotation mark'), '"'), - ); - $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' => '' . check_plain($entity[1]) . '
', 'class' => array('type')), - array('data' => $entity[1], 'class' => array('get')) - ); - } - $output .= theme('table', array('header' => $header, 'rows' => $rows)); - return $output; -} - -/** - * Filter URL settings callback: 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' => 'number', - '#title' => t('Maximum link text length'), - '#default_value' => $filter->settings['filter_url_length'], - '#min' => 1, - '#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.'), - ); - return $settings; -} - -/** * Converts text into hyperlinks automatically. * * This filter identifies and makes clickable three types of "links". @@ -1612,15 +1296,6 @@ function _filter_url_trim($text, $length = NULL) { } /** - * Filter tips callback: 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.'); -} - -/** * Scans the input and makes sure that HTML tags are properly closed. */ function _filter_htmlcorrector($text) { @@ -1695,20 +1370,6 @@ function _filter_autop($text) { } /** - * Filter tips callback: 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 <br /> line break, <p> paragraph and </p> 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.'); - } -} - -/** * Escapes all HTML tags, so they will be visible instead of being effective. */ function _filter_html_escape($text) { @@ -1716,15 +1377,6 @@ function _filter_html_escape($text) { } /** - * Filter tips callback: 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.'); -} - -/** * Process callback for local image filter. */ function _filter_html_image_secure_process($text) { @@ -1790,13 +1442,6 @@ function theme_filter_html_image_secure_image(&$variables) { } /** - * Filter tips callback for secure HTML image filter. - */ -function _filter_html_image_secure_tips($filter, $format, $long = FALSE) { - return t('Only images hosted on this site may be used in <img> tags.'); -} - -/** * @} End of "defgroup standard_filters". */ diff --git a/core/modules/filter/filter.services.yml b/core/modules/filter/filter.services.yml index af11a10..98efef5 100644 --- a/core/modules/filter/filter.services.yml +++ b/core/modules/filter/filter.services.yml @@ -10,3 +10,6 @@ services: class: Drupal\filter\Access\FormatDisableCheck tags: - { name: access_check } + plugin.manager.filter: + class: Drupal\filter\FilterPluginManager + arguments: ['@container.namespaces'] diff --git a/core/modules/filter/lib/Drupal/filter/Annotation/Filter.php b/core/modules/filter/lib/Drupal/filter/Annotation/Filter.php new file mode 100644 index 0000000..a8379eb --- /dev/null +++ b/core/modules/filter/lib/Drupal/filter/Annotation/Filter.php @@ -0,0 +1,26 @@ +manager = $manager; + $this->configurations = $configurations; + + if (!empty($configurations)) { + $this->instanceIDs = array_combine(array_keys($configurations), array_keys($configurations)); + } + } + + /** + * Retrieves filter definitions and creates an instance for each filter. + * + * This is exclusively used for the text format administration page, on which + * all available filter plugins are exposed, regardless of whether the current + * text format has an active instance. + * + * @todo Refactor text format administration to actually construct/create and + * destruct/remove actual filter plugin instances, using a library approach + * à la blocks. + */ + public function getAll() { + // Retrieve all available filter plugin definitions. + if (!$this->definitions) { + $this->definitions = $this->manager->getDefinitions(); + } + + // Ensure that there is an instance of all available filters. + // Note that getDefinitions() are keyed by $plugin_id. $instance_id is the + // $plugin_id for filters, since a single filter plugin can only exist once + // in a format. + foreach ($this->definitions as $plugin_id => $definition) { + $this->initializePlugin($plugin_id); + } + return $this->pluginInstances; + } + + /** + * Updates the configuration for a filter plugin instance. + * + * If there is no plugin instance yet, a new will be instantiated. Otherwise, + * the existing instance is updated with the new configuration. + * + * @param string $instance_id + * The ID of a filter plugin to set the configuration for. + * @param array $configuration + * The filter plugin configuration to set. + */ + public function setConfig($instance_id, array $configuration) { + $this->configurations[$instance_id] = $configuration; + $this->get($instance_id)->setPluginConfiguration($configuration); + } + + /** + * {@inheritdoc} + */ + protected function initializePlugin($instance_id) { + // If the filter was initialized before, just return. + if (isset($this->pluginInstances[$instance_id])) { + return; + } + + // Filters have a 1:1 relationship to text formats and can be added and + // instantiated at any time. + $definition = $this->manager->getDefinition($instance_id); + + if (isset($definition)) { + $this->addInstanceID($instance_id); + // $configuration is the whole filter plugin instance configuration, as + // contained in the text format configuration. The default configuration + // is the filter plugin definition. + // @todo Configuration should not be contained in definitions. Move into a + // FilterBase::init() method. + $configuration = $definition; + // Merge the actual configuration into the default configuration. + if (isset($this->configurations[$instance_id])) { + $configuration = NestedArray::mergeDeep($configuration, $this->configurations[$instance_id]); + } + $this->pluginInstances[$instance_id] = $this->manager->createInstance($instance_id, $configuration, $this); + } + else { + throw new PluginException(format_string("Unknown filter plugin ID '@filter'.", array('@filter' => $instance_id))); + } + } + + /** + * Sorts all filter plugin instances in this bag. + * + * @return \Drupal\filter\FilterBag + */ + public function sort() { + $this->getAll(); + uasort($this->instanceIDs, array($this, 'sortHelper')); + return $this; + } + + /** + * uasort() callback to sort filters by status, weight, module, and name. + * + * @see \Drupal\filter\FilterFormatStorageController::preSave() + */ + public function sortHelper($aID, $bID) { + $a = $this->get($aID); + $b = $this->get($bID); + if ($a->status != $b->status) { + return !empty($a->status) ? -1 : 1; + } + if ($a->weight != $b->weight) { + return $a->weight < $b->weight ? -1 : 1; + } + if ($a->module != $b->module) { + return strnatcasecmp($a->module, $b->module); + } + return strnatcasecmp($a->getPluginId(), $b->getPluginId()); + } + + /** + * Returns the current configuration of all filters in this bag. + * + * @return array + * An associative array keyed by filter plugin instance ID, whose values + * are filter configurations. + * + * @see \Drupal\filter\Plugin\Filter\FilterInterface::export() + */ + public function export() { + $filters = array(); + $this->rewind(); + foreach ($this as $instance_id => $instance) { + $filters[$instance_id] = $instance->export(); + } + return $filters; + } + +} diff --git a/core/modules/filter/lib/Drupal/filter/FilterFormatInterface.php b/core/modules/filter/lib/Drupal/filter/FilterFormatInterface.php index 001fa0a..beb83b3 100644 --- a/core/modules/filter/lib/Drupal/filter/FilterFormatInterface.php +++ b/core/modules/filter/lib/Drupal/filter/FilterFormatInterface.php @@ -15,11 +15,28 @@ interface FilterFormatInterface extends ConfigEntityInterface { /** - * Helper callback for uasort() to sort filters by status, weight, module, and name. + * Returns the collection of filter pugin instances or an individual plugin instance. * - * @see Drupal\filter\FilterFormatStorageController::preSave() - * @see filter_list_format() + * @param string $instance_id + * (optional) The ID of a filter plugin instance to return. + * + * @return \Drupal\filter\FilterBag|\Drupal\filter\Plugin\FilterInterface + * Either the filter bag or a specific filter plugin instance. + */ + public function filters($instance_id = NULL); + + /** + * Sets the configuration for a filter plugin instance. + * + * Sets or replaces the configuration of a filter plugin in $this->filters, + * and if instantianted already, also ensures that the actual filter plugin on + * the FilterBag contains the identical configuration. + * + * @param string $instance_id + * The ID of a filter plugin to set the configuration for. + * @param array $configuration + * The filter plugin configuration to set. */ - public static function sortFilters($a, $b); + public function setFilterConfig($instance_id, array $configuration); } diff --git a/core/modules/filter/lib/Drupal/filter/FilterFormatStorageController.php b/core/modules/filter/lib/Drupal/filter/FilterFormatStorageController.php index 1e8bdb2..4c19d8b 100644 --- a/core/modules/filter/lib/Drupal/filter/FilterFormatStorageController.php +++ b/core/modules/filter/lib/Drupal/filter/FilterFormatStorageController.php @@ -22,45 +22,17 @@ protected function preSave(EntityInterface $entity) { parent::preSave($entity); $entity->name = trim($entity->label()); - $entity->cache = _filter_format_is_cacheable($entity); - $filter_info = filter_get_filters(); - foreach ($filter_info as $name => $filter) { - // Merge the actual filter definition into the filter default definition. - $defaults = array( - 'module' => $filter['module'], - // The filter ID has to be temporarily injected into the properties, in - // order to sort all filters below. - // @todo Rethink filter sorting to remove dependency on filter IDs. - 'name' => $name, - // Unless explicitly enabled, all filters are disabled by default. - 'status' => 0, - // If no explicit weight was defined for a filter, assign either the - // default weight defined in hook_filter_info() or the default of 0 by - // filter_get_filters(). - 'weight' => $filter['weight'], - 'settings' => $filter['default settings'], - ); - // All available filters are saved for each format, in order to retain all - // filter properties regardless of whether a filter is currently enabled - // or not, since some filters require extensive configuration. - // @todo Do not save disabled filters whose properties are identical to - // all default properties. - if (isset($entity->filters[$name])) { - $entity->filters[$name] = array_merge($defaults, $entity->filters[$name]); - } - else { - $entity->filters[$name] = $defaults; - } - // The module definition from hook_filter_info() always takes precedence - // and needs to be updated in case it changes. - $entity->filters[$name]['module'] = $filter['module']; - } - // Sort all filters. - uasort($entity->filters, 'Drupal\filter\Plugin\Core\Entity\FilterFormat::sortFilters'); - // Remove the 'name' property from all filters that was added above. - foreach ($entity->filters as &$filter) { - unset($filter['name']); + // @todo Do not save disabled filters whose properties are identical to + // all default properties. + + // Determine whether the format can be cached. + // @todo This is a derived/computed definition, not configuration. + $entity->cache = TRUE; + foreach ($entity->filters() as $filter) { + if ($filter->status && !$filter->cache) { + $entity->cache = FALSE; + } } } diff --git a/core/modules/filter/lib/Drupal/filter/FilterPluginManager.php b/core/modules/filter/lib/Drupal/filter/FilterPluginManager.php new file mode 100644 index 0000000..be4be40 --- /dev/null +++ b/core/modules/filter/lib/Drupal/filter/FilterPluginManager.php @@ -0,0 +1,48 @@ + $namespaces['Drupal\filter']); + $this->discovery = new AnnotatedClassDiscovery('Filter', $namespaces, $annotation_namespaces, 'Drupal\filter\Annotation\Filter'); + $this->discovery = new AlterDecorator($this->discovery, 'filter_info'); + $this->discovery = new CacheDecorator($this->discovery, 'filter_plugins:' . language(LANGUAGE_TYPE_INTERFACE)->langcode, 'cache', array( + 'filter_formats' => TRUE, + )); + } + + /** + * {@inheritdoc} + */ + public function createInstance($plugin_id, array $configuration = array(), FilterBag $filter_bag = NULL) { + $plugin_definition = $this->discovery->getDefinition($plugin_id); + $plugin_class = DefaultFactory::getPluginClass($plugin_id, $plugin_definition); + return new $plugin_class($configuration, $plugin_id, $plugin_definition, $filter_bag); + } + +} diff --git a/core/modules/filter/lib/Drupal/filter/Plugin/Core/Entity/FilterFormat.php b/core/modules/filter/lib/Drupal/filter/Plugin/Core/Entity/FilterFormat.php index aa306bf..d6b1fe6 100644 --- a/core/modules/filter/lib/Drupal/filter/Plugin/Core/Entity/FilterFormat.php +++ b/core/modules/filter/lib/Drupal/filter/Plugin/Core/Entity/FilterFormat.php @@ -11,6 +11,7 @@ use Drupal\Core\Entity\Annotation\EntityType; use Drupal\Core\Annotation\Translation; use Drupal\filter\FilterFormatInterface; +use Drupal\filter\FilterBag; /** * Represents a text format. @@ -98,24 +99,30 @@ class FilterFormat extends ConfigEntityBase implements FilterFormatInterface { * Configured filters for this text format. * * An associative array of filters assigned to the text format, keyed by the - * ID of each filter (prefixed with module name) and using the properties: + * instance ID of each filter and using the properties: + * - plugin_id: The plugin ID of the filter plugin instance. * - module: The name of the module providing the filter. * - status: (optional) A Boolean indicating whether the filter is - * enabled in the text format. Defaults to disabled. - * - weight: (optional) The weight of the filter in the text format. If - * omitted, the default value is determined in the following order: - * - if any, the currently stored weight is retained. - * - if any, the default weight from hook_filter_info() is taken over. - * - otherwise, a default weight of 10, which usually sorts it last. + * enabled in the text format. Defaults to FALSE. + * - weight: (optional) The weight of the filter in the text format. Defaults + * to 0. * - settings: (optional) An array of configured settings for the filter. - * See hook_filter_info() for details. + * + * Use FilterFormat::filters() to access the actual filters. * * @var array */ - public $filters = array(); + protected $filters = array(); + + /** + * Holds the collection of filters that are attached to this format. + * + * @var \Drupal\filter\FilterBag + */ + protected $filterBag; /** - * Overrides \Drupal\Core\Entity\Entity::id(). + * {@inheritdoc} */ public function id() { return $this->format; @@ -124,21 +131,40 @@ public function id() { /** * {@inheritdoc} */ - public static function sortFilters($a, $b) { - if ($a['status'] != $b['status']) { - return !empty($a['status']) ? -1 : 1; + public function filters($instance_id = NULL) { + if (!isset($this->filterBag)) { + $this->filterBag = new FilterBag(\Drupal::service('plugin.manager.filter'), $this->filters); } - if ($a['weight'] != $b['weight']) { - return ($a['weight'] < $b['weight']) ? -1 : 1; + if (isset($instance_id)) { + return $this->filterBag->get($instance_id); } - if ($a['module'] != $b['module']) { - return strnatcasecmp($a['module'], $b['module']); + return $this->filterBag; + } + + /** + * {@inheritdoc} + */ + public function setFilterConfig($instance_id, array $configuration) { + $this->filters[$instance_id] = $configuration; + if (isset($this->filterBag)) { + $this->filterBag->setConfig($instance_id, $configuration); } - return strnatcasecmp($a['name'], $b['name']); + return $this; } /** - * Overrides \Drupal\Core\Config\Entity\ConfigEntityBase::disable(). + * {@inheritdoc} + */ + public function getExportProperties() { + $properties = parent::getExportProperties(); + // Sort and export the configuration of all filters. + $properties['filters'] = $this->filters()->sort()->export(); + + return $properties; + } + + /** + * {@inheritdoc} */ public function disable() { parent::disable(); diff --git a/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterAutoP.php b/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterAutoP.php new file mode 100644 index 0000000..62d3649 --- /dev/null +++ b/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterAutoP.php @@ -0,0 +1,45 @@ +<br><p>
)"), + * type = FILTER_TYPE_MARKUP_LANGUAGE + * ) + */ +class FilterAutoP extends FilterBase { + + /** + * {@inheritdoc} + */ + public function process($text, $langcode, $cache, $cache_id) { + return _filter_autop($text); + } + + /** + * {@inheritdoc} + */ + public function tips($long = FALSE) { + if ($long) { + return t('Lines and paragraphs are automatically recognized. The <br /> line break, <p> paragraph and </p> 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.'); + } + } + +} diff --git a/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterHtml.php b/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterHtml.php new file mode 100644 index 0000000..c939c27 --- /dev/null +++ b/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterHtml.php @@ -0,0 +1,161 @@ +
", + * "filter_html_help" = 1, + * "filter_html_nofollow" = 0 + * }, + * weight = -10 + * ) + */ +class FilterHtml extends FilterBase { + + /** + * {@inheritdoc} + */ + public function settingsForm(array $form, array &$form_state) { + $form['allowed_html'] = array( + '#type' => 'textfield', + '#title' => t('Allowed HTML tags'), + '#default_value' => $this->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.'), + ); + $form['filter_html_help'] = array( + '#type' => 'checkbox', + '#title' => t('Display basic HTML help in long filter tips'), + '#default_value' => $this->settings['filter_html_help'], + ); + $form['filter_html_nofollow'] = array( + '#type' => 'checkbox', + '#title' => t('Add rel="nofollow" to all links'), + '#default_value' => $this->settings['filter_html_nofollow'], + ); + return $form; + } + + /** + * {@inheritdoc} + */ + public function process($text, $langcode, $cache, $cache_id) { + return _filter_html($text, $this); + } + + /** + * {@inheritdoc} + */ + public function tips($long = FALSE) { + global $base_url; + + if (!($allowed_html = $this->settings['allowed_html'])) { + return; + } + $output = t('Allowed HTML tags: @tags', array('@tags' => $allowed_html)); + if (!$long) { + return $output; + } + + $output = '
' . $output . '
'; + if (!$this->settings['filter_html_help']) { + return $output; + } + + $output .= '' . 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.') . '
'; + $output .= '' . t('For more information see W3C\'s HTML Specifications or use your favorite search engine to find other sites that explain HTML.', array('@html-specifications' => 'http://www.w3.org/TR/html/')) . '
'; + $tips = array( + 'a' => array(t('Anchors are used to make links to other pages.'), '' . check_plain(config('system.site')->get('name')) . ''), + '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
line break')), + 'p' => array(t('By default paragraph tags are automatically added, so use this tag to add additional ones.'), '' . t('Paragraph one.') . '
' . t('Paragraph two.') . '
'), + 'strong' => array(t('Strong', array(), array('context' => 'Font weight')), '' . t('Strong', array(), array('context' => 'Font weight')) . ''), + 'em' => array(t('Emphasized'), '' . t('Emphasized') . ''), + 'cite' => array(t('Cited'), '' . t('Cited') . ''), + 'code' => array(t('Coded text used to show programming source code'), '' . t('Coded') . '
'), + 'b' => array(t('Bolded'), '' . t('Bolded') . ''), + 'u' => array(t('Underlined'), '' . t('Underlined') . ''), + 'i' => array(t('Italicized'), '' . t('Italicized') . ''), + 'sup' => array(t('Superscripted'), t('Superscripted')), + 'sub' => array(t('Subscripted'), t('Subscripted')), + 'pre' => array(t('Preformatted'), '' . t('Preformatted') . ''), + 'abbr' => array(t('Abbreviation'), t('Abbrev.')), + 'acronym' => array(t('Acronym'), t('TLA')), + 'blockquote' => array(t('Block quoted'), '' . t('Block quoted') . ''), + 'q' => array(t('Quoted inline'), '' . t('Quoted inline') . ''), + // Assumes and describes tr, td, th. + 'table' => array(t('Table'), ''), + 'tr' => NULL, 'td' => NULL, 'th' => NULL, + 'del' => array(t('Deleted'), '
' . t('Table header') . ' ' . t('Table cell') . ' ' . t('Deleted') . ''), + 'ins' => array(t('Inserted'), '' . t('Inserted') . ''), + // Assumes and describes li. + 'ol' => array(t('Ordered list - use the <li> to begin each list item'), ''), + 'ul' => array(t('Unordered list - use the <li> to begin each list item'), '
- ' . t('First item') . '
- ' . t('Second item') . '
'), + 'li' => NULL, + // Assumes and describes dt and dd. + 'dl' => array(t('Definition lists are similar to other HTML lists. <dl> begins the definition list, <dt> begins the definition term and <dd> begins the definition description.'), '
- ' . t('First item') . '
- ' . t('Second item') . '
'), + 'dt' => NULL, 'dd' => NULL, + 'h1' => array(t('Heading'), '
- ' . t('First term') . '
- ' . t('First definition') . '
- ' . t('Second term') . '
- ' . t('Second definition') . '
' . t('Title') . '
'), + 'h2' => array(t('Heading'), '' . t('Subtitle') . '
'), + 'h3' => array(t('Heading'), '' . t('Subtitle three') . '
'), + 'h4' => array(t('Heading'), '' . t('Subtitle four') . '
'), + 'h5' => array(t('Heading'), '' . t('Subtitle five') . '
'), + 'h6' => array(t('Heading'), '' . t('Subtitle six') . '
') + ); + $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' => '' . check_plain($tips[$tag][1]) . '
', '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 .= '' . t('Most unusual characters can be directly entered without any problems.') . '
'; + $output .= '' . t('If you do encounter problems, try using HTML character entities. A common example looks like & for an ampersand & character. For a full list of entities see HTML\'s entities page. Some of the available characters include:', array('@html-entities' => 'http://www.w3.org/TR/html4/sgml/entities.html')) . '
'; + + $entities = array( + array(t('Ampersand'), '&'), + array(t('Greater than'), '>'), + array(t('Less than'), '<'), + array(t('Quotation mark'), '"'), + ); + $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' => '' . check_plain($entity[1]) . '
', 'class' => array('type')), + array('data' => $entity[1], 'class' => array('get')) + ); + } + $output .= theme('table', array('header' => $header, 'rows' => $rows)); + return $output; + } + +} diff --git a/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterHtmlCorrector.php b/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterHtmlCorrector.php new file mode 100644 index 0000000..3f6b8e3 --- /dev/null +++ b/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterHtmlCorrector.php @@ -0,0 +1,34 @@ + 'number', + '#title' => t('Maximum link text length'), + '#default_value' => $this->settings['filter_url_length'], + '#min' => 1, + '#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.'), + ); + return $form; + } + + /** + * {@inheritdoc} + */ + public function process($text, $langcode, $cache, $cache_id) { + return _filter_url($text, $this); + } + + /** + * {@inheritdoc} + */ + public function tips($long = FALSE) { + return t('Web page addresses and e-mail addresses turn into links automatically.'); + } + +} diff --git a/core/modules/filter/lib/Drupal/filter/Plugin/FilterBase.php b/core/modules/filter/lib/Drupal/filter/Plugin/FilterBase.php new file mode 100644 index 0000000..ab56c91 --- /dev/null +++ b/core/modules/filter/lib/Drupal/filter/Plugin/FilterBase.php @@ -0,0 +1,156 @@ +module = $this->pluginDefinition['module']; + $this->cache = $this->pluginDefinition['cache']; + + $this->setPluginConfiguration($configuration); + + $this->bag = $bag; + } + + /** + * {@inheritdoc} + */ + public function setPluginConfiguration(array $configuration) { + if (isset($configuration['status'])) { + $this->status = (bool) $configuration['status']; + } + if (isset($configuration['weight'])) { + $this->weight = (int) $configuration['weight']; + } + if (isset($configuration['settings'])) { + $this->settings = (array) $configuration['settings']; + } + return $this; + } + + /** + * {@inheritdoc} + */ + public function export() { + return array( + 'module' => $this->pluginDefinition['module'], + 'status' => $this->status, + 'weight' => $this->weight, + 'settings' => $this->settings, + ); + } + + /** + * {@inheritdoc} + */ + public function getType() { + return $this->pluginDefinition['type']; + } + + /** + * {@inheritdoc} + */ + public function getLabel() { + return $this->pluginDefinition['title']; + } + + /** + * {@inheritdoc} + */ + public function getDescription() { + return $this->pluginDefinition['description']; + } + + /** + * {@inheritdoc} + */ + public function settingsForm(array $form, array &$form_state) { + // Implementations should work with and return $form. Returning an empty + // array here allows the text format administration form to identify whether + // the filter plugin has any settings form elements. + return array(); + } + + /** + * {@inheritdoc} + */ + public function prepare($text, $langcode, $cache, $cache_id) { + return $text; + } + + /** + * {@inheritdoc} + */ + public function tips($long = FALSE) { + } + +} diff --git a/core/modules/filter/lib/Drupal/filter/Plugin/FilterInterface.php b/core/modules/filter/lib/Drupal/filter/Plugin/FilterInterface.php new file mode 100644 index 0000000..0857139 --- /dev/null +++ b/core/modules/filter/lib/Drupal/filter/Plugin/FilterInterface.php @@ -0,0 +1,196 @@ +settings. + */ + public function settingsForm(array $form, array &$form_state); + + /** + * Prepares the text for processing. + * + * Filters should not use the prepare method for anything other than escaping, + * because that would short-circuit the control the user has over the order in + * which filters are applied. + * + * @param string $text + * The text string to be filtered. + * @param string $langcode + * The language code of the text to be filtered. + * @param bool $cache + * A Boolean indicating whether the filtered text is going to be cached in + * {cache_filter}. + * @param string $cache_id + * The ID of the filtered text in {cache_filter}, if $cache is TRUE. + * + * @return string + * The prepared, escaped text. + */ + public function prepare($text, $langcode, $cache, $cache_id); + + /** + * Performs the filter processing. + * + * @param string $text + * The text string to be filtered. + * @param string $langcode + * The language code of the text to be filtered. + * @param bool $cache + * A Boolean indicating whether the filtered text is going to be cached in + * {cache_filter}. + * @param string $cache_id + * The ID of the filtered text in {cache_filter}, if $cache is TRUE. + * + * @return string + * The filtered text. + */ + public function process($text, $langcode, $cache, $cache_id); + + /** + * Generates a filter's tip. + * + * A filter's tips should be informative and to the point. Short tips are + * preferably one-liners. + * + * @param bool $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 string|null + * Translated text to display as a tip, or NULL if this filter has no tip. + * + * @todo Split into getSummaryItem() and buildGuidelines(). + */ + public function tips($long = FALSE); + +} diff --git a/core/modules/filter/lib/Drupal/filter/Tests/FilterAdminTest.php b/core/modules/filter/lib/Drupal/filter/Tests/FilterAdminTest.php index 81d5321..b07acac 100644 --- a/core/modules/filter/lib/Drupal/filter/Tests/FilterAdminTest.php +++ b/core/modules/filter/lib/Drupal/filter/Tests/FilterAdminTest.php @@ -34,10 +34,12 @@ function setUp() { // Create users. $basic_html_format = filter_format_load('basic_html'); + $restricted_html_format = filter_format_load('restricted_html'); $full_html_format = filter_format_load('full_html'); $this->admin_user = $this->drupalCreateUser(array( 'administer filters', filter_permission_name($basic_html_format), + filter_permission_name($restricted_html_format), filter_permission_name($full_html_format), )); @@ -115,12 +117,11 @@ function testFormatAdmin() { * Tests filter administration functionality. */ function testFilterAdmin() { - // URL filter. - $first_filter = 'filter_url'; - // Line filter. - $second_filter = 'filter_autop'; + $first_filter = 'filter_autop'; + $second_filter = 'filter_url'; $basic = 'basic_html'; + $restricted = 'restricted_html'; $full = 'full_html'; $plain = 'plain_text'; @@ -138,9 +139,9 @@ function testFilterAdmin() { // Add an additional tag. $edit = array(); $edit['filters[filter_html][settings][allowed_html]'] = '
'; - $this->drupalPost('admin/config/content/formats/' . $basic, $edit, t('Save configuration')); + $this->drupalPost('admin/config/content/formats/' . $restricted, $edit, t('Save configuration')); $this->assertUrl('admin/config/content/formats'); - $this->drupalGet('admin/config/content/formats/' . $basic); + $this->drupalGet('admin/config/content/formats/' . $restricted); $this->assertFieldByName('filters[filter_html][settings][allowed_html]', $edit['filters[filter_html][settings][allowed_html]'], 'Allowed HTML tag added.'); $this->assertTrue(cache('filter')->isEmpty(), 'Cache cleared.'); @@ -157,7 +158,7 @@ function testFilterAdmin() { $edit['filters[' . $first_filter . '][weight]'] = 2; $this->drupalPost(NULL, $edit, t('Save configuration')); $this->assertUrl('admin/config/content/formats'); - $this->drupalGet('admin/config/content/formats/' . $basic); + $this->drupalGet('admin/config/content/formats/' . $restricted); $this->assertFieldByName('filters[' . $second_filter . '][weight]', 1, 'Order saved successfully.'); $this->assertFieldByName('filters[' . $first_filter . '][weight]', 2, 'Order saved successfully.'); @@ -167,13 +168,14 @@ function testFilterAdmin() { )); $this->assertTrue(!empty($elements), 'Reorder confirmed in admin interface.'); - $filter_format = entity_load('filter_format', $basic); - foreach ($filter_format->filters as $filter_name => $filter) { + $filter_format = entity_load('filter_format', $restricted); + foreach ($filter_format->filters() as $filter_name => $filter) { if ($filter_name == $second_filter || $filter_name == $first_filter) { $filters[] = $filter_name; } } - $this->assertTrue($filter_format->filters['filter_autop']['weight'] + 1 == $filter_format->filters['filter_url']['weight'], t('Order confirmed in configuration.')); + // Ensure that the second filter is now before the first filter. + $this->assertEqual($filter_format->filters($second_filter)->weight + 1, $filter_format->filters($first_filter)->weight, 'Order confirmed in configuration.'); // Add format. $edit = array(); @@ -209,7 +211,6 @@ function testFilterAdmin() { $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'); @@ -250,7 +251,6 @@ function testFilterAdmin() { ->save(); // Switch user. - $this->drupalLogout(); $this->drupalLogin($this->admin_user); // Clean up. diff --git a/core/modules/filter/lib/Drupal/filter/Tests/FilterCrudTest.php b/core/modules/filter/lib/Drupal/filter/Tests/FilterCrudTest.php index 7ba7b7d..80b9dd1 100644 --- a/core/modules/filter/lib/Drupal/filter/Tests/FilterCrudTest.php +++ b/core/modules/filter/lib/Drupal/filter/Tests/FilterCrudTest.php @@ -7,20 +7,19 @@ namespace Drupal\filter\Tests; -use Drupal\simpletest\WebTestBase; -use stdClass; +use Drupal\simpletest\DrupalUnitTestBase; /** * Tests for text format and filter CRUD operations. */ -class FilterCrudTest extends WebTestBase { +class FilterCrudTest extends DrupalUnitTestBase { /** * Modules to enable. * * @var array */ - public static $modules = array('filter_test'); + public static $modules = array('filter', 'filter_test'); public static function getInfo() { return array( @@ -46,28 +45,32 @@ function testTextFormatCrud() { $format = entity_create('filter_format', array()); $format->format = 'custom_format'; $format->name = 'Custom format'; - $format->filters = array( - 'filter_url' => array( - 'status' => 1, - 'settings' => array( - 'filter_url_length' => 30, - ), + $format->setFilterConfig('filter_url', array( + 'status' => 1, + 'settings' => array( + 'filter_url_length' => 30, ), - ); + )); $format->save(); $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; + $format->setFilterConfig('filter_url', array( + 'status' => 0, + )); + $format->setFilterConfig('filter_autop', array( + 'status' => 1, + )); $format->save(); $this->verifyTextFormat($format); $this->verifyFilters($format); // Add a uncacheable filter and save again. - $format->filters['filter_test_uncacheable']['status'] = 1; + $format->setFilterConfig('filter_test_uncacheable', array( + 'status' => 1, + )); $format->save(); $this->verifyTextFormat($format); $this->verifyFilters($format); @@ -96,13 +99,12 @@ function verifyTextFormat($format) { $this->assertEqual($format->langcode, $default_langcode, format_string('filter_format_load: Proper language code 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']) { + if ($filter->status && !$filter->cache) { $cacheable = FALSE; break; } @@ -114,28 +116,21 @@ function verifyTextFormat($format) { * Verifies that filters are properly stored for a text format. */ function verifyFilters($format) { - $format_filters = $format->filters; - - // Verify filter_list_format(). $filters = filter_list_format($format->format); - $format_filters = $format->filters; + $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)); + $this->assertEqual($filter->status, $format_filters->get($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)); + $this->assertEqual($filter->settings, $format_filters->get($name)->settings, 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.'); + $this->assertEqual(count($filters), count($format_filters), 'filter_list_format: All filters for the format are present.'); } + } diff --git a/core/modules/filter/lib/Drupal/filter/Tests/FilterDefaultConfigTest.php b/core/modules/filter/lib/Drupal/filter/Tests/FilterDefaultConfigTest.php index 85b5fef..ac078e8 100644 --- a/core/modules/filter/lib/Drupal/filter/Tests/FilterDefaultConfigTest.php +++ b/core/modules/filter/lib/Drupal/filter/Tests/FilterDefaultConfigTest.php @@ -42,7 +42,7 @@ function setUp() { function testInstallation() { // Verify that the format was installed correctly. $format = filter_format_load('filter_test'); - $this->assertTrue($format); + $this->assertTrue((bool) $format); $this->assertEqual($format->id(), 'filter_test'); $this->assertEqual($format->label(), 'Test format'); $this->assertEqual($format->get('weight'), 2); @@ -75,14 +75,6 @@ function testInstallation() { $this->assertEqual($filters['filter_url']['settings'], array( 'filter_url_length' => 72, )); - - // Verify disabled filters. - $this->assertEqual($filters['filter_html']['status'], 0); - $this->assertEqual($filters['filter_html']['weight'], -10); - $this->assertEqual($filters['filter_html']['module'], 'filter'); - $this->assertEqual($filters['filter_htmlcorrector']['status'], 0); - $this->assertEqual($filters['filter_htmlcorrector']['weight'], 10); - $this->assertEqual($filters['filter_htmlcorrector']['module'], 'filter'); } /** diff --git a/core/modules/filter/lib/Drupal/filter/Tests/FilterSettingsTest.php b/core/modules/filter/lib/Drupal/filter/Tests/FilterSettingsTest.php index 1a0be97..1b6c173 100644 --- a/core/modules/filter/lib/Drupal/filter/Tests/FilterSettingsTest.php +++ b/core/modules/filter/lib/Drupal/filter/Tests/FilterSettingsTest.php @@ -7,18 +7,19 @@ namespace Drupal\filter\Tests; -use Drupal\simpletest\WebTestBase; +use Drupal\simpletest\DrupalUnitTestBase; /** * Tests filter settings. */ -class FilterSettingsTest extends WebTestBase { +class FilterSettingsTest extends DrupalUnitTestBase { + /** - * The installation profile to use with this test class. + * Modules to enable. * - * @var string + * @var array */ - protected $profile = 'testing'; + public static $modules = array('filter'); public static function getInfo() { return array( @@ -32,7 +33,7 @@ public static function getInfo() { * Tests explicit and implicit default settings for filters. */ function testFilterDefaults() { - $filter_info = filter_filter_info(); + $filter_info = $this->container->get('plugin.manager.filter')->getDefinitions(); $filters = array_fill_keys(array_keys($filter_info), array()); // Create text format using filter default settings. @@ -45,11 +46,11 @@ function testFilterDefaults() { // 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( + foreach ($filter_defaults_format->filters() as $name => $filter) { + $expected_weight = $filter_info[$name]['weight']; + $this->assertEqual($filter->weight, $expected_weight, format_string('@name filter weight %saved equals %default', array( '@name' => $name, - '%saved' => $settings['weight'], + '%saved' => $filter->weight, '%default' => $expected_weight, ))); $saved_settings[$name]['weight'] = $expected_weight; @@ -59,14 +60,12 @@ function testFilterDefaults() { $filter_defaults_format->save(); // 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( + foreach (filter_list_format($filter_defaults_format->format) as $name => $filter) { + $this->assertEqual($filter->weight, $saved_settings[$name]['weight'], format_string('@name filter weight %saved equals %previous', array( '@name' => $name, - '%saved' => $settings->weight, + '%saved' => $filter->weight, '%previous' => $saved_settings[$name]['weight'], ))); } diff --git a/core/modules/filter/tests/filter_test/config/filter.format.filter_test.yml b/core/modules/filter/tests/filter_test/config/filter.format.filter_test.yml index f57eb00..e5ebf83 100644 --- a/core/modules/filter/tests/filter_test/config/filter.format.filter_test.yml +++ b/core/modules/filter/tests/filter_test/config/filter.format.filter_test.yml @@ -1,13 +1,26 @@ format: filter_test name: 'Test format' -weight: 2 +weight: '2' roles: - anonymous - authenticated +cache: '1' +status: '1' +langcode: en filters: filter_html_escape: + module: filter status: '1' - filter_url: - status: '1' + weight: '-10' + settings: { } filter_autop: + module: filter + status: '1' + weight: '0' + settings: { } + filter_url: + module: filter status: '1' + weight: '0' + settings: + filter_url_length: '72' diff --git a/core/modules/filter/tests/filter_test/filter_test.module b/core/modules/filter/tests/filter_test/filter_test.module index a61941a..e3983b4 100644 --- a/core/modules/filter/tests/filter_test/filter_test.module +++ b/core/modules/filter/tests/filter_test/filter_test.module @@ -25,40 +25,3 @@ function filter_test_filter_format_update($format) { function filter_test_filter_format_disable($format) { drupal_set_message('hook_filter_format_disable invoked.'); } - -/** - * Implements hook_filter_info(). - */ -function filter_test_filter_info() { - $filters['filter_test_uncacheable'] = array( - 'title' => 'Uncacheable filter', - 'type' => FILTER_TYPE_TRANSFORM_IRREVERSIBLE, - 'description' => 'Does nothing, but makes a text format uncacheable.', - 'cache' => FALSE, - ); - $filters['filter_test_replace'] = array( - 'title' => 'Testing filter', - 'type' => FILTER_TYPE_TRANSFORM_IRREVERSIBLE, - 'description' => 'Replaces all content with filter and text format information.', - 'process callback' => 'filter_test_replace', - ); - return $filters; -} - -/** - * Process handler for filter_test_replace filter. - * - * Replaces all text with filter and text format information. - */ -function filter_test_replace($text, $filter, $format, $langcode, $cache, $cache_id) { - $text = array(); - $text[] = 'Filter: ' . $filter->title . ' (' . $filter->name . ')'; - $text[] = 'Format: ' . $format->name . ' (' . $format->format . ')'; - $text[] = 'Language: ' . $langcode; - $text[] = 'Cache: ' . ($cache ? 'Enabled' : 'Disabled'); - if ($cache_id) { - $text[] = 'Cache ID: ' . $cache_id; - } - return implode("
\n", $text); -} - diff --git a/core/modules/filter/tests/filter_test/lib/Drupal/filter_test/Plugin/Filter/FilterTestReplace.php b/core/modules/filter/tests/filter_test/lib/Drupal/filter_test/Plugin/Filter/FilterTestReplace.php new file mode 100644 index 0000000..df208e2 --- /dev/null +++ b/core/modules/filter/tests/filter_test/lib/Drupal/filter_test/Plugin/Filter/FilterTestReplace.php @@ -0,0 +1,41 @@ +getLabel() . ' (' . $this->getPluginID() . ')'; + $text[] = 'Language: ' . $langcode; + $text[] = 'Cache: ' . ($cache ? 'Enabled' : 'Disabled'); + if ($cache_id) { + $text[] = 'Cache ID: ' . $cache_id; + } + return implode("
\n", $text); + } + +} diff --git a/core/modules/filter/tests/filter_test/lib/Drupal/filter_test/Plugin/Filter/FilterTestUncacheable.php b/core/modules/filter/tests/filter_test/lib/Drupal/filter_test/Plugin/Filter/FilterTestUncacheable.php new file mode 100644 index 0000000..7c3140d --- /dev/null +++ b/core/modules/filter/tests/filter_test/lib/Drupal/filter_test/Plugin/Filter/FilterTestUncacheable.php @@ -0,0 +1,35 @@ +' . t('Using custom PHP code') . ''; + $output .= '' . t('Custom PHP code may be embedded in some types of site content, including posts and blocks. While embedding PHP code inside a post or block is a powerful and flexible feature when used by a trusted user with PHP experience, it is a significant and dangerous security risk when used improperly. Even a small mistake when posting PHP code may accidentally compromise your site.') . '
'; + $output .= '' . t('If you are unfamiliar with PHP, SQL, or Drupal, avoid using custom PHP code within posts. Experimenting with PHP may corrupt your database, render your site inoperable, or significantly compromise security.') . '
'; + $output .= '' . t('Notes:') . '
'; + $output .= ''; + $output .= '
- ' . t('Remember to double-check each line for syntax and logic errors before saving.') . '
'; + $output .= '- ' . t('Statements must be correctly terminated with semicolons.') . '
'; + $output .= '- ' . t('Global variables used within your PHP code retain their values after your script executes.') . '
'; + $output .= '- ' . t('
'; + $output .= 'register_globals
is turned off. If you need to use forms, understand and use the functions in the Drupal Form API.', array('@formapi' => url('http://api.drupal.org/api/group/form_api/8'))) . '- ' . t('Use a
'; + $output .= 'return
statement in your code to output content.') . '- ' . t('Develop and test your PHP code using a separate test script and sample database before deploying on a production site.') . '
'; + $output .= '- ' . t('Consider including your custom PHP code within a site-specific module or theme rather than embedding it directly into a post or block.') . '
'; + $output .= '- ' . t('Be aware that the ability to embed PHP code within content is provided by the PHP Filter module. If this module is disabled or deleted, then blocks and posts with embedded PHP may display, rather than execute, the PHP code.') . '
' . t('A basic example: Creating a "Welcome" block that greets visitors with a simple message.') . '
'; + $output .= ''; + $output .= '
- ' . t('
'; + $output .= 'Add a custom block to your site, named "Welcome" . With its text format set to "PHP code" (or another format supporting PHP input), add the following in the Block body:
++ print t(\'Welcome visitor! Thank you for visiting.\'); +') . '- ' . t('
To display the name of a registered user, use this instead:
++ global $user; + if ($user->uid) { + print t(\'Welcome @name! Thank you for visiting.\', array(\'@name\' => user_format_name($user))); + } + else { + print t(\'Welcome visitor! Thank you for visiting.\'); + } +') . '' . t('Drupal.org offers some example PHP snippets, or you can create your own with some PHP experience and knowledge of the Drupal system.', array('@drupal' => url('http://drupal.org'), '@php-snippets' => url('http://drupal.org/documentation/customization/php-snippets'))) . '
'; + return $output; + } + else { + return t('You may post PHP code. You should include <?php ?> tags.'); + } + } + +} diff --git a/core/modules/php/lib/Drupal/php/Tests/PhpTestBase.php b/core/modules/php/lib/Drupal/php/Tests/PhpTestBase.php index abb3412..c18273a 100644 --- a/core/modules/php/lib/Drupal/php/Tests/PhpTestBase.php +++ b/core/modules/php/lib/Drupal/php/Tests/PhpTestBase.php @@ -40,7 +40,7 @@ function setUp() { // Verify that the format has the PHP code filter enabled. $filters = filter_list_format($php_format_id); - $this->assertTrue($filters['php_code']->status, 'PHP code filter is enabled.'); + $this->assertTrue($filters->get('php_code')->status, 'PHP code filter is enabled.'); // Verify that the format exists on the administration page. $this->drupalGet('admin/config/content/formats'); diff --git a/core/modules/php/php.module b/core/modules/php/php.module index e31b4c5..02fd1dd 100644 --- a/core/modules/php/php.module +++ b/core/modules/php/php.module @@ -86,63 +86,3 @@ function php_eval($code) { return $output; } - -/** - * Implements hook_filter_FILTER_tips(). - * - * @see php_filter_info() - */ -function _php_filter_tips($filter, $format, $long = FALSE) { - global $base_url; - if ($long) { - $output = '' . t('Using custom PHP code') . '
'; - $output .= '' . t('Custom PHP code may be embedded in some types of site content, including posts and blocks. While embedding PHP code inside a post or block is a powerful and flexible feature when used by a trusted user with PHP experience, it is a significant and dangerous security risk when used improperly. Even a small mistake when posting PHP code may accidentally compromise your site.') . '
'; - $output .= '' . t('If you are unfamiliar with PHP, SQL, or Drupal, avoid using custom PHP code within posts. Experimenting with PHP may corrupt your database, render your site inoperable, or significantly compromise security.') . '
'; - $output .= '' . t('Notes:') . '
'; - $output .= ''; - $output .= '
- ' . t('Remember to double-check each line for syntax and logic errors before saving.') . '
'; - $output .= '- ' . t('Statements must be correctly terminated with semicolons.') . '
'; - $output .= '- ' . t('Global variables used within your PHP code retain their values after your script executes.') . '
'; - $output .= '- ' . t('
'; - $output .= 'register_globals
is turned off. If you need to use forms, understand and use the functions in the Drupal Form API.', array('@formapi' => url('http://api.drupal.org/api/group/form_api/8'))) . '- ' . t('Use a
'; - $output .= 'return
statement in your code to output content.') . '- ' . t('Develop and test your PHP code using a separate test script and sample database before deploying on a production site.') . '
'; - $output .= '- ' . t('Consider including your custom PHP code within a site-specific module or theme rather than embedding it directly into a post or block.') . '
'; - $output .= '- ' . t('Be aware that the ability to embed PHP code within content is provided by the PHP Filter module. If this module is disabled or deleted, then blocks and posts with embedded PHP may display, rather than execute, the PHP code.') . '
' . t('A basic example: Creating a "Welcome" block that greets visitors with a simple message.') . '
'; - $output .= ''; - $output .= '
- ' . t('
'; - $output .= 'Add a custom block to your site, named "Welcome" . With its text format set to "PHP code" (or another format supporting PHP input), add the following in the Block body:
--print t(\'Welcome visitor! Thank you for visiting.\'); -') . '- ' . t('
To display the name of a registered user, use this instead:
--global $user; -if ($user->uid) { - print t(\'Welcome @name! Thank you for visiting.\', array(\'@name\' => user_format_name($user))); -} -else { - print t(\'Welcome visitor! Thank you for visiting.\'); -} -') . '' . t('Drupal.org offers some example PHP snippets, or you can create your own with some PHP experience and knowledge of the Drupal system.', array('@drupal' => url('http://drupal.org'), '@php-snippets' => url('http://drupal.org/documentation/customization/php-snippets'))) . '
'; - return $output; - } - else { - return t('You may post PHP code. You should include <?php ?> tags.'); - } -} - -/** - * Implements hook_filter_info(). - * - * Provide PHP code filter. Use with care. - */ -function php_filter_info() { - $filters['php_code'] = array( - 'title' => t('PHP evaluator'), - 'type' => FILTER_TYPE_MARKUP_LANGUAGE, - 'description' => t('Executes a piece of PHP code. The usage of this filter should be restricted to administrators only!'), - 'process callback' => 'php_eval', - 'tips callback' => '_php_filter_tips', - 'cache' => FALSE, - ); - return $filters; -} diff --git a/core/modules/text/lib/Drupal/text/Tests/TextSummaryTest.php b/core/modules/text/lib/Drupal/text/Tests/TextSummaryTest.php index f679109..aec92e0 100644 --- a/core/modules/text/lib/Drupal/text/Tests/TextSummaryTest.php +++ b/core/modules/text/lib/Drupal/text/Tests/TextSummaryTest.php @@ -7,13 +7,14 @@ namespace Drupal\text\Tests; -use Drupal\simpletest\WebTestBase; +use Drupal\simpletest\DrupalUnitTestBase; /** * Tests the text field summary. */ -class TextSummaryTest extends WebTestBase { - protected $profile = 'standard'; +class TextSummaryTest extends DrupalUnitTestBase { + + public static $modules = array('system', 'user', 'filter', 'text'); public static function getInfo() { return array( @@ -25,7 +26,10 @@ public static function getInfo() { function setUp() { parent::setUp(); - $this->article_creator = $this->drupalCreateUser(array('create article content', 'edit own article content')); + + $this->installSchema('system', 'url_alias'); + $this->installSchema('user', 'role_permission'); + $this->installConfig(array('text')); } /** @@ -36,7 +40,7 @@ function setUp() { function testFirstSentenceQuestion() { $text = 'A question? A sentence. Another sentence.'; $expected = 'A question? A sentence.'; - $this->callTextSummary($text, $expected, NULL, 30); + $this->assertTextSummary($text, $expected, NULL, 30); } /** @@ -51,133 +55,174 @@ function testLongSentence() { 'Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. ' . 'Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.'; // First three sentences add up to: 336, so add one for space and then 3 to get half-way into next word. - $this->callTextSummary($text, $expected, NULL, 340); + $this->assertTextSummary($text, $expected, NULL, 340); } /** * Test various summary length edge cases. */ function testLength() { + entity_create('filter_format', array( + 'format' => 'autop', + 'filters' => array( + 'filter_autop' => array( + 'status' => 1, + ), + ), + ))->save(); + entity_create('filter_format', array( + 'format' => 'autop_correct', + 'filters' => array( + 'filter_autop' => array( + 'status' => 1, + ), + 'filter_htmlcorrector' => array( + 'status' => 1, + ), + ), + ))->save(); + // This string tests a number of edge cases. $text = "\nHi\n
\n\nfolks\n
"; // The summaries we expect text_summary() to return when $size is the index // of each array item. // Using no text format: - $expected = array( - "
\n!\n\nHi\n
\n\nfolks\n
", - "<", - "
\n!\n", - "
\n", - "
\nH", - "
\nHi", - "
\nHi\n", - "
\nHi\n<", - "
\nHi\n", - "
\nHi\n
\nHi\n", - "\nHi\n
", - "\nHi\n
", - "\nHi\n
", - "\nHi\n
", - "\nHi\n
", - "\nHi\n
", - "\nHi\n
", - "\nHi\n
", - "\nHi\n
", - "\nHi\n
", - "\nHi\n
", - "\nHi\n
", - "\nHi\n
", - "\nHi\n
", - "\nHi\n
", - "\nHi\n
", - "\nHi\n
", - "\nHi\n
", - "\nHi\n
", - "\nHi\n
", - "\nHi\n
", - "\nHi\n
", - "\nHi\n
", - "\nHi\n
\n\nfolks\n
", - "
\n!\n\nHi\n
\n\nfolks\n
", - "
\n!\n\nHi\n
\n\nfolks\n
", - ); + $format = NULL; + $i = 0; + $this->assertTextSummary($text, "
\n!\n\nHi\n
\n\nfolks\n
", $format, $i++); + $this->assertTextSummary($text, "<", $format, $i++); + $this->assertTextSummary($text, "
\n!\nassertTextSummary($text, "
", $format, $i++); + $this->assertTextSummary($text, "
\n", $format, $i++); + $this->assertTextSummary($text, "
\nH", $format, $i++); + $this->assertTextSummary($text, "
\nHi", $format, $i++); + $this->assertTextSummary($text, "
\nHi\n", $format, $i++); + $this->assertTextSummary($text, "
\nHi\n<", $format, $i++); + $this->assertTextSummary($text, "
\nHi\n", $format, $i++); + $this->assertTextSummary($text, "
\nHi\n
assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
\n\nfolks\n
", $format, $i++); + $this->assertTextSummary($text, "
\n!\n\nHi\n
\n\nfolks\n
", $format, $i++); + $this->assertTextSummary($text, "
\n!\n\nHi\n
\n\nfolks\n
", $format, $i++); - // And using a text format WITH the line-break and htmlcorrector filters. - $expected_lb = array( - "
\n!\n\nHi\n
\n\nfolks\n
", - "", - "", - "", - "", - "", - "", - "
\n!\n\nHi
", - "\nHi
", - "\nHi
", - "\nHi
", - "\nHi\n
", - "\nHi\n
", - "\nHi\n
", - "\nHi\n
", - "\nHi\n
", - "\nHi\n
", - "\nHi\n
", - "\nHi\n
", - "\nHi\n
", - "\nHi\n
", - "\nHi\n
", - "\nHi\n
", - "\nHi\n
", - "\nHi\n
", - "\nHi\n
", - "\nHi\n
", - "\nHi\n
", - "\nHi\n
", - "\nHi\n
", - "\nHi\n
", - "\nHi\n
", - "\nHi\n
", - "\nHi\n
", - "\nHi\n
", - "\nHi\n
\n\nfolks\n
", - "
\n!\n\nHi\n
\n\nfolks\n
", - "
\n!\n\nHi\n
\n\nfolks\n
", - ); + // Using a text format with filter_autop enabled. + $format = 'autop'; + $i = 0; + $this->assertTextSummary($text, "
\n!\n\nHi\n
\n\nfolks\n
", $format, $i++); + $this->assertTextSummary($text, "<", $format, $i++); + $this->assertTextSummary($text, "
\n!\nassertTextSummary($text, "
", $format, $i++); + $this->assertTextSummary($text, "
", $format, $i++); + $this->assertTextSummary($text, "
", $format, $i++); + $this->assertTextSummary($text, "
", $format, $i++); + $this->assertTextSummary($text, "
\nHi", $format, $i++); + $this->assertTextSummary($text, "
\nHi", $format, $i++); + $this->assertTextSummary($text, "
\nHi", $format, $i++); + $this->assertTextSummary($text, "
\nHi", $format, $i++); + $this->assertTextSummary($text, "
\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
\n\nfolks\n
", $format, $i++); + $this->assertTextSummary($text, "
\n!\n\nHi\n
\n\nfolks\n
", $format, $i++); + $this->assertTextSummary($text, "
\n!\n\nHi\n
\n\nfolks\n
", $format, $i++); - // Test text_summary() for different sizes. - for ($i = 0; $i <= 37; $i++) { - $this->callTextSummary($text, $expected[$i], NULL, $i); - $this->callTextSummary($text, $expected_lb[$i], 'plain_text', $i); - $this->callTextSummary($text, $expected_lb[$i], 'basic_html', $i); - } + // Using a text format with filter_autop and filter_htmlcorrector enabled. + $format = 'autop_correct'; + $i = 0; + $this->assertTextSummary($text, "
\n!\n\nHi\n
\n\nfolks\n
", $format, $i++); + $this->assertTextSummary($text, "", $format, $i++); + $this->assertTextSummary($text, "", $format, $i++); + $this->assertTextSummary($text, "", $format, $i++); + $this->assertTextSummary($text, "", $format, $i++); + $this->assertTextSummary($text, "", $format, $i++); + $this->assertTextSummary($text, "", $format, $i++); + $this->assertTextSummary($text, "
\n!\n\nHi
", $format, $i++); + $this->assertTextSummary($text, "\nHi
", $format, $i++); + $this->assertTextSummary($text, "\nHi
", $format, $i++); + $this->assertTextSummary($text, "\nHi
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
", $format, $i++); + $this->assertTextSummary($text, "\nHi\n
\n\nfolks\n
", $format, $i++); + $this->assertTextSummary($text, "
\n!\n\nHi\n
\n\nfolks\n
", $format, $i++); + $this->assertTextSummary($text, "
\n!\n\nHi\n
\n\nfolks\n
", $format, $i++); } /** * Calls text_summary() and asserts that the expected teaser is returned. */ - function callTextSummary($text, $expected, $format = NULL, $size = NULL) { + function assertTextSummary($text, $expected, $format = NULL, $size = NULL) { $summary = text_summary($text, $format, $size); - $this->assertIdentical($summary, $expected, t('Generated summary "@summary" matches expected "@expected".', array('@summary' => $summary, '@expected' => $expected))); + $this->assertIdentical($summary, $expected, format_string('
\n!\n@actualis identical to@expected', array( + '@actual' => $summary, + '@expected' => $expected, + ))); } - /** - * Test sending only summary. - */ - function testOnlyTextSummary() { - // Login as article creator. - $this->drupalLogin($this->article_creator); - // Create article with summary but empty body. - $summary = $this->randomName(); - $edit = array( - "title" => $this->randomName(), - "body[und][0][summary]" => $summary, - ); - $this->drupalPost('node/add/article', $edit, t('Save')); - $node = $this->drupalGetNodeByTitle($edit['title']); - - $this->assertIdentical($node->body['und'][0]['summary'], $summary, 'Article with with summary and no body has been submitted.'); - } } diff --git a/core/modules/text/text.module b/core/modules/text/text.module index 4bf51b6..4fe2115 100644 --- a/core/modules/text/text.module +++ b/core/modules/text/text.module @@ -272,16 +272,24 @@ function text_summary($text, $format = NULL, $size = NULL) { return substr($text, 0, $delimiter); } - // We check for the presence of the PHP evaluator filter in the current - // format. If the body contains PHP code, we do not split it up to prevent - // parse errors. + // Retrieve the filters of the specified text format, if any. if (isset($format)) { $filters = filter_list_format($format); - if (isset($filters['php_code']) && $filters['php_code']->status && strpos($text, '') !== FALSE) { - return $text; + // If the specified format does not exist, return nothing. $text is already + // filtered text, but the remainder of this function will not be able to + // ensure a sane and secure summary. + if (!$filters) { + return ''; } } + // We check for the presence of the PHP evaluator filter in the current + // format. If the body contains PHP code, we do not split it up to prevent + // parse errors. + if (isset($format) && $filters->has('php_code') && $filters->get('php_code')->status && strpos($text, '') !== FALSE) { + return $text; + } + // If we have a short body, the entire body is the summary. if (drupal_strlen($text) <= $size) { return $text; @@ -316,7 +324,7 @@ function text_summary($text, $format = NULL, $size = NULL) { $line_breaks = array('
' => 6, '
' => 4); // Newline only indicates a line break if line break converter // filter is present. - if (isset($filters['filter_autop'])) { + if (isset($format) && $filters->has('filter_autop') && $filters->get('filter_autop')->status) { $line_breaks["\n"] = 1; } $break_points[] = $line_breaks; @@ -344,7 +352,7 @@ function text_summary($text, $format = NULL, $size = NULL) { } // If the htmlcorrector filter is present, apply it to the generated summary. - if (isset($filters['filter_htmlcorrector'])) { + if (isset($format) && $filters->has('filter_htmlcorrector') && $filters->get('filter_htmlcorrector')->status) { $summary = _filter_htmlcorrector($summary); } diff --git a/core/profiles/standard/config/filter.format.basic_html.yml b/core/profiles/standard/config/filter.format.basic_html.yml index e97db8d..8685b20 100644 --- a/core/profiles/standard/config/filter.format.basic_html.yml +++ b/core/profiles/standard/config/filter.format.basic_html.yml @@ -9,13 +9,19 @@ filters: filter_html: module: filter status: '1' + weight: '-10' settings: allowed_html: '
' filter_html_help: '0' - filter_htmlcorrector: + filter_html_nofollow: '0' + filter_html_image_secure: module: filter status: '1' - filter_html_image_secure: + weight: '9' + settings: { } + filter_htmlcorrector: module: filter status: '1' + weight: '10' + settings: { } langcode: und diff --git a/core/profiles/standard/config/filter.format.full_html.yml b/core/profiles/standard/config/filter.format.full_html.yml index 89b232b..204a342 100644 --- a/core/profiles/standard/config/filter.format.full_html.yml +++ b/core/profiles/standard/config/filter.format.full_html.yml @@ -9,4 +9,6 @@ filters: filter_htmlcorrector: module: filter status: '1' + weight: '10' + settings: { } langcode: en diff --git a/core/profiles/standard/config/filter.format.restricted_html.yml b/core/profiles/standard/config/filter.format.restricted_html.yml index 2e14ce2..40203fe 100644 --- a/core/profiles/standard/config/filter.format.restricted_html.yml +++ b/core/profiles/standard/config/filter.format.restricted_html.yml @@ -6,18 +6,28 @@ roles: - anonymous cache: '1' filters: - filter_url: - module: filter - status: '1' filter_html: module: filter status: '1' - setttings: - filter_html_nofollow: '1' + weight: '-10' + settings: + allowed_html: '
' + filter_html_help: '1' + filter_html_nofollow: '0' filter_autop: module: filter status: '1' + weight: '0' + settings: { } + filter_url: + module: filter + status: '1' + weight: '0' + settings: + filter_url_length: '72' filter_htmlcorrector: module: filter status: '1' + weight: '10' + settings: { } langcode: en