';
if ($key == 1) {
$text .= 'Summary: ';
}
// Don't want substr to wrap.
$start = $match[1] - $context_length > 0 ? $match[1] - $context_length : 0;
// If the match is close to the beginning of the string, need
// less context.
$length = $match[1] >= $context_length ? $context_length : $match[1];
if ($prepend = substr($string, $start, $length)) {
if ($length == $context_length) {
$text .= '...';
}
$text .= htmlentities($prepend, ENT_COMPAT, 'UTF-8');
}
$text .= '' . htmlentities($match[0], ENT_COMPAT, 'UTF-8') . '';
if ($append = substr($string, $match[1] + strlen($match[0]), $context_length)) {
$text .= htmlentities($append, ENT_COMPAT, 'UTF-8');
if (strlen($string) - ($match[1] + strlen($match[0])) > $context_length) {
$text .= '...';
}
}
$text .= '
';
}
}
$text .= '
';
}
else {
$text = '
Warning message
'
. t("Can't display search result due to conflict between search term and internal preg_match_all function.")
. '
';
}
$results[] = array(
'title' => $row->title,
'type' => $type,
'count' => $hits,
'field' => $field,
'field_label' => $field_label,
'nid' => $row->nid,
'text' => $text,
);
}
// Replace (and check to see if already processed).
elseif (!isset($processed[$field][$row->nid][$row->delta])) {
// Check first if pathauto_persist, a newer version of pathauto, or some
// other module has already set $node->path['pathauto']. If not, set it
// to false (to prevent pathauto from touching the node during
// node_save()) if a custom alias exists that doesn't follow pathauto
// rules.
if (!isset($node->path['pathauto']) && module_exists('pathauto') && $pathauto) {
list($id, , $bundle) = entity_extract_ids('node', $node);
if (!empty($id)) {
module_load_include('inc', 'pathauto');
$uri = entity_uri('node', $node);
$path = drupal_get_path_alias($uri['path']);
$pathauto_alias = pathauto_create_alias('node', 'return', $uri['path'], array('node' => $node), $bundle);
$node->path['pathauto'] = ($path != $uri['path'] && $path == $pathauto_alias);
}
}
$hits = 0;
$content_new = preg_replace("/$search_php/$flag", $replace, $content, -1, $hits);
preg_match('/(.+)_' . $suffix . '$/', $field, $matches);
// Field collections.
if (!empty($field_collection_parents)) {
foreach ($node->{$field_collection_parents[0]} as $fc_lang => $fc_data) {
foreach ($fc_data as $key => $fc_item) {
$fc = field_collection_item_load($fc_item['value']);
$fc_changed = FALSE;
foreach ($fc->{$matches[1]}[LANGUAGE_NONE] as $fc_key => $fc_val) {
$fc_hits = 0;
$fc_content = preg_replace("/$search_php/$flag", $replace, $fc_val[$suffix], -1, $fc_hits);
if ($fc_content != $fc_val['value']) {
$fc_changed = TRUE;
$fc->{$matches[1]}[LANGUAGE_NONE][$fc_key][$suffix] = $fc_content;
}
// Also need to handle the summary part of text+summary fields.
if (isset($fc_val['summary'])) {
$summary_hits = 0;
$fc_summary = preg_replace("/$search_php/$flag", $replace, $fc_val['summary'], -1, $summary_hits);
if ($fc_summary != $fc_val['summary']) {
$fc_hits += $summary_hits;
$fc_changed = TRUE;
$fc->{$matches[1]}[LANGUAGE_NONE][$fc_key]['summary'] = $fc_summary;
}
}
if ($fc_hits > 0) {
$results[] = array(
'title' => $node->title,
'type' => $node->type,
'count' => $fc_hits,
'field' => $field,
'field_label' => $field_label,
'nid' => $node->nid,
);
}
}
// If field collection revision handling is enabled, update the
// revision ID on the field.
// @todo Handle scenarios were the same FC is updated multiple
// times on the same request.
if ($fc_revision_field == 'revision_id') {
$fc->revision = 1;
}
// Update the field collection.
$fc->save(TRUE);
// If field collection revision handling is enabled, update the
// revision ID on the field; the entity's revision_id is updated
// during the save() method, so this is safe to do.
if ($fc_revision_field == 'revision_id') {
$node->{$field_collection_parents[0]}[$fc_lang][$key]['revision_id'] = $fc->revision_id;
}
}
}
}
// Normal node fields.
else {
if (!empty($matches[0])) {
$language = field_language('node', $node, $matches[1]);
// Text Field or Text Area.
$node->{$matches[1]}[$language][$row->delta][$suffix] = $content_new;
// Summary.
if (isset($node->{$matches[1]}[$language][$row->delta]['summary'])) {
$summary = $node->{$matches[1]}[$language][$row->delta]['summary'];
$node->{$matches[1]}[$language][$row->delta]['summary'] = preg_replace("/$search_php/$flag", $replace, $summary, -1, $hits_summary);
$hits += $hits_summary;
}
}
else {
// Other type such as a Title.
$node->$field = $content_new;
}
// Update the counter.
$results[] = array(
'title' => $node->title,
'type' => $node->type,
'count' => $hits,
'field' => $field,
'field_label' => $field_label,
'nid' => $node->nid,
);
}
// A revision only created for the first change of the node. Subsequent
// changes of the same node do not generate additional revisions.
// @todo Need a better way of handling this.
if (!isset($undo_data[$node->nid]['new_vid'])) {
$node->revision = TRUE;
$node->log = t('@name replaced %search with %replace via Scanner Search and Replace module.', array('@name' => $user->name, '%search' => $search, '%replace' => $replace));
$undo_data[$node->nid]['old_vid'] = $node->vid;
}
node_save($node);
// Array to log completed fields in case of shutdown.
$processed[$field][$row->nid][$row->delta] = TRUE;
// Undo data construction.
// Now set to updated vid after node_save().
$undo_data[$node->nid]['new_vid'] = $node->vid;
}
}
}
// If completed.
if (isset($shutting_down) && !$shutting_down) {
variable_del('scanner_partially_processed_' . $user->uid);
variable_del('scanner_partial_undo_' . $user->uid);
}
if ($searchtype == 'search') {
return theme('scanner_results', array('results' => $results));
}
// searchtype == 'replace'.
else {
if (count($undo_data) && !$shutting_down) {
db_insert('scanner')
->fields(array(
'undo_data' => serialize($undo_data),
'undone' => 0,
'searched' => $search,
'replaced' => $replace,
'count' => count($undo_data),
'time' => REQUEST_TIME,
))
->execute();
}
return theme('scanner_replace_results', array('results' => $results));
}
}
// ***************************************************************************
// Internal Utility Functions ************************************************
// ***************************************************************************
/**
* Add all text fields that are buried in field collections.
*
* This function will search recursively down any level of field collection
* nesting to find all text fields. Note that if a field collection is nested
* within itself, this function will not traverse the field collection a second
* time (which would otherwise result in infinite recusion).
*
* @param array $all_field_records
* Array of field records curerntly being built.
* @param string $node_bundle
* The name of the node type being searched. Initially passed in as NULL b/c
* the first time this function runs it finds all "top level" field
* collections, which can be across multiple node types. For each field
* collection instance found, however, if there are field collections inside
* of it we call this function recursively to find more fields. On that
* recursive call, we pass in the node type where the top level field
* collection was found so that we have the appropriate node type to add to
* the text field records added to $all_field_records.
* @param string $parent_bundle
* The bundle in which we are currently searching for field collections.
* Initially passed in as NULL b/c the first time this function runs it finds
* all "top level" field collections, which can be across multiple node types
* (a.k.a. bundles). For each field collection instance found, however, if
* there are field collections inside of it we call this function recursively
* to find more fields. On that recursive call, we pass in the bundle of the
* current field collection being searched so that the query in the recursive
* call can search for fields within that bundle.
* @param array $parents
* An array tracking all field collection parents for text fields we
* eventually find. Initially passed in as NULL b/c the first time this
* function runs it finds all "top level" field collections, which must each
* have its own instance of the $parents array. The $parents array serves
* two purposes. First, it is used to set the "field_collection_parents"
* value for all text fields found. Second, it is used to prevent infinite
* recursion in the case where a field collection is nested within itself.
*/
function _scanner_add_field_collection_fields(array &$all_field_records, $node_bundle = NULL, $parent_bundle = NULL, $parents = NULL) {
$query = db_select('field_config_instance', 'instance_parent');
$query->join('field_config', 'config_parent', 'instance_parent.field_name = config_parent.field_name');
$query->join('field_config_instance', 'instance_child', 'instance_child.bundle = config_parent.field_name');
$query->join('field_config', 'config_child', 'instance_child.field_name = config_child.field_name');
$query->fields('config_child', array('field_name', 'module'));
$query->fields('instance_parent', array('bundle'));
$query->addField('config_parent', 'field_name', 'field_collection_name');
if ($parent_bundle) {
$query->condition('config_parent.field_name', $parent_bundle);
$query->condition('instance_parent.entity_type', 'field_collection_item');
}
else {
$query->condition('instance_parent.entity_type', 'node');
}
$query->condition('instance_child.entity_type', 'field_collection_item');
$query->condition('config_child.module', array('text', 'field_collection'), 'IN');
$result = $query->execute();
foreach ($result as $record) {
$field_parents = isset($parents) ? $parents : array($record->field_collection_name);
$record->node_bundle = isset($node_bundle) ? $node_bundle : $record->bundle;
if ($record->module == 'text') {
$record->field_collection_parents = $field_parents;
$all_field_records[] = $record;
}
elseif ($record->module == 'field_collection') {
// This if statement prevents infinite recursion if a field collection is
// nested within itself.
if (!in_array($record->field_name, $field_parents)) {
$field_parents[] = $record->field_name;
_scanner_add_field_collection_fields($all_field_records, $record->node_bundle, $record->field_name, $field_parents);
}
}
}
}
/**
* Comparison function for sorting fields by table/field label.
*
* @param array $left
* One field.
* @param array $right
* The other field.
*
* @return number
* Comparison value determining which order these two fields should be sorted
* in relation to each other based on field label.
*/
function _scanner_compare_fields_by_label(array $left, array $right) {
$cmp = strcmp($left['type'], $right['type']);
if ($cmp != 0) {
return $cmp;
}
return strcmp($left['field_label'], $right['field_label']);
}
/**
* Comparison function for sorting fields by table/field name.
*
* @param array $left
* One field.
* @param array $right
* The other field.
*
* @return number
* Comparison value determining which order these two fields should be sorted
* in relation to each other based on field name.
*/
function _scanner_compare_fields_by_name(array $left, array $right) {
$cmp = strcmp($left['type'], $right['type']);
if ($cmp != 0) {
return $cmp;
}
return strcmp($left['field'], $right['field']);
}
/**
* Get all text fields.
*
* @return array
* List of all fields, each of which is an array containing relevant data
* used for diplaying/querying.
*/
function _scanner_get_all_tables_map() {
// Build list of title fields for all node types.
$ntypes = node_type_get_types();
foreach ($ntypes as $type) {
if ($type->has_title) {
$tables_map[] = array(
'type' => $type->type,
'field' => 'title',
'field_label' => 'title',
'table' => 'node_revision',
);
}
}
$all_field_records = $fields_type = array();
if (module_exists('field')) {
$fields_type[] = 'text';
}
if (module_exists('link')) {
$fields_type[] = 'link';
}
if (!empty($fields_type)) {
foreach ($fields_type as $fieldType) {
$query = db_select('field_config_instance', 'fci');
$query->join('field_config', 'fc', 'fci.field_name = fc.field_name');
$query->fields('fci', array('field_name'));
$query->fields('fc', array('module'));
$query->addField('fci', 'bundle', 'node_bundle');
$query->condition('fci.entity_type', 'node');
$query->condition('fc.module', $fieldType, '=');
$result = $query->execute();
foreach ($result as $record) {
$all_field_records[] = $record;
}
}
}
if (module_exists('field_collection')) {
_scanner_add_field_collection_fields($all_field_records);
}
foreach ($all_field_records as $record) {
$tables_map[] = array(
'type' => $record->node_bundle,
'field' => $record->field_name,
'field_label' => (empty($record->field_collection_parents) ? '' : join('->', $record->field_collection_parents) . '->') . $record->field_name,
'table' => 'field_revision_' . $record->field_name,
'field_collection_parents' => isset($record->field_collection_parents) ? $record->field_collection_parents : NULL,
'module' => $record->module,
);
}
return $tables_map;
}
/**
* Get the fields that have been selected for scanning.
*
* @return map of selected fields and tables.
*/
function _scanner_get_selected_tables_map() {
$tables_map = _scanner_get_all_tables_map();
foreach ($tables_map as $i => $item) {
$key = 'scanner_' . $item['field'] . '_' . $item['table'] . '_' . $item['type'];
if (!variable_get($key, TRUE)) {
unset($tables_map[$i]);
}
}
return $tables_map;
}
/**
* Attempt to stretch the amount of time available for processing.
*
* This way timeouts won't interrupt search and replace actions. This only works
* in hosting environments where changing PHP and Apache settings on the fly is
* allowed.
*
* @param $setting
* The name of the PHP setting to change.
* @param $value
* The new value to assign.
* @param bool $verbose
* If set to TRUE, an extra message will be displayed indicating the status of
* the execution.
*
* @return bool
* Indicates whether the setting is changed.
*/
function _scanner_change_env($setting, $value, $verbose = FALSE) {
$old_value = ini_get($setting);
if ($old_value != $value && $old_value != 0) {
if (ini_set($setting, $value)) {
if ($verbose) {
drupal_set_message(t('%setting changed from %old_value to %value.', array('%setting' => $setting, '%old_value' => $old_value, '%value' => $value)));
}
return TRUE;
}
else {
if ($verbose) {
drupal_set_message(t('%setting could not be changed from %old_value to %value.', array('%setting' => $setting, '%old_value' => $old_value, '%value' => $value)), 'error');
}
return FALSE;
}
}
}