cms/drupal/sites/all/modules/htmlpurifier/htmlpurifier.module
changeset 541 e756a8c72c3d
equal deleted inserted replaced
540:07239de796bb 541:e756a8c72c3d
       
     1 <?php
       
     2 
       
     3 /**
       
     4  * @file
       
     5  * Implements HTML Purifier as a Drupal filter.
       
     6  */
       
     7 
       
     8 
       
     9 
       
    10 // -- HOOK IMPLEMENTATIONS -------------------------------------------------- //
       
    11 
       
    12 /**
       
    13  * Implements hook_flush_caches().
       
    14  */
       
    15 function htmlpurifier_flush_caches() {
       
    16     return array('cache_htmlpurifier');
       
    17 }
       
    18 
       
    19 /**
       
    20  * Implements hook_help().
       
    21  */
       
    22 function htmlpurifier_help($path, $arg) {
       
    23   $output = NULL;
       
    24   switch ($path) {
       
    25     case 'admin/help#htmlpurifier':
       
    26       $output = t(<<<TEXT
       
    27 The HTML Purifier Drupal module provides a text filter that removes
       
    28 malicious HTML and ensures standards compliant output.  You can modify
       
    29 text formats at <a href="@formats">the format configurations page</a>.
       
    30 TEXT
       
    31 , array('@formats' => url('admin/config/content/formats')));
       
    32       break;
       
    33   }
       
    34   return $output;
       
    35 }
       
    36 
       
    37 /*
       
    38  * Implements hook_menu().
       
    39  */
       
    40 function htmlpurifier_menu() {
       
    41   $items['admin/config/content/htmlpurifier'] = array(
       
    42     'title' => 'HTML Purifier settings',
       
    43     'description' => 'Configure overall settings for the HTML Purifier filters, including how they are cached.',
       
    44     'page callback' => 'drupal_get_form',
       
    45     'page arguments' => array('htmlpurifier_admin_settings'),
       
    46     'access arguments' => array('administer filters'),
       
    47     'file' => 'htmlpurifier.admin.inc',
       
    48   );
       
    49 
       
    50   return $items;
       
    51 }
       
    52 
       
    53 /**
       
    54  * Implements hook_cron().
       
    55  */
       
    56 function htmlpurifier_cron() {
       
    57   // Force an attempt at checking for a new version; this is safe to do in
       
    58   // hook_cron because a slow timeout will not degrade the user experience.
       
    59   htmlpurifier_check_version(TRUE);
       
    60 }
       
    61 
       
    62 /**
       
    63  * Checks for updates to the HTML Purifier library.
       
    64  */
       
    65 function htmlpurifier_check_version($force = FALSE) {
       
    66   if ($force || !variable_get('htmlpurifier_version_check_failed', FALSE)) {
       
    67     // Maybe this should be changed in the future:
       
    68     $result  = drupal_http_request('http://htmlpurifier.org/live/VERSION');
       
    69     if ($result->code == 200) {
       
    70       $version = trim($result->data);
       
    71       if (variable_get('html_purifier_version_check_failed', FALSE) == TRUE) {
       
    72         variable_set('htmlpurifier_version_check_failed', FALSE);
       
    73       }
       
    74       $current_version = variable_get('htmlpurifier_version_current', '');
       
    75       if ($current_version != $version) {
       
    76         variable_set('htmlpurifier_version_current', $version);
       
    77       }
       
    78       return $version;
       
    79     }
       
    80     else {
       
    81       variable_set('htmlpurifier_version_check_failed', TRUE);
       
    82       // Delete any previously known "latest" version so that people can be
       
    83       // alerted if a problem appears on a previously working site.
       
    84       variable_del('htmlpurifier_version_current');
       
    85     }
       
    86   }
       
    87 }
       
    88 
       
    89 /**
       
    90  * Implements hook_filter_info().
       
    91  */
       
    92 function htmlpurifier_filter_info() {
       
    93   _htmlpurifier_load();
       
    94   $filters['htmlpurifier_basic'] = array(
       
    95     'title' => t('HTML Purifier'),
       
    96     'description' => t('Removes malicious HTML code and ensures that the ' .
       
    97        'output is standards compliant. <strong>Warning:</strong> For ' .
       
    98        'performance reasons, please ensure that there are no highly dynamic ' .
       
    99        'filters before HTML Purifier. '),
       
   100     'process callback' => '_htmlpurifier_process',
       
   101     'settings callback' => '_htmlpurifier_settings',
       
   102     'default settings' => array(
       
   103       'htmlpurifier_help' => TRUE,
       
   104     ),
       
   105     'tips callback' => '_htmlpurifier_filter_tips',
       
   106   );
       
   107   $filters['htmlpurifier_advanced'] = array(
       
   108     'title' => t('HTML Purifier (advanced)'),
       
   109     'description' => $filters['htmlpurifier_basic']['description'] . t('<em>This version has advanced configuration options, do not enable both at the same time.</em>'),
       
   110   ) + $filters['htmlpurifier_basic'];
       
   111   return $filters;
       
   112 }
       
   113 
       
   114 /**
       
   115  * Implements of hook_nodeapi().
       
   116  */
       
   117 function htmlpurifier_nodeapi(&$node, $op, $a3, $a4) {
       
   118   if ($op == 'view') {
       
   119 
       
   120     // Should we load CSS cache data from teaser or body?
       
   121     if ($a3 == TRUE) {
       
   122       _htmlpurifier_add_css( $node->content['teaser']['#value'], $node->nid );
       
   123     }
       
   124     else {
       
   125       _htmlpurifier_add_css( $node->content['body']['#value'], $node->nid );
       
   126     }
       
   127   }
       
   128   // @todo: Deal with CCK fields - probably needs to go in op alter?
       
   129 }
       
   130 
       
   131 // -- INTERNAL FUNCTIONS ---------------------------------------------------- //
       
   132 
       
   133 /**
       
   134  * Filter tips callback, used by htmlpurifier_filter_info().
       
   135  */
       
   136 function _htmlpurifier_filter_tips($delta, $format, $long = FALSE) {
       
   137   if (!empty($delta->settings['htmlpurifier_help'])) {
       
   138     return t('HTML tags will be transformed to conform to HTML standards.');
       
   139   }
       
   140 }
       
   141 
       
   142 /**
       
   143  * Process callback, used by htmlpurifier_filter_info().
       
   144  *
       
   145  * Passes data along to the helper function with instructions to always try to
       
   146  * use this module's custom cache mechanism.
       
   147  *
       
   148  * We need this helper function because the filter system passes in $cache as
       
   149  * fifth parameter to this hook (which corresponds to whether or not the core
       
   150  * filter system itself will cache the data), but we want to cache it always so
       
   151  * we need to ignore that parameter.
       
   152  */
       
   153 function _htmlpurifier_process($text, $filter, $format, $langcode, $cache) {
       
   154   return _htmlpurifier_process_text($text, $filter, $format, $langcode, TRUE);
       
   155 }
       
   156 
       
   157 /**
       
   158  * Helper function for hook_nodeapi
       
   159  *  Finds extracted style blocks based on a cache link left by hook_filter
       
   160  *  Aggregates the extracted style blocks and adds them to the document head
       
   161  *  Also removes the cache link left in hook_filter to the CSS cache
       
   162  *
       
   163  * @param string &$field
       
   164  *    Field to process, this should be the actual field value
       
   165  *      ex. $node->content['body']['#value']
       
   166  *
       
   167  * @param int $nid
       
   168  *    Node ID of the node to which these stylesheets belong
       
   169  *    Since filters don't know their node context, we have to use a token
       
   170  *      to generate the stylesheet scope, and replace it in hook_nodeapi
       
   171  */
       
   172 function _htmlpurifier_add_css( &$field, $nid ) {
       
   173 
       
   174   // Some basic validation to assure we really got a rendered field
       
   175   if (!is_string($field)) {
       
   176     return;
       
   177   }
       
   178 
       
   179   $cache_matches = array();
       
   180   $cache_match = preg_match('#<!-- HTML Purifier Cache \#([-\w]*:[\w]*) -->#', $field, $cache_matches);
       
   181 
       
   182   // If there's an HTML Purifier Cache #, we need to load CSSTidy blocks
       
   183   if ($cache_match == 1) {
       
   184     $cid = 'css:' . $cache_matches[1];
       
   185     $old = cache_get($cid, 'cache_htmlpurifier');
       
   186 
       
   187     // We should always have some cached style blocks to load, but if we don't, just bail
       
   188     if ($old) {
       
   189       $styles = array();
       
   190       $style_rendered = '';
       
   191       foreach ($old->data as $i => $style) {
       
   192 
       
   193         // Replace Node ID tokens if necessary, otherwise use cached CSSTidy blocks
       
   194         // NOTE: This token is forgeable, but we expect that if the user
       
   195         // is able to invoke this transformation, it will be relatively
       
   196         // harmless.
       
   197         if (strpos($style, '[%HTMLPURIFIER:NID%]') !== FALSE) {
       
   198           $styles[$i] = str_replace('[%HTMLPURIFIER:NID%]', (int) $nid, $style);
       
   199         }
       
   200         else {
       
   201           $styles[$i] = $style;
       
   202         }
       
   203 
       
   204         // Save any CSSTidy blocks we find to be rendered in the document head
       
   205         if (!empty($style)) {
       
   206           $style_rendered .= $styles[$i] . "\n";
       
   207         }
       
   208       }
       
   209 
       
   210       // Add the rendered stylesheet to the document header
       
   211       if ($style_rendered != '') {
       
   212         drupal_set_html_head('<style type="text/css">' . "\n" . '<!--' . "\n" . $style_rendered . '--></style>');
       
   213       }
       
   214 
       
   215       // Remove the HTML Purifier cache key from the field argument
       
   216       $field = str_replace($cache_matches[0], '', $field);
       
   217 
       
   218       // If we had to update CSSTidy blocks, cache the results
       
   219       if ($old->data != $styles) {
       
   220         cache_set($cid, $styles, 'cache_htmlpurifier', CACHE_PERMANENT);
       
   221       }
       
   222     }
       
   223   }
       
   224 }
       
   225 
       
   226 
       
   227 
       
   228 /**
       
   229  * Processes HTML according to a format and returns purified HTML. Makes a
       
   230  * cache pass if possible.
       
   231  *
       
   232  * @param string $text
       
   233  *    Text to purify
       
   234  * @param object $filter
       
   235  *   The filter object containing settings for the given format.
       
   236  * @param object $format
       
   237  *    The format object of the text to be filtered.
       
   238  * @param string $langcode
       
   239  *    The language code of the text to be filtered.
       
   240  * @param boolean $cache
       
   241  *    Whether or not to check the cache.
       
   242  *
       
   243  * @note
       
   244  *    We ignore $delta because the only difference it makes is in the configuration
       
   245  *    screen.
       
   246  */
       
   247 function _htmlpurifier_process_text($text, $filter, $format, $langcode, $cache = TRUE) {
       
   248   // No need to run the filter if there isn't anything to filter
       
   249   // See https://drupal.org/node/1821178
       
   250   if ($text === '') {
       
   251     return;
       
   252   }
       
   253 
       
   254   if ($cache) {
       
   255     $cid = $format->format . ':' . $langcode . ':' . hash('sha256', $text);
       
   256     $old = cache_get($cid, 'cache_htmlpurifier');
       
   257     if ($old) return $old->data;
       
   258   }
       
   259 
       
   260   _htmlpurifier_load();
       
   261   $config = _htmlpurifier_get_config($format->format);
       
   262 
       
   263   // If ExtractStyleBlocks is enabled, we'll need to do a bit more for CSSTidy
       
   264   $config_extractstyleblocks = $config->get('Filter.ExtractStyleBlocks');
       
   265 
       
   266   // Maybe this works if CSSTidy is at root? CSSTidy could be other places though
       
   267   if ($config_extractstyleblocks == TRUE) {
       
   268     _htmlpurifier_load_csstidy();
       
   269   }
       
   270 
       
   271   $purifier = new HTMLPurifier($config);
       
   272   $ret = $purifier->purify($text);
       
   273 
       
   274   // If using Filter.ExtractStyleBlocks we need to handle the CSSTidy output
       
   275   if ($config_extractstyleblocks == TRUE) {
       
   276 
       
   277     // We're only going to bother if we're caching! - no caching? no style blocks!
       
   278     if ($cache) {
       
   279 
       
   280       // Get style blocks, cache them, and help hook_nodeapi find the cache
       
   281       $styles = $purifier->context->get('StyleBlocks');
       
   282       cache_set('css:' . $cid, $styles, 'cache_htmlpurifier', CACHE_PERMANENT);
       
   283       $ret = '<!-- HTML Purifier Cache #' . $cid . ' -->' . $ret;
       
   284     }
       
   285   }
       
   286 
       
   287   if ($cache) cache_set($cid, $ret, 'cache_htmlpurifier', CACHE_PERMANENT);
       
   288 
       
   289   return $ret;
       
   290 }
       
   291 
       
   292 /**
       
   293  * Loads the HTML Purifier library, and performs global initialization.
       
   294  */
       
   295 function _htmlpurifier_load() {
       
   296   static $done = FALSE;
       
   297   if ($done) {
       
   298     return;
       
   299   }
       
   300   $done = TRUE;
       
   301   $module_path = drupal_get_path('module', 'htmlpurifier');
       
   302   $library_path = $module_path;
       
   303   if (function_exists('libraries_get_path')) {
       
   304     $library_path = libraries_get_path('htmlpurifier');
       
   305     // This may happen if the user has HTML Purifier installed under the
       
   306     // old configuration, but also installed libraries and forgot to
       
   307     // move it over.  There is code for emitting errors in
       
   308     // htmlpurifier.install when this is the case.
       
   309     if (!file_exists("$library_path/library/HTMLPurifier.auto.php")) {
       
   310       // Check for an alternate phrasing and error about it
       
   311       if (file_exists("$library_path/HTMLPurifier.auto.php") &&
       
   312         !file_exists("$module_path/library/HTMLPurifier.auto.php")) {
       
   313         echo "HTML Purifier was installed improperly; move contents of folder $library_path to $library_path/library";
       
   314         exit;
       
   315       }
       
   316       $library_path = $module_path;
       
   317     }
       
   318   }
       
   319 
       
   320   require_once "$library_path/library/HTMLPurifier.auto.php";
       
   321   require_once "$module_path/HTMLPurifier_DefinitionCache_Drupal.php";
       
   322 
       
   323   $factory = HTMLPurifier_DefinitionCacheFactory::instance();
       
   324   $factory->register('Drupal', 'HTMLPurifier_DefinitionCache_Drupal');
       
   325 
       
   326   // Register the version as a variable:
       
   327   $current_version = variable_get('htmlpurifier_version_ours', FALSE);
       
   328   if ($current_version != HTMLPurifier::VERSION) {
       
   329     variable_set('htmlpurifier_version_ours', HTMLPurifier::VERSION);
       
   330   }
       
   331 }
       
   332 
       
   333 /**
       
   334  * Returns the HTMLPurifier_Config object corresponding to a text format.
       
   335  * @param int $format
       
   336  *    (Optional) Text format ID. If left empty, the default configuration is
       
   337  *    returned.
       
   338  * @return
       
   339  *    Instance of HTMLPurifier_Config.
       
   340  */
       
   341 function _htmlpurifier_get_config($format = 0) {
       
   342 
       
   343   $config = HTMLPurifier_Config::createDefault();
       
   344 
       
   345   $config->set('AutoFormat.AutoParagraph', TRUE);
       
   346   $config->set('AutoFormat.Linkify', TRUE);
       
   347   $config->set('HTML.Doctype', 'XHTML 1.0 Transitional'); // Probably
       
   348   $config->set('Core.AggressivelyFixLt', TRUE);
       
   349   $config->set('Cache.DefinitionImpl', 'Drupal');
       
   350 
       
   351   if (!empty($_SERVER['SERVER_NAME'])) {
       
   352     // SERVER_NAME is more reliable than HTTP_HOST
       
   353     $config->set('URI.Host', $_SERVER['SERVER_NAME']);
       
   354   }
       
   355 
       
   356   if (defined('LANGUAGE_RTL') && $GLOBALS['language']->direction === LANGUAGE_RTL) {
       
   357     $config->set('Attr.DefaultTextDir', 'rtl');
       
   358   }
       
   359 
       
   360   if ($format && ($config_function = _htmlpurifier_config_load($format))) {
       
   361     $config_function($config);
       
   362   }
       
   363   else {
       
   364     // We only support one instance of this module's filters (either basic or
       
   365     // advanced) per text format, so choose the first settings we find.
       
   366     // TODO: This is awkward, but the most straightforward conversion from the
       
   367     // D6 version, which also treated this as a per-format setting and
       
   368     // therefore had the same limitation.
       
   369     $filters = $format ? filter_list_format($format) : array();
       
   370     if (!empty($filters['htmlpurifier_advanced']->status) && isset($filters['htmlpurifier_advanced']->settings['htmlpurifier_advanced_config'])) {
       
   371       $config_data = $filters['htmlpurifier_advanced']->settings['htmlpurifier_advanced_config'];
       
   372     }
       
   373     elseif (!empty($filters['htmlpurifier_basic']->status) && isset($filters['htmlpurifier_basic']->settings['htmlpurifier_basic_config'])) {
       
   374       $config_data = $filters['htmlpurifier_basic']->settings['htmlpurifier_basic_config'];
       
   375     }
       
   376     else {
       
   377       $config_data = FALSE;
       
   378     }
       
   379 
       
   380     if (!empty($config_data['Filter.ExtractStyleBlocks'])) {
       
   381       if (!_htmlpurifier_load_csstidy()) {
       
   382         $config_data['Filter.ExtractStyleBlocks'] = '0';
       
   383         drupal_set_message(t("Could not enable ExtractStyleBlocks because CSSTidy was not installed.  You can download CSSTidy module from <a href='http://drupal.org/project/csstidy'>http://drupal.org/project/csstidy</a>"), 'error', FALSE);
       
   384       }
       
   385     }
       
   386     // {FALSE, TRUE, FALSE} = {no index, everything is allowed, don't do mq fix}
       
   387     $config->mergeArrayFromForm($config_data, FALSE, TRUE, FALSE);
       
   388   }
       
   389 
       
   390   return $config;
       
   391 
       
   392 }
       
   393 
       
   394 function _htmlpurifier_load_csstidY() {
       
   395   // If CSSTidy module is installed, it should have a copy we can use
       
   396   $csstidy_path = drupal_get_path('module', 'csstidy') . '/csstidy';
       
   397 
       
   398   // Some future-proofing for library path
       
   399   if (function_exists('libraries_get_path')) {
       
   400     $csstidy_library = libraries_get_path('csstidy');
       
   401     if (file_exists("$csstidy_library/class.csstidy.php")) {
       
   402       $csstidy_path = $csstidy_library;
       
   403     }
       
   404   }
       
   405 
       
   406   // Load CSSTidy if we can find it
       
   407   if (file_exists("$csstidy_path/class.csstidy.php")) {
       
   408     require_once "$csstidy_path/class.csstidy.php";
       
   409     return TRUE;
       
   410   }
       
   411   return FALSE;
       
   412 }
       
   413 
       
   414 /**
       
   415  * Returns the name of the configuration function for $format, or FALSE if none
       
   416  * exists. Function name will be htmlpurifier_config_N.
       
   417  *
       
   418  * @param int $format
       
   419  *    Integer format to check function for.
       
   420  * @return
       
   421  *    String function name for format, or FALSE if none.
       
   422  */
       
   423 function _htmlpurifier_config_load($format) {
       
   424   $config_file = drupal_get_path('module', 'htmlpurifier') . "/config/$format.php";
       
   425   $config_function = "htmlpurifier_config_$format";
       
   426   if (
       
   427     !function_exists($config_function) &&
       
   428     file_exists($config_file)
       
   429   ) {
       
   430     include_once $config_file;
       
   431   }
       
   432   return function_exists($config_function) ? $config_function : FALSE;
       
   433 }
       
   434 
       
   435 /**
       
   436  * Generates a settings form for configuring HTML Purifier.
       
   437  *
       
   438  * @param array $form
       
   439  *   The prepopulated form array.
       
   440  * @param array $form_state
       
   441  *   The form state of the (entire) configuration form.
       
   442  * @param object $filter
       
   443  *   The filter object containing settings for the given format. $filter->name
       
   444  *   can be either 'htmlpurifier_basic' or 'htmlpurifier_advanced' (the two
       
   445  *   filters defined by this module).
       
   446  * @param object $format
       
   447  *   The format object being configured.
       
   448  * @param array $defaults
       
   449  *   The default settings for the filter, as defined in 'default settings' in
       
   450  *   hook_filter_info().
       
   451  *
       
   452  * @return
       
   453  *    Form API array.
       
   454  */
       
   455 function _htmlpurifier_settings($form, &$form_state, $filter, $format, $defaults) {
       
   456   _htmlpurifier_load();
       
   457 
       
   458   // Dry run, testing for errors:
       
   459   _htmlpurifier_process_text('', $filter, $format, LANGUAGE_NONE, FALSE);
       
   460 
       
   461   $module_path = drupal_get_path('module', 'htmlpurifier');
       
   462 
       
   463   $settings = array();
       
   464 
       
   465   $settings['#attached']['css'][] = "$module_path/config-form.css";
       
   466   $settings['#attached']['js'][] = "$module_path/config-form.js";
       
   467   $settings['#attached']['js'][] = array(
       
   468     'data' => HTMLPurifier_Printer_ConfigForm::getJavaScript(),
       
   469     'type' => 'inline',
       
   470   );
       
   471 
       
   472   $settings['htmlpurifier_help'] = array(
       
   473     '#type' => 'checkbox',
       
   474     '#title' => t('Display help text'),
       
   475     '#default_value' => isset($filter->settings['htmlpurifier_help']) ? $filter->settings['htmlpurifier_help'] : $defaults['htmlpurifier_help'],
       
   476     '#description' => t('If enabled, a short note will be added to the filter tips explaining that HTML will be transformed to conform with HTML standards. You may want to disable this option when the HTML Purifier is used to check the output of another filter like BBCode.'),
       
   477   );
       
   478   if ($config_function = _htmlpurifier_config_load($format->format)) {
       
   479     $settings['notice'] = array(
       
   480       '#type' => 'markup',
       
   481       '#value' => t('<div>Configuration function <code>!function()</code> is already defined. To edit HTML Purifier\'s configuration, edit the corresponding configuration file, which is usually <code>htmlpurifier/config/!format.php</code>. To restore the web configuration form, delete or rename this file.</div>',
       
   482         array('!function' => $config_function, '!format' => $format->format)),
       
   483     );
       
   484   }
       
   485   else {
       
   486     if ($filter->name == 'htmlpurifier_basic') {
       
   487       $title = t('Configure HTML Purifier');
       
   488       $allowed = array(
       
   489         'URI.DisableExternalResources',
       
   490         'URI.DisableResources',
       
   491         'URI.Munge',
       
   492         'Attr.EnableID',
       
   493         'HTML.Allowed',
       
   494         'HTML.ForbiddenElements',
       
   495         'HTML.ForbiddenAttributes',
       
   496         'HTML.SafeObject',
       
   497         'Output.FlashCompat',
       
   498         'AutoFormat.RemoveEmpty',
       
   499         'AutoFormat.Linkify',
       
   500         'AutoFormat.AutoParagraph',
       
   501       );
       
   502     }
       
   503     else {
       
   504       $title = t('Advanced configuration options');
       
   505       $allowed = TRUE;
       
   506     }
       
   507 
       
   508     $intro =
       
   509         '<div class="form-item"><h3>' .
       
   510         $title .
       
   511         '</h3><div class="description">' .
       
   512         t('Please click on a directive name for more information on what it does before enabling or changing anything!  Changes will not apply to old entries until you clear the cache (see the <a href="@url">settings page</a>).', array('@url' => url('admin/config/content/htmlpurifier'))) .
       
   513         '</div></div>';
       
   514 
       
   515     $config = _htmlpurifier_get_config($format->format);
       
   516     $config_form = new HTMLPurifier_Printer_ConfigForm(
       
   517       $filter->name . '_config', 'http://htmlpurifier.org/live/configdoc/plain.html#%s'
       
   518     );
       
   519     $settings[$filter->name . '_config'] = array(
       
   520       '#markup' => $intro . $config_form->render($config, $allowed, FALSE),
       
   521       '#after_build' => array('_htmlpurifier_config_hack'),
       
   522     );
       
   523   }
       
   524 
       
   525   return $settings;
       
   526 }
       
   527 
       
   528 /**
       
   529  * Fills out the form state with extra post data originating from the
       
   530  * HTML Purifier configuration form. This is an #after_build hook function.
       
   531  *
       
   532  * @warning
       
   533  *    If someone ever gets the smart idea of changing the parameters to
       
   534  *    this function, I'm SOL! ;-)
       
   535  *    Also, this function does not work correctly if both filters from this
       
   536  *    module are enabled for the same format, since only one set of submitted
       
   537  *    values will make it through.
       
   538  */
       
   539 function _htmlpurifier_config_hack($form_element, &$form_state) {
       
   540   $parents = $form_element['#parents'];
       
   541   $key = end($parents);
       
   542   if (isset($form_state['input'][$key])) {
       
   543     $value = $form_state['input'][$key];
       
   544     foreach (array_reverse($parents) as $parent) {
       
   545       $value = array($parent => $value);
       
   546     }
       
   547     $form_state['values'] = array_merge_recursive($form_state['values'], $value);
       
   548   }
       
   549   foreach ($form_state['values'] as $i => $config_data) {
       
   550     if (!is_array($config_data)) continue;
       
   551     if (!empty($config_data['Filter.ExtractStyleBlocks'])) {
       
   552       if (!empty($config_data['Null_Filter.ExtractStyleBlocks.Scope'])) {
       
   553         drupal_set_message(t("You have not set <code>Filter.ExtractStyleBlocks.Scope</code>; this means that users can add CSS that affects all of your Drupal theme and not just their content block.  It is recommended to set this to <code>#node-[%HTMLPURIFIER:NID%]</code> (including brackets) which will automatically ensure that CSS directives only apply to their node."), 'warning', FALSE);
       
   554       }
       
   555       elseif (!isset($config_data['Filter.ExtractStyleBlocks.Scope']) || $config_data['Filter.ExtractStyleBlocks.Scope'] !== '#node-[%HTMLPURIFIER:NID%]') {
       
   556         drupal_set_message(t("You have enabled Filter.ExtractStyleBlocks.Scope, but you did not set it to <code>#node-[%HTMLPURIFIER:NID%]</code>; CSS may not work unless you have special theme support."), 'warning', FALSE);
       
   557       }
       
   558     }
       
   559   }
       
   560   return $form_element;
       
   561 }
       
   562 
       
   563 /**
       
   564  * Clears the HTML Purifier internal Drupal cache.
       
   565  */
       
   566 function _htmlpurifier_clear_cache($form, &$form_state) {
       
   567   drupal_set_message(t('Cache cleared.'));
       
   568   cache_clear_all('*', 'cache_htmlpurifier', TRUE);
       
   569   cache_clear_all('htmlpurifier:', 'cache', TRUE);
       
   570 }
       
   571 
       
   572 // vim: syntax=php