[ Index ]
 

Code source de Drupal 5.3

Accédez au Source d'autres logiciels libres

Classes | Fonctions | Variables | Constantes | Tables

title

Body

[fermer]

/modules/filter/ -> filter.module (source)

   1  <?php
   2  // $Id: filter.module,v 1.160.2.3 2007/08/08 06:54:10 drumm Exp $
   3  
   4  /**
   5   * @file
   6   * Framework for handling filtering of content.
   7   */
   8  
   9  // This is a special format ID which means "use the default format". This value
  10  // can be passed to the filter APIs as a format ID: this is equivalent to not
  11  // passing an explicit format at all.
  12  define('FILTER_FORMAT_DEFAULT', 0);
  13  
  14  define('FILTER_HTML_STRIP', 1);
  15  define('FILTER_HTML_ESCAPE', 2);
  16  
  17  /**
  18   * Implementation of hook_help().
  19   */
  20  function filter_help($section) {
  21    switch ($section) {
  22      case 'admin/help#filter':
  23        $output = '<p>'. t("The filter module allows administrators to configure  text input formats for the site. For example, an administrator may want a filter to strip out malicious HTML from user's comments. Administrators may also want to make URLs linkable even if they are only entered in an unlinked format.") .'</p>';
  24        $output .= '<p>'. t('Users can choose between the available input formats when creating or editing content. Administrators can configure which input formats are available to which user roles, as well as choose a default input format. Administrators can also create new input formats. Each input format can be configured to use a selection of filters.') .'</p>';
  25        $output .= '<p>'. t('For more information please read the configuration and customization handbook <a href="@filter">Filter page</a>.', array('@filter' => 'http://drupal.org/handbook/modules/filter/')) .'</p>';
  26        return $output;
  27  
  28      case 'admin/settings/filters':
  29        return t('
  30  <p><em>Input formats</em> define a way of processing user-supplied text in Drupal. Every input format has its own settings of which <em>filters</em> to apply. Possible filters include stripping out malicious HTML and making URLs clickable.</p>
  31  <p>Users can choose between the available input formats when submitting content.</p>
  32  <p>Below you can configure which input formats are available to which roles, as well as choose a default input format (used for imported content, for example).</p>
  33  <p>Note that (1) the default format is always available to all roles, and (2) all filter formats can always be used by roles with the "administer filters" permission even if they are not explicitly listed in the Roles column of this table.</p>');
  34  
  35      case 'admin/settings/filters/'. arg(3):
  36        return t('
  37  <p>Every <em>filter</em> performs one particular change on the user input, for example stripping out malicious HTML or making URLs clickable. Choose which filters you want to apply to text in this input format.</p>
  38  <p>If you notice some filters are causing conflicts in the output, you can <a href="@rearrange">rearrange them</a>.</p>', array('@rearrange' => url('admin/settings/filters/'. arg(3) .'/order')));
  39  
  40      case 'admin/settings/filters/'. arg(3) .'/configure':
  41        return '<p>'. t('If you cannot find the settings for a certain filter, make sure you have enabled it on the <a href="@url">view tab</a> first.', array('@url' => url('admin/settings/filters/'. arg(3)))) .'</p>';
  42  
  43      case 'admin/settings/filters/'. arg(3) .'/order':
  44        return t('
  45  <p>Because of the flexible filtering system, you might encounter a situation where one filter prevents another from doing its job. For example: a word in an URL gets converted into a glossary term, before the URL can be converted in a clickable link. When this happens, you will need to rearrange the order in which filters get executed.</p>
  46  <p>Filters are executed from top-to-bottom. You can use the weight column to rearrange them: heavier filters "sink" to the bottom.</p>');
  47    }
  48  }
  49  
  50  /**
  51   * Implementation of hook_menu().
  52   */
  53  function filter_menu($may_cache) {
  54    $items = array();
  55  
  56    if ($may_cache) {
  57      $items[] = array('path' => 'admin/settings/filters',
  58        'title' => t('Input formats'),
  59        'description' => t('Configure how content input by users is filtered, including allowed HTML tags, PHP code tags. Also allows enabling of module-provided filters.'),
  60        'callback' => 'drupal_get_form',
  61        'callback arguments' => array('filter_admin_overview'),
  62        'access' => user_access('administer filters'),
  63      );
  64      $items[] = array('path' => 'admin/settings/filters/list',
  65        'title' => t('List'),
  66        'callback' => 'filter_admin_overview',
  67        'type' => MENU_DEFAULT_LOCAL_TASK,
  68  'access' => user_access('administer filters'),
  69      );
  70      $items[] = array('path' => 'admin/settings/filters/add',
  71        'title' => t('Add input format'),
  72        'callback' => 'drupal_get_form',
  73        'callback arguments' => array('filter_admin_format_form'),
  74        'type' => MENU_LOCAL_TASK,
  75        'weight' => 1,
  76        'access' => user_access('administer filters'),
  77      );
  78      $items[] = array('path' => 'admin/settings/filters/delete',
  79        'title' => t('Delete input format'),
  80        'callback' => 'drupal_get_form',
  81        'callback arguments' => array('filter_admin_delete'),
  82        'type' => MENU_CALLBACK,
  83        'access' => user_access('administer filters'),
  84      );
  85      $items[] = array('path' => 'filter/tips',
  86        'title' => t('Compose tips'),
  87        'callback' => 'filter_tips_long',
  88        'access' => TRUE,
  89        'type' => MENU_SUGGESTED_ITEM,
  90      );
  91    }
  92    else {
  93      if (arg(0) == 'admin' && arg(1) == 'settings' && arg(2) == 'filters' && is_numeric(arg(3))) {
  94        $formats = filter_formats();
  95  
  96        if (isset($formats[arg(3)])) {
  97          $items[] = array('path' => 'admin/settings/filters/'. arg(3),
  98            'title' => t("!format input format", array('!format' => $formats[arg(3)]->name)),
  99            'callback' => 'drupal_get_form',
 100            'callback arguments' => array('filter_admin_format_form', $formats[arg(3)]),
 101            'type' => MENU_CALLBACK,
 102            'access' => user_access('administer filters'),
 103          );
 104          $items[] = array('path' => 'admin/settings/filters/'. arg(3) .'/list',
 105            'title' => t('View'),
 106            'callback' => 'drupal_get_form',
 107            'callback arguments' => array('filter_admin_format_form', $formats[arg(3)]),
 108            'type' => MENU_DEFAULT_LOCAL_TASK,
 109            'weight' => 0,
 110            'access' => user_access('administer filters'),
 111          );
 112          $items[] = array('path' => 'admin/settings/filters/'. arg(3) .'/configure',
 113            'title' => t('Configure'),
 114            'callback' => 'drupal_get_form',
 115            'callback arguments' => array('filter_admin_configure'),
 116            'type' => MENU_LOCAL_TASK,
 117            'weight' => 1,
 118            'access' => user_access('administer filters'),
 119          );
 120          $items[] = array('path' => 'admin/settings/filters/'. arg(3) .'/order',
 121            'title' => t('Rearrange'),
 122            'callback' => 'drupal_get_form',
 123            'callback arguments' => array('filter_admin_order', 'format' => $formats[arg(3)]),
 124            'type' => MENU_LOCAL_TASK,
 125            'weight' => 2,
 126            'access' => user_access('administer filters'),
 127          );
 128        }
 129      }
 130    }
 131  
 132    return $items;
 133  }
 134  
 135  /**
 136   * Implementation of hook_perm().
 137   */
 138  function filter_perm() {
 139    return array('administer filters');
 140  }
 141  
 142  /**
 143   * Implementation of hook_cron().
 144   *
 145   * Expire outdated filter cache entries
 146   */
 147  function filter_cron() {
 148    cache_clear_all(NULL, 'cache_filter');
 149  }
 150  
 151  /**
 152   * Implementation of hook_filter_tips().
 153   */
 154  function filter_filter_tips($delta, $format, $long = FALSE) {
 155    global $base_url;
 156    switch ($delta) {
 157      case 0:
 158        if (variable_get("filter_html_$format", FILTER_HTML_STRIP) ==  FILTER_HTML_STRIP) {
 159          if ($allowed_html = variable_get("allowed_html_$format", '<a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>')) {
 160            switch ($long) {
 161              case 0:
 162                return t('Allowed HTML tags: @tags', array('@tags' => $allowed_html));
 163              case 1:
 164                $output = '<p>'. t('Allowed HTML tags: @tags', array('@tags' => $allowed_html)) .'</p>';
 165                if (!variable_get("filter_html_help_$format", 1)) {
 166                  return $output;
 167                }
 168  
 169                $output .= t('
 170  <p>This site allows HTML content. While learning all of HTML may feel intimidating, learning how to use a very small number of the most basic HTML "tags" is very easy. This table provides examples for each tag that is enabled on this site.</p>
 171  <p>For more information see W3C\'s <a href="http://www.w3.org/TR/html/">HTML Specifications</a> or use your favorite search engine to find other sites that explain HTML.</p>');
 172                $tips = array(
 173                  'a' => array( t('Anchors are used to make links to other pages.'), '<a href="'. $base_url .'">'. variable_get('site_name', 'Drupal') .'</a>'),
 174                  'br' => array( t('By default line break tags are automatically added, so use this tag to add additional ones. Use of this tag is different because it is not used with an open/close pair like all the others. Use the extra " /" inside the tag to maintain XHTML 1.0 compatibility'), t('Text with <br />line break')),
 175                  'p' => array( t('By default paragraph tags are automatically added, so use this tag to add additional ones.'), '<p>'. t('Paragraph one.') .'</p> <p>'. t('Paragraph two.') .'</p>'),
 176                  'strong' => array( t('Strong'), '<strong>'. t('Strong'). '</strong>'),
 177                  'em' => array( t('Emphasized'), '<em>'. t('Emphasized') .'</em>'),
 178                  'cite' => array( t('Cited'), '<cite>'. t('Cited') .'</cite>'),
 179                  'code' => array( t('Coded text used to show programming source code'), '<code>'. t('Coded') .'</code>'),
 180                  'b' => array( t('Bolded'), '<b>'. t('Bolded') .'</b>'),
 181                  'u' => array( t('Underlined'), '<u>'. t('Underlined') .'</u>'),
 182                  'i' => array( t('Italicized'), '<i>'. t('Italicized') .'</i>'),
 183                  'sup' => array( t('Superscripted'), t('<sup>Super</sup>scripted')),
 184                  'sub' => array( t('Subscripted'), t('<sub>Sub</sub>scripted')),
 185                  'pre' => array( t('Preformatted'), '<pre>'. t('Preformatted') .'</pre>'),
 186                  'abbr' => array( t('Abbreviation'), t('<abbr title="Abbreviation">Abbrev.</abbr>')),
 187                  'acronym' => array( t('Acronym'), t('<acronym title="Three-Letter Acronym">TLA</acronym>')),
 188                  'blockquote' => array( t('Block quoted'), '<blockquote>'. t('Block quoted') .'</blockquote>'),
 189                  'q' => array( t('Quoted inline'), '<q>'. t('Quoted inline') .'</q>'),
 190                  // Assumes and describes tr, td, th.
 191                  'table' => array( t('Table'), '<table> <tr><th>'. t('Table header') .'</th></tr> <tr><td>'. t('Table cell') .'</td></tr> </table>'),
 192                  'tr' => NULL, 'td' => NULL, 'th' => NULL,
 193                  'del' => array( t('Deleted'), '<del>'. t('Deleted') .'</del>'),
 194                  'ins' => array( t('Inserted'), '<ins>'. t('Inserted') .'</ins>'),
 195                   // Assumes and describes li.
 196                  'ol' => array( t('Ordered list - use the &lt;li&gt; to begin each list item'), '<ol> <li>'. t('First item') .'</li> <li>'. t('Second item') .'</li> </ol>'),
 197                  'ul' => array( t('Unordered list - use the &lt;li&gt; to begin each list item'), '<ul> <li>'. t('First item') .'</li> <li>'. t('Second item') .'</li> </ul>'),
 198                  'li' => NULL,
 199                  // Assumes and describes dt and dd.
 200                  'dl' => array( t('Definition lists are similar to other HTML lists. &lt;dl&gt; begins the definition list, &lt;dt&gt; begins the definition term and &lt;dd&gt; begins the definition description.'), '<dl> <dt>'. t('First term') .'</dt> <dd>'. t('First definition') .'</dd> <dt>'. t('Second term') .'</dt> <dd>'. t('Second definition') .'</dd> </dl>'),
 201                  'dt' => NULL, 'dd' => NULL,
 202                  'h1' => array( t('Header'), '<h1>'. t('Title') .'</h1>'),
 203                  'h2' => array( t('Header'), '<h2>'. t('Subtitle') .'</h2>'),
 204                  'h3' => array( t('Header'), '<h3>'. t('Subtitle three') .'</h3>'),
 205                  'h4' => array( t('Header'), '<h4>'. t('Subtitle four') .'</h4>'),
 206                  'h5' => array( t('Header'), '<h5>'. t('Subtitle five') .'</h5>'),
 207                  'h6' => array( t('Header'), '<h6>'. t('Subtitle six') .'</h6>')
 208                );
 209                $header = array(t('Tag Description'), t('You Type'), t('You Get'));
 210                preg_match_all('/<([a-z0-9]+)[^a-z0-9]/i', $allowed_html, $out);
 211                foreach ($out[1] as $tag) {
 212                  if (array_key_exists($tag, $tips)) {
 213                    if ($tips[$tag]) {
 214                      $rows[] = array(
 215                        array('data' => $tips[$tag][0], 'class' => 'description'),
 216                        array('data' => '<code>'. check_plain($tips[$tag][1]) .'</code>', 'class' => 'type'),
 217                        array('data' => $tips[$tag][1], 'class' => 'get')
 218                      );
 219                    }
 220                  }
 221                  else {
 222                    $rows[] = array(
 223                      array('data' => t('No help provided for tag %tag.', array('%tag' => $tag)), 'class' => 'description', 'colspan' => 3),
 224                    );
 225                  }
 226                }
 227                $output .= theme('table', $header, $rows);
 228  
 229                $output .= t('
 230  <p>Most unusual characters can be directly entered without any problems.</p>
 231  <p>If you do encounter problems, try using HTML character entities. A common example looks like &amp;amp; for an ampersand &amp; character. For a full list of entities see HTML\'s <a href="http://www.w3.org/TR/html4/sgml/entities.html">entities</a> page. Some of the available characters include:</p>');
 232                $entities = array(
 233                  array( t('Ampersand'), '&amp;'),
 234                  array( t('Greater than'), '&gt;'),
 235                  array( t('Less than'), '&lt;'),
 236                  array( t('Quotation mark'), '&quot;'),
 237                );
 238                $header = array(t('Character Description'), t('You Type'), t('You Get'));
 239                unset($rows);
 240                foreach ($entities as $entity) {
 241                  $rows[] = array(
 242                    array('data' => $entity[0], 'class' => 'description'),
 243                    array('data' => '<code>'. check_plain($entity[1]) .'</code>', 'class' => 'type'),
 244                    array('data' => $entity[1], 'class' => 'get')
 245                  );
 246                }
 247                $output .= theme('table', $header, $rows);
 248                return $output;
 249            }
 250          }
 251          else {
 252            return t('No HTML tags allowed');
 253          }
 254        }
 255        break;
 256  
 257      case 1:
 258        switch ($long) {
 259          case 0:
 260            return t('You may post PHP code. You should include &lt;?php ?&gt; tags.');
 261          case 1:
 262            return t('
 263  <h4>Using custom PHP code</h4>
 264  <p>If you know how to script in PHP, Drupal gives you the power to embed any script you like. It will be executed when the page is viewed and dynamically embedded into the page. This gives you amazing flexibility and power, but of course with that comes danger and insecurity if you do not write good code. If you are not familiar with PHP, SQL or with the site engine, avoid experimenting with PHP because you can corrupt your database or render your site insecure or even unusable! If you do not plan to do fancy stuff with your content then you are probably better off with straight HTML.</p>
 265  <p>Remember that the code within each PHP item must be valid PHP code - including things like correctly terminating statements with a semicolon. It is highly recommended that you develop your code separately using a simple test script on top of a test database before migrating to your production environment.</p>
 266  <p>Notes:</p><ul><li>You can use global variables, such as configuration parameters, within the scope of your PHP code but remember that global variables which have been given values in your code will retain these values in the engine afterwards.</li><li>register_globals is now set to <strong>off</strong> by default. If you need form information you need to get it from the "superglobals" $_POST, $_GET, etc.</li><li>You can either use the <code>print</code> or <code>return</code> statement to output the actual content for your item.</li></ul>
 267  <p>A basic example:</p>
 268  <blockquote><p>You want to have a box with the title "Welcome" that you use to greet your visitors. The content for this box could be created by going:</p>
 269  <pre>
 270    print t("Welcome visitor, ... welcome message goes here ...");
 271  </pre>
 272  <p>If we are however dealing with a registered user, we can customize the message by using:</p>
 273  <pre>
 274    global $user;
 275    if ($user->uid) {
 276      print t("Welcome $user->name, ... welcome message goes here ...");
 277    }
 278    else {
 279      print t("Welcome visitor, ... welcome message goes here ...");
 280    }
 281  </pre></blockquote>
 282  <p>For more in-depth examples, we recommend that you check the existing Drupal code and use it as a starting point, especially for sidebar boxes.</p>');
 283        }
 284  
 285      case 2:
 286        switch ($long) {
 287          case 0:
 288            return t('Lines and paragraphs break automatically.');
 289          case 1:
 290            return t('Lines and paragraphs are automatically recognized. The &lt;br /&gt; line break, &lt;p&gt; paragraph and &lt;/p&gt; close paragraph tags are inserted automatically. If paragraphs are not recognized simply add a couple blank lines.');
 291        }
 292  
 293      case 3:
 294         return t('Web page addresses and e-mail addresses turn into links automatically.');
 295    }
 296  }
 297  
 298  /**
 299   * Displays a list of all input formats and which one is the default
 300   */
 301  function filter_admin_overview() {
 302  
 303    // Overview of all formats.
 304    $formats = filter_formats();
 305    $error = FALSE;
 306  
 307    foreach ($formats as $id => $format) {
 308      $roles = array();
 309      foreach (user_roles() as $rid => $name) {
 310        // Prepare a roles array with roles that may access the filter
 311        if (strstr($format->roles, ",$rid,")) {
 312          $roles[] = $name;
 313        }
 314      }
 315      $default = ($id == variable_get('filter_default_format', 1));
 316      $options[$id] = '';
 317      $form[$format->name]['id'] = array('#value' => $id);
 318      $form[$format->name]['roles'] = array('#value' => $default ? t('All roles may use default format') : ($roles ? implode(', ',$roles) : t('No roles may use this format')));
 319      $form[$format->name]['configure'] = array('#value' => l(t('configure'), 'admin/settings/filters/'. $id));
 320      $form[$format->name]['delete'] = array('#value' => $default ? '' : l(t('delete'), 'admin/settings/filters/delete/'. $id));
 321    }
 322    $form['default'] = array('#type' => 'radios', '#options' => $options, '#default_value' => variable_get('filter_default_format', 1));
 323    $form['submit'] = array('#type' => 'submit', '#value' => t('Set default format'));
 324    return $form;
 325  }
 326  
 327  function filter_admin_overview_submit($form_id, $form_values) {
 328    // Process form submission to set the default format
 329    if (is_numeric($form_values['default'])) {
 330      drupal_set_message(t('Default format updated.'));
 331      variable_set('filter_default_format', $form_values['default']);
 332    }
 333  }
 334  
 335  function theme_filter_admin_overview($form) {
 336    $rows = array();
 337    foreach ($form as $name => $element) {
 338      if (isset($element['roles']) && is_array($element['roles'])) {
 339        $rows[] = array(
 340          drupal_render($form['default'][$element['id']['#value']]),
 341          check_plain($name),
 342          drupal_render($element['roles']),
 343          drupal_render($element['configure']),
 344          drupal_render($element['delete'])
 345        );
 346        unset($form[$name]);
 347      }
 348    }
 349    $header = array(t('Default'), t('Name'), t('Roles'), array('data' => t('Operations'), 'colspan' => 2));
 350    $output = theme('table', $header, $rows);
 351    $output .= drupal_render($form);
 352  
 353    return $output;
 354  }
 355  
 356  /**
 357   * Menu callback; confirm deletion of a format.
 358   */
 359  function filter_admin_delete() {
 360    $format = arg(4);
 361    $format = db_fetch_object(db_query('SELECT * FROM {filter_formats} WHERE format = %d', $format));
 362  
 363    if ($format) {
 364      if ($format->format != variable_get('filter_default_format', 1)) {
 365        $form['format'] = array('#type' => 'hidden', '#value' => $format->format);
 366        $form['name'] = array('#type' => 'hidden', '#value' => $format->name);
 367  
 368        return confirm_form($form, t('Are you sure you want to delete the input format %format?', array('%format' => $format->name)), 'admin/settings/filters', t('If you have any content left in this input format, it will be switched to the default input format. This action cannot be undone.'), t('Delete'), t('Cancel'));
 369      }
 370      else {
 371        drupal_set_message(t('The default format cannot be deleted.'));
 372        drupal_goto('admin/settings/filters');
 373      }
 374    }
 375    else {
 376      drupal_not_found();
 377    }
 378  }
 379  
 380  /**
 381   * Process filter delete form submission.
 382   */
 383  function filter_admin_delete_submit($form_id, $form_values) {
 384    db_query("DELETE FROM {filter_formats} WHERE format = %d", $form_values['format']);
 385    db_query("DELETE FROM {filters} WHERE format = %d", $form_values['format']);
 386  
 387    $default = variable_get('filter_default_format', 1);
 388    // Replace existing instances of the deleted format with the default format.
 389    db_query("UPDATE {node_revisions} SET format = %d WHERE format = %d", $default, $form_values['format']);
 390    db_query("UPDATE {comments} SET format = %d WHERE format = %d", $default, $form_values['format']);
 391    db_query("UPDATE {boxes} SET format = %d WHERE format = %d", $default, $form_values['format']);
 392  
 393    cache_clear_all($form_values['format'] .':', 'cache_filter', TRUE);
 394    drupal_set_message(t('Deleted input format %format.', array('%format' => $form_values['name'])));
 395  
 396    return 'admin/settings/filters';
 397  }
 398  
 399  /**
 400   * Generate a filter format form.
 401   */
 402  function filter_admin_format_form($format = NULL) {
 403    $default = ($format->format == variable_get('filter_default_format', 1));
 404    if ($default) {
 405      $help = t('All roles for the default format must be enabled and cannot be changed.');
 406      $form['default_format'] = array('#type' => 'hidden', '#value' => 1);
 407    }
 408  
 409    $form['name'] = array('#type' => 'textfield',
 410      '#title' => 'Name',
 411      '#default_value' => $format->name,
 412      '#description' => t('Specify a unique name for this filter format.'),
 413      '#required' => TRUE,
 414    );
 415  
 416    // Add a row of checkboxes for form group.
 417    $form['roles'] = array('#type' => 'fieldset',
 418      '#title' => t('Roles'),
 419      '#description' => $default ? $help : t('Choose which roles may use this filter format. Note that roles with the "administer filters" permission can always use all the filter formats.'),
 420      '#tree' => TRUE,
 421    );
 422  
 423    foreach (user_roles() as $rid => $name) {
 424      $checked = strstr($format->roles, ",$rid,");
 425      $form['roles'][$rid] = array('#type' => 'checkbox',
 426        '#title' => $name,
 427        '#default_value' => ($default || $checked),
 428      );
 429      if ($default) {
 430        $form['roles'][$rid]['#disabled'] = TRUE;
 431      }
 432    }
 433    // Table with filters
 434    $all = filter_list_all();
 435    $enabled = filter_list_format($format->format);
 436  
 437    $form['filters'] = array('#type' => 'fieldset',
 438      '#title' => t('Filters'),
 439      '#description' => t('Choose the filters that will be used in this filter format.'),
 440      '#tree' => TRUE,
 441    );
 442    foreach ($all as $id => $filter) {
 443      $form['filters'][$id] = array('#type' => 'checkbox',
 444        '#title' => $filter->name,
 445        '#default_value' => isset($enabled[$id]),
 446        '#description' => module_invoke($filter->module, 'filter', 'description', $filter->delta),
 447      );
 448    }
 449    if (isset($format)) {
 450      $form['format'] = array('#type' => 'hidden', '#value' => $format->format);
 451  
 452      // Composition tips (guidelines)
 453      $tips = _filter_tips($format->format, FALSE);
 454      $extra = '<p>'. l(t('More information about formatting options'), 'filter/tips') .'</p>';
 455      $tiplist = theme('filter_tips', $tips, FALSE, $extra);
 456      if (!$tiplist) {
 457        $tiplist = '<p>'. t('No guidelines available.') .'</p>';
 458      }
 459      $group = '<p>'. t('These are the guidelines that users will see for posting in this input format. They are automatically generated from the filter settings.') .'</p>';
 460      $group .= $tiplist;
 461      $form['tips'] = array('#value' => '<h2>'. t('Formatting guidelines') .'</h2>'. $group);
 462    }
 463    $form['submit'] = array('#type' => 'submit', '#value' => t('Save configuration'));
 464  
 465    return $form;
 466  }
 467  
 468  /**
 469   * Validate filter format form submissions.
 470   */
 471  function filter_admin_format_form_validate($form_id, $form_values) {
 472    if (!isset($form_values['format'])) {
 473      $name = trim($form_values['name']);
 474      $result = db_fetch_object(db_query("SELECT format FROM {filter_formats} WHERE name='%s'", $name));
 475      if ($result) {
 476        form_set_error('name', t('Filter format names need to be unique. A format named %name already exists.', array('%name' => $name)));
 477      }
 478    }
 479  }
 480  
 481  /**
 482   * Process filter format form submissions.
 483   */
 484  function filter_admin_format_form_submit($form_id, $form_values) {
 485    $format = isset($form_values['format']) ? $form_values['format'] : NULL;
 486    $current = filter_list_format($format);
 487    $name = trim($form_values['name']);
 488    $cache = TRUE;
 489  
 490    // Add a new filter format.
 491    if (!$format) {
 492      $new = TRUE;
 493      db_query("INSERT INTO {filter_formats} (name) VALUES ('%s')", $name);
 494      $format = db_result(db_query("SELECT MAX(format) AS format FROM {filter_formats}"));
 495      drupal_set_message(t('Added input format %format.', array('%format' => $name)));
 496    }
 497    else {
 498      drupal_set_message(t('The input format settings have been updated.'));
 499    }
 500  
 501    db_query("DELETE FROM {filters} WHERE format = %d", $format);
 502    foreach ($form_values['filters'] as $id => $checked) {
 503      if ($checked) {
 504        list($module, $delta) = explode('/', $id);
 505        // Add new filters to the bottom.
 506        $weight = isset($current[$id]->weight) ? $current[$id]->weight : 10;
 507        db_query("INSERT INTO {filters} (format, module, delta, weight) VALUES (%d, '%s', %d, %d)", $format, $module, $delta, $weight);
 508  
 509        // Check if there are any 'no cache' filters.
 510        $cache &= !module_invoke($module, 'filter', 'no cache', $delta);
 511      }
 512    }
 513  
 514    // We store the roles as a string for ease of use.
 515    // We should always set all roles to TRUE when saving a default role.
 516    // We use leading and trailing comma's to allow easy substring matching.
 517    $roles = array();
 518    if (isset($form_values['roles'])) {
 519      foreach ($form_values['roles'] as $id => $checked) {
 520        if ($checked) {
 521          $roles[] = $id;
 522        }
 523      }
 524    }
 525    $roles = ','. implode(',', ($form_values['default_format'] ? array_keys(user_roles()) : $roles)) .',';
 526  
 527    db_query("UPDATE {filter_formats} SET cache = %d, name='%s', roles = '%s' WHERE format = %d", $cache, $name, $roles, $format);
 528  
 529    cache_clear_all($format .':', 'cache_filter', TRUE);
 530  
 531    // If a new filter was added, return to the main list of filters. Otherwise, stay on edit filter page to show new changes.
 532    if ($new) {
 533      return 'admin/settings/filters/';
 534    }
 535    else {
 536      return 'admin/settings/filters/'. $format;
 537    }
 538  }
 539  
 540  /**
 541   * Menu callback; display form for ordering filters for a format.
 542   */
 543  function filter_admin_order($format = NULL) {
 544    // Get list (with forced refresh)
 545    $filters = filter_list_format($format->format);
 546  
 547    $form['weights'] = array('#tree' => TRUE);
 548    foreach ($filters as $id => $filter) {
 549      $form['names'][$id] = array('#value' => $filter->name);
 550      $form['weights'][$id] = array('#type' => 'weight', '#default_value' => $filter->weight);
 551    }
 552    $form['format'] = array('#type' => 'hidden', '#value' => $format->format);
 553    $form['submit'] = array('#type' => 'submit', '#value' => t('Save configuration'));
 554  
 555    return $form;
 556  }
 557  
 558  /**
 559   * Theme filter order configuration form.
 560   */
 561  function theme_filter_admin_order($form) {
 562    $header = array(t('Name'), t('Weight'));
 563    $rows = array();
 564    foreach (element_children($form['names']) as $id) {
 565      // Don't take form control structures
 566      if (is_array($form['names'][$id])) {
 567        $rows[] = array(drupal_render($form['names'][$id]), drupal_render($form['weights'][$id]));
 568      }
 569    }
 570  
 571    $output = theme('table', $header, $rows);
 572    $output .= drupal_render($form);
 573  
 574    return $output;
 575  }
 576  
 577  /**
 578   * Process filter order configuration form submission.
 579   */
 580  function filter_admin_order_submit($form_id, $form_values) {
 581    foreach ($form_values['weights'] as $id => $weight) {
 582      list($module, $delta) = explode('/', $id);
 583      db_query("UPDATE {filters} SET weight = %d WHERE format = %d AND module = '%s' AND delta = %d", $weight, $form_values['format'], $module, $delta);
 584    }
 585    drupal_set_message(t('The filter ordering has been saved.'));
 586  
 587    cache_clear_all($form_values['format'] .':', 'cache_filter', TRUE);
 588  }
 589  
 590  /**
 591   * Menu callback; display settings defined by filters.
 592   */
 593  function filter_admin_configure() {
 594    $format = arg(3);
 595  
 596    $list = filter_list_format($format);
 597    $form = array();
 598    foreach ($list as $filter) {
 599      $form_module = module_invoke($filter->module, 'filter', 'settings', $filter->delta, $format);
 600      if (isset($form_module) && is_array($form_module)) {
 601        $form = array_merge($form, $form_module);
 602      }
 603    }
 604  
 605    if (!empty($form)) {
 606      $form = system_settings_form($form);
 607    }
 608    else {
 609      $form['error'] = array('#value' => t('No settings are available.'));
 610    }
 611  
 612    return $form;
 613  }
 614  
 615  /**
 616   * Retrieve a list of input formats.
 617   */
 618  function filter_formats() {
 619    global $user;
 620    static $formats;
 621  
 622    // Administrators can always use all input formats.
 623    $all = user_access('administer filters');
 624  
 625    if (!isset($formats)) {
 626      $formats = array();
 627  
 628      $query = 'SELECT * FROM {filter_formats}';
 629  
 630      // Build query for selecting the format(s) based on the user's roles.
 631      $args = array();
 632      if (!$all) {
 633        $where = array();
 634        foreach ($user->roles as $rid => $role) {
 635          $where[] = "roles LIKE '%%,%d,%%'";
 636          $args[] = $rid;
 637        }
 638        $query .= ' WHERE '. implode(' OR ', $where) . ' OR format = %d';
 639        $args[] = variable_get('filter_default_format', 1);
 640      }
 641  
 642      $result = db_query($query, $args);
 643      while ($format = db_fetch_object($result)) {
 644        $formats[$format->format] = $format;
 645      }
 646    }
 647    return $formats;
 648  }
 649  
 650  /**
 651   * Build a list of all filters.
 652   */
 653  function filter_list_all() {
 654    $filters = array();
 655  
 656    foreach (module_list() as $module) {
 657      $list = module_invoke($module, 'filter', 'list');
 658      if (isset($list) && is_array($list)) {
 659        foreach ($list as $delta => $name) {
 660          $filters[$module .'/'. $delta] = (object)array('module' => $module, 'delta' => $delta, 'name' => $name);
 661        }
 662      }
 663    }
 664  
 665    uasort($filters, '_filter_list_cmp');
 666  
 667    return $filters;
 668  }
 669  
 670  /**
 671   * Helper function for sorting the filter list by filter name.
 672   */
 673  function _filter_list_cmp($a, $b) {
 674    return strcmp($a->name, $b->name);
 675  }
 676  
 677  /**
 678   * Resolve a format id, including the default format.
 679   */
 680  function filter_resolve_format($format) {
 681    return $format == FILTER_FORMAT_DEFAULT ? variable_get('filter_default_format', 1) : $format;
 682  }
 683  /**
 684   * Check if text in a certain input format is allowed to be cached.
 685   */
 686  function filter_format_allowcache($format) {
 687    static $cache = array();
 688    $format = filter_resolve_format($format);
 689    if (!isset($cache[$format])) {
 690      $cache[$format] = db_result(db_query('SELECT cache FROM {filter_formats} WHERE format = %d', $format));
 691    }
 692    return $cache[$format];
 693  }
 694  
 695  /**
 696   * Retrieve a list of filters for a certain format.
 697   */
 698  function filter_list_format($format) {
 699    static $filters = array();
 700  
 701    if (!isset($filters[$format])) {
 702      $filters[$format] = array();
 703      $result = db_query("SELECT * FROM {filters} WHERE format = %d ORDER BY weight ASC", $format);
 704      while ($filter = db_fetch_object($result)) {
 705        $list = module_invoke($filter->module, 'filter', 'list');
 706        if (isset($list) && is_array($list) && isset($list[$filter->delta])) {
 707          $filter->name = $list[$filter->delta];
 708          $filters[$format][$filter->module .'/'. $filter->delta] = $filter;
 709        }
 710      }
 711    }
 712  
 713    return $filters[$format];
 714  }
 715  
 716  /**
 717   * @name Filtering functions
 718   * @{
 719   * Modules which need to have content filtered can use these functions to
 720   * interact with the filter system.
 721   *
 722   * For more info, see the hook_filter() documentation.
 723   *
 724   * Note: because filters can inject JavaScript or execute PHP code, security is
 725   * vital here. When a user supplies a $format, you should validate it with
 726   * filter_access($format) before accepting/using it. This is normally done in
 727   * the validation stage of the node system. You should for example never make a
 728   * preview of content in a disallowed format.
 729   */
 730  
 731  /**
 732   * Run all the enabled filters on a piece of text.
 733   *
 734   * @param $text
 735   *    The text to be filtered.
 736   * @param $format
 737   *    The format of the text to be filtered. Specify FILTER_FORMAT_DEFAULT for
 738   *    the default format.
 739   * @param $check
 740   *    Whether to check the $format with filter_access() first. Defaults to TRUE.
 741   *    Note that this will check the permissions of the current user, so you
 742   *    should specify $check = FALSE when viewing other people's content. When
 743   *    showing content that is not (yet) stored in the database (eg. upon preview),
 744   *    set to TRUE so the user's permissions are checked.
 745   */
 746  function check_markup($text, $format = FILTER_FORMAT_DEFAULT, $check = TRUE) {
 747    // When $check = TRUE, do an access check on $format.
 748    if (isset($text) && (!$check || filter_access($format))) {
 749      $format = filter_resolve_format($format);
 750  
 751      // Check for a cached version of this piece of text.
 752      $id = $format .':'. md5($text);
 753      if ($cached = cache_get($id, 'cache_filter')) {
 754        return $cached->data;
 755      }
 756  
 757      // See if caching is allowed for this format.
 758      $cache = filter_format_allowcache($format);
 759  
 760      // Convert all Windows and Mac newlines to a single newline,
 761      // so filters only need to deal with one possibility.
 762      $text = str_replace(array("\r\n", "\r"), "\n", $text);
 763  
 764      // Get a complete list of filters, ordered properly.
 765      $filters = filter_list_format($format);
 766  
 767      // Give filters the chance to escape HTML-like data such as code or formulas.
 768      foreach ($filters as $filter) {
 769        $text = module_invoke($filter->module, 'filter', 'prepare', $filter->delta, $format, $text);
 770      }
 771  
 772      // Perform filtering.
 773      foreach ($filters as $filter) {
 774        $text = module_invoke($filter->module, 'filter', 'process', $filter->delta, $format, $text);
 775      }
 776  
 777      // Store in cache with a minimum expiration time of 1 day.
 778      if ($cache) {
 779        cache_set($id, 'cache_filter', $text, time() + (60 * 60 * 24));
 780      }
 781    }
 782    else {
 783      $text = t('n/a');
 784    }
 785  
 786    return $text;
 787  }
 788  
 789  /**
 790   * Generate a selector for choosing a format in a form.
 791   *
 792   * @param $value
 793   *   The ID of the format that is currently selected.
 794   * @param $weight
 795   *   The weight of the input format.
 796   * @param $parents
 797   *   Required when defining multiple input formats on a single node or having a different parent than 'format'.
 798   * @return
 799   *   HTML for the form element.
 800   */
 801  function filter_form($value = FILTER_FORMAT_DEFAULT, $weight = NULL, $parents = array('format')) {
 802    $value = filter_resolve_format($value);
 803    $formats = filter_formats();
 804  
 805    $extra = theme('filter_tips_more_info');
 806  
 807    if (count($formats) > 1) {
 808      $form = array(
 809        '#type' => 'fieldset',
 810        '#title' => t('Input format'),
 811        '#collapsible' => TRUE,
 812        '#collapsed' => TRUE,
 813        '#weight' => $weight,
 814        '#validate' => array('filter_form_validate' => array()),
 815      );
 816      // Multiple formats available: display radio buttons with tips.
 817      foreach ($formats as $format) {
 818        $form[$format->format] = array(
 819          '#type' => 'radio',
 820          '#title' => $format->name,
 821          '#default_value' => $value,
 822          '#return_value' => $format->format,
 823          '#parents' => $parents,
 824          '#description' => theme('filter_tips', _filter_tips($format->format, FALSE)),
 825        );
 826      }
 827    }
 828    else {
 829      // Only one format available: use a hidden form item and only show tips.
 830      $format = array_shift($formats);
 831      $form[$format->format] = array('#type' => 'value', '#value' => $format->format, '#parents' => $parents);
 832      $tips = _filter_tips(variable_get('filter_default_format', 1), FALSE);
 833      $form['format']['guidelines'] = array(
 834        '#title' => t('Formatting guidelines'),
 835        '#value' => theme('filter_tips', $tips, FALSE, $extra),
 836      );
 837    }
 838    $form[] = array('#value' => $extra);
 839    return $form;
 840  }
 841  
 842  function filter_form_validate($form) {
 843    foreach (element_children($form) as $key) {
 844      if ($form[$key]['#value'] == $form[$key]['#return_value']) {
 845        return;
 846      }
 847    }
 848    form_error($form, t('An illegal choice has been detected. Please contact the site administrator.'));
 849    watchdog('form', t('Illegal choice %choice in %name element.', array('%choice' => $form[$key]['#value'], '%name' => empty($form['#title']) ? $form['#parents'][0] : $form['#title'])), WATCHDOG_ERROR);
 850  }
 851  
 852  /**
 853   * Returns TRUE if the user is allowed to access this format.
 854   */
 855  function filter_access($format) {
 856    $format = filter_resolve_format($format);
 857    if (user_access('administer filters') || ($format == variable_get('filter_default_format', 1))) {
 858      return TRUE;
 859    }
 860    else {
 861      $formats = filter_formats();
 862      return isset($formats[$format]);
 863    }
 864  }
 865  /**
 866   * @} End of "Filtering functions".
 867   */
 868  
 869  /**
 870   * Menu callback; show a page with long filter tips.
 871   */
 872  function filter_tips_long() {
 873    $format = arg(2);
 874    if ($format) {
 875      $output = theme('filter_tips', _filter_tips($format, TRUE), TRUE);
 876    }
 877    else {
 878      $output = theme('filter_tips', _filter_tips(-1, TRUE), TRUE);
 879    }
 880    return $output;
 881  }
 882  
 883  /**
 884   * Helper function for fetching filter tips.
 885   */
 886  function _filter_tips($format, $long = FALSE) {
 887    if ($format == -1) {
 888      $formats = filter_formats();
 889    }
 890    else {
 891      $formats = array(db_fetch_object(db_query("SELECT * FROM {filter_formats} WHERE format = %d", $format)));
 892    }
 893  
 894    $tips = array();
 895  
 896    foreach ($formats as $format) {
 897      $filters = filter_list_format($format->format);
 898  
 899      $tips[$format->name] = array();
 900      foreach ($filters as $id => $filter) {
 901        if ($tip = module_invoke($filter->module, 'filter_tips', $filter->delta, $format->format, $long)) {
 902          $tips[$format->name][] = array('tip' => $tip, 'id' => $id);
 903        }
 904      }
 905    }
 906  
 907    return $tips;
 908  }
 909  
 910  /**
 911   * Format a set of filter tips.
 912   *
 913   * @ingroup themeable
 914   */
 915  function theme_filter_tips($tips, $long = FALSE, $extra = '') {
 916    $output = '';
 917  
 918    $multiple = count($tips) > 1;
 919    if ($multiple) {
 920      $output = t('input formats') .':';
 921    }
 922  
 923    if (count($tips)) {
 924      if ($multiple) {
 925        $output .= '<ul>';
 926      }
 927      foreach ($tips as $name => $tiplist) {
 928        if ($multiple) {
 929          $output .= '<li>';
 930          $output .= '<strong>'. $name .'</strong>:<br />';
 931        }
 932  
 933        $tips = '';
 934        foreach ($tiplist as $tip) {
 935          $tips .= '<li'. ($long ? ' id="filter-'. str_replace("/", "-", $tip['id']) .'">' : '>') . $tip['tip'] . '</li>';
 936        }
 937  
 938        if ($tips) {
 939          $output .= "<ul class=\"tips\">$tips</ul>";
 940        }
 941  
 942        if ($multiple) {
 943          $output .= '</li>';
 944        }
 945      }
 946      if ($multiple) {
 947        $output .= '</ul>';
 948      }
 949    }
 950  
 951    return $output;
 952  }
 953  
 954  /**
 955   * Format a link to the more extensive filter tips.
 956   *
 957   * @ingroup themeable
 958   */
 959  
 960  function theme_filter_tips_more_info() {
 961    return '<p>'. l(t('More information about formatting options'), 'filter/tips') .'</p>';
 962  }
 963  
 964  /**
 965   * @name Standard filters
 966   * @{
 967   * Filters implemented by the filter.module.
 968   */
 969  
 970  /**
 971   * Implementation of hook_filter(). Contains a basic set of essential filters.
 972   * - HTML filter:
 973   *     Validates user-supplied HTML, transforming it as necessary.
 974   * - PHP evaluator:
 975   *     Executes PHP code.
 976   * - Line break converter:
 977   *     Converts newlines into paragraph and break tags.
 978   */
 979  function filter_filter($op, $delta = 0, $format = -1, $text = '') {
 980    switch ($op) {
 981      case 'list':
 982        return array(0 => t('HTML filter'), 1 => t('PHP evaluator'), 2 => t('Line break converter'), 3 => t('URL filter'));
 983  
 984      case 'no cache':
 985        return $delta == 1; // No caching for the PHP evaluator.
 986  
 987      case 'description':
 988        switch ($delta) {
 989          case 0:
 990            return t('Allows you to restrict if users can post HTML and which tags to filter out.');
 991          case 1:
 992            return t('Runs a piece of PHP code. The usage of this filter should be restricted to administrators only!');
 993          case 2:
 994            return t('Converts line breaks into HTML (i.e. &lt;br&gt; and &lt;p&gt; tags).');
 995          case 3:
 996            return t('Turns web and e-mail addresses into clickable links.');
 997          default:
 998            return;
 999        }
1000  
1001      case 'process':
1002        switch ($delta) {
1003          case 0:
1004            return _filter_html($text, $format);
1005          case 1:
1006            return drupal_eval($text);
1007          case 2:
1008            return _filter_autop($text);
1009          case 3:
1010            return _filter_url($text, $format);
1011          default:
1012            return $text;
1013        }
1014  
1015      case 'settings':
1016        switch ($delta) {
1017          case 0:
1018            return _filter_html_settings($format);
1019          case 3:
1020            return _filter_url_settings($format);
1021          default:
1022            return;
1023        }
1024  
1025      default:
1026        return $text;
1027    }
1028  }
1029  
1030  /**
1031   * Settings for the HTML filter.
1032   */
1033  function _filter_html_settings($format) {
1034    $form['filter_html'] = array(
1035      '#type' => 'fieldset',
1036      '#title' => t('HTML filter'),
1037      '#collapsible' => TRUE,
1038    );
1039    $form['filter_html']["filter_html_$format"] = array(
1040      '#type' => 'radios',
1041      '#title' => t('Filter HTML tags'),
1042      '#default_value' => variable_get("filter_html_$format", FILTER_HTML_STRIP),
1043      '#options' => array(FILTER_HTML_STRIP => t('Strip disallowed tags'), FILTER_HTML_ESCAPE => t('Escape all tags')),
1044      '#description' => t('How to deal with HTML tags in user-contributed content. If set to "Strip disallowed tags", dangerous tags are removed (see below). If set to "Escape tags", all HTML is escaped and presented as it was typed.'),
1045    );
1046    $form['filter_html']["allowed_html_$format"] = array(
1047      '#type' => 'textfield',
1048      '#title' => t('Allowed HTML tags'),
1049      '#default_value' => variable_get("allowed_html_$format", '<a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>'),
1050      '#size' => 64,
1051      '#maxlength' => 255,
1052      '#description' => t('If "Strip disallowed tags" is selected, optionally specify tags which should not be stripped. JavaScript event attributes are always stripped.'),
1053    );
1054    $form['filter_html']["filter_html_help_$format"] = array(
1055      '#type' => 'checkbox',
1056      '#title' => t('Display HTML help'),
1057      '#default_value' => variable_get("filter_html_help_$format", 1),
1058      '#description' => t('If enabled, Drupal will display some basic HTML help in the long filter tips.'),
1059    );
1060    $form['filter_html']["filter_html_nofollow_$format"] = array(
1061      '#type' => 'checkbox',
1062      '#title' => t('Spam link deterrent'),
1063      '#default_value' => variable_get("filter_html_nofollow_$format", FALSE),
1064      '#description' => t('If enabled, Drupal will add rel="nofollow" to all links, as a measure to reduce the effectiveness of spam links. Note: this will also prevent valid links from being followed by search engines, therefore it is likely most effective when enabled for anonymous users.'),
1065    );
1066    return $form;
1067  }
1068  
1069  /**
1070   * HTML filter. Provides filtering of input into accepted HTML.
1071   */
1072  function _filter_html($text, $format) {
1073    if (variable_get("filter_html_$format", FILTER_HTML_STRIP) == FILTER_HTML_STRIP) {
1074      $allowed_tags = preg_split('/\s+|<|>/', variable_get("allowed_html_$format", '<a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>'), -1, PREG_SPLIT_NO_EMPTY);
1075      $text = filter_xss($text, $allowed_tags);
1076    }
1077  
1078    if (variable_get("filter_html_$format", FILTER_HTML_STRIP) == FILTER_HTML_ESCAPE) {
1079      // Escape HTML
1080      $text = check_plain($text);
1081    }
1082  
1083    if (variable_get("filter_html_nofollow_$format", FALSE)) {
1084      $text = preg_replace('/<a([^>]+)>/i', '<a\\1 rel="nofollow">', $text);
1085    }
1086  
1087    return trim($text);
1088  }
1089  
1090  /**
1091   * Settings for URL filter.
1092   */
1093  function _filter_url_settings($format) {
1094    $form['filter_urlfilter'] = array(
1095      '#type' => 'fieldset',
1096      '#title' => t('URL filter'),
1097      '#collapsible' => TRUE,
1098    );
1099    $form['filter_urlfilter']['filter_url_length_'. $format] = array(
1100      '#type' => 'textfield',
1101      '#title' => t('Maximum link text length'),
1102      '#default_value' => variable_get('filter_url_length_'. $format, 72),
1103      '#maxlength' => 4,
1104      '#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.'),
1105    );
1106    return $form;
1107  }
1108  
1109  /**
1110   * URL filter. Automatically converts text web addresses (URLs, e-mail addresses,
1111   * ftp links, etc.) into hyperlinks.
1112   */
1113  function _filter_url($text, $format) {
1114    // Pass length to regexp callback
1115    _filter_url_trim(NULL, variable_get('filter_url_length_'. $format, 72));
1116  
1117    $text   = ' '. $text .' ';
1118  
1119    // Match absolute URLs.
1120    $text = preg_replace_callback("`(<p>|<li>|<br\s*/?>|[ \n\r\t\(])((http://|https://|ftp://|mailto:|smb://|afp://|file://|gopher://|news://|ssl://|sslv2://|sslv3://|tls://|tcp://|udp://)([a-zA-Z0-9@:%_+*~#?&=.,/;-]*[a-zA-Z0-9@:%_+*~#&=/;-]))([.,?!]*?)(?=(</p>|</li>|<br\s*/?>|[ \n\r\t\)]))`i", '_filter_url_parse_full_links', $text);
1121  
1122    // Match e-mail addresses.
1123    $text = preg_replace("`(<p>|<li>|<br\s*/?>|[ \n\r\t\(])([A-Za-z0-9._-]+@[A-Za-z0-9._+-]+\.[A-Za-z]{2,4})([.,?!]*?)(?=(</p>|</li>|<br\s*/?>|[ \n\r\t\)]))`i", '\1<a href="mailto:\2">\2</a>\3', $text);
1124  
1125    // Match www domains/addresses.
1126    $text = preg_replace_callback("`(<p>|<li>|[ \n\r\t\(])(www\.[a-zA-Z0-9@:%_+*~#?&=.,/;-]*[a-zA-Z0-9@:%_+~#\&=/;-])([.,?!]*?)(?=(</p>|</li>|<br\s*/?>|[ \n\r\t\)]))`i", '_filter_url_parse_partial_links', $text);
1127    $text = substr($text, 1, -1);
1128  
1129    return $text;
1130  }
1131  
1132  /**
1133   * Make links out of absolute URLs.
1134   */
1135  function _filter_url_parse_full_links($match) {
1136    $match[2] = decode_entities($match[2]);
1137    $caption = check_plain(_filter_url_trim($match[2]));
1138    $match[2] = check_url($match[2]);
1139    return $match[1] . '<a href="'. $match[2] .'" title="'. $match[2] .'">'. $caption .'</a>'. $match[5];
1140  }
1141  
1142  /**
1143   * Make links out of domain names starting with "www."
1144   */
1145  function _filter_url_parse_partial_links($match) {
1146    $match[2] = decode_entities($match[2]);
1147    $caption = check_plain(_filter_url_trim($match[2]));
1148    $match[2] = check_plain($match[2]);
1149    return $match[1] . '<a href="http://'. $match[2] .'" title="'. $match[2] .'">'. $caption .'</a>'. $match[3];
1150  }
1151  
1152  /**
1153   * Shortens long URLs to http://www.example.com/long/url...
1154   */
1155  function _filter_url_trim($text, $length = NULL) {
1156    static $_length;
1157    if ($length !== NULL) {
1158      $_length = $length;
1159    }
1160  
1161    if (strlen($text) > $_length) {
1162      $text = substr($text, 0, $_length) .'...';
1163    }
1164  
1165    return $text;
1166  }
1167  
1168  /**
1169   * Convert line breaks into <p> and <br> in an intelligent fashion.
1170   * Based on: http://photomatt.net/scripts/autop
1171   */
1172  function _filter_autop($text) {
1173    // All block level tags
1174    $block = '(?:table|thead|tfoot|caption|colgroup|tbody|tr|td|th|div|dl|dd|dt|ul|ol|li|pre|select|form|blockquote|address|p|h[1-6])';
1175  
1176    // Split at <pre>, <script>, <style> and </pre>, </script>, </style> tags.
1177    // We don't apply any processing to the contents of these tags to avoid messing
1178    // up code. We look for matched pairs and allow basic nesting. For example:
1179    // "processed <pre> ignored <script> ignored </script> ignored </pre> processed"
1180    $chunks = preg_split('@(</?(?:pre|script|style)[^>]*>)@i', $text, -1, PREG_SPLIT_DELIM_CAPTURE);
1181    // Note: PHP ensures the array consists of alternating delimiters and literals
1182    // and begins and ends with a literal (inserting NULL as required).
1183    $ignore = FALSE;
1184    $ignoretag = '';
1185    $output = '';
1186    foreach ($chunks as $i => $chunk) {
1187      if ($i % 2) {
1188        // Opening or closing tag?
1189        $open = ($chunk[1] != '/');
1190        list($tag) = split('[ >]', substr($chunk, 2 - $open), 2);
1191        if (!$ignore) {
1192          if ($open) {
1193            $ignore = TRUE;
1194            $ignoretag = $tag;
1195          }
1196        }
1197        // Only allow a matching tag to close it.
1198        else if (!$open && $ignoretag == $tag) {
1199          $ignore = FALSE;
1200          $ignoretag = '';
1201        }
1202      }
1203      else if (!$ignore) {
1204        $chunk = preg_replace('|\n*$|', '', $chunk) ."\n\n"; // just to make things a little easier, pad the end
1205        $chunk = preg_replace('|<br />\s*<br />|', "\n\n", $chunk);
1206        $chunk = preg_replace('!(<'. $block .'[^>]*>)!', "\n$1", $chunk); // Space things out a little
1207        $chunk = preg_replace('!(</'. $block .'>)!', "$1\n\n", $chunk); // Space things out a little
1208        $chunk = preg_replace("/\n\n+/", "\n\n", $chunk); // take care of duplicates
1209        $chunk = preg_replace('/\n?(.+?)(?:\n\s*\n|\z)/s', "<p>$1</p>\n", $chunk); // make paragraphs, including one at the end
1210        $chunk = preg_replace('|<p>\s*</p>\n|', '', $chunk); // under certain strange conditions it could create a P of entirely whitespace
1211        $chunk = preg_replace("|<p>(<li.+?)</p>|", "$1", $chunk); // problem with nested lists
1212        $chunk = preg_replace('|<p><blockquote([^>]*)>|i', "<blockquote$1><p>", $chunk);
1213        $chunk = str_replace('</blockquote></p>', '</p></blockquote>', $chunk);
1214        $chunk = preg_replace('!<p>\s*(</?'. $block .'[^>]*>)!', "$1", $chunk);
1215        $chunk = preg_replace('!(</?'. $block .'[^>]*>)\s*</p>!', "$1", $chunk);
1216        $chunk = preg_replace('|(?<!<br />)\s*\n|', "<br />\n", $chunk); // make line breaks
1217        $chunk = preg_replace('!(</?'. $block .'[^>]*>)\s*<br />!', "$1", $chunk);
1218        $chunk = preg_replace('!<br />(\s*</?(?:p|li|div|th|pre|td|ul|ol)>)!', '$1', $chunk);
1219        $chunk = preg_replace('/&([^#])(?![A-Za-z0-9]{1,8};)/', '&amp;$1', $chunk);
1220      }
1221      $output .= $chunk;
1222    }
1223    return $output;
1224  }
1225  
1226  /**
1227   * Very permissive XSS/HTML filter for admin-only use.
1228   *
1229   * Use only for fields where it is impractical to use the
1230   * whole filter system, but where some (mainly inline) mark-up
1231   * is desired (so check_plain() is not acceptable).
1232   *
1233   * Allows all tags that can be used inside an HTML body, save
1234   * for scripts and styles.
1235   */
1236  function filter_xss_admin($string) {
1237    return filter_xss($string, array('a', 'abbr', 'acronym', 'address', 'b', 'bdo', 'big', 'blockquote', 'br', 'caption', 'cite', 'code', 'col', 'colgroup', 'dd', 'del', 'dfn', 'div', 'dl', 'dt', 'em', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'img', 'ins', 'kbd', 'li', 'object', 'ol', 'p', 'param', 'pre', 'q', 'samp', 'small', 'span', 'strong', 'sub', 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr', 'tt', 'ul', 'var'));
1238  }
1239  
1240  /**
1241   * Filters XSS. Based on kses by Ulf Harnhammar, see
1242   * http://sourceforge.net/projects/kses
1243   *
1244   * For examples of various XSS attacks, see:
1245   * http://ha.ckers.org/xss.html
1246   *
1247   * This code does four things:
1248   * - Removes characters and constructs that can trick browsers
1249   * - Makes sure all HTML entities are well-formed
1250   * - Makes sure all HTML tags and attributes are well-formed
1251   * - Makes sure no HTML tags contain URLs with a disallowed protocol (e.g. javascript:)
1252   *
1253   * @param $string
1254   *   The string with raw HTML in it. It will be stripped of everything that can cause
1255   *   an XSS attack.
1256   * @param $allowed_tags
1257   *   An array of allowed tags.
1258   * @param $format
1259   *   The format to use.
1260   */
1261  function filter_xss($string, $allowed_tags = array('a', 'em', 'strong', 'cite', 'code', 'ul', 'ol', 'li', 'dl', 'dt', 'dd')) {
1262    // Store the input format
1263    _filter_xss_split($allowed_tags, TRUE);
1264    // Remove NUL characters (ignored by some browsers)
1265    $string = str_replace(chr(0), '', $string);
1266    // Remove Netscape 4 JS entities
1267    $string = preg_replace('%&\s*\{[^}]*(\}\s*;?|$)%', '', $string);
1268  
1269    // Defuse all HTML entities
1270    $string = str_replace('&', '&amp;', $string);
1271    // Change back only well-formed entities in our whitelist
1272    // Named entities
1273    $string = preg_replace('/&amp;([A-Za-z][A-Za-z0-9]*;)/', '&\1', $string);
1274    // Decimal numeric entities
1275    $string = preg_replace('/&amp;#([0-9]+;)/', '&#\1', $string);
1276    // Hexadecimal numeric entities
1277    $string = preg_replace('/&amp;#[Xx]0*((?:[0-9A-Fa-f]{2})+;)/', '&#x\1', $string);
1278  
1279    return preg_replace_callback('%
1280      (
1281      <(?=[^a-zA-Z!/])  # a lone <
1282      |                 # or
1283      <[^>]*.(>|$)      # a string that starts with a <, up until the > or the end of the string
1284      |                 # or
1285      >                 # just a >
1286      )%x', '_filter_xss_split', $string);
1287  }
1288  
1289  /**
1290   * Processes an HTML tag.
1291   *
1292   * @param @m
1293   *   An array with various meaning depending on the value of $store.
1294   *   If $store is TRUE then the array contains the allowed tags.
1295   *   If $store is FALSE then the array has one element, the HTML tag to process.
1296   * @param $store
1297   *   Whether to store $m.
1298   * @return
1299   *   If the element isn't allowed, an empty string. Otherwise, the cleaned up
1300   *   version of the HTML element.
1301   */
1302  function _filter_xss_split($m, $store = FALSE) {
1303    static $allowed_html;
1304  
1305    if ($store) {
1306      $allowed_html = array_flip($m);
1307      return;
1308    }
1309  
1310    $string = $m[1];
1311  
1312    if (substr($string, 0, 1) != '<') {
1313      // We matched a lone ">" character
1314      return '&gt;';
1315    }
1316    else if (strlen($string) == 1) {
1317      // We matched a lone "<" character
1318      return '&lt;';
1319    }
1320  
1321    if (!preg_match('%^<\s*(/\s*)?([a-zA-Z0-9]+)([^>]*)>?$%', $string, $matches)) {
1322      // Seriously malformed
1323      return '';
1324    }
1325  
1326    $slash = trim($matches[1]);
1327    $elem = &$matches[2];
1328    $attrlist = &$matches[3];
1329  
1330    if (!isset($allowed_html[strtolower($elem)])) {
1331      // Disallowed HTML element
1332      return '';
1333    }
1334  
1335    if ($slash != '') {
1336      return "</$elem>";
1337    }
1338  
1339    // Is there a closing XHTML slash at the end of the attributes?
1340    // In PHP 5.1.0+ we could count the changes, currently we need a separate match
1341    $xhtml_slash = preg_match('%\s?/\s*$%', $attrlist) ? ' /' : '';
1342    $attrlist = preg_replace('%(\s?)/\s*$%', '\1', $attrlist);
1343  
1344    // Clean up attributes
1345    $attr2 = implode(' ', _filter_xss_attributes($attrlist));
1346    $attr2 = preg_replace('/[<>]/', '', $attr2);
1347    $attr2 = strlen($attr2) ? ' '. $attr2 : '';
1348  
1349    return "<$elem$attr2$xhtml_slash>";
1350  }
1351  
1352  /**
1353   * Processes a string of HTML attributes.
1354   *
1355   * @return
1356   *   Cleaned up version of the HTML attributes.
1357   */
1358  function _filter_xss_attributes($attr) {
1359    $attrarr = array();
1360    $mode = 0;
1361    $attrname = '';
1362  
1363    while (strlen($attr) != 0) {
1364      // Was the last operation successful?
1365      $working = 0;
1366  
1367      switch ($mode) {
1368        case 0:
1369          // Attribute name, href for instance
1370          if (preg_match('/^([-a-zA-Z]+)/', $attr, $match)) {
1371            $attrname = strtolower($match[1]);
1372            $skip = ($attrname == 'style' || substr($attrname, 0, 2) == 'on');
1373            $working = $mode = 1;
1374            $attr = preg_replace('/^[-a-zA-Z]+/', '', $attr);
1375          }
1376  
1377          break;
1378  
1379        case 1:
1380          // Equals sign or valueless ("selected")
1381          if (preg_match('/^\s*=\s*/', $attr)) {
1382            $working = 1; $mode = 2;
1383            $attr = preg_replace('/^\s*=\s*/', '', $attr);
1384            break;
1385          }
1386  
1387          if (preg_match('/^\s+/', $attr)) {
1388            $working = 1; $mode = 0;
1389            if (!$skip) {
1390              $attrarr[] = $attrname;
1391            }
1392            $attr = preg_replace('/^\s+/', '', $attr);
1393          }
1394  
1395          break;
1396  
1397        case 2:
1398          // Attribute value, a URL after href= for instance
1399          if (preg_match('/^"([^"]*)"(\s+|$)/', $attr, $match)) {
1400            $thisval = filter_xss_bad_protocol($match[1]);
1401  
1402            if (!$skip) {
1403              $attrarr[] = "$attrname=\"$thisval\"";
1404            }
1405            $working = 1;
1406            $mode = 0;
1407            $attr = preg_replace('/^"[^"]*"(\s+|$)/', '', $attr);
1408            break;
1409          }
1410  
1411          if (preg_match("/^'([^']*)'(\s+|$)/", $attr, $match)) {
1412            $thisval = filter_xss_bad_protocol($match[1]);
1413  
1414            if (!$skip) {
1415              $attrarr[] = "$attrname='$thisval'";;
1416            }
1417            $working = 1; $mode = 0;
1418            $attr = preg_replace("/^'[^']*'(\s+|$)/", '', $attr);
1419            break;
1420          }
1421  
1422          if (preg_match("%^([^\s\"']+)(\s+|$)%", $attr, $match)) {
1423            $thisval = filter_xss_bad_protocol($match[1]);
1424  
1425            if (!$skip) {
1426              $attrarr[] = "$attrname=\"$thisval\"";
1427            }
1428            $working = 1; $mode = 0;
1429            $attr = preg_replace("%^[^\s\"']+(\s+|$)%", '', $attr);
1430          }
1431  
1432          break;
1433      }
1434  
1435      if ($working == 0) {
1436        // not well formed, remove and try again
1437        $attr = preg_replace('/
1438          ^
1439          (
1440          "[^"]*("|$)     # - a string that starts with a double quote, up until the next double quote or the end of the string
1441          |               # or
1442          \'[^\']*(\'|$)| # - a string that starts with a quote, up until the next quote or the end of the string
1443          |               # or
1444          \S              # - a non-whitespace character
1445          )*              # any number of the above three
1446          \s*             # any number of whitespaces
1447          /x', '', $attr);
1448        $mode = 0;
1449      }
1450    }
1451  
1452    // the attribute list ends with a valueless attribute like "selected"
1453    if ($mode == 1) {
1454      $attrarr[] = $attrname;
1455    }
1456    return $attrarr;
1457  }
1458  
1459  /**
1460   * Processes an HTML attribute value and ensures it does not contain an URL
1461   * with a disallowed protocol (e.g. javascript:)
1462   *
1463   * @param $string
1464   *   The string with the attribute value.
1465   * @param $decode
1466   *   Whether to decode entities in the $string. Set to FALSE if the $string
1467   *   is in plain text, TRUE otherwise. Defaults to TRUE.
1468   * @return
1469   *   Cleaned up and HTML-escaped version of $string.
1470   */
1471  function filter_xss_bad_protocol($string, $decode = TRUE) {
1472    static $allowed_protocols;
1473    if (!isset($allowed_protocols)) {
1474      $allowed_protocols = array_flip(variable_get('filter_allowed_protocols', array('http', 'https', 'ftp', 'news', 'nntp', 'telnet', 'mailto', 'irc', 'ssh', 'sftp', 'webcal')));
1475    }
1476  
1477    // Get the plain text representation of the attribute value (i.e. its meaning).
1478    if ($decode) {
1479      $string = decode_entities($string);
1480    }
1481  
1482    // Iteratively remove any invalid protocol found.
1483  
1484    do {
1485      $before = $string;
1486      $colonpos = strpos($string, ':');
1487      if ($colonpos > 0) {
1488        // We found a colon, possibly a protocol. Verify.
1489        $protocol = substr($string, 0, $colonpos);
1490        // If a colon is preceded by a slash, question mark or hash, it cannot
1491        // possibly be part of the URL scheme. This must be a relative URL,
1492        // which inherits the (safe) protocol of the base document.
1493        if (preg_match('![/?#]!', $protocol)) {
1494          break;
1495        }
1496        // Per RFC2616, section 3.2.3 (URI Comparison) scheme comparison must be case-insensitive.
1497        // Check if this is a disallowed protocol.
1498        if (!isset($allowed_protocols[strtolower($protocol)])) {
1499          $string = substr($string, $colonpos + 1);
1500        }
1501      }
1502    } while ($before != $string);
1503    return check_plain($string);
1504  }
1505  
1506  /**
1507   * @} End of "Standard filters".
1508   */
1509  


Généré le : Fri Nov 30 16:20:15 2007 par Balluche grâce à PHPXref 0.7
  Clicky Web Analytics