Commit de4972c0 authored by Tao Starbow's avatar Tao Starbow
Browse files

Got CCK field seaching working.

Only modifiying most recent revision in node_revisions table.
Added title search/replace.
Added case insensitive searching.
Got custom fields working.
parent bcbccad1
......@@ -4,6 +4,27 @@
* @file
* Search and Replace Scanner - works on all nodes text content.
*
* The Search and Replace Scanner can do regular expression matches
* against the body and CCK text content fields on all nodes in your system.
* This is useful for finding html strings that Drupal's normal search will
* ignore. And it can replace the matched text. Very handy if you are changing
* the name of your company, or are changing the URL of a link included
* multiple times in multiple nodes.
*
* The module allow you to configure which fields and tables to work with,
* and also to add in custom tables and fields for modules that don't use CCK.
*
* Limitations:
* Only works with Mysql
*
* Warning:
* This is a very powerful tool, and as such is very dangerous. You can
* easy distroy your entire site with it. Be sure to backup your database
* before using it. No, really.
*
* Todo:
* * Add more warning text.
* * Deal with lots of results and possible timeouts.
*/
/**
......@@ -20,11 +41,25 @@ function scanner_menu($may_cache) {
'access' => ($user->uid == 1),
);
$items[] = array(
'path' => 'admin/content/scanner/scan',
'title' => t('Search'),
'access' => ($user->uid == 1),
'type' => MENU_DEFAULT_LOCAL_TASK,
);
$items[] = array( // Shows up on scanner page as tab.
'path' => 'admin/content/scanner/settings',
'callback' => 'drupal_get_form',
'callback arguments' => array('scanner_admin_form'),
'access' => ($user->uid == 1),
'type' => MENU_LOCAL_TASK,
'title' => t('Settings'),
);
$items[] = array( // Shows up on admin page.
'path' => 'admin/settings/scanner',
'callback' => 'drupal_get_form',
'callback arguments' => array('scanner_admin_form'),
'type' => MENU_CALLBACK,
'access' => ($user->uid == 1),
'title' => t('Search and Replace'),
);
}
return $items;
......@@ -134,29 +169,39 @@ function scanner_form_submit($form_id, $form_values) {
* @return The themed results.
*/
function scanner_search( $search ) {
drupal_set_message( "Looking for $search." );
$mode = variable_get('scanner_mode', 0);
drupal_set_message( "Looking for $search. Mode = $mode." );
$tables_map = _scanner_get_selected_tables_map();
if( $mode == 'cs' ) { // case sensitive
$flag = null;
$where = "CAST(t.%s AS BINARY) REGEXP '%s'"; // BINARY to force case sensative.
}
else { // case insensitive
$flag = 'i';
$where = "t.%s REGEXP '%s'";
}
foreach( $tables_map as $map ) {
$table = $map['table'];
$field = $map['field'];
$on = $map['on'] ? $map['on'] : 'vid';
$result = db_query( "SELECT t.%s as content, t.nid, n.title FROM {%s} t ".
" INNER JOIN {node} n ON t.nid = n.nid ".
" WHERE CAST(%s AS BINARY) REGEXP '.*%s.*'", // BINARY to force case sensative.
$field, $table, $field, $search);
drupal_set_message("Scanning $field in $table.");
" INNER JOIN {node} n ON t.%s = n.%s ".
" WHERE $where",
$field, $table, $on, $on, $field, $search);
drupal_set_message("Scanning $field in $table on $on.");
while( $obj = db_fetch_object($result) ){
$content = $obj->content;
$matches = array();
$text = '';
$hits = preg_match_all("/(.{0,8})($search)(.{0,8})/", $content, $matches, PREG_SET_ORDER);
$hits = preg_match_all("/(.{0,8})($search)(.{0,8})/$flag", $content, $matches, PREG_SET_ORDER);
if ($hits > 0) {
foreach( $matches as $match ) {
if( $match[1] ) {
$text .= '...'. htmlentities($match[1]);
}
$text .= '<b>'. htmlentities($match[2]) .'</b>';
$text .= '<b>'. htmlentities($match[2]) .'</b>'; // Sadly destroys unicode, but needed to show html.
if( $match[3] ) {
$text .= htmlentities($match[3]) .'... ';
}
......@@ -176,7 +221,7 @@ function scanner_search( $search ) {
}
/**
* do a search and replace in the db
* Do a search and replace in the db.
*
* @param str $search - regexp.
* @param str $replace - replacement text.
......@@ -188,17 +233,33 @@ function scanner_replace($search, $replace) {
foreach( $tables_map as $map ) {
$table = $map['table'];
$field = $map['field'];
$sql = "UPDATE {$table} SET $field=REPLACE($field, '$search', '$replace') ".
"WHERE CAST($field AS BINARY) REGEXP ('$search')";
//$output .= "<p>$sql</p>";
// Only set most recent version.
$sql = "UPDATE {".$table."} t, {node} n SET t.$field=REPLACE(t.$field, '$search', '$replace') ".
"WHERE n.vid = t.vid AND CAST(t.$field AS BINARY) REGEXP ('$search')";
// $output .= "<p>$sql</p>";
$output .= "Updating $field in $table.<br/>";
db_query( $sql );
$updated += db_affected_rows();
if( db_query( $sql ) ) {
$updated += db_affected_rows();
// Special case for node_revisions.title, sync node.title.
if ($field == 'title' && $table == 'node_revisions') {
db_query("UPDATE {node} n SET n.title=REPLACE(n.title, '%s', '%s') ".
"WHERE CAST(n.title AS BINARY) REGEXP ('%s')",
$search, $replace, $search );
}
}
else {
$output .= "<p>Bad SQL: $sql</p>";
}
}
$output .= "<p>Updated $updated rows.</p>";
return $output;
}
// ***************************************************************************
// Settings ******************************************************************
// ***************************************************************************
/**
* Search and Replace Settings form.
*
......@@ -213,13 +274,27 @@ function scanner_admin_form() {
);
$table_map = _scanner_get_selected_tables_map();
foreach($table_map as $item) {
$output .= '<li><b>'. $item['field'] .'</b> in <b>'. $item['table'] .'</b></li>';
$output .= '<li><b>'. $item['field'] .'</b> in <b>'. $item['table'] .'*** </b>';
if( $item['on'] ) {
$output .= 'on <b>'. $item['on'] .'</b>';
}
$output .= '</li>';
}
$form['settings']['info']['#value'] = '<p>Fields that will be searched.</p><ul>'. $output .'</ul>';
$form['settings']['scanner_mode'] = array(
'#type' => 'radios',
'#title' => t('Search Mode'),
'#options' => array(
'cs' => t('Case Sensitive'),
'ci' => t('Case Insensitive'),
),
'#default_value' => variable_get('scanner_mode', 'cs'),
);
$form['tables'] = array(
'#type' => 'fieldset',
'#title' => t('Fields that could be searched'),
'#title' => t('Fields that can be searched'),
'#collapsible' => TRUE,
);
$table_map = _scanner_get_all_tables_map();
......@@ -236,7 +311,7 @@ function scanner_admin_form() {
'#type' => 'textarea',
'#title' => t('Custom Fields'),
'#default_value' => variable_get('scanner_custom', NULL),
'#description' => "one per row, <i>field</i> in <i>table</i>",
'#description' => "one per row, <i>field</i> in <i>table</i> on <i>vid or nid</i>",
);
return system_settings_form($form);
......@@ -257,16 +332,30 @@ function scanner_admin_form_validate() {
/**
* Get all text fields.
* This is all very fragle based on how CCK stores fields.
* Works for CCK 1.6.
*
* @return map of fields and tables.
*/
function _scanner_get_all_tables_map() {
$tables_map[] = array('table' => 'node_revisions', 'field' => 'title');
$tables_map[] = array('table' => 'node_revisions', 'field' => 'body');
$tables_map[] = array('table' => 'node_revisions', 'field' => 'teaser');
$results = db_query("SELECT field_name, type_name FROM {node_field_instance} WHERE widget_type='text'");
$results = db_query("SELECT nfi.field_name, nfi.type_name, nf.db_storage ".
"FROM {node_field_instance} nfi INNER JOIN {node_field} nf USING (field_name) ".
"WHERE nfi.widget_type='text'");
while($field=db_fetch_array($results)){
$tables_map[] = array('table'=>$field['type_name'], 'field'=>$field['field_name']);
if( $field['db_storage'] ) {
$table = 'content_type_'. $field['type_name'];
}
else {
$table = 'content_'. $field['field_name'];
}
$tables_map[] = array( // Modify to match current CCK storage rules.
'table' => $table,
'field' => $field['field_name'] .'_value',
);
}
return $tables_map;
}
......@@ -285,9 +374,9 @@ function _scanner_get_selected_tables_map() {
}
}
$custom = variable_get('scanner_custom', NULL);
preg_match_all( '/(.*) in (.*)/', $custom, $matches, PREG_SET_ORDER );
preg_match_all( '/(.*) in (.*) on (.*)/', $custom, $matches, PREG_SET_ORDER );
foreach($matches as $match){
$tables_map[] = array('table'=>$match[1], 'field'=>$match[2]);
$tables_map[] = array('table'=>trim($match[2]), 'field'=>trim($match[1]), 'on'=>trim($match[3]));
}
return $tables_map;
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment