You are here

customfilter.module in Custom filter 7.2

Allows the users with the right permission to define custom filters.

File

customfilter.module
View source
<?php

/**
 * @file
 * Allows the users with the right permission to define custom filters.
 */

/**
 * @addtogroup customfilter
 * @{
 */

/**
 * The version of the API currently implemented.
 */
define('CUSTOMFILTER_API', '1.2');

/**
 * Implements hook_filter_info().
 * 
 * Return a list of filter to use in admin/config/content/formats/add
 * 
 */
function customfilter_filter_info() {
  $filters = _customfilter_get_filters();
  $itens = array();
  foreach ($filters as $filter) {
    $itens[$filter['type']] = array(
      'title' => t($filter['name']),
      'description' => t($filter['description']),
      'process callback' => '_customfilter_filter_process',
      'default settings' => array(
        'allowed_html' => '',
        'filter_html_help' => 1,
        'filter_html_nofollow' => 0,
      ),
      'tips callback' => '_customfilter_filter_tips',
    );
  }
  return $itens;
}

/**
 * Implements filter_tips()
 * 
 * @param object $filter
 *   This is a object with informations about the filter
 *   
 * @param object $format
 *   This is a object with informations about the filter
 *   
 * @param boolean $long
 *   (optional). TRUE if is the log tip, FALSE otherwise
 *   
 * @return string
 *   string with the tip for the filter
 */
function _customfilter_filter_tips($filter, $format, $long = FALSE) {
  $col = $long ? 'longtip' : 'shorttip';
  $filters = _customfilter_get_filters();
  foreach ($filters as $item) {
    if ($item['type'] == $filter->name) {
      return $item[$col];
    }
  }
  return '';
}

/**
 * Process the text, apply the filters
 * 
 * @param string $text 
 *   The text to process(the node content)
 * 
 * @param object $filter
 *   Information about the filter from drupal
 *      
 * @param object $format
 *   Information about the format from drupal
 *   
 * @return string
 *   The text after all changes by the filters are done
 */
function _customfilter_filter_process($text, $filter, $format) {

  // If the text passed is an empty string, then return it immediately.
  if (empty($text)) {
    return '';
  }
  $filters = _customfilter_get_filters();
  $item = '';

  // @todo look if there is not a php function for this
  foreach ($filters as $itens) {
    if ($itens['type'] == $filter->name) {
      $item = $itens;
    }
  }

  // If the filter cannot be loaded from the database, then return the text
  // passed to the function.
  if (!$item) {
    return $text;
  }
  $globals =& _customfilter_globals('push');
  $globals->text = $text;
  $result = db_query("SELECT fid FROM {customfilter_filter} WHERE name = :name", array(
    ':name' => $item['name'],
  ))
    ->fetchField();
  $rules = _customfilter_get_rules($result);
  if (is_array($rules) && count($rules)) {

    // Reset the object containing the global variables.
    _customfilter_code_vars(TRUE);

    // Prepare the stack used to save the parent rule.
    $globals->stack = array();
    foreach ($rules as $rule) {
      if ($rule['enabled']) {
        $globals->stack[] = $rule;
        $globals->text = preg_replace_callback($rule['pattern'], '_customfilter_process', $globals->text);
        array_pop($globals->stack);
      }
    }
  }
  $result = $globals->text;
  _customfilter_globals('pop');
  return $result;
}

/**
 * Implements hook_flush_caches().
 */
function customfilter_flush_caches() {
  return array(
    'cache_customfilter',
  );
}

/**
 * Implements hook_help().
 * 
 * Set the help messages to show to user.
 * 
 * @todo Document the param $arg.
 * 
 * @param string $path
 *   The path where the user is.
 *   
 * @param $arg
 * 
 * @return string
 *   The help message
 */
function customfilter_help($path, $arg) {
  $help = '';
  switch ($path) {
    case 'admin/modules#description':
      $help = t('Allows the users with the right permission to create custom filters.');
      break;
    case 'admin/config/content/customfilter':
      $help = '<p>' . t('Custom filter provides the ability for creating user defined filters using regular expressions. Instead of creating filter modules, users can create their own filter for specific site purpose.') . '</p>';
      $help .= '<p>' . t('A filter is a collection of replacement rules. Each filter will appear in the input format settings page. Click on the filter name to see its replacement rules.') . '</p>';
      break;
    case 'admin/config/content/customfilter/%/add':
    case 'admin/config/content/customfilter/%/%':
    case 'admin/config/content/customfilter/%/%/edit':
    case 'admin/config/content/customfilter/%/%/add':
      $help = '<p>' . t('For more information about the regular expressions syntax, see <a href="http://www.php.net/manual/en/regexp.reference.php">Regular expression details</a>.') . '</p>';
      break;
  }
  return $help;
}

/**
 * Implements hook_menu().
 * 
 * This function set which action we do on a path
 * 
 * @return array
 *   An array with the paths we want and what to do on it
 */
function customfilter_menu() {
  $access = array(
    'administer customfilter',
  );
  $items = array();
  $items['admin/config/content/customfilter'] = array(
    'title' => 'Custom filters',
    'description' => 'Allow the users to define custom filters.',
    'page callback' => 'customfilter_settings',
    'access arguments' => $access,
  );
  $items['admin/config/content/customfilter/list'] = array(
    'title' => 'Filters',
    'access arguments' => $access,
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -1,
  );
  $items['admin/config/content/customfilter/add'] = array(
    'title' => 'Add filter',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'customfilter_filter_add',
    ),
    'access arguments' => $access,
    'type' => MENU_LOCAL_TASK,
  );
  $items['admin/config/content/customfilter/tools'] = array(
    'title' => 'Tools',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'customfilter_export_form',
    ),
    'access arguments' => $access,
    'type' => MENU_LOCAL_TASK,
  );
  $items['admin/config/content/customfilter/tools/export'] = array(
    'title' => 'Export',
    'access arguments' => $access,
    'type' => MENU_DEFAULT_LOCAL_TASK,
  );
  $items['admin/config/content/customfilter/tools/import'] = array(
    'title' => 'Import',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'customfilter_import_form',
    ),
    'access arguments' => $access,
    'type' => MENU_LOCAL_TASK,
  );
  $items['admin/config/content/customfilter/%'] = array(
    'title' => 'Rules',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'customfilter_rules_form',
      4,
    ),
    'access arguments' => $access,
    'type' => MENU_CALLBACK,
  );
  $items['admin/config/content/customfilter/%/list'] = array(
    'title' => 'List rules',
    'access arguments' => $access,
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -1,
  );
  $items['admin/config/content/customfilter/%/edit'] = array(
    'title' => 'Edit filter',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'customfilter_filter_edit',
      4,
    ),
    'access arguments' => $access,
    'type' => MENU_CALLBACK,
  );
  $items['admin/config/content/customfilter/%/delete'] = array(
    'title' => 'Delete filter',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'customfilter_filter_delete',
      4,
    ),
    'access arguments' => $access,
    'type' => MENU_CALLBACK,
    'weight' => 2,
  );
  $items['admin/config/content/customfilter/%/add'] = array(
    'title' => 'Add rule',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'customfilter_rule_edit',
      'add',
      4,
    ),
    'access arguments' => $access,
    'type' => MENU_LOCAL_TASK,
    'weight' => 4,
  );
  $items['admin/config/content/customfilter/%/%'] = array(
    'title' => 'Edit rule',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'customfilter_rule_edit',
      'edit',
      4,
      5,
    ),
    'access arguments' => $access,
    'type' => MENU_CALLBACK,
  );
  $items['admin/config/content/customfilter/%/%/edit'] = array(
    'title' => 'Edit rule',
    'access arguments' => $access,
    'type' => MENU_CALLBACK,
    'weight' => -1,
  );
  $items['admin/config/content/customfilter/%/%/delete'] = array(
    'title' => 'Delete rule',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'customfilter_rule_delete',
      5,
    ),
    'access arguments' => $access,
    'type' => MENU_CALLBACK,
  );
  $items['admin/config/content/customfilter/%/%/add'] = array(
    'title' => 'Add subrule',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'customfilter_rule_edit',
      'add',
      4,
      5,
    ),
    'access arguments' => $access,
    'type' => MENU_CALLBACK,
  );
  return $items;
}

/**
 * Implements hook_permission().
 * 
 * This function tell to drupal what permissions your module have
 * 
 * @return array
 *   Return an array of permissions
 */
function customfilter_permission() {
  $perm = array(
    'administer customfilter' => array(
      'title' => t('administer customfilter'),
      'description' => t('Administer the module customfilter, which allows the
         creation of custom filters'),
      'restrict access' => TRUE,
    ),
  );
  return $perm;
}

/**
 * Implements hook_theme
 *
 * @todo Document this function
 * 
 * @param $existing
 * 
 * @param $type
 * 
 * @param $theme
 * 
 * @param $path
 */
function customfilter_theme($existing, $type, $theme, $path) {
  return array(
    'customfilter_rules_form' => array(
      'arguments' => array(
        'form',
      ),
      'render element' => 'form',
    ),
  );
}

/**
 * Clear the cache
 * 
 * @todo Please document this function.
 */
function customfilter_cache_clear($fid = NULL) {
  cache_clear_all('filters', 'cache_customfilter');
  cache_clear_all(isset($fid) ? "rules:{$fid}" : 'rules', 'cache_customfilter', TRUE);
}

/**
 * Set the current menu breadcrumb for pages that don't have it correctly set.
 *
 * @param $page_breadcrumb
 *   The part of the breadcrumb specific for the page.
 */
function _customfilter_breadcrumb($page_breadcrumb = array()) {
  $base = array(
    l(t('Home'), NULL),
    l(t('Administer'), 'admin'),
    l(t('Site configuration'), 'admin/config'),
    l(t('Content'), 'admin/config/content'),
    l(t('Custom filters'), 'admin/config/content/customfilter'),
  );
  if (count($page_breadcrumb)) {
    drupal_set_breadcrumb(array_merge($base, $page_breadcrumb));
  }
  else {
    drupal_set_breadcrumb($base);
  }
}

/**
 * Delete a rule from the database table.
 * @param $rid
 *   The rule ID.
 */
function _customfilter_delete_rule($rid) {
  $result = db_query("SELECT * FROM {customfilter_rule} where prid = :rid", array(
    ':rid' => $rid,
  ));
  foreach ($result as $rule) {
    _customfilter_delete_rule($rule->rid);
  }
  db_delete('customfilter_rule')
    ->condition('rid', $rid)
    ->execute();
}

/**
 * Get the list of filters.
 *
 * @return
 *   An array of filters.
 */
function _customfilter_get_filters() {
  if ($cache = cache_get('filters', 'cache_customfilter')) {
    $filters = $cache->data;
  }
  else {
    $filters = array();
    $result = db_query("SELECT * FROM {customfilter_filter} ORDER BY name");
    while ($filter = $result
      ->fetchAssoc()) {
      $filters[$filter['fid']] = $filter;
    }
    cache_set('filters', $filters, 'cache_customfilter');
  }
  return $filters;
}

/**
 * Retrieve the replacement rules for a specific filter.
 *
 * @param $fid
 *   The filter ID.
 * @param $root
 *   The root rule.
 * @param $nocache
 *   If FALSE, the function will get the rules from the cache table, if there
 *   are any.
 *
 * @return
 *   An array of rules, which include any subrules.
 */
function _customfilter_get_rules($fid, $root = 0, $nocache = FALSE) {
  if (!$nocache && ($cache = cache_get("rules:{$fid}:{$root}", 'cache_customfilter'))) {
    $rules = $cache->data;
  }
  else {
    $rules = array();
    $result = db_query("SELECT * FROM {customfilter_rule} WHERE fid = :fid and prid = :prid ORDER BY weight", array(
      ':fid' => $fid,
      ':prid' => $root,
    ));
    while ($rule = $result
      ->fetchAssoc()) {
      $rule['sub'] = _customfilter_get_rules($fid, $rule['rid'], TRUE);
      $rules[$rule['rid']] = $rule;
    }
    if (!$nocache) {
      cache_set("rules:{$fid}:{$root}", $rules, 'cache_customfilter');
    }
  }
  return $rules;
}

/**
 * Return the global object containing the global properties used in the
 * replacement PHP code.
 *
 * @param $reset
 *  Boolean value set to TRUE when the global object must be reset.
 */
function &_customfilter_code_vars($reset = FALSE) {
  static $vars;
  if (!isset($vars) || $reset) {
    $vars = new stdClass();
  }
  return $vars;
}

/**
 * Return an object that contains the global variables used during the
 * execution of a rule.
 */
function &_customfilter_globals($op = '') {
  static $globals = array(), $index = 0;
  if ($op == 'push') {
    $globals[++$index] = new stdClass();
  }
  elseif ($op == 'pop' && $index) {
    unset($globals[$index--]);
  }
  return $globals[$index];
}

/**
 * 
 * @todo Document this function
 * 
 * @param $matches
 * 
 * @return
 */
function _customfilter_process($matches) {
  $globals =& _customfilter_globals();
  $result = $matches[0];
  $rule = end($globals->stack);
  $code = $rule['code'];
  $pattern = $rule['pattern'];
  $replacement = $rule['replacement'];
  if (is_array($sub = $rule['sub']) && count($sub)) {
    foreach ($sub as $subrule) {
      if ($subrule['enabled']) {
        $globals->stack[] = $subrule;
        $substr =& $matches[$subrule['matches']];
        $substr = preg_replace_callback($subrule['pattern'], '_customfilter_process', $substr);
        array_pop($globals->stack);
      }
    }
    if ($code) {
      _customfilter_replace_callback($replacement, TRUE);
      $result = _customfilter_replace_callback($matches);
    }
    else {
      $result = $replacement;
      $rmatches = array();
      $reps = array();
      preg_match_all('/([^\\\\]|^)(\\$([0-9]{1,2}|\\{([0-9]{1,2})\\}))/', $replacement, $rmatches, PREG_OFFSET_CAPTURE);
      foreach ($rmatches[4] as $key => $val) {
        if ($val == '') {
          $index = $rmatches[3][$key][0];
        }
        else {
          $index = $rmatches[4][$key][0];
        }
        $offset = $rmatches[2][$key][1];
        $length = strlen($rmatches[2][$key][0]);
        $reps[] = array(
          'index' => $index,
          'offset' => $offset,
          'length' => $length,
        );
      }
      krsort($reps);
      foreach ($reps as $rep) {
        $result = substr_replace($result, $matches[$rep['index']], $rep['offset'], $rep['length']);
      }
    }
  }
  elseif ($code) {
    _customfilter_replace_callback($replacement, TRUE);
    $result = preg_replace_callback($pattern, '_customfilter_replace_callback', $result);
  }
  else {
    $result = preg_replace($pattern, $replacement, $result);
  }
  return $result;
}

/**
 * Helper function for preg_replace_callback().
 */
function _customfilter_replace_callback($matches, $init = FALSE) {
  static $code;
  if ($init) {
    $code = $matches;
    return;
  }
  $vars =& _customfilter_code_vars();
  @eval($code);
  return isset($result) ? $result : '';
}

/**
 * Create the form to export a filter.
 * 
 * This function create the form used in admin/settings/customfilter/tools.
 * 
 * @todo See if there is a better way to store the current step
 *   than using a session variable.
 *   
 * @param $variables
 * 
 * @param $form_state
 * 
 * @return array
 *   return a form to be rendered by Drupal.
 */
function customfilter_export_form($variables, &$form_state) {
  $form = array();
  $filters = _customfilter_get_filters();
  if (isset($_SESSION['customfilter_storage'])) {
    $form_state['storage'] = $_SESSION['customfilter_storage'];
  }
  else {
    $form_state['storage'] = NULL;
  }
  $step = isset($form_state['storage']['step']) ? $form_state['storage']['step'] : 1;
  switch ($step) {
    case 1:
      if ($filters) {
        foreach ($filters as $filter) {
          $opt[$filter['fid']] = check_plain($filter['name']);
        }
        $form['#filters'] = $filters;
        $form['filters'] = array(
          '#type' => 'radios',
          '#title' => t('Filters'),
          '#description' => 'Choose the filter to export.',
          '#options' => $opt,
        );
        $form['submit'] = array(
          '#type' => 'submit',
          '#value' => 'Export',
        );
      }
      else {
        $form['filters'] = array(
          '#value' => '<p>' . t('There are no custom filters defined.') . '</p>',
        );
      }
      break;
    case 2:
      if ($form_state['storage']['export_data']) {
        $form['export'] = array(
          '#type' => 'textarea',
          '#title' => t('Export data'),
          '#description' => t('Copy the export text and paste it into the import form.'),
          '#cols' => 60,
          '#default_value' => $form_state['storage']['export_data'],
          '#rows' => 40,
        );
      }
      else {
        $form['export'] = array(
          '#value' => '<p>' . t('The selected filter has not been found in the database table.') . '</p>',
        );
      }
      $form_state['storage']['step'] = 1;
      break;
  }
  $_SESSION['customfilter_storage'] = $form_state['storage'];
  return $form;
}

/**
 * Return the form with exported data.
 * 
 * @param $form
 * 
 * @param $form_state
 * 
 * @return array
 *   A form with the exported data
 */
function customfilter_export_form_submit($form, &$form_state) {
  $form_state['storage'] = $_SESSION['customfilter_storage'];
  $step = isset($form_state['storage']['step']) ? $form_state['storage']['step'] : 1;
  if ($step == 1) {
    $export_data = array();
    $fid = $form_state['values']['filters'];
    $filters = $form['#filters'];
    if ($filter = $filters[$fid]) {
      $export_data[] = '$filter = array(';
      $export_data[] = "  'fid' => _customfilter_map_filter_id('" . $filter['fid'] . "'),";
      $export_data[] = "  'type' => base64_decode('" . base64_encode($filter['type']) . "'),";
      $export_data[] = "  'name' => base64_decode('" . base64_encode($filter['name']) . "'),";
      $export_data[] = "  'cache' => {$filter['cache']},";
      $export_data[] = "  'description' => base64_decode('" . base64_encode($filter['description']) . "'),";
      $export_data[] = "  'shorttip' => base64_decode('" . base64_encode($filter['shorttip']) . "'),";
      $export_data[] = "  'longtip' => base64_decode('" . base64_encode($filter['longtip']) . "'),";
      $export_data[] = ");";
      $export_data[] = '';
      $rules = array();
      _customfilter_rules_tree($rules, $fid, 0);
      foreach ($rules as $rule) {
        $export_data[] = '$rules[] = array(';
        $export_data[] = "  'rid' => _customfilter_map_rule_id('" . $rule['rid'] . "'),";
        $export_data[] = "  'fid' => _customfilter_map_filter_id('" . $filter['fid'] . "'),";
        $export_data[] = "  'prid' => _customfilter_map_rule_id('" . $rule['prid'] . "'),";
        $export_data[] = "  'name' => base64_decode('" . base64_encode($rule['name']) . "'),";
        $export_data[] = "  'description' => base64_decode('" . base64_encode($rule['description']) . "'),";
        $export_data[] = "  'enabled' => {$rule['enabled']},";
        $export_data[] = "  'matches' => base64_decode('" . base64_encode($rule['matches']) . "'),";
        $export_data[] = "  'pattern' => base64_decode('" . base64_encode($rule['pattern']) . "'),";
        $export_data[] = "  'replacement' => base64_decode('" . base64_encode($rule['replacement']) . "'),";
        $export_data[] = "  'code' => {$rule['code']},";
        $export_data[] = "  'weight' => {$rule['weight']},";
        $export_data[] = ");";
      }
    }
    $form_state['storage']['step'] = 2;
    $form_state['storage']['export_data'] = count($export_data) ? implode("\n", $export_data) : '';
    $_SESSION['customfilter_storage'] = $form_state['storage'];
  }
}

/**
 * Return the form to add a new filter.
 * 
 * Create the form used at admin/settings/customfilter/add
 * 
 * @return array
 *   Return an array with the form
 */
function customfilter_filter_add() {
  $fid = (int) db_query("SELECT MAX(fid) FROM {customfilter_filter}")
    ->fetchField() + 1;
  if ($fid > 128) {
    drupal_set_message(t('It is not possible to add further filters; the limit of 128 filters has been reached.'), 'error');
    return $form;
  }
  $item = array(
    'fid' => $fid,
    'type' => "filter_{$fid}",
    'name' => t('Filter #!fid', array(
      '!fid' => $fid,
    )),
    'cache' => 1,
    'description' => '',
    'shorttip' => '',
    'longtip' => '',
  );
  _customfilter_filter_add_edit_fields($form, $item);
  return $form;
}

/**
 * Validate callback for the add filter form.
 * 
 * @see customfilter_filter_add()
 */
function customfilter_filter_add_validate($form, &$form_state) {
  if (!empty($form_state['values']['type'])) {
    $type[] = trim($form_state['values']['type']);
    if (!preg_match('!^[a-z0-9_]+$!', $type[0])) {
      form_set_error('type', t('The machine-readable name must contain only lowercase letters, numbers, and underscores.'));
    }
    elseif (db_query("SELECT count(*) FROM {customfilter_filter} WHERE type = ':type'", array(
      ':type' => $type,
    ))
      ->fetchField()) {
      form_set_error('type', t('The machine-readable name must be unique.'));
    }
  }
}

/**
 * Submission callback for the add filter form.
 * 
 * @see customfilter_filter_add()
 */
function customfilter_filter_add_submit($form, &$form_state) {
  drupal_write_record('customfilter_filter', $form_state['values']);
  customfilter_cache_clear('filters', $form_state['values']['fid']);
  $form_state['redirect'] = 'admin/config/content/customfilter';
}

/**
 * Return the filter delete form.
 * 
 * Create the form used in admin/config/content/customfilter/%/delete
 */
function customfilter_filter_delete($variables, $form_state, $fid) {
  $filters = _customfilter_get_filters();
  $form = array();
  _customfilter_breadcrumb();
  if (!$fid || !isset($filters[$fid])) {
    drupal_set_message(t('The filter data have not been found, or the menu callback received a wrong parameter.'), 'error');
    watchdog('customfilter', 'The filter data have not been found, or the menu callback received a wrong parameter.', NULL, WATCHDOG_ERROR);
    return $form;
  }
  $form['#fid'] = $fid;
  return confirm_form($form, t('Are you sure you want to delete %name?', array(
    '%name' => $filters[$fid]['name'],
  )), 'admin/config/content/customfilter', NULL, t('Delete'));
}

/**
 * Submission callback for the filter delete form.
 * 
 * @see customfilter_filter_delete()
 */
function customfilter_filter_delete_submit($form, $form_state) {
  $fid = $form['#fid'];
  db_delete('customfilter_rule')
    ->condition('fid', $fid)
    ->execute();
  db_delete('customfilter_filter')
    ->condition('fid', $fid)
    ->execute();
  customfilter_cache_clear($fid);
  drupal_goto('admin/config/content/customfilter');
}

/**
 * Return the filter edit form.
 * 
 * Create the form used in admin/config/content/customfilter/%/edit
 */
function customfilter_filter_edit($vars, $form_state, $fid) {
  $form = array();
  $filters = _customfilter_get_filters();
  $item = $filters[$fid];
  _customfilter_breadcrumb();
  if (!$fid || !isset($filters[$fid])) {
    drupal_set_message(t('The filter data have not been found, or the menu callback received a wrong parameter.'), 'error');
    watchdog('customfilter', 'The filter data have not been found, or the menu callback received a wrong parameter.', NULL, WATCHDOG_ERROR);
    return $form;
  }
  _customfilter_filter_add_edit_fields($form, $item);
  return $form;
}

/**
 * Validate callback for the filter edit form.
 * 
 * @see customfilter_filter_edit()
 */
function customfilter_filter_edit_validate($form, &$form_state) {
  if (!empty($form_state['values']['type'])) {
    $type = trim($form_state['values']['type']);
    if (!preg_match('!^[a-z0-9_]+$!', $type)) {
      form_set_error('type', t('The machine-readable name must contain only lowercase letters, numbers, and underscores.'));
    }
    elseif (db_query("SELECT count(*) FROM {customfilter_filter} WHERE type = :type AND fid <> :fid", array(
      ':type' => $type,
      ':fid' => $form_state['values']['fid'],
    ))
      ->fetchField()) {
      form_set_error('type', t('The machine-readable name must be unique.'));
    }
  }
}

/**
 * Submission callback for the filter edit form.
 * 
 * @see customfilter_filter_edit()
 */
function customfilter_filter_edit_submit($form, &$form_state) {
  drupal_write_record('customfilter_filter', $form_state['values'], 'fid');
  customfilter_cache_clear($form_state['values']['fid']);
  $form_state['redirect'] = 'admin/config/content/customfilter';
}

/**
 * Return the import form.
 * 
 * Create the form used at admin/config/content/customfilter/tools/import
 */
function customfilter_import_form() {
  $form = array();
  $form['import'] = array(
    '#type' => 'textarea',
    '#title' => t('Import data'),
    '#description' => t('Paste the text you obtained from the export form.'),
    '#cols' => 60,
    '#default_value' => '',
    '#rows' => 40,
    '#required' => TRUE,
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Import'),
  );
  return $form;
}

/**
 * Validation callback for the import form.
 * 
 * @see customfilter_import_form()
 */
function customfilter_import_form_validate($form, &$form_state) {
  @eval($form_state['values']['import']);
  if (!isset($filter) || !isset($rules) || !is_array($filter) || !is_array($rules)) {
    form_set_error('import', t('The imported data is not valid.'));
    return;
  }
  $form_state['filter'] = $filter;
  $form_state['rules'] = $rules;
}

/**
 * Submission callback for the import form.
 * 
 * @see customfilter_import_form().
 */
function customfilter_import_form_submit($form, &$form_state) {
  drupal_write_record('customfilter_filter', $form_state['filter']);
  foreach ($form_state['rules'] as $rule) {
    drupal_write_record('customfilter_rule', $rule);
  }
  customfilter_cache_clear();
  drupal_set_message(format_plural(count($form_state['rules']), '1 replacement rule has been imported.', '@count replacement rules have been imported.'));
  $form_state['redirect'] = 'admin/config/content/customfilter';
}

/**
 * Return the list of replacement rules form.
 * 
 * Create the form used at admin/config/content/customfilter/%
 */
function customfilter_rules_form($variables, &$form_state, $fid, $rid = 0) {
  $form = array(
    '#tree' => TRUE,
  );
  $rules = _customfilter_get_rules($fid, $rid);
  _customfilter_breadcrumb();
  if (count($rules)) {
    $form['fid'] = array(
      '#type' => 'hidden',
      '#value' => $fid,
    );
    $form['rules'] = _customfilter_rules_tree_form($rules, 0);
    $form['submit'] = array(
      '#type' => 'submit',
      '#value' => t('Save'),
    );
  }
  return $form;
}

/**
 * Submission callback for the list of replacement rules form.
 * 
 * @see customfilter_rules_form().
 */
function customfilter_rules_form_submit($form, &$form_state) {
  foreach ($form_state['values']['rules'] as $rule) {
    drupal_write_record('customfilter_rule', $rule, 'rid');
  }
  customfilter_cache_clear($form_state['values']['fid']);
  $form_state['redirect'] = "admin/config/content/customfilter/{$form_state['values']['fid']}";
}

/**
 * Return the replacement rule delete form.
 * 
 * Create the form used at admin/config/content/customfilter/%/%/delete
 */
function customfilter_rule_delete($vars, $form_state, $rid) {
  $filters = _customfilter_get_filters();
  $rule = db_query("SELECT * FROM {customfilter_rule} WHERE rid = :rid", array(
    ':rid' => $rid,
  ))
    ->fetchObject();
  $rules_count = db_query("SELECT COUNT(*) FROM {customfilter_rule} WHERE prid = :rid", array(
    ':rid' => $rid,
  ))
    ->fetchField();
  if ($rule === FALSE) {
    drupal_set_message(t('The replacement rule has been deleted.'));
    drupal_goto(isset($_REQUEST['destination']) ? $_REQUEST['destination'] : 'admin/config/content/customfilter');
  }
  if (isset($filters[$rule->fid])) {
    _customfilter_breadcrumb(array(
      l($filters[$rule->fid]['name'], "admin/config/content/customfilter/{$rule->fid}"),
    ));
  }
  $form = array(
    '#fid' => $rule->fid,
    '#rid' => $rid,
  );
  $msg_text = t('This action cannot be undone.');
  if ($rules_count) {
    $msg_text .= " <p>" . t('This rule has subrules. If you delete this rule, they will be deleted too.') . "</p>";
  }
  return confirm_form($form, t('Are you sure you want to delete the replacement rule %name?', array(
    '%name' => $rule->name,
  )), "admin/config/content/customfilter/{$rule->fid}", $msg_text, t('Delete'));
}

/**
 * Submission callback for the replacement rule delete form.
 * 
 * @see customfile_rule_delete()
 */
function customfilter_rule_delete_submit($form, &$form_state) {
  _customfilter_delete_rule($form['#rid']);
  dpm($form_state);
  customfilter_cache_clear($form_state['values']['fid']);
  $form_state['redirect'] = 'admin/config/content/customfilter/' . $form['#fid'];
}

/**
 * Return the replacement rule edit form.
 */
function customfilter_rules_form_edit($variables, $form_state, $op, $fid, $rid = 0) {
  $filters = _customfilter_get_filters();
  $form = array();
  if (isset($filters[$fid])) {
    _customfilter_breadcrumb(array(
      l($filters[$fid]['name'], "admin/config/content/customfilter/{$fid}"),
    ));
  }
  if ($op == 'edit') {
    $item = db_fetch_array(db_query("SELECT * FROM {customfilter_rule} WHERE rid = %d", $rid));
  }
  elseif ($op == 'add') {
    $item = array(
      'rid' => (int) db_query("SELECT MAX(rid) FROM {customfilter_rule}")
        ->fetchField() + 1,
      'prid' => $rid,
      'fid' => $fid,
      'name' => '$1',
      'description' => '',
      'enabled' => 1,
      'matches' => 1,
      'pattern' => '/regex/i',
      'replacement' => 'Regular Expressions',
      'code' => 0,
      'weight' => 0,
    );
  }
  $matchopt = drupal_map_assoc(range(0, 99));
  $form['rid'] = array(
    '#type' => 'value',
    '#value' => $item['rid'],
  );
  $form['fid'] = array(
    '#type' => 'value',
    '#value' => $fid,
  );
  $form['prid'] = array(
    '#type' => 'value',
    '#value' => $item['prid'],
  );
  $form['operation'] = array(
    '#type' => 'value',
    '#value' => $op,
  );
  if ($item['prid'] != 0) {
    $form['matches'] = array(
      '#type' => 'select',
      '#title' => t('# Match'),
      '#description' => t('n-th matched substring in parent rule. This replacement rule will replace only for that substring.'),
      '#options' => $matchopt,
      '#default_value' => $item['matches'],
    );
  }
  $form['name'] = array(
    '#type' => 'textfield',
    '#description' => t('The name of this replacement rule.'),
    '#title' => t('Name'),
    '#default_value' => $item['name'],
    '#required' => TRUE,
  );
  $form['weight'] = array(
    '#title' => t('Weight'),
    '#default_value' => $item['weight'],
  );
  $form['enabled'] = array(
    '#type' => 'checkbox',
    '#title' => t('Enabled'),
    '#description' => t('If selected, the rule is used.'),
    '#default_value' => $item['enabled'],
  );
  $form['description'] = array(
    '#type' => 'textarea',
    '#title' => t('Description'),
    '#description' => t('The description of this rule.'),
    '#default_value' => $item['description'],
  );
  $form['pattern'] = array(
    '#type' => 'textarea',
    '#title' => t('Pattern'),
    '#description' => t('The regular expression to use. Look at <a href="http://www.php.net/manual/en/regexp.reference.php">Regular Expression Details</a> for more help.'),
    '#default_value' => $item['pattern'],
    '#rows' => 3,
  );
  $form['code'] = array(
    '#type' => 'checkbox',
    '#title' => t('PHP Code'),
    '#description' => t('If selected, the replacement text is PHP code.'),
    '#default_value' => $item['code'],
  );
  $form['replacement']['replacement'] = array(
    '#type' => 'textarea',
    '#title' => t('Replacement text'),
    '#description' => t('The replacement text that will replace the matched string. Use $n (i.e. $1, $25) or ${n} (i.e. ${1}, ${25}), with n range from 0 to 99, to get the n-th original strings matched ($0 represents the entire matched string). If you select <strong>PHP Code</strong>, you can enter PHP code that will be executed during the elaboration of the rules. n-th matched string is provided in <code>$matches[n]</code>, and there is a global variable <code>$vars</code> you can use to store values that will be kept during the execution of different rules of the same filter. PHP code must set a value for <code>$result</code>, and must not be entered between <code><</code><code>?php ?></code>. Note that executing incorrect PHP-code can break your Drupal site.'),
    '#default_value' => $item['replacement'],
    '#rows' => 16,
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save'),
  );
  return $form;
}
function customfilter_rule_edit($vars, $form_state, $op, $fid, $rid = 0) {
  $filters = _customfilter_get_filters();
  $form = array();
  if (isset($filters[$fid])) {
    _customfilter_breadcrumb(array(
      l($filters[$fid]['name'], "admin/config/content/customfilter/{$fid}"),
    ));
  }
  if ($op == 'edit') {
    $item = db_query("SELECT * FROM {customfilter_rule} WHERE rid = :rid", array(
      ':rid' => $rid,
    ))
      ->fetchAssoc();
  }
  elseif ($op == 'add') {
    $item = array(
      'rid' => (int) db_query("SELECT MAX(rid) FROM {customfilter_rule}")
        ->fetchField() + 1,
      'prid' => $rid,
      'fid' => $fid,
      'name' => '$1',
      'description' => '',
      'enabled' => 1,
      'matches' => 1,
      'pattern' => '/regex/i',
      'replacement' => 'Regular Expressions',
      'code' => 0,
      'weight' => 0,
    );
  }
  $matchopt = drupal_map_assoc(range(0, 99));
  $form['rid'] = array(
    '#type' => 'value',
    '#value' => $item['rid'],
  );
  $form['fid'] = array(
    '#type' => 'value',
    '#value' => $fid,
  );
  $form['prid'] = array(
    '#type' => 'value',
    '#value' => $item['prid'],
  );
  $form['operation'] = array(
    '#type' => 'value',
    '#value' => $op,
  );
  if ($item['prid'] != 0) {
    $form['matches'] = array(
      '#type' => 'select',
      '#title' => t('# Match'),
      '#description' => t('n-th matched substring in parent rule. This replacement rule will replace only for that substring.'),
      '#options' => $matchopt,
      '#default_value' => $item['matches'],
    );
  }
  $form['name'] = array(
    '#type' => 'textfield',
    '#description' => t('The name of this replacement rule.'),
    '#title' => t('Name'),
    '#default_value' => $item['name'],
    '#required' => TRUE,
  );
  $form['weight'] = array(
    '#title' => t('Weight'),
    '#default_value' => $item['weight'],
  );
  $form['enabled'] = array(
    '#type' => 'checkbox',
    '#title' => t('Enabled'),
    '#description' => t('If selected, the rule is used.'),
    '#default_value' => $item['enabled'],
  );
  $form['description'] = array(
    '#type' => 'textarea',
    '#title' => t('Description'),
    '#description' => t('The description of this rule.'),
    '#default_value' => $item['description'],
  );
  $form['pattern'] = array(
    '#type' => 'textarea',
    '#title' => t('Pattern'),
    '#description' => t('The regular expression to use. Look at <a href="http://www.php.net/manual/en/regexp.reference.php">Regular Expression Details</a> for more help.'),
    '#default_value' => $item['pattern'],
    '#rows' => 3,
  );
  $form['code'] = array(
    '#type' => 'checkbox',
    '#title' => t('PHP Code'),
    '#description' => t('If selected, the replacement text is PHP code.'),
    '#default_value' => $item['code'],
  );
  $form['replacement']['replacement'] = array(
    '#type' => 'textarea',
    '#title' => t('Replacement text'),
    '#description' => t('The replacement text that will replace the matched string. Use $n (i.e. $1, $25) or ${n} (i.e. ${1}, ${25}), with n range from 0 to 99, to get the n-th original strings matched ($0 represents the entire matched string). If you select <strong>PHP Code</strong>, you can enter PHP code that will be executed during the elaboration of the rules. n-th matched string is provided in <code>$matches[n]</code>, and there is a global variable <code>$vars</code> you can use to store values that will be kept during the execution of different rules of the same filter. PHP code must set a value for <code>$result</code>, and must not be entered between <code><</code><code>?php ?></code>. Note that executing incorrect PHP-code can break your Drupal site.'),
    '#default_value' => $item['replacement'],
    '#rows' => 16,
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save'),
  );
  return $form;
}

/**
 * Submission callback for the replacement rule edit form.
 */
function customfilter_rule_edit_submit($form, &$form_state) {
  switch ($form_state['values']['operation']) {
    case 'edit':
      drupal_write_record('customfilter_rule', $form_state['values'], array(
        'rid',
      ));
      break;
    case 'add':
      drupal_write_record('customfilter_rule', $form_state['values']);
      break;
  }
  customfilter_cache_clear($form_state['values']['fid']);
  $form_state['redirect'] = "admin/config/content/customfilter/{$form_state['values']['fid']}";
}

/**
 * Module settings page.
 */
function customfilter_settings() {
  $rows = array();
  foreach (_customfilter_get_filters() as $id => $filter) {
    $row = array();
    $row[] = l($filter['name'], "admin/config/content/customfilter/{$id}");
    $row[] = l(t('edit'), "admin/config/content/customfilter/{$id}/edit");
    $row[] = l(t('delete'), "admin/config/content/customfilter/{$id}/delete");
    $rows[] = $row;
  }
  if (!count($rows)) {
    $rows[] = array(
      array(
        'data' => t('No filters have been defined.'),
        'colspan' => 3,
      ),
    );
  }
  $header[] = t('Name');
  $header[] = array(
    'data' => t('Operations'),
    'colspan' => '2',
  );
  $variables["rows"] = $rows;
  $variables["header"] = $header;
  return theme('table', $variables);
}

/**
 * Theme for draggable tree of replacement rules.
 * 
 * @see customfilter_rules_form().
 */
function theme_customfilter_rules_form($vars) {
  $form = $vars['form'];
  $rows = array();
  if (isset($form['rules'])) {
    foreach (element_children($form['rules']) as $rid) {
      $row = array();
      $element =& $form['rules'][$rid];
      $element['prid']['#attributes']['class'] = array(
        'rule-prid',
      );
      $element['rid']['#attributes']['class'] = array(
        'rule-rid',
      );
      $element['weight']['#attributes']['class'] = array(
        'rule-weight',
      );
      $row[] = theme('indentation', array(
        'size' => $element['level']['#value'],
      )) . $element['name']['#value'];
      $row[] = drupal_render($element['rid']) . drupal_render($element['prid']) . drupal_render($element['weight']);
      $row[] = drupal_render($element['enabled']);
      $row[] = $element['operations']['add']['#value'];
      $row[] = $element['operations']['edit']['#value'];
      $row[] = $element['operations']['delete']['#value'];
      $rows[] = array(
        'data' => $row,
        'class' => array(
          'draggable',
        ),
      );
    }
  }
  if (!count($rows)) {
    $rows[] = array(
      array(
        'data' => t('There are no replacement rules currently defined.'),
        'colspan' => '6',
      ),
    );
  }
  $header[] = t('Name');
  $header[] = t('Weight');
  $header[] = t('Enabled');
  $header[] = array(
    'data' => t('Operations'),
    'colspan' => '3',
  );
  drupal_add_tabledrag('customfilter-rules-tree', 'order', 'sibling', 'rule-weight', NULL, NULL, TRUE);
  drupal_add_tabledrag('customfilter-rules-tree', 'match', 'parent', 'rule-prid', 'rule-prid', 'rule-rid', TRUE);
  $args['header'] = $header;
  $args['rows'] = $rows;
  $args['attributes'] = array(
    'id' => 'customfilter-rules-tree',
  );
  $args['caption'] = null;
  $args['colgroups'] = array();
  $args['sticky'] = false;
  $args['empty'] = null;
  return theme_table($args) . drupal_render_children($form);
}
function _customfilter_filter_add_edit_fields(&$form, $item) {
  $form['fid'] = array(
    '#type' => 'value',
    '#value' => $item['fid'],
  );
  $form['type'] = array(
    '#type' => 'textfield',
    '#title' => t('Type'),
    '#description' => t('The machine-readable name of the filter. This name must contain only lowercase letters, numbers, and underscores.'),
    '#default_value' => $item['type'],
    '#maxlength' => 32,
    // This function do not exist or is with wrong name

    //'#element_validate' => array('customfilter_filter_type_validate'),
    '#required' => TRUE,
  );
  $form['name'] = array(
    '#type' => 'textfield',
    '#title' => t('Name'),
    '#description' => t('The human-readable name of the filter. It is recommended that this name begin with a capital letter and contain only letters, numbers, and <em>spaces</em>.'),
    '#default_value' => $item['name'],
    '#maxlength' => 64,
    '#required' => TRUE,
  );
  $form['cache'] = array(
    '#type' => 'checkbox',
    '#title' => t('Cache'),
    '#description' => t('If checked, the content will be cached.'),
    '#default_value' => $item['cache'],
  );
  $form['description'] = array(
    '#type' => 'textarea',
    '#title' => t('Description'),
    '#description' => t('A description of the purpose of this filter.'),
    '#default_value' => $item['description'],
  );
  $form['shorttip'] = array(
    '#type' => 'textarea',
    '#title' => t('Tips (short)'),
    '#default_value' => $item['shorttip'],
    '#rows' => 5,
  );
  $form['longtip'] = array(
    '#type' => 'textarea',
    '#title' => t('Tips (full)'),
    '#default_value' => $item['longtip'],
    '#rows' => 20,
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save'),
  );
}

/**
 * Map the imported filter ID to a new one.
 */
function _customfilter_map_filter_id($id) {
  static $base = 0, $fids = array();
  if (!$base) {
    $base = (int) db_query("SELECT MAX(fid) FROM {customfilter_filter}")
      ->fetchField() + 1;
  }
  if (!isset($fids[$id])) {
    $fids[$id] = $base++;
  }
  return $fids[$id];
}

/**
 * Map the imported rule ID to a new one.
 */
function _customfilter_map_rule_id($id) {
  static $base = 0, $rids = array();
  if (!$id) {
    return 0;
  }
  if (!$base) {
    $base = (int) db_query("SELECT MAX(rid) FROM {customfilter_rule}")
      ->fetchField() + 1;
  }
  if (!isset($rids[$id])) {
    $rids[$id] = $base++;
  }
  return $rids[$id];
}

/**
 * Helper function for customfilter_export_form.
 * Return the list made of a rule, and all the sub-rules.
 */
function _customfilter_rules_tree(&$rules, $fid, $prid = 0) {
  $result = db_query("SELECT * FROM {customfilter_rule} WHERE fid = :fid and prid = :prid ORDER BY weight", array(
    ':fid' => $fid,
    ':prid' => $prid,
  ));
  while ($rule = $result
    ->fetchAssoc()) {
    $rules[] = $rule;
    _customfilter_rules_tree($rules, $fid, $rule['rid']);
  }
}

/**
 * Return the rules tree form.
 */
function _customfilter_rules_tree_form($rules, $level = 0) {
  $base = 'admin/config/content/customfilter';
  $form = array();
  foreach ($rules as $rule) {
    $fid = $rule['fid'];
    $rid = $rule['rid'];
    $form[$rid] = array(
      'name' => array(
        '#value' => l($rule['name'], "{$base}/{$fid}/{$rid}/edit"),
      ),
      'enabled' => array(
        '#type' => 'checkbox',
        '#default_value' => $rule['enabled'],
      ),
      'weight' => array(
        '#type' => 'weight',
        '#default_value' => $rule['weight'],
      ),
      'rid' => array(
        '#type' => 'hidden',
        '#value' => $rid,
      ),
      'prid' => array(
        '#type' => 'textfield',
        '#default_value' => $rule['prid'],
        '#size' => 4,
      ),
      'level' => array(
        '#type' => 'value',
        '#value' => $level,
      ),
    );
    $form[$rid]['operations'] = array(
      'add' => array(
        '#value' => l(t('add'), "{$base}/{$fid}/{$rid}/add"),
      ),
      'edit' => array(
        '#value' => l(t('edit'), "{$base}/{$fid}/{$rid}/edit"),
      ),
      'delete' => array(
        '#value' => l(t('delete'), "{$base}/{$fid}/{$rid}/delete"),
      ),
    );
    if ($rule['sub']) {
      $form += _customfilter_rules_tree_form($rule['sub'], $level + 1);
    }
  }
  return $form;
}

/**
 * @} End of "addtogroup customfilter".
 */

Functions

Namesort descending Description
customfilter_cache_clear Clear the cache
customfilter_export_form Create the form to export a filter.
customfilter_export_form_submit Return the form with exported data.
customfilter_filter_add Return the form to add a new filter.
customfilter_filter_add_submit Submission callback for the add filter form.
customfilter_filter_add_validate Validate callback for the add filter form.
customfilter_filter_delete Return the filter delete form.
customfilter_filter_delete_submit Submission callback for the filter delete form.
customfilter_filter_edit Return the filter edit form.
customfilter_filter_edit_submit Submission callback for the filter edit form.
customfilter_filter_edit_validate Validate callback for the filter edit form.
customfilter_filter_info Implements hook_filter_info().
customfilter_flush_caches Implements hook_flush_caches().
customfilter_help Implements hook_help().
customfilter_import_form Return the import form.
customfilter_import_form_submit Submission callback for the import form.
customfilter_import_form_validate Validation callback for the import form.
customfilter_menu Implements hook_menu().
customfilter_permission Implements hook_permission().
customfilter_rules_form Return the list of replacement rules form.
customfilter_rules_form_edit Return the replacement rule edit form.
customfilter_rules_form_submit Submission callback for the list of replacement rules form.
customfilter_rule_delete Return the replacement rule delete form.
customfilter_rule_delete_submit Submission callback for the replacement rule delete form.
customfilter_rule_edit
customfilter_rule_edit_submit Submission callback for the replacement rule edit form.
customfilter_settings Module settings page.
customfilter_theme Implements hook_theme
theme_customfilter_rules_form Theme for draggable tree of replacement rules.
_customfilter_breadcrumb Set the current menu breadcrumb for pages that don't have it correctly set.
_customfilter_code_vars Return the global object containing the global properties used in the replacement PHP code.
_customfilter_delete_rule Delete a rule from the database table.
_customfilter_filter_add_edit_fields
_customfilter_filter_process Process the text, apply the filters
_customfilter_filter_tips Implements filter_tips()
_customfilter_get_filters Get the list of filters.
_customfilter_get_rules Retrieve the replacement rules for a specific filter.
_customfilter_globals Return an object that contains the global variables used during the execution of a rule.
_customfilter_map_filter_id Map the imported filter ID to a new one.
_customfilter_map_rule_id Map the imported rule ID to a new one.
_customfilter_process @todo Document this function
_customfilter_replace_callback Helper function for preg_replace_callback().
_customfilter_rules_tree Helper function for customfilter_export_form. Return the list made of a rule, and all the sub-rules.
_customfilter_rules_tree_form Return the rules tree form.

Constants

Namesort descending Description
CUSTOMFILTER_API The version of the API currently implemented.