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?
Related
Ok, so what I have is a wordpress site with a lot of posts containing many paragraphs I don't need. Using a SQL query to remove these from the database would be ideal, however I doubt it can be done, so I'm focusing on getting the post content with the get_the_content() and filtering what I don't need using the DOMDocument. To make things more complicated, these elements cannot be identified by ids or classes.
Example: I have a WP article/post containing this sentence:
<p>How is everything going today?</p>
I want to search for "How is" (case insensitive, if possible) and remove the entire P element.
This is what I have got so far:
<?php
// the_content();
error_reporting(0);
$dom = new DOMDocument;
libxml_use_internal_errors(true);
$dom->loadHTML(strip_tags(mb_convert_encoding(get_the_content(), 'HTML-ENTITIES', 'UTF-8'), '<a>,<iframe>,<figure>,<figcaption>,<video>,<img>,<p>,<br>,<div>,<table>,<thead>,<tbody>,<tfoot>,<tr>,<th>,<td>,<ul>,<ol>,<li>,<h2>,<h3>,<h4>,<h5>,<h6>'), LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD | LIBXML_NOWARNING | LIBXML_NONET | LIBXML_NOERROR);
libxml_clear_errors();
$xpath = new DOMXPath($dom);
//this should keep the BR tags
foreach ($xpath->query('//br') as $br) {
$br->parentNode->replaceChild($dom->createTextNode("\n"), $br);
}
//this should remove empty html tags
while (($node_list = $xpath->query('//*[not(*) and not(#*) and not(text()[normalize-space()])]')) && $node_list->length) {
foreach ($node_list as $node) {
$node->parentNode->removeChild($node);
}
}
//this should remove the only tags
$nbsp = $xpath->query("//*[text()=\" \xC2\xA0\" or text()=\"\xC2\xA0\"]"); # this should remove ony elements such as <p> </p> or <p> </p>
foreach($nbsp as $e) {
$e->parentNode->removeChild($e);
}
//this should remove tags containing certain text strings - THIS IS THE ISSUE
foreach ($xpath->query("//*[text()[contains(.,'This is a') //I want this to remove <p>This is a sentence I do not want.</p>
or contains(.,'This is another') //I want this to remove <span>This is another sentence I do not want.</span>
or contains(.,'height:') //I want this to remove <span>height:50px;width:40px</span>
or contains(.,'înapoi') //I want this to remove <i>Înainte, nu inapoi</i>
]]") as $attr) {
$attr->parentNode->removeChild($attr);
}
echo wpautop($dom->saveHTML(), true);
?>
This is working, but not always, and I cannot understand why. Sometimes all the text from the post is removed. Other times, even if the sentence is in a p paragraph at the end of the post, it gets removed along with other 2-3 paragraphs before it. It seems to happen random, for some posts it is working, for others not.
I should mention that there are almost 150 sentences/strings that I need removed, so I have almost 150 or contains lines. Maybe too many and the site cannot handle them?!
So, is there anything with my code, or do you have other better idea about how to remove elements (p, div, span, any basically) containing certain text strings?
If it matters, I'm on an Ubuntu 20.04 laptop, running Wordpress on nginx and php 7.4.
EDIT:
I will try again to give a more precise answer.
You want to
keep the br tags
remove empty tags
remove -tags
replace some strings
point 3.) There are no -tags. This you can only see in the Wordpress-Editor, but not in the frontend. In the database I think there is always a line break between two paragraphs then . (a space) and again a line break. Wordpress then makes P-tags out of this in the output. However, this can also be turned off completely. (End of code.)
1.,2.,4.)
functions.php
// 1. br tags will stay
// 3. replace strings
function replaceTextinTheContent($the_content){
$replace = array(
// 'WORD TO REPLACE' => 'REPLACE WORD WITH THIS'
'<span>This is another sentence I do not want.</span>' => '',
'<i>Înainte, nu inapoi</i>' => 'bar',
'<span>height:50px;width:40px</span>' => '',
'This is a sentence I do not want.' => '', // P-tags are not saved in the Database, so we could only search the text
);
$the_content = str_replace(array_keys($replace), $replace, $the_content);
return $the_content;
}
add_filter('the_content', 'replaceTextinTheContent',998); //998, to execute the script as second to last
// 2. remove empty tags
function removeEmptyParagraphs($content) {
//$pattern = "/<p[^>]*><\\/p[^>]*>/"; // use this pattern to only delete P-tags
$pattern = "/<[^\/>]*>([\s]?)*<\/[^>]*>/"
$content = preg_replace($pattern, '', $content);
return $content;
}
add_filter('the_content', 'removeEmptyParagraphs', 999); //999, to execute the script as last
// bonus: don't load P-tags
function disableWpAutoP( $content ) {
remove_filter( 'the_content', 'wpautop' );
remove_filter( 'the_excerpt', 'wpautop' );
return $content;
}
add_filter( 'the_content', 'disableWpAutoP', 0 );
i've not testet this. but i think it should work. :D
screenshot of the post_content in wp_posts table:
OLD:
i think you should try this in your functions.php
I don't get the point of your method.. maybe i'm missing whats your goal.
function replaceTextinTheContent($the_content){
$replace = array(
// 'WORD TO REPLACE' => 'REPLACE WORD WITH THIS'
'foo' => 'bar',
);
$the_content = str_replace(array_keys($replace), $replace, $the_content);
return $the_content;
}
add_filter('the_content', 'replaceTextinTheContent',99);
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"}}'
);
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.
The site I'm working on has a database table filled with glossary terms. I am building a function that will take some HTML and replace the first instances of the glossary terms with tooltip links.
I am running into a problem though. Since it's not just one replace, the function is replacing text that has been inserted in previous iterations, so the HTML is getting mucked up.
I guess the bottom line is, I need to ignore text if it:
Appears within the < and > of any HTML tag, or
Appears within the text of an <a></a> tag.
Here's what I have so far. I was hoping someone out there would have a clever solution.
function insertGlossaryLinks($html)
{
// Get glossary terms from database, once per request
static $terms;
if (is_null($terms)) {
$query = Doctrine_Query::create()
->select('gt.title, gt.alternate_spellings, gt.description')
->from('GlossaryTerm gt');
$glossaryTerms = $query->rows();
// Create whole list in $terms, including alternate spellings
$terms = array();
foreach ($glossaryTerms as $glossaryTerm) {
// Initialize with title
$term = array(
'wordsHtml' => array(
h(trim($glossaryTerm['title']))
),
'descriptionHtml' => h($glossaryTerm['description'])
);
// Add alternate spellings
foreach (explode(',', $glossaryTerm['alternate_spellings']) as $alternateSpelling) {
$alternateSpelling = h(trim($alternateSpelling));
if (empty($alternateSpelling)) {
continue;
}
$term['wordsHtml'][] = $alternateSpelling;
}
$terms[] = $term;
}
}
// Do replacements on this HTML
$newHtml = $html;
foreach ($terms as $term) {
$callback = create_function('$m', 'return \'<span>\'.$m[0].\'</span>\';');
$term['wordsHtmlPreg'] = array_map('preg_quote', $term['wordsHtml']);
$pattern = '/\b('.implode('|', $term['wordsHtmlPreg']).')\b/i';
$newHtml = preg_replace_callback($pattern, $callback, $newHtml, 1);
}
return $newHtml;
}
Using Regexes to process HTML is always risky business. You will spend a long time fiddling with the greediness and laziness of your Regexes to only capture text that is not in a tag, and not in a tag name itself. My recommendation would be to ditch the method you are currently using and parse your HTML with an HTML parser, like this one: http://simplehtmldom.sourceforge.net/. I have used it before and have recommended it to others. It is a much simpler way of dealing with complex HTML.
I ended up using preg_replace_callback to replace all existing links with placeholders. Then I inserted the new glossary term links. Then I put back the links that I had replaced.
It's working great!
I'd like to be able to replace things in a file with a regular expression using the following scheme:
I have an array:
$data = array(
'title' => 'My Cool Title',
'content' => ''
)
I also have a template (for clarity's sake, we'll assume the below is assigned to a variable $template)
<html>
<title><% title %></title>
<body><% content %></body>
</html>
I'd like to be able to use one regular expression to do the whole thing, so it can be as general as possible; The following is my stab at it (which doesn't work)
$endMarkup = preg_replace('/<% ([a-z]+) %>/',$data["\\1"], $template);
Is there a clean solution for putting associative array data into a template by array index?
With a little work before hand you can do it with a single preg_replace call:
$replacements = array();
foreach ($data as $search => $replacement) {
$replacements["/<% $search %>/"] = $replacement;
}
$endMarkup = preg_replace(
array_keys($replacements),
array_values($replacements),
$template);
You could try without regex if you wanted to. The function str_replace was made for this. If you don't understand regex this will be the best option for you.
Test data:
$data = array(
'title' => 'My Cool Title',
'content' => ''
);
$file = <<< EOF
<html>
<title><% title %></title>
<body><% content %></body>
</html>
EOF;
Function and example:
function replace_template($template, $data)
{
$replace = array_fill(0, count($data), '<%% %s %%>');
$keys = array_map('sprintf', $replace, array_keys($data));
$output = str_replace($keys, $data, $template);
return $output;
}
echo replace_template($file, $data);
Why do you want to bother with templating at all? PHP already is a templating language. Unless you're trying to rule out the possibility of code execution, everything you need is built into the language.
You can write your templates using plain PHP with short tags. You can wrap it all in a function to limit variable scope a bit. You can use import() to get a handful of variables out of an array. You can use include or require() to handle loading, parsing & inserting values into the file. You can use output buffering (ob_start()/ob_end()) to handle capturing the output in a string. Everything's built in, fast & thoroughly tested.
You can do this with preg_replace_callback(). Here is how I'd do it:
function get_data($matches) {
global $data;
return $data[$matches[1]];
}
$endMarkup = preg_replace_callback('/<% ([a-z]+) %>/', 'get_data', $template);
Or all in one line using create_function() if you prefer:
$endMarkup = preg_replace_callback('/<% ([a-z]+) %>/', create_function('$matches', 'global $data; return $data[$matches[1]];'), $template);