Release 2.3.
[htmlpurifier-drupal.git] / htmlpurifier.module
blob93d443fb9481bfae54802a5af44e162dd33c3a6a
1 <?php
2 // $Id: htmlpurifier.module,v 1.17 2010/06/09 17:07:14 ezyang Exp $
4 /**
5  * @file
6  * Implements HTML Purifier as a Drupal filter.
7  */
11 // -- HOOK IMPLEMENTATIONS -------------------------------------------------- //
13 /**
14  * Implementation of hook_flush_caches().
15  */
16 function htmlpurifier_flush_caches() {
17     return array('cache_htmlpurifier');
20 /**
21  * Implementation of hook_help().
22  */
23 function htmlpurifier_help($path, $arg) {
24   $output = NULL;
25   switch ($path) {
26     case 'admin/modules#htmlpurifier':
27       $output = t('Filter that removes malicious HTML and ensures standards compliant output.');
28       break;
29   }
30   return $output;
33 /**
34  * Implementation of hook_cron().
35  * @note
36  *    Checks for updates to the HTML Purifier library.
37  */
38 function htmlpurifier_cron() {
39   // Maybe this should be changed in the future:
40   $result  = drupal_http_request('http://htmlpurifier.org/live/VERSION');
41   $version = trim($result->data);
42   variable_set('htmlpurifier_version_current', $version);
46 /**
47  * Implementation of hook_filter().
48  */
49 function htmlpurifier_filter($op, $delta = 0, $format = -1, $text = '') {
50   switch ($op) {
51     case 'list':
52       return array(0 => t('HTML Purifier'), 1 => t('HTML Purifier (advanced)'));
54     case 'no cache':
55       // Since HTML Purifier implements its own caching layer, having filter
56       // cache it again is wasteful. Returns FALSE if double caching is permitted.
57       return !variable_get("htmlpurifier_doublecache", FALSE);
59     case 'description':
60       $common = t(
61         'Removes malicious HTML code and ensures that the output '.
62         'is standards compliant. <strong>Warning:</strong> For performance '.
63         'reasons, please ensure that there are no highly dynamic filters before HTML Purifier. '
64       );
65       switch ($delta) {
66         case 0:
67           return $common;
68         case 1:
69           return $common . t('<em>This version has advanced configuration options, do not enable both at the same time.</em>');
70       }
72     case 'prepare':
73       return $text;
75     case 'process':
76       return _htmlpurifier_process($text, $format);
78     case 'settings':
79       return _htmlpurifier_settings($delta, $format);
81     default:
82       return NULL;
83   }
86 /**
87  * Implementation of hook_filter_tips().
88  */
89 function htmlpurifier_filter_tips($delta, $format, $long = FALSE) {
90   if (variable_get("htmlpurifier_help_$format", TRUE)) {
91     return t('HTML tags will be transformed to conform to HTML standards.');
92   }
97 // -- INTERNAL FUNCTIONS ---------------------------------------------------- //
99 /**
100  * Processes HTML according to a format and returns purified HTML. Makes a 
101  * cache pass if possible.
102  * 
103  * @param string $text
104  *    Text to purify
105  * @param int $format
106  *    Input format corresponding to HTML Purifier's configuration.
107  * @param boolean $cache
108  *    Whether or not to check the cache.
109  * 
110  * @note
111  *    We ignore $delta because the only difference it makes is in the configuration
112  *    screen.
113  */
114 function _htmlpurifier_process($text, $format, $cache = TRUE) {
115   
116   if ($cache) {
117     $cid = $format . ':' . md5($text);
118     $old = cache_get($cid, 'cache_htmlpurifier');
119     if ($old) return $old->data;
120   }
121   
122   _htmlpurifier_load();
123   $config = _htmlpurifier_get_config($format);
124   $purifier = new HTMLPurifier($config);
125   $ret = $purifier->purify($text);
126   
127   if ($cache) cache_set($cid, $ret, 'cache_htmlpurifier', CACHE_PERMANENT);
128   
129   return $ret;
133  * Loads the HTML Purifier library, and performs global initialization.
134  */
135 function _htmlpurifier_load() {
136   static $done = false;
137   if ($done) {
138     return;
139   }
140   $done = true;
141   $module_path = drupal_get_path('module', 'htmlpurifier');
142   $library_path = $module_path;
143   if (function_exists('libraries_get_path')) {
144     $library_path = libraries_get_path('htmlpurifier');
145     // This may happen if the user has HTML Purifier installed under the
146     // old configuration, but also installed libraries and forgot to
147     // move it over.  There is code for emitting errors in
148     // htmlpurifier.install when this is the case.
149     if (!file_exists("$library_path/library/HTMLPurifier.auto.php")) {
150       $library_path = $module_path;
151     }
152   }
154   require_once "$library_path/library/HTMLPurifier.auto.php";
155   require_once "$module_path/HTMLPurifier_DefinitionCache_Drupal.php";
157   $factory = HTMLPurifier_DefinitionCacheFactory::instance();
158   $factory->register('Drupal', 'HTMLPurifier_DefinitionCache_Drupal');
160   // Register the version as a variable:
161   variable_set('htmlpurifier_version_ours', HTMLPurifier::VERSION);
165  * Returns the HTMLPurifier_Config object corresponding to an input format.
166  * @param int $format
167  *    Input format.
168  * @return
169  *    Instance of HTMLPurifier_Config.
170  */
171 function _htmlpurifier_get_config($format) {
172   
173   $config = HTMLPurifier_Config::createDefault();
174   
175   $config->set('AutoFormat.AutoParagraph', TRUE);
176   $config->set('AutoFormat.Linkify', TRUE);
177   $config->set('HTML.Doctype', 'XHTML 1.0 Transitional'); // Probably
178   $config->set('Core.AggressivelyFixLt', TRUE);
179   $config->set('Cache.DefinitionImpl', 'Drupal');
180   
181   // Filter HTML doesn't allow external images, so neither will we...
182   // for now. This can be configured off.
183   $config->set('URI.DisableExternalResources', TRUE);
184   
185   if (!empty($_SERVER['SERVER_NAME'])) {
186     // SERVER_NAME is more reliable than HTTP_HOST
187     $config->set('URI.Host', $_SERVER['SERVER_NAME']);
188   }
189   
190   if (defined('LANGUAGE_RTL') && $GLOBALS['language']->direction === LANGUAGE_RTL) {
191     $config->set('Attr.DefaultTextDir', 'rtl');
192   }
193   
194   if ($config_function = _htmlpurifier_config_load($format)) {
195     $config_function($config);
196   } else {
197     $config_data = variable_get("htmlpurifier_config_$format", FALSE);
198     // {FALSE, TRUE, FALSE} = {no index, everything is allowed, don't do mq fix}
199     $config->mergeArrayFromForm($config_data, FALSE, TRUE, FALSE);
200   }
201   
202   return $config;
203   
207  * Returns the name of the configuration function for $format, or FALSE if none
208  * exists. Function name will be htmlpurifier_config_N.
209  * 
210  * @param int $format
211  *    Integer format to check function for.
212  * @return
213  *    String function name for format, or FALSE if none.
214  */
215 function _htmlpurifier_config_load($format) {
216   $config_file     = drupal_get_path('module', 'htmlpurifier') ."/config/$format.php";
217   $config_function = "htmlpurifier_config_$format";
218   if (
219     !function_exists($config_function) &&
220     file_exists($config_file)
221   ) {
222     include_once $config_file;
223   }
224   return function_exists($config_function) ? $config_function : FALSE;
228  * Generates a settings form for configuring HTML Purifier.
229  * @param int $delta
230  *    Whether or not to use advanced form (1) or not (0).
231  * @param int $format
232  *    Input format being configured.
233  * @return
234  *    Form API array.
235  */
236 function _htmlpurifier_settings($delta, $format) {
237   _htmlpurifier_load();
238   
239   // Dry run, testing for errors:
240   _htmlpurifier_process('', $format, FALSE);
241   
242   $module_path = drupal_get_path('module', 'htmlpurifier');
243   drupal_add_css("$module_path/config-form.css");
244   // Makes all configuration links open in new windows; can safe lots of grief!
245   drupal_add_js('$(function(){$(".hp-config a").click(function(){window.open(this.href);return false;});});', 'inline');
246   drupal_add_js(HTMLPurifier_Printer_ConfigForm::getJavaScript(), 'inline');
247   
248   $form = array();
249   $form['dashboard'] = array(
250     '#type' => 'fieldset',
251     '#title' => t('HTML Purifier Dashboard'),
252     '#collapsible' => true,
253   );
254   $form['dashboard']["htmlpurifier_clear_cache"] = array(
255     '#type' => 'submit',
256     '#value' => t('Clear cache (Warning: Can result in performance degradation)'),
257     '#submit' => array('_htmlpurifier_clear_cache')
258   );
260   $form['htmlpurifier'] = array(
261     '#type' => 'fieldset',
262     '#title' => t('HTML Purifier'),
263     '#collapsible' => TRUE,
264   );
265   $form['htmlpurifier']["htmlpurifier_help_$format"] = array(
266     '#type' => 'checkbox',
267     '#title' => t('Display help text'),
268     '#default_value' => variable_get("htmlpurifier_help_$format", TRUE),
269     '#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.'),
270   );
271   if ($config_function = _htmlpurifier_config_load($format)) {
272     $form['htmlpurifier']['notice'] = array(
273       '#type' => 'markup',
274       '#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>',
275         array('!function' => $config_function, '!format' => $format)),
276     );
277   } else {
278     if ($delta == 0) {
279       $title = t('Configure HTML Purifier');
280       $allowed = array(
281         'URI.DisableExternalResources',
282         'URI.DisableResources',
283         'URI.Munge',
284         'Filter.YouTube',
285         'Attr.EnableID',
286         'HTML.Allowed',
287         'HTML.ForbiddenElements',
288         'HTML.ForbiddenAttributes',
289         'AutoFormat.RemoveEmpty',
290         'AutoFormat.Linkify',
291         'AutoFormat.AutoParagraph',
292       );
293     } else {
294       $title = t('Advanced configuration options');
295       $allowed = TRUE;
296       $form['htmlpurifier']["htmlpurifier_doublecache"] = array(
297         '#type' => 'checkbox',
298         '#title' => t('Allow double caching'),
299         '#default_value' => variable_get("htmlpurifier_doublecache", FALSE),
300         '#description' => t('If enabled, HTML Purifier will tell filter that its output is cacheable. This is not usually necessary, because HTML Purifier maintains its own cache, but may be helpful if you have later filters that need to be cached. Warning: this applies to ALL filters, not just this one'),
301       );
302     }
303     
304     $intro =
305         '<div class="form-item"><h3>'.
306         $title.
307         '</h3><div class="description">'.
308         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 dashboard)').
309         '</div></div>';
310     
311     $config = _htmlpurifier_get_config($format);
312     $config_form = new HTMLPurifier_Printer_ConfigForm(
313       "htmlpurifier_config_$format", 'http://htmlpurifier.org/live/configdoc/plain.html#%s'
314     );
315     $form['htmlpurifier']["htmlpurifier_config_$format"] = array(
316       '#value' => $intro . $config_form->render($config, $allowed, FALSE),
317       '#after_build' => array('_htmlpurifier_config_hack'),
318     );
319   }
320   
321   return $form;
325  * Fills out the form state with extra post data originating from the
326  * HTML Purifier configuration form. This is an #after_build hook function.
327  * 
328  * @warning
329  *    If someone ever gets the smart idea of changing the parameters to
330  *    this function, I'm SOL! ;-)
331  */
332 function _htmlpurifier_config_hack($form_element, &$form_state) {
333   $key = $form_element['#parents'][0];
334   if (!empty($form_element['#post']) && isset($form_element['#post'][$key])) {
335     $form_state['values'][$key] = $form_element['#post'][$key];
336   }
337   return $form_element;
341  * Clears the HTML Purifier internal Drupal cache.
342  */
343 function _htmlpurifier_clear_cache($form, &$form_state) {
344     drupal_set_message("Cache cleared");
345     db_query("DELETE FROM {cache_htmlpurifier}");
346     db_query("DELETE FROM {cache} WHERE cid LIKE '%s%%'", 'htmlpurifier:');