<?php
// $Id: customfilter.module,v 1.3.2.1 2007/10/28 13:33:22 arhip Exp $

define('CUSTOMFILTER_CODE_DECLARE',
  'global $_customfilter_code_vars;'
  .'$vars = & $_customfilter_code_vars;');
  
global $_customfilter_code_vars;
global $_customfilter_globals;

function customfilter_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
  global $_customfilter_code_vars;

  if ($op == 'load') {
    $_customfilter_code_vars->node = $node;
  }
}

/**
 * Get filter sets from database
 *
 * @param $cols
 *   Columns to be retrieved
 * @param $cond
 *   Condition, return all results by default
 *
 * @return
 *   Array of filter sets
 */
function customfilter_get_sets($cols = array('sid', 'name', 'description'), $cond = '1=1') {
  $sets = array();
  
  // Prepare columns to select
  if (! is_array($cols)) $cols = array($cols);
  $columns = join(', ', $cols);

  // Query & fetch
  $result = db_query("SELECT %s FROM {customfilter_set} WHERE %s ORDER BY name", $columns, $cond);
  while ($set = db_fetch_array($result)) {
    $sets[] = $set;
  };
   
  return $sets;
}
 
/**
 * Get a filter set from database
 *
 * @param $sid
 *   ID of the filterset
 * @param $cols
 *   Columns to be retrieved
 *
 * @return
 *   The filter set
 */
function customfilter_get_set($sid, $cols = array('sid', 'name', 'description')) {
  // Prepare columns to select
  if (! is_array($cols)) $cols = array($cols);
  $columns = join(', ', $cols);

  // Query & Fetch
  $set = db_fetch_array(db_query("SELECT %s FROM {customfilter_set} WHERE sid=%d", $columns, $sid));

  return $set;
}

/**
 * Get filters from database
 *
 * @param $sid
 *   ID of the filter set
 * @param $root
 *   Root filter. Returns tree of filters with this filter
 *   as the root 
 * @param $sortby
 *   Sort the result (and subfilters) by this field. 
 *   Default: sort by weight
 *
 * @return
 *   Array of filters (each have ['sub'], contains subfilters if any)
 */
function customfilter_get_filters($sid, $root = 0, $sortby = 'weight', $cols = '*') {
  $filters = array();

  // Prepare columns to select
  if (! is_array($cols)) $cols = array($cols);
  $columns = join(', ', $cols);
  
  // Prepare nodes
  $nodes = array();
  if (is_array($root)) {
    $nodes = $root;
  }
  else {
    $nodes = array($root);
  }

  foreach ($nodes as $node) {
    $result = db_query("SELECT %s FROM {customfilter_filter} WHERE sid=%d and parentid=%d ORDER BY %s", $columns, $sid, $node, $sortby);
    
    while ($filter = db_fetch_array($result)) {
      $filter['sub'] = customfilter_get_filters($sid, $filter['fid'], $sortby, $cols);
    
      $filters[$filter['fid']] = $filter;
    };
  };
   
  return $filters;
}
 
/**
 * Get a filter from database
 *
 * @param $fid
 *   ID of the filter
 * @param $cols
 *   Columns
 *
 * @return
 *   The filter
 */
function customfilter_get_filter($fid, $cols = '*') {
  // Prepare columns to select
  if (! is_array($cols)) $cols = array($cols);
  $columns = join(', ', $cols);

  $filter = db_fetch_array(db_query("SELECT %s FROM {customfilter_filter} WHERE fid=%d", $columns, $fid));
  
  return $filter;
}

/**
 * Implementation of hook_help()
 */
function customfilter_help($section) {
  $help = '';
  
  $sid = (is_numeric(arg(3)))? arg(3): '';
  $fid = (is_numeric(arg(4)))? arg(4): '';

  switch ($section) {
    case "admin/modules#description":
      $help = t('Custom Filter');
      break;
      
    case "admin/settings/customfilter":
      $help = '<p>'. t('Custom Filter provides an 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>';
      
      if (count(customfilter_get_sets()) > 0) {
        $help .= '<p>'. t('Below are the filter sets. ');
      }
      else {
        $help .= '<p>'. t('Before you can use custom filters, you must have at least one filter set. ');
      }
       
      $help .= t('Filter set is a container of filters. Each will appear in Input Format configuration. Click at their name to see what filter they have.') .'</p>';
      break;
      
    case "admin/settings/customfilter/export":
      $help = '<p>'. t('You can export your custom filters as XML document. Just check filters you want to export below, and click the button Export.') .'</p>';
      break;
      
    case "admin/settings/customfilter/import":
      $help = '<p>'. t('You can import custom filters from an XML file.') .'</p>';
      break;

    case "admin/settings/customfilter/$sid":
      $help = '<p>'. t('This filterset has filters listed below. Each filter can have subfilters.') .'</p>';
      break;

    case "admin/settings/customfilter/add":
    case "admin/settings/customfilter/$sid/edit":
      $help = '<p>'. t('Give this filter set some name and description.') .'</p>';
      break;
      
    case "admin/settings/customfilter/$sid/add":
    case "admin/settings/customfilter/$sid/$fid":
    case "admin/settings/customfilter/$sid/$fid/edit":
    case "admin/settings/customfilter/$sid/$fid/add":
      $help = '<p>'. t('Here you can define your own filters using regular expressions. For some information about regular expressions, please look at ') . l('http://www.regular-expressions.info', 'http://www.regular-expressions.info') .'</p>';
      break;      
  }
   
  return $help;
}
 
/**
 * Implementation of hook_perm()
 */ 
function customfilter_perm() {
  return array('administer customfilter');
}

/** 
 * Implementation of hook_menu()
 */
function customfilter_menu($may_cache) {
  $access = user_access('administer customfilter');

  $items = array();
  
  if ($may_cache) {
    $items[] = array(
      'path' => 'admin/settings/customfilter',
      'title' => t('Custom Filter'),
      'description' => t('User defined filters.'),
      'callback' => 'customfilter_settings',
      'access' => $access,
      'type' => MENU_NORMAL_ITEM,
    );

    // Customfilter: List
    $items[] = array(
      'path' => 'admin/settings/customfilter/list',
      'title' => t('List'),
      'access' => $access,
      'type' => MENU_DEFAULT_LOCAL_TASK,
      'weight' => 1);

    // Customfilter: Add filterset
    $items[] = array(
      'path' => 'admin/settings/customfilter/add',
      'title' => t('Add filter set'),
      'callback' => 'drupal_get_form',
      'callback arguments' => array('customfilter_set_edit', 'add'),
      'access' => $access,
      'type' => MENU_LOCAL_TASK,
      'weight' => 2);
      
    // Customfilter: Export
    $items[] = array(
      'path' => 'admin/settings/customfilter/export',
      'title' => t('Export'),
      'callback' => 'drupal_get_form',
      'access' => $access,
      'callback arguments' => array('customfilter_export_form'),
      'type' => MENU_LOCAL_TASK,
      'weight' => 3);
      
    // Customfilter: Import
    $items[] = array(
      'path' => 'admin/settings/customfilter/import',
      'title' => t('Import'),
      'callback' => 'drupal_get_form',
      'access' => $access,
      'callback arguments' => array('customfilter_import_form'),
      'type' => MENU_LOCAL_TASK,
      'weight' => 4);
      
    // Customfilter: Get XML
    $items[] = array(
      'path' => 'admin/settings/customfilter/export/xml',
      'title' => t('XML'),
      'callback' => 'customfilter_xml_export',
      'access' => $access,
      'type' => MENU_CALLBACK);
      
  }
  else {
    if (arg(0) == 'admin' && arg(1) == 'settings' && arg(2) == 'customfilter' && is_numeric(arg(3))) {
      $sid = arg(3);
      $set = customfilter_get_set($sid, 'name');

      if ($set) {
        // Filterset: List filters
        $items[] = array(
          'path' => "admin/settings/customfilter/$sid",
          'title' => t("Custom filter: {$set['name']}"),
          'callback' => 'customfilter_filters',
          'callback arguments' => array(arg(3)),
          'access' => $access,
          'type' => MENU_CALLBACK);
          
        $items[] = array(
          'path' => "admin/settings/customfilter/$sid/list",
          'title' => t('List'),
          'access' => $access,
          'type' => MENU_DEFAULT_LOCAL_TASK,
          'weight' => 1);

        // Filterset: Edit filterset
        $items[] = array(
          'path' => "admin/settings/customfilter/$sid/edit",
          'title' => t('Edit'),
          'callback' => 'drupal_get_form',
          'access' => $access,
          'callback arguments' => array('customfilter_set_edit', 'edit', $sid),
          'type' => MENU_LOCAL_TASK,
          'weight' => 2);

        // Filterset: Delete filterset
        $items[] = array(
          'path' => "admin/settings/customfilter/$sid/delete",
          'title' => t('Delete'),
          'callback' => 'drupal_get_form',
          'access' => $access,
          'callback arguments' => array('customfilter_set_delete', $sid),
          'type' => MENU_LOCAL_TASK,
          'weight' => 3);
          
        // Filterset: Add filter
        $items[] = array(
          'path' => "admin/settings/customfilter/$sid/add",
          'title' => t('Add filter'),
          'callback' => 'drupal_get_form',
          'callback arguments' => array('customfilter_filter_edit', 'add', $sid),
          'access' => $access,
          'type' => MENU_LOCAL_TASK,
          'weight' => 4);

        if (is_numeric(arg(4))) {
          $fid = arg(4);
          $filter = customfilter_get_filter($fid, 'name');
          
          // Filter: List
          $items[] = array(
            'path' => "admin/settings/customfilter/$sid/$fid",
            'title' => t("Filter: {$filter['name']}"),
            'callback' => 'drupal_get_form',
            'callback arguments' => array('customfilter_filter_edit', 'edit', $sid, $fid),
            'access' => $access,
            'type' => MENU_CALLBACK);

          // Filter: Edit filter
          $items[] = array(
            'path' => "admin/settings/customfilter/$sid/$fid/edit",
            'title' => t('Edit'),
            'access' => $access,
            'type' => MENU_DEFAULT_LOCAL_TASK,
            'weight' => 1);

          // Filter: Delete filter       
          $items[] = array(
            'path' => "admin/settings/customfilter/$sid/$fid/delete",
            'title' => t('Delete filter'),
            'callback' => 'drupal_get_form',
            'callback arguments' => array('customfilter_filter_delete', $fid),
            'access' => $access,
            'type' => MENU_LOCAL_TASK,
            'weight' => 3);
            
          // Filter: Add subfilter
          $items[] = array(
            'path' => "admin/settings/customfilter/$sid/$fid/add",
            'title' => t('Add subfilter'),
            'callback' => 'drupal_get_form',
            'callback arguments' => array('customfilter_filter_edit', 'add', $sid, $fid),
            'access' => $access,
            'type' => MENU_LOCAL_TASK,
            'weight' => 2);
        }        
      }
    } 
  }
    
  return $items;
}

/** 
 * Administer Page
 */
function customfilter_settings() {
  return customfilter_sets();
}

/**
 * Lists defined filter sets
 */
function customfilter_sets() {
  return customfilter_set_render_table();
}

/**
 * Renders table of filter sets
 * 
 * @return
 *   Themed table of filters
 */
function customfilter_set_render_table() {
  $header = array(
    t('Name'),
    t('Description'),
    array('data' => t('Operations'), 'colspan' => '2'));

  $rows = customfilter_set_get_rows();
  
  $table = theme(
    'table',
    $header,
    $rows ? $rows : array(array(array('data' => t('No custom filter defined.'), 'colspan' => 5))));
    
  return $table;
}
 
/**
 * Renders table rows of filter table
 *
 * @return
 *   Generated rows
 */
function customfilter_set_get_rows() {
  $sets = customfilter_get_sets();

  foreach ($sets as $set) {
    $rows[] = array(
      l(t($set['name']), "admin/settings/customfilter/{$set['sid']}"),
      '<em>'. t($set['description']) .'</em>',
      l(t('edit'), "admin/settings/customfilter/{$set['sid']}/edit"),
      l(t('delete'), "admin/settings/customfilter/{$set['sid']}/delete"),
    );
  }
   
  return $rows;
}

/**
 * Edit a filter set
 */
function customfilter_set_edit($op, $sid = 0) {
  if ($op == 'edit') {
    $item = customfilter_get_set($sid, '*');
  }
  elseif ($op == 'add') {
    $item = array(
      'sid' => 0,
      'name' => 'Filter set #',
      'cache' => 1,
      'description' => '',
      'shorttips' => '',
      'longtips' => '',
    );
  }
   
  $form['sid'] = array('#type' => 'value', '#value' => $item['sid']);
  $form['operation'] = array('#type' => 'value', '#value' => $op);

  $form['name'] = array(
    '#type' => 'textfield',
    '#title' => t('Name'),
    '#default_value' => $item['name'],
    '#description' => t('The name of the filter set.'),
    '#required' => TRUE);
    
  $form['cache'] = array(
    '#type' => 'checkbox',
    '#title' => t('Cache'),
    '#default_value' => $item['cache'],
    '#description' => t('If checked, the content will be cached (i.e. this filter will be executed once per content edit).'));

  $form['description'] = array(
    '#type' => 'textarea',
    '#title' => t('Description'),
    '#default_value' => $item['description'],
    '#description' => t('Some text to describe this filter set.'));

  $form['shorttips'] = array(
    '#type' => 'textarea',
    '#title' => t('Tips (short)'),
    '#default_value' => $item['shorttips'],
    '#description' => t(''));

  $form['longtips'] = array(
    '#type' => 'textarea',
    '#title' => t('Tips (full)'),
    '#default_value' => $item['longtips'],
    '#description' => t(''));

  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save'));

  return $form;
}
 
/**
 * Submit modified filter set
 */
function customfilter_set_edit_submit($form_id, $form_values) {
  switch ($form_values['operation']) {
    case 'edit':
      db_query("UPDATE {customfilter_set}
                SET
                  name='%s', cache=%d, description='%s',
                  shorttips='%s', longtips='%s'
                WHERE sid=%d",
            $form_values['name'],
            $form_values['cache'],
            $form_values['description'],
            $form_values['shorttips'],
            $form_values['longtips'],
            $form_values['sid']);
      break;
    case 'add':
      $nextid = db_next_id('{customfilter_set}_sid');

      db_query("INSERT INTO {customfilter_set}
                (sid, name, cache, description, shorttips, longtips)
                VALUES(%d, '%s', %d, '%s', '%s', '%s');",
            $nextid,
            $form_values['name'],
            $form_values['cache'],
            $form_values['description'],
            $form_values['shorttips'],
            $form_values['longtips']);
      break;
  }

  return 'admin/settings/customfilter';
}
 
/**
 * Delete filter set
 */ 
function customfilter_set_delete($sid) {
  $set = customfilter_get_set($sid, '*');
  $filters = customfilter_get_filters($sid);

  $form['sid'] = array('#type' => 'value', '#value' => $sid);
  
  $message = t('Are you sure you want to delete this filter set?');
    
  $msg_text = 
    "<h3>". $set['name'] ."</h3>" 
    ."<p>". $set->description ."</p>";
    
  if (count($filters) > 0) {
    $msg_text .=
      "<p>". t('This set has filters. If you delete this, they will be deleted too.') ."</p>"
      . customfilter_filter_render_table($sid, $fid, FALSE);
  }

  $msg_text .= "<p>". t('This action cannot be undone.') ."</p>";
    
  return confirm_form(
    $form, $message,
    'admin/settings/customfilter',
    $msg_text,
    t('Delete'));
}

/**
 * Execute filter set deletion
 */ 
function customfilter_set_delete_submit($form_id, $form_values) {
  customfilter_delete_set($form_values['sid']);

  return 'admin/settings/customfilter';
}
 
function customfilter_delete_set($sid) {
  $filters = customfilter_get_filters($sid);
  
  foreach ($filters as $filter) {
    customfilter_delete_filter($filter['fid']);
  }
   
  db_query('DELETE FROM {customfilter_set} WHERE sid=%d', $sid);
}

/**
 * Lists defined filters
 */
function customfilter_filters($sid) {
  return customfilter_filter_render_table($sid, 0);
}

/**
 * Renders table of filters
 * 
 * @param $sid
 *   ID of the filter set
 * @param $fid
 *   ID of the root filter
 * @param $op
 *   If TRUE, will render the 'operations' column
 *
 * @return
 *   Themed table of filters
 */
function customfilter_filter_render_table($sid = 0, $fid = 0, $op = TRUE) {
  // Table header
  $header = array(
    t('Name'),
    t('Description'),
    t('Pattern'),
    t('Match'),
    t('Weight'));
    
  if ($op)
    $header[] = array('data' => t('Operations'), 'colspan' => '3');
    
  // Table rows
  $rows = array();
    
  $filters = customfilter_get_filters($sid, $fid);
   
  if (count($filters) > 0) {
    customfilter_filter_get_rows($filters, 0, $rows, $op);
  }
  else {
    $rows[] = array(
      0 => array('data' => 'No custom filter defined.', 'colspan' => 5),
    );
  }
  
  $table = theme(
    'table',
    $header,
    $rows);

  return $table;
}

/**
 * Renders table rows of filter table
 *
 * @param $filters
 *   Array of fetched filters from database (from customfilter_get_filters)
 * @param $depth
 *   The level of subfilters that should be rendered
 * @param $rows
 *   Generated rows
 * @param $op
 *   If TRUE, will render the 'operations' column
 */
function customfilter_filter_get_rows($filters, $depth, &$rows, $op = TRUE) {
  foreach ($filters as $filter) {
    $format = (($depth == 0)? "strong": "em");
  
    $row = array(
      str_repeat('&raquo;&nbsp;', $depth) .'<'. $format .'>'. t($filter['name']) .'</'. $format .'>',
      '<em>'. t($filter['description']) .'</em>',
      htmlspecialchars($filter['pattern']),
      ($filter['parentid'] == 0)? "" : $filter['matches'],
      $filter['weight'],
    );
     
    if ($op) {
      $row[] = l(t('add'), "admin/settings/customfilter/{$filter['sid']}/{$filter['fid']}/add");
      $row[] = l(t('edit'), "admin/settings/customfilter/{$filter['sid']}/{$filter['fid']}/edit");
      $row[] = l(t('delete'), "admin/settings/customfilter/{$filter['sid']}/{$filter['fid']}/delete");
    }
     
    $rows[] = $row;
     
    if (is_array($filter['sub'])) {
      customfilter_filter_get_rows($filter['sub'], $depth + 1, $rows, $op);
    }
  }
}
 
/**
 * Edit a filter
 */
function customfilter_filter_edit($op, $sid, $fid = 0) {
  if ($op == 'edit') {
    $item = customfilter_get_filter($fid, '*');
  }
  elseif ($op == 'add') {
    $item = array(
      'fid' => 0,
      'parentid' => $fid,
      'sid' => $sid,
      'name' => '$1',
      'description' => '',
      'matches' => 1,
      'pattern' => '/regex/i',
      'replacement' => 'Regular Expressions',
      'func' => 0,
      'weight' => 0,
    );
  }
   
  $matchopt = array();
  for ($i = 0; $i <=99; $i++) {
    $matchopt[$i] = $i;
  };
   
  $form['fid'] = array('#type' => 'value', '#value' => $item['fid']);
  $form['sid'] = array('#type' => 'value', '#value' => $sid);
  $form['parentid'] = array('#type' => 'value', '#value' => $item['parentid']);
  $form['operation'] = array('#type' => 'value', '#value' => $op);

  if ($item['parentid'] != 0) {
    $form['matches'] = array(
      '#type' => 'select',
      '#title' => t('# Match'),
      '#options' => $matchopt,
      '#default_value' => $item['matches'],
      '#description' => t('Matches.'));
  }

  $form['name'] = array(
    '#type' => 'textfield',
    '#title' => t('Name'),
    '#default_value' => $item['name'],
    '#description' => t('The name of the filter.'),
    '#required' => TRUE);

  $form['pattern'] = array(
    '#type' => 'textarea',
    '#title' => t('Pattern'),
    '#default_value' => $item['pattern'],
    '#description' => t('Regular expression. Look at <a href="http://www.regular-expressions.info">http://www.regular-expressions.info</a> for more help.'));

  $form['replacement'] = array(
    '#type' => 'textarea',
    '#title' => t('Replacement text'),
    '#default_value' => $item['replacement'],
    '#description' => t('Replacement Text. Matched string will be replaced with text supplied here. Use $n (e.g. $1, $25) or ${n} (e.g. ${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 set the <strong>PHP Code</strong> below, you can enter replaced text with some PHP Code. n-th matched string will be provided in $matches[n], and there will be a global variable named $vars you can use it for your own purpose. Don\'t forget to write the return statement.'));

  $form['func'] = array(
    '#type' => 'checkbox',
    '#title' => t('PHP Code'),
    '#default_value' => $item['func'],
    '#description' => t('Check to allow using PHP code to replace the text.'));

  $form['weight'] = array(
    '#type' => 'weight',
    '#title' => t('Weight'),
    '#default_value' => $item['weight'],
    '#description' => t('Filter weight.'));

  $form['description'] = array(
    '#type' => 'textarea',
    '#title' => t('Description'),
    '#default_value' => $item['description'],
    '#description' => t('Some text to describe this filter.'));

  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save Configuration'));

  return $form;
}
 
/**
 * Submit modified filter
 */
function customfilter_filter_edit_submit($form_id, $form_values) {
  switch ($form_values['operation']) {
    case 'edit':
      db_query("UPDATE {customfilter_filter}
                SET sid=%d, parentid=%d,
                  name='%s', description='%s',
                  matches=%d, pattern='%s', replacement='%s',
                  func=%d, weight=%d
                WHERE fid=%d",
            $form_values['sid'],
            $form_values['parentid'],
            $form_values['name'],
            $form_values['description'],
            $form_values['matches'],
            $form_values['pattern'],
            $form_values['replacement'],
            $form_values['func'],
            $form_values['weight'],
            $form_values['fid']);
      break;
    case 'add':
      $nextid = db_next_id('{customfilter_filter}_fid');

      db_query("INSERT INTO {customfilter_filter}
                (fid, sid, parentid, name, description, matches, pattern, replacement, func, weight)
                VALUES(%d, %d, %d, '%s', '%s', %d, '%s', '%s', %d, %d);",
            $nextid,
            $form_values['sid'],
            $form_values['parentid'],
            $form_values['name'],
            $form_values['description'],
            $form_values['matches'],
            $form_values['pattern'],             
            $form_values['replacement'],
            $form_values['func'],
            $form_values['weight']);
      break;
  }
   
  return "admin/settings/customfilter/{$form_values['sid']}";
}

/**
 * Delete a filter
 */
function customfilter_filter_delete($fid) {
  $filter = db_fetch_object(db_query("SELECT * FROM {customfilter_filter} WHERE fid=%d", $fid));
  $subfilter = db_fetch_object(db_query("SELECT * FROM {customfilter_filter} WHERE parentid=%d", $fid));

  $form['fid'] = array('#type' => 'value', '#value' => $fid);
  $form['sid'] = array('#type' => 'value', '#value' => $filter->sid);
  
  $message = t('Are you sure you want to delete this custom filter?');
    
  $msg_text = 
    (($filter->parentid == 0)? "<h3>{$filter->name}</h3>" : "")
    ."<p>{$filter->description}</p>" 
    ."<h3>Pattern</h3>"
    ."<pre>{$filter->pattern}</pre>"
    ."<h3>Replacer</h3>"
    ."<pre>{$filter->replacer}</pre>";
    
  if ($subfilter) {
    $msg_text .=
      "<p>". t('This filter has subfilters. If you delete this, they will be deleted too.') ."</p>"
      . customfilter_filter_render_table($filter->sid, $fid, FALSE);
  }

  $msg_text .= "<p>". t('This action cannot be undone.') ."</p>";
    
  return confirm_form(
    $form, $message,
    "admin/settings/customfilter/{$filter->sid}",
    $msg_text,
    t('Delete'));
}
 
/**
 * Execute deletion
 */
function customfilter_filter_delete_submit($form_id, $form_values) {
  customfilter_delete_filter($form_values['fid']);

  return "admin/settings/customfilter/{$form_values['sid']}";
}
 
function customfilter_delete_filter($fid) {
  $result = db_query("SELECT * FROM {customfilter_filter} where parentid=%d", $fid);
  
  while ($filter = db_fetch_object($result)) {
    customfilter_delete_filter($filter->fid);
  }

  db_query("DELETE FROM {customfilter_filter} WHERE fid=%d", $fid);
}

/**
 * Get complete description of a filter, including it's subfilters
 */
function customfilter_get_descriptions($filter) {
  $desc = "<em>". $filter['description'] ."</em>";

  if (is_array($filter['sub']) && (count($filter['sub']) > 0)) {
    $desc .= "<ul>";
    foreach ($filter['sub'] as $subfilter) {
      $desc .= "<li>";
      $desc .= customfilter_get_descriptions($subfilter);
      $desc .= "</li>";
    }
    $desc .= "</ul>";
  }
  
  return $desc;
}
 
/**
 * Implementation of hook_filter_tips()
 */
function customfilter_filter_tips($delta, $format, $long = FALSE) {
  $col = ($long)? 'longtips': 'shorttips';
  
  $tips = customfilter_get_set($delta, array('sid', $col));
    
  return $tips[$col];
}
 
/**
 * Implementation of hook_filter().
 */
function customfilter_filter($op, $delta = 0, $format = -1, $text = '' ) { 
  switch ($op) { 
    case 'list': 
      return _customfilter_filter_list(); 
      
    case 'no cache':
      return ! _customfilter_filter_cache($delta);

    case 'description':      
      return _customfilter_filter_desc($delta);
      
    case 'prepare': 
      return $text;
	
    case "process":
      return _customfilter_process($delta, $format, $text);
	
    default: 
      return $text; 
  } 
}

function _customfilter_filter_list() {
  $sets = customfilter_get_sets('sid, name');
  
  $s = array();
  foreach ($sets as $set) {
    $s[$set['sid']] = t($set['name']);
  }

  return $s;
}

function _customfilter_filter_cache($delta) {
  $set = customfilter_get_set($delta, array('sid', 'cache'));
  
  return $set['cache'];
 }
 
function _customfilter_filter_desc($delta) {
  $set = customfilter_get_set($delta, array('sid', 'description'));
    
  return t($set['description']);
}

/**
 * Custom Filter process
 */
function _customfilter_process($delta, $format, $text) {
  global $_customfilter_globals;
  
  $_customfilter_globals->text = $text;
  
  // Get the filter set, according to $delta
  $set = customfilter_get_set($delta, 'sid');
  if ($set) {
    $filters = customfilter_get_filters($delta);
  
    if (count($filters) > 0) {
      // Preparation
      // The stack is used to save the parent filter when traversing
      $_customfilter_globals->stack = array();

      foreach ($filters as $filter) {
        $_customfilter_globals->stack[] = $filter;
        
        $_customfilter_globals->text = preg_replace_callback(
          $filter['pattern'],
          '_customfilter_process_filter',
          $_customfilter_globals->text);
         
        array_pop($_customfilter_globals->stack);
      }
    }
  }
   
  return $_customfilter_globals->text;
}
 
function _customfilter_process_extract_rep($replacement) {
  $reps = array();
  
  preg_match_all(
    '/([^\\\\]|^)(\$([0-9]{1,2}|\{([0-9]{1,2})\}))/',
    $replacement,
    $reps,
    PREG_OFFSET_CAPTURE);

  $arr = array();

  foreach ($reps[4] as $key => $val) {
    if ($val == '') {
      $str = $reps[3][$key][0];
    }
    else {
      $str = $reps[4][$key][0];
    }
     
    $offset = $reps[2][$key][1];
    $length = strlen($reps[2][$key][0]);

    $arr[] = array(
      'index' => $str,
      'offset' => $offset,
      'length' => $length
    );
  }
   
  return $arr;
}
 
function _customfilter_process_replace_sub($replacement, $sub, $func = 0) {
  if ($func == 1) {
    $code = create_function(
        '$matches',
        CUSTOMFILTER_CODE_DECLARE . $replacement
      );
       
    $text = $code($sub);
  }
  else {
    $text = $replacement;
    
    $reps = _customfilter_process_extract_rep($replacement);
    krsort($reps);
    
    foreach ($reps as $rep) {
      $text = substr_replace(
        $text,
        $sub[$rep['index']],
        $rep['offset'],
        $rep['length']);
    };
  }
  
  return $text;
}
 
function _customfilter_process_filter($matches) {
  global $_customfilter_globals;
  
  $result = $matches[0];
  
  $filter = end($_customfilter_globals->stack);

  // if there is subfilter  
  if (is_array($filter['sub']) && (count($filter['sub']) > 0)) {
    // do the same thing to each of them
    foreach ($filter['sub'] as $subfilter) {
      $_customfilter_globals->stack[] = $subfilter;
      
      $substr = & $matches[$subfilter['matches']];
      $substr = preg_replace_callback(
        $subfilter['pattern'],
        '_customfilter_process_filter',
        $substr
      );
       
      array_pop($_customfilter_globals->stack);
    }
     
    $result = _customfilter_process_replace_sub(
      $filter['replacement'],
      $matches,
      $filter['func']);
    
  } // if there is not, replace
  elseif ($filter['func'] == 1) {
    $result = preg_replace_callback(
      $filter['pattern'],
      create_function(
        '$matches',
        CUSTOMFILTER_CODE_DECLARE
          . $filter['replacement']
      ),
      $result); 
  }
  else {
    $result = preg_replace(
      $filter['pattern'],
      $filter['replacement'],
      $result);
  }
   
  return $result;
}

/**
 * Export
 */
function customfilter_export_form() {
  $form = array();
  
  $sets = customfilter_get_sets();
  foreach ($sets as $set) {
    $opt[$set['sid']] = t($set['name']);
  }
  
  $form['sets'] = array(
    '#type' => 'checkboxes',
    '#title' => t('Filter sets'),
    '#options' => $opt,
    '#description' => 'Choose filter sets.',
  );
   
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => 'Export',
  );
  
  return $form;
}
 
function customfilter_export_form_submit($form_id, $form_values) {
  $sids = array();
  foreach ($form_values['sets'] as $sid) {
    if ($sid > 0) $sids[] = $sid;
  }

  $arg = join('.', $sids);

  return "admin/settings/customfilter/export/xml/$arg";
}

function customfilter_xml_export($sets) {
  $sids = explode('.', $sets);

  $xml = customfilter_xml_create($sids);

  header('Content-Type: application/octet-stream');
  header('Content-Length: '. strlen($xml));
  header("Content-Disposition: attachment; filename=customfilter.xml");
  
  echo $xml;
}
 
function customfilter_xml_create($sids) {
  $xml = "<?xml version=\"1.0\" standalone=\"yes\"?>\n";
  $xml .= "<customfilter>\n";
  
  foreach ($sids as $sid) {
    $xml .= customfilter_xml_filterset($sid);
  }
  
  $xml .= "</customfilter>";
  
  return $xml;
}
 
function customfilter_xml_filterset($sid) {
  $set = customfilter_get_set($sid, '*');
  
  $filters = customfilter_get_filters($sid, 0);
  
  $xml = "  <filterset name=\"{$set['name']}\" cache=\"{$set['cache']}\">\n";
  $xml .= "    <description>{$set['description']}</description>\n";
  $xml .= "    <tips>\n";
  $xml .= "      <short><![CDATA[{$set['shorttips']}]]></short>\n";
  $xml .= "      <long><![CDATA[{$set['longtips']}]]></long>\n";
  $xml .= "    </tips>\n";
  $xml .= "    <filters>\n";

  foreach ($filters as $filter) {
    $xml .= customfilter_xml_filter($filter);
  }  
 
  $xml .= "    </filters>\n";
  $xml .= "  </filterset>\n";
    
  return $xml;
}
 
function customfilter_xml_filter($filter, $level = 0) {
  $indent = str_repeat('  ', $level + 3);
  
  $xml = $indent ."<filter name=\"{$filter['name']}\" matches=\"{$filter['matches']}\" func=\"{$filter['func']}\" weight=\"{$filter['weight']}\">\n";
  $xml .= $indent ."  <description><![CDATA[{$filter['description']}]]></description>\n";
  $xml .= $indent ."  <pattern><![CDATA[{$filter['pattern']}]]></pattern>\n";
  $xml .= $indent ."  <replacement><![CDATA[{$filter['replacement']}]]></replacement>\n";
  
  $subfilters = $filter['sub'];
  if (is_array($subfilters) && (count($subfilters) > 0)) {
    $xml .= $indent ."  <subfilters>\n";
    foreach ($subfilters as $subfilter) {
      $xml .= customfilter_xml_filter($subfilter, $level + 2);
    }
    $xml .= $indent ."  </subfilters>\n";
  }
  
  $xml .= $indent ."</filter>\n";

  return $xml;
}

/**
 * Import
 */
function customfilter_import_form() {
  $form = array();

  $form['xml'] = array(
    '#type' => 'file',
    '#title' => t('Import from file'),
    '#description' => t('The XML file to be imported.'),  
  );
  
  $form[] = array(
    '#type' => 'submit',
    '#value' => t('Import'),
  );
  
  $form['#attributes'] = array('enctype' => 'multipart/form-data');

  return $form;
}
 
function customfilter_import_form_submit($form_id, $form_values) {
  global $_customfilter_globals;
  $cg = & $_customfilter_globals;

  $file = file_check_upload('xml');
  
  if ($file) {
    $thefile = fopen($file->filepath, "rb");

    if ($thefile) {
      $fstat = fstat($thefile);
      $xml = fread($thefile, $fstat['size']);

      fclose($thefile);
      drupal_set_message(t('XML file %f imported.', array('%f' => $file->filename)));

      customfilter_xml_read($xml);
      _customfilter_xml_sql($cg->sets, $cg->filters);      
    }
  }
}

function customfilter_xml_read(& $xml) {
  global $_customfilter_globals;  
  $cg = & $_customfilter_globals;
  
  $cg->elements = array();
  $cg->sets = array();
  $cg->filters = array();
  $cg->set = array();
  $cg->filter = array();
  $cg->parents = array();

  $parser = drupal_xml_parser_create($xml);
  xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, true);
  xml_set_element_handler($parser, '_customfilter_xml_element_start', '_customfilter_xml_element_end');
  xml_set_character_data_handler($parser, '_customfilter_xml_element_cdata');
  xml_parse($parser, $xml, TRUE);
  xml_parser_free($parser);
}
 
function _customfilter_xml_element_start($parser, $name, $attr) {
  global $_customfilter_globals;  
  $cg = & $_customfilter_globals;
  
  $cg->elements[] = $name;
  switch ($name) {
    case 'FILTERSET':
      $cg->set['sid'] = db_next_id('{customfilter_set}_sid');
      $cg->set['name'] = $attr['NAME'];
      $cg->set['cache'] = $attr['CACHE'];
      $cg->set['description'] = '';
      $cg->set['shorttips'] = '';
      $cg->set['longtips'] = '';
      break;
    case 'FILTER':
      $cg->filter['fid'] = db_next_id('{customfilter_filter}_fid');
      $cg->filter['sid'] = $cg->set['sid'];
      $cg->filter['name'] = $attr['NAME'];
      $cg->filter['matches'] = $attr['MATCHES'];
      $cg->filter['func'] = $attr['FUNC'];
      $cg->filter['weight'] = $attr['WEIGHT'];
      $cg->filter['description'] = '';
      $cg->filter['pattern'] = '';
      $cg->filter['replacement'] = '';
      
      if (count($cg->parents) > 0) {
        $parent = end($cg->parents);
        $cg->filter['parentid'] = $parent['fid'];
      }
      else
        $cg->filter['parentid'] = 0;
      break;
    case 'SUBFILTERS':
      $cg->parents[] = $cg->filter;
      $cg->filter = array();
      break;
    case 'FILTERS':
      $cg->parents = array();
      $cg->filter = array();
      break;
  }
}
 
function _customfilter_xml_element_end($parser, $name) {
  global $_customfilter_globals;
  $cg = & $_customfilter_globals;

  switch ($name) {
    case 'FILTERSET':
      $cg->sets[$cg->set['sid']] = $cg->set;
      $cg->set = array();
      break;
    case 'FILTER':
      $cg->filters[$cg->filter['fid']] = $cg->filter;
      $cg->filter = array();
      break;
    case 'SUBFILTERS':
      $cg->filter = array_pop($cg->parents);
      break;
  }
  
  array_pop($cg->elements);
}
 
function _customfilter_xml_element_cdata($parser, $data) {
  global $_customfilter_globals;  
  $cg = & $_customfilter_globals;
  
  $element = end($cg->elements);
  $context = prev($cg->elements);
  
  switch ($element) {
    case 'DESCRIPTION':
      if ($context == 'FILTERSET') {
        $cg->set['description'] = $data; 
      }
      elseif ($context == 'FILTER') {
        $cg->filter['description'] = $data; 
      }
      break;
    case 'SHORT':
    case 'LONG':
      $cg->set[strtolower($element) .'tips'] = $data;
      break;
    case 'PATTERN':
    case 'REPLACEMENT':
      $cg->filter[strtolower($element)] = $data;
      break;  
  }
}
 
function _customfilter_xml_sql($sets, $filters) {
  foreach ($sets as $set) {
    db_query("INSERT INTO {customfilter_set}
              (sid, name, cache, description, shorttips, longtips)
              VALUES(%d, '%s', %d, '%s', '%s', '%s');",
          $set['sid'],
          $set['name'],
          $set['cache'],
          $set['description'],
          $set['shorttips'],
          $set['longtips']);    
  }
   
  foreach ($filters as $filter) {
    db_query("INSERT INTO {customfilter_filter}
              (fid, sid, parentid, name, description, matches, pattern, replacement, func, weight)
              VALUES(%d, %d, %d, '%s', '%s', %d, '%s', '%s', %d, %d);",
          $filter['fid'],
          $filter['sid'],
          $filter['parentid'],
          $filter['name'],
          $filter['description'],
          $filter['matches'],
          $filter['pattern'],             
          $filter['replacement'],
          $filter['func'],
          $filter['weight']);
  }
}