PHP preg_quote in replacement string - php

I use preg_replace and I want to include a url inside the replacement string. How do I quote that string? It appears preg_quote is only for the search pattern.
$replace = '\1'.addslashes($url).'\3'.addslashes($title).'\4';

Escapes are needed
addslashes is not sufficient
preg_quote escapes too much
See this demo.
As Mario commented you can use addcslashes($str, "\\$").

Unfortunately there is no generic way to do this, but addslashes should be enough in most cases.
For maximum safety you can use the ${1} syntax. E.g.
$replace = '${1}'.addslashes($url).'${3}'.addslashes($title).'${4}';
If you really want to be totally bulletproof, use a callback replacement function with preg_replace_callback(). The string returned from the callback function is used entirely as-is, so you don't have to worry about mixing replacement syntax with normal text.
Example with preg_replace_callback():
class URLReplacer {
public $pattern = '/my regex/';
public $url;
public $title;
function __construct($url, $title) {
$this->url = $url;
$this->title = $title;
}
function _callback($matches) {
return $matches[1].$url.$matches[3].$title.$matches[4];
}
function replace($subject) {
return preg_replace_callback($this->pattern, array($this, '_callback'), $subject);
}
}
$repl = new URLReplacer($url, $title);
$replaced = $repl->replace($subject);

You did not provide an example, so I compiled one on my own. The working solution I came up with is using a simple callback function:
$url = 'http://example.com/';
$title = 'Make it Complex \4';
$subject = 'Call \\4 " $me an url';
$pattern = '/(.*)an()( )(url)/';
$replace = function($m) use ($url, $title)
{
return "$m[1]$url$m[3]$title$m[4]";
};
$result = preg_replace_callback($pattern, $replace, $subject);
Result:
Call \4 " $me http://example.com/ Make it Complex \4url
The callback function is a so called anonymous function Docs which makes it easy to edit the code in place.
In case you need this more often you can put that into a function of your own, probably to make it more re-useable. You can even go to that far and create yourself your own pattern to replace subgroup matches and variables. For examle {\1} stands for the subpattern 1 match, {$2} for the second variable. Wrapping this into a function of it's own:
$patternf = function()
{
$values = func_get_args();
$mask = $values ? array_shift($values) : NULL;
return function($matches) use ($mask, $values)
{
$parts = preg_split('/({[\\\\\\$][0-9]{1,3}})/', $mask, 0, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
foreach($parts as &$part)
if (preg_match('/^{([\\\\\\$])([0-9]{1,3})}$/', $part, $p))
$part = $p[1] == '\\' ? $matches[(int)$p[2]] : $values[$p[2]-1];
return implode('', $parts);
};
};
Would allow you to make the replacement more handy:
$replace = $patternf('{\1}{$1}{\3}{$2}{\4}', $url, $title);
$result = preg_replace_callback($pattern, $replace, $subject);
Demo. Wrapping this into a function of it's own:
function preg_replace_subst($pattern, $replace, $subject)
{
$values = func_get_args();
$pattern = array_shift($values);
$mask = array_shift($values);
$subject = array_shift($values);
$callback = function($matches) use ($mask, $values)
{
$parts = preg_split('/({[\\\\\\$][0-9]{1,3}})/', $mask, 0, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
foreach($parts as &$part)
if (preg_match('/^{([\\\\\\$])([0-9]{1,3})}$/', $part, $p))
$part = $p[1] == '\\' ? $matches[(int)$p[2]] : $values[$p[2]-1];
return implode('', $parts);
};
return preg_replace_callback($pattern, $callback, $subject);
}
Would give it an easy interface:
$url = 'http://example.com/';
$title = 'Make it Complex \4';
$subject = 'Call \\4 " $me an url';
$pattern = '/(.*)an()( )(url)/';
$replace = '{\1}{$1}{\3}{$2}{\4}';
$result = preg_replace_subst($pattern, $replace, $subject, $url, $title);
But with many substitution variables it should be possible to pass them as an array probably otherwise it gets a bit lengthy.
Using the e modifier with preg_replace (and why it does not work)
When using the e modifier, the matches get replaced in the replace string and it then get's evaluated. As other variables do not get escaped, the matches do interfere with PHP variable substitution, which is dangerous:
$url = 'http://example.com/';
$title = 'Make it Complex \4';
$subject = 'Call me an url.';
$pattern = '/(.*)an()( )(url)/e';
$replace = '"$1{$url}$3{$title}$4"';
$result = preg_replace($pattern, $replace, $subject);
Outputs:
Call me http://example.com/ Make it Complex \4url.
As written, the first e-modifier example is broken because $ won't get escape in $subject, so PHP would have looked for unset variables. That's dangerous, too. I came up with a variant, it solves that issue but it can't handle double-quotes in the subject:
$url = 'http://example.com/';
$title = 'Make it Complex \4';
$subject = 'Call \\4 " $me an url';
$pattern = '/(.*)an()( )(url)/e';
$replace = "'\$1'.\$url.'\$3'.\$title.'$4'";
Output:
Call \4 \" $me http://example.com/ Make it Complex \4url
^ problem, not in input.
So not really fool-proof, that's why it needs the callback function because it gets the matching sub-patterns unquoted.

To make it obvious one is escaping any potential back-references in the $replacement parameter of preg_replace(), use a function:
function preg_quote_replacement($input) {
return addcslashes($input, '\\$');
}
In OP's case:
$subject = preg_replace(
$pattern,
'\1'.preg_quote_replacement($url).'\3'.preg_quote_replacement($title).'\4',
$subject
);

You should use Prepared Patterns. It works like Prepared Statements in SQL:
Pattern::inject("\1#url\2#url\3", ['url' => $input]);

Related

Trying to call a function inside preg_replace

$string = 'Hello [user=1]';
$bbc = array('/\[user=(.*?)\]/is');
$replace = array(user('textlink',$1));
$s = preg_replace($bbc , $replace, $string);
echo $s
How do I change $1 in preg_replace with a function?
If I understand you correctly, you want to execute user() function on each match? As mentioned in comments, use the preg_replace_callback() function.
<?php
$string = 'Hello [user=1]';
$s = preg_replace_callback(
'/\[user=(.*?)\]/is',
function($m) {return user('textlink', $m[1]);},
$string
);
echo $s;
You may use a preg_replace_callback_array:
$string = 'Hello [user=1]';
$bbc = array(
'/\[user=(.*?)\]/is' => function ($m) {
return user('textlink',$m[1]);
});
$s = preg_replace_callback_array($bbc, $string);
echo $s;
See PHP demo.
You may add up the patterns and callbacks to $bbc.

PHP Get the word replaced by preg_replace

How can I get the replaced word by preg_replace() function.
preg_replace('/[#]+([A-Za-z0-9-_]+)/', '$0', $post );
I want to get $1 variable so that I can user it further.
Capture it before you replace the expression:
// This is where the match will be kept
$matches = array();
$pattern = '/[#]+([A-Za-z0-9-_]+)/';
// Check if there are matches and capture the user (first group)
if (preg_match($pattern, $post, $matches)) {
// First match is the user
$user = $matches[1];
// Do the replace
preg_replace($pattern, '$0', $post );
}
This isn't possible with preg_replace() as it returns the finished string/array, but does not preserve the replaced phrases. You can use preg_replace_callback() to manually achieve this.
$pattern = '/[#]+([A-Za-z0-9-_]+)/';
$subject = '#jurgemaister foo #hynner';
$tokens = array();
$result = preg_replace_callback(
$pattern,
function($matches) use(&$tokens) {
$tokens[] = $matches[1];
return ''.$matches[0].'';
},
$subject
);
echo $result;
// #jurgemaister foo #hynner
print_r($tokens);
// Array
// (
// [0] => jurgemaister
// [1] => hynner
// )
You should use preg_match in addition to preg_replace. preg_replace is just for replacing.
$regex = '/[#]+([A-Za-z0-9-_]+)/';
preg_match($regex, $post, $matches);
preg_replace($regex, '$0', $post );
You can't do that with preg_replace, but you can do it with preg_replace_callback:
preg_replace_callback($regex, function($matches){
notify_user($matches[1]);
return "<a href='/$matches[1]' target='_blank'>$matches[0]</a>";
}, $post);
replace notify_user with whatever you would call to notify the user.
This can also be modified to check whether the user exists and replace only valid mentions.

Replace and retrieve placeholder value

Is there any way I can replace one value and retrieve another in the same string in a more efficient way than in the code below, for example a method that combines preg_replace() and preg_match()?
$string = 'abc123';
$variable = '123';
$newString = preg_replace("/(abc)($variable)/",'$1$2xyz', $string);
preg_match("/(abc)($variable)/", $string, $matches);
$number = $matches[2];
You can use a single call to preg_replace_callback() and update the value of $number in the code of the callback function:
$string = 'abc123';
$variable = '123';
$number = NULL;
$newString = preg_replace_callback(
"/(abc)($variable)/",
function ($matches) use (& $number) {
$number = $matches[2];
return $matches[1].$matches[2].'xyz';
},
$string
);
I don't think there is a big speed improvement. The only advantage could be on the readability.

Preg_replace return $1 insted of real content

I am having this a function inside a preg_replace().
I am using it like this:
$pattern[] = "/\[test\](.*?)\[\/test\]/is";
$replace[] = $this->test('$1');
$content = preg_replace($pattern, $replace, $content);
Then the function test() prints out the value which is sent to it.
But the value is always just $1, while it should be the content from [test]...[/test].
Any ideas how to do this?
test() will never receive the value of $1, it will always get the literal string "$1". When you do $this->test(), you call the test() function, and it receives what you put in the parenthesis as arguments.
By the time test() executes, the regular expression has yet to be evaluated. You would have to do:
$pattern = "/\[test\](.*?)\[\/test\]/is";
$content = $this->test( preg_replace( $pattern, '$1', $content));
This would cause test() to receive the value of $1. Otherwise, you'd need preg_replace_callback():
$pattern[] = "/\[test\](.*?)\[\/test\]/is";
$content = preg_replace($pattern, function( $match) {
return $this->test( $match[1]);
}, $content);
If you want the matches being replaced by the return value of the $this->test method with the corresponding matched string of the first sub-pattern, you need to use preg_replace_callback and a wrapper function instead:
$pattern = "/\[test\](.*?)\[\/test\]/is";
$replace = function($match) use ($this) { return $this->test($match[1]); };
$content = preg_replace_callback($pattern, $replace, $content);
Single quotes means literal string.
so '$1' will return $1
while "$1" will interpret the regex captured value stored in $1 to its value

How do I use a part of preg_replace pattern as a variable?

function anchor($text)
{
return preg_replace('#\>\>([0-9]+)#','<span class=anchor>>>$1</span>', $text);
}
This piece of code is used to render page anchor.
I need to use the
([0-9]+)
part as a variable to do some maths to define the exact url for the href tag.
Thanks.
Use preg_replace_callback instead.
In php 5.3 +:
$matches = array();
$text = preg_replace_callback(
$pattern,
function($match) use (&$matches){
$matches[] = $match[1];
return '<span class=anchor><a href="#$1">'.$match[1].'</span>';
}
);
In php <5.3 :
global $matches;
$matches = array();
$text = preg_replace_callback(
$pattern,
create_function('$match','global $matches; $matches[] = $match[1]; return \'<span class=anchor><a href="#$1">\'.$match[1].\'</span>\';')
);

Categories