Regular expression for substitution - php

I'm working on a way to pass variables into a notification, and to accomplish this, I'm currently going through the message once with the following method:
private static function set($var, $content, &$message) {
$message = str_replace("{{" . $var . "}}", $content, $message);
}
This correctly matches; if I have '{{name}}' within message and I run the below method, all instances of '{{name}}' are correctly replaced with 'Johnny Test'. Splendid.
self::set('name', 'Johnny Test', $message);
I'm now looking to expand this to allow for the possibility of a template field not being passed into the notification. That is, if I need 'name' and 'email' for a specific notification but only 'name' gets passed in, I want a default value ('No email address') to be passed in (instead of the notification showing '{{email}}').
To this end, I've concocted the following using this tool:
$returnValue = preg_replace(
'/\\{\\{.*?\\:\\s*?[\'"](.*?)[\'"]\\s*?\\}\\}/',
'$1',
'{{color : "No color selected"}}'
);
However, it doesn't properly match. Instead of $returnValue containing 'No color selected', it instead contains the full-on '{{color : "No color selected"}}'.
What am I missing?

You have too many backslashes. You're working with single quotes, so \ alone has no special meaning.
Therefore:
'/\{\{.*?:\s*[\'"](.*?)[\'"]\s*\}\}/'
Should do it.

This should work:
$returnValue = preg_replace(
'/{{[^:]*:\s*[\'"]([^\'"]*)[\'"]\s*}}/',
'$1',
'{{color : "No color selected"}}'
);

Related

PHP mention system with usernames with space

I wanted to know if it's possible to make a PHP mention system with usernames with space ?
I tried this
preg_replace_callback('##([a-zA-Z0-9]+)#', 'mentionUser', htmlspecialchars_decode($r['content']))
My function:
function mentionUser($matches) {
global $db;
$req = $db->prepare('SELECT id FROM members WHERE username = ?');
$req->execute(array($matches[1]));
if($req->rowCount() == 1) {
$idUser = $req->fetch()['id'];
return '<a class="mention" href="members/profile.php?id='.$idUser.'">'.$matches[0].'</a>';
}
return $matches[0];
It works, but not for the usernames with space...
I tried to add \s, it works, but not well, the preg_replace_callback detect the username and the other parts of the message, so the mention don't appear...
Is there any solution ?
Thanks !
I know you said that you just removed the ability to add a space, but I still wanted to post a solution. To be clear, I don't necessarily think you should use this code, because it probably is just easier to keep things simple, but I think it should work still.
Your major problem is that almost every mention will incur two lookups because #bob johnson went to the store could be either bob or bob johnson and there's no way to determine that without going to the databases. Caching will greatly reduce this problem, luckily.
Below is some code that generally does what you are looking for. I made a fake database using just an array for clarity and reproducibility. The inline code comments should hopefully make sense.
function mentionUser($matches)
{
// This is our "database" of users
$users = [
'bob johnson',
'edward',
];
// First, grab the full match which might be 'name' or 'name name'
$fullMatch = $matches['username'];
// Create a search array where the key is the search term and the value is whether or not
// the search term is a subset of the value found in the regex
$names = [$fullMatch => false];
// Next split on the space. If there isn't one, we'll have an array with just a single item
$maybeTwoParts = explode(' ', $fullMatch);
// Basically, if the string contained a space, also search only for the first item before the space,
// and flag that we're using a subset
if (count($maybeTwoParts) > 1) {
$names[array_shift($maybeTwoParts)] = true;
}
foreach ($names as $name => $isSubset) {
// Search our "database"
if (in_array($name, $users, true)) {
// If it was found, wrap in HTML
$ret = '<span>#' . $name . '</span>';
// If we're in a subset, we need to append back on the remaining string, joined with a space
if ($isSubset) {
$ret .= ' ' . array_shift($maybeTwoParts);
}
return $ret;
}
}
// Nothing was found, return what was passed in
return '#' . $fullMatch;
}
// Our search pattern with an explicitly named capture
$pattern = '##(?<username>\w+(?:\s\w+)?)#';
// Three tests
assert('hello <span>#bob johnson</span> test' === preg_replace_callback($pattern, 'mentionUser', 'hello #bob johnson test'));
assert('hello <span>#edward</span> test' === preg_replace_callback($pattern, 'mentionUser', 'hello #edward test'));
assert('hello #sally smith test' === preg_replace_callback($pattern, 'mentionUser', 'hello #sally smith test'));
Try this RegEx:
/#[a-zA-Z0-9]+( *[a-zA-Z0-9]+)*/g
It will find an at sign first, and then try to find one or more letter or numbers. It will try to find zero or more inner spaces and zero or more letters and numbers coming after that.
I am assuming the username only contains A-Za-z0-9 and space.

Calling a variable that is nowhere else

In the following code, from Beginning PHP and mySQL 5e, if acronym function is called without mentioning $matches, how ,in the definition of acronym, $matches is never linked to anything, but rather used in the isset($acronym[$matches[1]])) ?, How isset knows what is $matches in the first place?
The following is the code and I have tested that it is working.
I just cannot follow up with the use of an arbitrary term; $matches, and its use.
// This function will add the acronym's long form
// directly after any acronyms found in $matches
function acronym($matches) {
$acronyms = array(
'WWW' => 'World Wide Web',
'IRS' => 'Internal Revenue Service',
'PDF' => 'Portable Document Format');
if (isset($acronyms[$matches[1]]))
return $acronyms[$matches[1]] . " (" . $matches[1] . ")";
else
return $matches[1];
}
// The target text
$text = "The <acronym>IRS</acronym> offers tax forms in
<acronym>PDF</acronym> format on the <acronym>WWW</acronym>.";
// Add the acronyms' long forms to the target text
$newtext = preg_replace_callback("/<acronym>(.*)<\/acronym>/U", 'acronym',
$text);
print_r($newtext);
The output is:
The Internal Revenue Service (IRS) offers tax forms inPortable Document Format (PDF) format on the World Wide Web (WWW).
Reminder: The input, for the function preg_replace_callback is:
The <acronym>IRS</acronym> offers tax forms in <acronym>PDF</acronym> format on the <acronym>WWW</acronym>.
The preg_replace_callback() function is written in that way, that it calls the function with a well defined argument. See the manual of this function:
A callback that will be called and passed an array of matched elements in the subject string. The callback should return the replacement string. This is the callback signature:
handler ( array $matches ) : string
So your function acronym() will get an array with the matches from the regex. Keep in mind that you are not calling the acronym() function by yourself, the function preg_replace_callback() does that for you (with the argument defined in the documentation).

Remove certain part of Posted value with php

Hello everyone im trying to retrieve data from a form with a POST request.
This data is posted into another website.
On the website where the data is created i have a text field called website. The data filled in this field goes to the other website where the data is collected. Now i want to exclude the 'www' part. for example if the user enters www.hello.nl i want to receive hello.nl only.
What i tried:
function website () {
$str = $_POST['billing_myfield12'];
echo chop($str,"www");
}
// end remove www
// prepare the sales payload
$sales_payload = array(
'organization_id' => $organization_id,
'contact_id' => $contact_id,
'status' => 'Open',
'subject' => $_product->post_title." ".website(), <----- here i call it
This is not working. Is there a way to do this?
You can use trim() or specifically ltrim() to trim way the www. on the left side. Please don't forget the . after www.
echo ltrim($str, "www.");
Sample Code
echo ltrim("www.hello.nl", "www."); // hello.nl
Demo: http://ideone.com/bqMY7X
Looks like there are side effects with the above code. Let's go with the traditional str_replace method:
echo str_replace("www.", "", $str);
Also, we are sure that it should replace only from the first characters. So, we need to use a preg_replace instead, making it replace from the start.
echo preg_replace("/^www\./g", "", $str);
Verified the above code with: https://regex101.com/r/dv8N6d/1

wordpress content disappears when using if statement or switch/case

When I created a function (in child-theme/functions.php) to modify text in the_content(), the following function worked well.
function my_text_changes ( $text ) {
$text = str_replace( 'My Site Name', '<em>My Site Name</em>', $text );
return $text;
}
add_filter( 'the_content','my_text_changes' );
This function modifies only one portion of text (multiple times). When I changed the function so I could modify more portions of text, I took this same variable and str_replace and put it into a switch/case (also tried if statements) and all the content disappears.
function my_text_changes ( $text ) {
switch( $text ) {
case "My Site Name":
$text = str_replace( 'My Site Name', '<em>My Site Name</em>', $text );
return $text;
break;
}
}
add_filter( 'the_content','my_text_changes' );
I want to build multiple cases but cannot get the first one to work. The same holds true if I change the switch/case to an if statement. I have tried changing $text = and the return $text to $newtext = and return $newtext to no avail. Any ideas?
The $text argument that you are passing there contains the whole content. You switch assignment will never be true (unless the entirety of the content consists of “My Site Name”), and your text will not be substituted.
The reason why everything disappears is because you have to return the $text variable outside of the switch statement (or your if/elses) or else it will display nothing (basically you are substituting the entire content with nothing).
It is, in fact, sufficient that you run your str_replaces without any if/else or switch, if I understand your problem correctly.
Edit:
While the original answer works, there is a better way. You argue in your comment that the second $text will overwrite the first assignment, which is true, but not a problem, since the previous one is already the text with the string correctly replaced. Just give it a try and see by yourself.
In any case, I checked the docs for str_replace and it does accept arrays as arguments, so your problem may be solved like so:
function my_text_changes ( $text ) {
$searches = array( 'My Site Name', 'a second string' );
$replaces = array( '<em>My Site Name</em>', 'a <strong>second</strong> string' );
$new_text = str_replace( $searches, $replaces, $text );
/* ... */
return $new_text;
}
Original answer
Just do:
function my_text_changes ( $text ) {
$text = str_replace( 'My Site Name', '<em>My Site Name</em>', $text );
$text = str_replace( 'second_string', 'replacement', $text);
/* ... */
return $text;
}
You shouldn't use return statement before break.
But the issue seems to be different than the code cited. Can you check the php error logs on the hosting and share the same?

Can I add variable name within a string?

I am creating an OpenCart extension where the admin can change his email templates using the user interface in the admin panel.
I would like the user to have the option to add variables to his custom email templates. For example he could put in:
Hello $order['customer_firstname'], your order has been processed.
At this point $order would be undefined, the user is simply telling defining the message that is to be sent. This would be stored to the database and called when the email is to be sent.
The problem is, how do I get "$order['customer_firstname']" to become a litteral string, and then be converted to a variable when necessary?
Thanks
Peter
If I understand your question correctly, you could do something like this:
The customer has a textarea or similar to input the template
Dear %NAME%, blah blah %SOMETHING%
Then you could have
$values = array('%SOMETHING%' => $order['something'], '%NAME%' => $order['name']);
$str = str_replace(array_keys($values), array_values($values), $str);
the user will be using around 40 variables. Is there a way I can set it to do that for each "%VARIABLE%"?
Yes, you can do so for each variable easily with the help of a callback function.
This allows you, to process each match with a function of your choice, returning the desired replacement.
$processed = preg_replace_callback("/%(\S+)%/", function($matches) {
$name = $matches[1]; // between the % signs
$replacement = get_replacement_if_valid($name);
return $replacement;
},
$text_to_replace_in
);
From here, you can do anything you like, dot notation, for example:
function get_replacement_if_valid($name) {
list($var, $key) = explode(".", $name);
if ($var === "order") {
$order = init_oder(); // symbolic
if(array_key_exists($key, $order)) {
return $order[$key];
}
}
return "<invalid key: $name>";
}
This simplistic implementation allows you, to process replacements such as %order.name% substituting them with $order['name'].
You could define your own simple template engine:
function template($text, $context) {
$tags = preg_match_all('~%([a-zA-Z0-9]+)\.([a-zA-Z0-9]+)%~', $text, $matches);
for($i = 0; $i < count($matches[0]); $i++) {
$subject = $matches[0][$i];
$ctx = $matches[1][$i];
$key = $matches[3][$i];
$value = $context[$ctx][$key];
$text = str_replace($subject, $value, $text);
}
return $text;
}
This allows you to transform a string like this:
$text = 'Hello %order.name%. You have %order.percent%% discount. Pay a total ammount of %payment.ammount% using %payment.type%.';
$templated = template($text, array(
'order' => array(
'name' => 'Alex',
'percent' => 20
),
'payment' => array(
'type' => 'VISA',
'ammount' => '$299.9'
)
));
echo $templated;
Into this:
Hello Alex. You have 20% discount. Pay a total ammount of $299.9 using VISA.
This allows you to have any number of variables defined.
If you want to keep the PHP-syntax, then a regex would be appropriate to filter them:
$text = preg_replace(
"/ [$] (\w+) \[ '? (\w+) \'? \] /exi",
"$$1['$2']", # basically a constrained eval
$text
);
Note that it needs to be executed in the same scope as $order is defined. Else (and preferrably) use preg_replace_callback instead for maximum flexibility.
You could also allow another syntax this way. For example {order[customer]} or %order.customer% is more common and possibly easier to use than the PHP syntax.
You can store it as Hello $order['customer_firstname'] and while accessing make sure you have double-quotes "" to convert the variable to its corresponding value.
echo "Hello $order['customer_firstname']";
Edit: As per the comments, a variation to Prash's answer,
str_replace('%CUSTOMERNAME%', $order['customer_name'], $str);
What you're looking for is:
eval("echo \"" . $input . "\";");
but please, PLEASE don't do that, because that lets the user run any code he wants.
A much better way would be a custom template-ish system, where you provide a list of available values for the user to drop in the code using something like %user_firstname%. Then, you can use str_replace and friends to swap those tags out with the actual values, but you can still scan for any sort of malicious code.
This is why Markdown and similar are popular; they give the user control over presentation of his content while still making it easy to scan for HTML/JS/PHP/SQL injection/anything else they might try to sneak in, because whitelisting is easier than blacklisting.
Perhaps you can have a template like this:
$tpl = "Hello {$order['customer_firstname']}, your order has been processed.".
If $order and that specific key is not null, you can use echo $tpl directly and show the content of 'customer_firstname' key in the text. The key are the curly braces here.

Categories