|'case'|'regex'
* }
*/
class String_Value extends Modifier\Modifier {
const BEFORE = '';
const AFTER = '';
/**
* Value to search for. Only used in a search/replace
*/
private ?string $search_value = null;
/**
* Value to replace, or the column value in a 'set'
*/
private ?string $replace_value = null;
/**
* Search flags
*/
private Search\Flags $search_flags;
/**
* Position within the column to replace
*/
private ?int $pos_id = null;
/**
* Constructor
*
* @param StringModifierOption $option String modification options.
* @param Schema\Column $schema Schema.
*/
public function __construct( $option, Schema\Column $schema ) {
parent::__construct( $option, $schema );
// @phpstan-ignore booleanAnd.rightAlwaysTrue
if ( isset( $option['searchValue'] ) && is_string( $option['searchValue'] ) ) {
$this->search_value = $option['searchValue'];
}
// @phpstan-ignore booleanAnd.rightAlwaysTrue
if ( isset( $option['replaceValue'] ) && is_string( $option['replaceValue'] ) ) {
$this->replace_value = $option['replaceValue'];
}
$this->operation = 'set';
if ( isset( $option['operation'] ) && in_array( $option['operation'], [ 'set', 'replace' ], true ) ) {
$this->operation = $option['operation'];
}
if ( isset( $option['posId'] ) ) {
$this->pos_id = intval( $option['posId'], 10 );
}
$flags = $option['searchFlags'] ?? [ 'case' ];
if ( ! is_array( $flags ) ) {
$flags = [ $flags ];
}
$this->search_flags = new Search\Flags( $flags );
}
public function is_valid() {
if ( $this->operation === 'replace' && $this->search_value === '' ) {
return false;
}
return parent::is_valid();
}
public function to_json() {
$parent_json = parent::to_json();
return [
'column' => $parent_json['column'],
'source' => $parent_json['source'],
'operation' => $this->operation,
'searchValue' => $this->search_value,
'replaceValue' => $this->replace_value,
'searchFlags' => $this->search_flags->to_json(),
];
}
/**
* Return all the replace positions - the positions within the content where the search is matched.
*
* @param string $value Value to search and replace within.
* @return array Array of match positions
*/
public function get_replace_positions( $value ) {
if ( ! $this->search_value || $this->replace_value === null ) {
return [];
}
$replace_value = $this->replace_value;
if ( ! $this->search_flags->is_regex() ) {
// Escape the replace value, in case it has a $ in it
$replace_value = \preg_replace( '/(?replace_value );
}
// Global replace
$result = $this->replace_all( $this->search_value, self::BEFORE . $replace_value . self::AFTER, $value );
// Split into array
$pattern = '@' . self::BEFORE . '(.*?)' . self::AFTER . '@s';
if ( $this->search_flags->is_case_insensitive() ) {
$pattern .= 'i';
}
if ( \preg_match_all( $pattern, $result, $searches ) > 0 ) {
return $searches[1];
}
return [];
}
/**
* Perform a global replacement
*
* @internal
* @param string $search Search string.
* @param string $replace Replacement value.
* @param string $value Content to replace.
* @return string
*/
private function replace_all( $search, $replace, $value ) {
$pattern = Search\Text::get_pattern( $search, $this->search_flags );
if ( ! $this->search_flags->is_regex() && is_serialized( $value ) ) {
$serial = '/s:(\d*):"(.*?)";/s';
return (string) preg_replace_callback(
$serial, function ( $matches ) use ( $search, $replace ) {
if ( strpos( $matches[2], $search ) !== false ) {
$replaced = str_replace( $search, $replace, $matches[2] );
return 's:' . (string) strlen( $replaced ) . ':"' . $replaced . '";';
}
return $matches[0];
}, $value
);
}
// Global replace
return (string) preg_replace( $pattern, $replace, $value );
}
public function perform( $row_id, $row_value, Source\Source $source, Search\Column $column, array $raw, $save_mode ) {
if ( $this->operation === 'set' ) {
// Identical - just return value
if ( $this->replace_value === $row_value ) {
return $column;
}
$value = apply_filters( 'searchregex_text', $this->replace_value, $row_id, $row_value, $raw, $source->get_schema_item() );
if ( $value !== $row_value ) {
$replacement = new Context\Type\Replace( $row_value );
$replacement->set_replacement( $value );
$column->set_contexts( [ $replacement ] );
}
return $column;
}
if ( ! $this->search_value ) {
return $column;
}
if ( $this->pos_id === null ) {
if ( $this->replace_value === null ) {
return $column;
}
// When not saving we need to return the individual replacements. If saving then we want to return the whole text
if ( $save_mode ) {
$global_replace = $this->replace_all( $this->search_value, $this->replace_value, $row_value );
$value = apply_filters( 'searchregex_text', $global_replace, $row_id, $row_value, $raw, $source->get_schema_item() );
// Global replace
if ( $row_value !== $value ) {
$replacement = new Context\Type\Replace( $row_value );
$replacement->set_replacement( $value );
$column->set_contexts( [ $replacement ] );
}
return $column;
}
$replacements = $this->get_replace_positions( $row_value );
$filter = new Filter\Type\Filter_String(
[
'value' => $this->search_value,
'logic' => 'contains',
'flags' => $this->search_flags->to_json(),
], $this->schema
);
$matches = $filter->get_match( $source, new Action\Type\Nothing(), 'contains', $this->search_value, $row_value, $this->search_flags, $replacements );
// If we replaced anything then update the context with our new matches, otherwise just return whatever we have
if ( ( count( $matches ) === 1 && ! $matches[0] instanceof Context\Type\Value ) || count( $matches ) > 1 ) {
$column->set_contexts( $matches );
}
return $column;
}
// Replace a specific position
$replacements = $this->get_replace_positions( $row_value );
$contexts = Search\Text::get_all( $this->search_value, $this->search_flags, $replacements, $row_value );
foreach ( $contexts as $context ) {
$match = $context->get_match_at_position( $this->pos_id );
if ( is_object( $match ) ) {
$value = apply_filters( 'searchregex_text', $match->replace_at_position( $row_value ), $row_id, $row_value, $raw, $source->get_schema_item() );
// Need to replace the match with the result in the raw data
if ( $row_value !== $value ) {
$context = new Context\Type\Replace( $row_value );
$context->set_replacement( $value );
$column->set_contexts( [ $context ] );
}
return $column;
}
}
return $column;
}
/**
* Get replacement value
*
* @return string|null
*/
public function get_replace_value() {
return $this->replace_value;
}
}