function scanner_execute in Search and Replace Scanner 7
Same name and namespace in other branches
- 5.2 scanner.module \scanner_execute()
- 6 scanner.module \scanner_execute()
Handles the actual search and replace.
Parameters
string $searchtype:
Return value
The themed results.
1 call to scanner_execute()
- scanner_view in ./
scanner.module - Menu callback; presents the scan form and results.
File
- ./
scanner.module, line 706 - Search and Replace Scanner - works on all nodes text content.
Code
function scanner_execute($searchtype = 'search') {
global $user;
// Variables to monitor possible timeout.
$max_execution_time = ini_get('max_execution_time');
$start_time = REQUEST_TIME;
$expanded = FALSE;
// Get process and undo data if saved from timeout.
$processed = variable_get('scanner_partially_processed_' . $user->uid, array());
$undo_data = variable_get('scanner_partial_undo_' . $user->uid, array());
// Get the field collection field to use when joining revisions, based on
// whether the current version of the field_collection module has revisions
// enabled (7.x-1.0-beta5)
$fc_revision_field = drupal_get_schema('field_collection_item_revision') ? 'revision_id' : 'value';
unset($_SESSION['scanner_status']);
$search = $_SESSION['scanner_search'];
$replace = $_SESSION['scanner_replace'];
$preceded = $_SESSION['scanner_preceded'];
$followed = $_SESSION['scanner_followed'];
$mode = $_SESSION['scanner_mode'];
// Case sensitivity flag for use in php preg_search and preg_replace.
$flag = $mode ? NULL : 'i';
$wholeword = $_SESSION['scanner_wholeword'];
$regex = $_SESSION['scanner_regex'];
$published = $_SESSION['scanner_published'];
$pathauto = $_SESSION['scanner_pathauto'];
$terms = isset($_SESSION['scanner_terms']) ? $_SESSION['scanner_terms'] : NULL;
$results = NULL;
if ($searchtype == 'search') {
drupal_set_message(t('Searching for: [%search] ...', array(
'%search' => $search,
)));
}
else {
drupal_set_message(t('Replacing [%search] with [%replace] ...', array(
'%search' => $search,
'%replace' => $replace,
)));
}
$preceded_php = '';
if (!empty($preceded)) {
if (!$regex) {
$preceded = addcslashes($preceded, SCANNER_REGEX_CHARS);
}
$preceded_php = '(?<=' . $preceded . ')';
}
$followed_php = '';
if (!empty($followed)) {
if (!$regex) {
$followed = addcslashes($followed, SCANNER_REGEX_CHARS);
}
$followed_php = '(?=' . $followed . ')';
}
// Case 1.
if ($wholeword && $regex) {
$where = "[[:<:]]" . $preceded . $search . $followed . "[[:>:]]";
$search_php = '\\b' . $preceded_php . $search . $followed_php . '\\b';
}
elseif ($wholeword && !$regex) {
$where = "[[:<:]]" . $preceded . addcslashes($search, SCANNER_REGEX_CHARS) . $followed . "[[:>:]]";
$search_php = '\\b' . $preceded_php . addcslashes($search, SCANNER_REGEX_CHARS) . $followed_php . '\\b';
}
elseif (!$wholeword && $regex) {
$where = $preceded . $search . $followed;
$search_php = $preceded_php . $search . $followed_php;
}
else {
$where = $preceded . addcslashes($search, SCANNER_REGEX_CHARS) . $followed;
$search_php = $preceded_php . addcslashes($search, SCANNER_REGEX_CHARS) . $followed_php;
}
// If terms selected, then put together extra join and where clause.
$join = '';
if (is_array($terms) && count($terms)) {
$terms_where = array();
$terms_params = array();
foreach ($terms as $term) {
$terms_where[] = 'tn.tid = %d';
$terms_params[] = $term;
}
$join = 'INNER JOIN {taxonomy_term_node} tn ON t.nid = tn.nid';
$where .= ' AND (' . implode(' OR ', $terms_where) . ')';
}
$table_map = _scanner_get_selected_tables_map();
usort($table_map, '_scanner_compare_fields_by_name');
// Examine each field instance as chosen in settings.
foreach ($table_map as $map) {
$table = $map['table'];
$field = $map['field'];
$field_label = $map['field_label'];
$type = $map['type'];
$module = isset($map['module']) ? $map['module'] : NULL;
$field_collection_parents = isset($map['field_collection_parents']) ? $map['field_collection_parents'] : NULL;
// Allow the table suffix to be altered.
$suffix = 'value';
// Trigger hook_scanner_field_suffix_alter().
drupal_alter('scanner_field_suffix', $suffix, $map);
$query = db_select($table, 't');
if ($table == 'node_revision') {
$vid = 'vid';
}
else {
$field = $field . '_' . $suffix;
$field_label = $field_label . '_' . $suffix;
$vid = 'revision_id';
}
if (!empty($field_collection_parents)) {
$cnt_fc = count($field_collection_parents);
// Loop thru the parents backwards, so that the joins can all be created.
for ($i = $cnt_fc; $i > 0; $i--) {
$fc_this = $field_collection_parents[$i - 1];
$fc_alias = 'fc' . ($i - 1);
$fc_table = 'field_revision_' . $fc_this;
$prev_alias = $i == $cnt_fc ? 't' : 'fc' . $i;
$query
->join($fc_table, $fc_alias, format_string('!PREV_ALIAS.entity_id = !FC_ALIAS.!FC_THIS_value AND !PREV_ALIAS.revision_id = !FC_ALIAS.!FC_THIS_!FC_REV', array(
'!FC_ALIAS' => $fc_alias,
'!FC_THIS' => $fc_this,
'!PREV_ALIAS' => $prev_alias,
'!FC_REV' => $fc_revision_field,
)));
if ($i == 1) {
$query
->join('node', 'n', format_string('!FC_ALIAS.entity_id = n.nid AND !FC_ALIAS.revision_id = n.vid', array(
'!FC_ALIAS' => $fc_alias,
)));
}
}
}
else {
// Must use vid and revision_id here. Make sure it saves as new revision.
$query
->join('node', 'n', 't.' . $vid . ' = n.vid');
}
if (is_array($terms) && !empty($terms)) {
$terms_or = db_or();
$query
->join('taxonomy_index', 'tx', 'n.nid = tx.nid');
foreach ($terms as $term) {
$terms_or
->condition('tx.tid', $term);
}
$query
->condition($terms_or);
}
$query
->addField('t', $field, 'content');
if ($table != 'node_revision') {
$query
->fields('t', array(
'delta',
));
}
$query
->fields('n', array(
'nid',
'title',
));
$query
->condition('n.type', $type, '=');
// Build the master 'where' arguments.
$or = db_or();
$binary = $mode ? ' BINARY' : '';
// Trigger hook_scanner_query_where().
foreach (module_implements('scanner_query_where') as $module_name) {
$function = $module_name . '_scanner_query_where';
$function($or, $table, $field, $where, $binary);
}
$query
->condition($or);
if ($published) {
$query
->condition('n.status', '1', '=');
}
$result = $query
->execute();
$shutting_down = FALSE;
// Perform the search or replace on each hit for the current field instance.
foreach ($result as $row) {
// Results of an entity property, e.g. the node title, won't have a
// 'delta' attribute, so make sure there is one.
if (!isset($row->delta)) {
$row->delta = 0;
}
$content = $row->content;
$summary = isset($row->summary) ? $row->summary : '';
$matches = array();
$text = '';
// If the max_execution_time setting has been set then check for possible
// timeout. If within 5 seconds of timeout, attempt to expand environment.
if ($max_execution_time > 0 && REQUEST_TIME >= $start_time + $max_execution_time - 5) {
if (!$expanded) {
if ($user->uid > 0) {
$verbose = TRUE;
}
else {
$verbose = FALSE;
}
if (_scanner_change_env('max_execution_time', '600', $verbose)) {
drupal_set_message(t('Default max_execution_time too small and changed to 10 minutes.'), 'error');
$max_execution_time = 600;
}
$expanded = TRUE;
}
else {
$shutting_down = TRUE;
variable_set('scanner_partially_processed_' . $user->uid, $processed);
variable_set('scanner_partial_undo_' . $user->uid, $undo_data);
if ($searchtype == 'search') {
drupal_set_message(t('Did not have enough time to complete search.'), 'error');
}
else {
drupal_set_message(t('Did not have enough time to complete. Please re-submit replace'), 'error');
}
break 2;
}
}
$node = node_load($row->nid);
// Build the regular expression used later.
$regexstr = "/{$search_php}/{$flag}";
// Search.
if ($searchtype == 'search') {
$matches = array(
'0' => array(),
);
$hits = 0;
// Assign matches in the base text field to $matches[0].
// Trigger hook_scanner_preg_match_all().
foreach (module_implements('scanner_preg_match_all') as $module_name) {
$function = $module_name . '_scanner_preg_match_all';
$new_matches = array();
$hits += $function($new_matches, $regexstr, $row);
$matches = array_merge($matches, $new_matches);
}
if ($hits > 0) {
$context_length = 70;
$text .= '<ul>';
foreach ($matches as $key => $item) {
$string = $key == 0 ? $content : $summary;
foreach ($item as $match) {
$text .= '<li>';
// if ($key == 1) {
// $text .= '<i>Summary:</i> ';
// }
// 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 .= '<strong>' . htmlentities($match[0], ENT_COMPAT, 'UTF-8') . '</strong>';
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 .= '</li>';
}
}
$text .= '</ul>';
}
else {
$text = '<div class="messages warning"><h2 class="element-invisible">Warning message</h2>' . t("Can't display search result due to conflict between search term and internal preg_match_all function.") . '</div>';
}
$results[] = array(
'title' => $row->title,
'type' => $type,
'count' => $hits,
'field' => $field,
'field_label' => $field_label,
'nid' => $row->nid,
'text' => $text,
);
}
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;
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($regexstr, $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($regexstr, $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;
}
}
}
}
else {
// Trigger hook_scanner_preg_replace().
foreach (module_implements('scanner_preg_replace') as $module_name) {
$function = $module_name . '_scanner_preg_replace';
$hits += $function($node, $field, $matches, $row, $regexstr, $replace);
}
// Update the counter.
$results[] = array(
'title' => $node->title,
'nid' => $node->nid,
'type' => $node->type,
'count' => $hits,
'field' => $field,
'field_label' => $field_label,
);
}
// 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,
));
}
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,
));
}
}