Replace Wordpress URL domain in HTML output (for SEO optimisation) [duplicate] - wordpress

WordPress has great filter support for getting at all sorts of specific bits of content and modifying it before output. Like the_content filter, which lets you access the markup for a post before it's output to the screen.
I'm trying to find a catch-all filter that gives me one last crack at modifying the final markup in its entirety before output.
I've browsed the list of filters a number of times, but nothing jumps out at me:
https://codex.wordpress.org/Plugin_API/Filter_Reference
Anyone know of one?

WordPress doesn't have a "final output" filter, but you can hack together one. The below example resides within a "Must Use" plugin I've created for a project.
Note: I haven't tested with any plugins that might make use of the "shutdown" action.
The plugin works by iterating through all the open buffer levels, closing them and capturing their output. It then fires off the "final_output" filter, echoing the filtered content.
Sadly, WordPress performs almost the exact same process (closing the open buffers), but doesn't actually capture the buffer for filtering (just flushes it), so additional "shutdown" actions won't have access to it. Because of this, the below action is prioritized above WordPress's.
wp-content/mu-plugins/buffer.php
<?php
/**
* Output Buffering
*
* Buffers the entire WP process, capturing the final output for manipulation.
*/
ob_start();
add_action('shutdown', function() {
$final = '';
// We'll need to get the number of ob levels we're in, so that we can iterate over each, collecting
// that buffer's output into the final output.
$levels = ob_get_level();
for ($i = 0; $i < $levels; $i++) {
$final .= ob_get_clean();
}
// Apply any filters to the final output
echo apply_filters('final_output', $final);
}, 0);
An example of hooking into the final_output filter:
<?php
add_filter('final_output', function($output) {
return str_replace('foo', 'bar', $output);
});
Edit:
This code uses anonymous functions, which are only supported in PHP 5.3 or newer. If you're running a website using PHP 5.2 or older, you're doing yourself a disservice. PHP 5.2 was released in 2006, and even though Wordpress (edit: in WP version < 5.2) STILL supports it, you should not use it.

The question is may be old, but I have found a better way to do it:
function callback($buffer) {
// modify buffer here, and then return the updated code
return $buffer;
}
function buffer_start() { ob_start("callback"); }
function buffer_end() { ob_end_flush(); }
add_action('wp_head', 'buffer_start');
add_action('wp_footer', 'buffer_end');
Explanation
This plugin code registers two actions – buffer_start and buffer_end.
buffer_start is executed at the end of the header section of the html. The parameter, the callback function, is called at the end of the output buffering. This occurs at the footer of the page, when the second registered action, buffer_end, executes.
The callback function is where you add your code to change the value of the output (the $buffer variable). Then you simply return the modified code and the page will be displayed.
Notes
Be sure to use unique function names for buffer_start, buffer_end, and callback, so they do not conflict with other functions you may have in plugins.

AFAIK, there is no hook for this, since the themes uses HTML which won't be processed by WordPress.
You could, however, use output buffering to catch the final HTML:
<?php
// example from php.net
function callback($buffer) {
// replace all the apples with oranges
return (str_replace("apples", "oranges", $buffer));
}
ob_start("callback");
?>
<html><body>
<p>It's like comparing apples to oranges.</p>
</body></html>
<?php ob_end_flush(); ?>
/* output:
<html><body>
<p>It's like comparing oranges to oranges.</p>
</body></html>
*/

#jacer, if you use the following hooks, the header.php also gets included.
function callback($buffer) {
$buffer = str_replace('replacing','width',$buffer);
return $buffer;
}
function buffer_start() { ob_start("callback"); }
function buffer_end() { ob_end_flush(); }
add_action('after_setup_theme', 'buffer_start');
add_action('shutdown', 'buffer_end');

I was using the top solution of this post (by kfriend) for a while. It uses an mu-plugin to buffer the whole output.
But this solution breaks the caching of wp-super-cache and no supercache-files are generated when i upload the mu-plugin.
So: If you are using wp-super-cache, you can use the filter of this plugin like this:
add_filter('wp_cache_ob_callback_filter', function($buffer) {
$buffer = str_replace('foo', 'bar', $buffer);
return $buffer;
});

Modified https://stackoverflow.com/users/419673/kfriend answer.
All code will be on functions.php. You can do whatever you want with the html on the "final_output" filter.
On your theme's 'functions.php'
//we use 'init' action to use ob_start()
add_action( 'init', 'process_post' );
function process_post() {
ob_start();
}
add_action('shutdown', function() {
$final = '';
// We'll need to get the number of ob levels we're in, so that we can iterate over each, collecting
// that buffer's output into the final output.
$levels = ob_get_level();
for ($i = 0; $i < $levels; $i++) {
$final .= ob_get_clean();
}
// Apply any filters to the final output
echo apply_filters('final_output', $final);
}, 0);
add_filter('final_output', function($output) {
//this is where changes should be made
return str_replace('foo', 'bar', $output);
});

You might try looking in the wp-includes/formatting.php file. For example, the wpautop function.
If you are looking for doing something with the entire page, look at the Super Cache plugin. That writes the final web page to a file for caching. Seeing how that plug-in works may give you some ideas.

Indeed there was a discusussion recently on the WP-Hackers mailing list about the topic of full page modification and it seems the consensus was that output buffering with ob_start() etc was the only real solution. There was also some discussion about the upsides and downsides of it: http://groups.google.com/group/wp-hackers/browse_thread/thread/e1a6f4b29169209a#
To summarize: It works and is the best solution when necessary (like in the WP-Supercache plugin) but slows down overall speeds because your content isn't allowed to be sent to the browser as its ready, but instead has to wait for the full document to be rendered (for ob_end() ) before it can be processed by you and sent to the browser.

To simplify previous answers, just use this in functions.php:
ob_start();
add_action('shutdown', function () {
$html = ob_get_clean();
// ... modify $html here
echo $html;
}, 0);

I've been testing the answers here now for a while, and since the cache breaking thing is still an issue, I came up with a slightly different solution. In my tests no page cache broke. This solution has been implemented into my WordPress plugin OMGF (which has 50k+ users right now) and no issues with page cache breaking has been reported.
First, we start an output buffer on template redirect:
add_action('template_redirect', 'maybe_buffer_output', 3);
function maybe_buffer_output()
{
/**
* You can run all sorts of checks here, (e.g. if (is_admin()) if you don't want the buffer to start in certain situations.
*/
ob_start('return_buffer');
}
Then, we apply our own filter to the HTML.
function return_buffer($html)
{
if (!$html) {
return $html;
}
return apply_filters('buffer_output', $html);
}
And then we can hook into the output by adding a filter:
add_filter('buffer_output', 'parse_output');
function parse_output($html)
{
// Do what you want. Just don't forget to return the $html.
return $html;
}
Hope it helps anyone.

I have run into problems with this code, as I end up with what seems to be the original source for the page so that some plugins has no effect on the page. I am trying to solve this now - I haven't found much info regarding best practises for collecting the output from WordPress.
Update and solution:
The code from KFRIEND didnt work for me as this captures unprocessed source from WordPress, not the same output that ends up in the browser in fact. My solution is probably not elegant using a globals variable to buffer up the contents - but at least I know get the same collected HTML as is delivered to the browser. Could be that different setups of plugins creates problems but thanks to code example by Jacer Omri above I ended up with this.
This code is in my case located typically in functions.php in theme folder.
$GLOBALS['oldschool_buffer_variable'] = '';
function sc_callback($data){
$GLOBALS['final_html'] .= $data;
return $data;
}
function sc_buffer_start(){
ob_start('sc_callback');
}
function sc_buffer_end(){
// Nothing makes a difference in my setup here, ob_get_flush() ob_end_clean() or whatever
// function I try - nothing happens they all result in empty string. Strange since the
// different functions supposedly have very different behaviours. Im guessing there are
// buffering all over the place from different plugins and such - which makes it so
// unpredictable. But that's why we can do it old school :D
ob_end_flush();
// Your final HTML is here, Yeeha!
$output = $GLOBALS['oldschool_buffer_variable'];
}
add_action('wp_loaded', 'sc_buffer_start');
add_action('shutdown', 'sc_buffer_end');

If you want to modify the output, you can use template_include:
add_filter( 'template_include', static function ( $template ) {
if ( basename( $template ) === 'foo-template.php' ) {
echo str_replace( 'foo', 'bar', file_get_contents( $template ) );
}
return null;
} );
If instead you want to override the output completely, you can use the action template_redirect.
add_action( 'template_redirect', static function () {
wp_head();
echo 'My output.';
wp_footer();
exit;
} );

Related

How to prevent shortcodes from being executed multiple times

It seems shortcodes added to the front page in Wordpress will execute multiple times, possibly this is expected behavior. For example, adding a shortcode called [myshortcode] to the front page, and defining it like this in functions.php:
function myshortcode()
{
ob_start();
// This is what really needs to be run only once
update_my_database();
return ob_get_clean();
}
add_shortcode('myshortcode','myshortcode');
WordPress seems to execute this multiple times. Can that be prevented? Or is there another way to execute a function one time and only one time on the WP front page without using a shortcode?
I've tried multiple checks including in_the_loop() and is_singular() but none seem to work. Also tried adding remove_shortcode('myshortcode'); within the myshortcode function, thinking that might prevent subsequent executions, but that doesn't work either: Tried adding static variables per https://stackoverflow.com/a/3695685/8552218 but doesn't work.
Here's what it looks like with all these approaches, and this still doesn't work. Any ideas?
function myshortcode()
{
ob_start();
if (!is_singular()) return ob_get_clean;
if (!in_the_loop()) return ob_get_clean;
if (!is_main_query()) return ob_get_clean;
static $runonce=false;
if (!$runonce)
{
$runonce=true;
return;
}
// This is what really needs to be run only once
update_my_database();
remove_shortcode('myshortcode');
return ob_get_clean();
}
add_shortcode('myshortcode','myshortcode');
function update_my_database()
{
error_log("Executing...",1,$myemail);
}

How can I add no-follow to all links in a WordPress site in one go?

I'm coding a WordPress website in which I want all the external links to be "no-follow" by default.
It is quite long to go and edit each single link to add the "no-follow", is there a way to code it once for all?
Thank you
IMHO, the best way to do so is using a custom filter. Being generated dinamically by PHP, you can edit links on post and pages server-side even before a user visit them: WordPress used to add rel="nofollow" to each link by default in the past, but I see it doesn’t happen anymore. Then, I found a solution by Debjit Saha you may try on functions.php.
add_filter('the_content', 'my_nofollow');
add_filter('the_excerpt', 'my_nofollow');
function my_nofollow($content) {
return preg_replace_callback('/<a[^>]+/', 'my_nofollow_callback', $content);
}
function my_nofollow_callback($matches) {
$link = $matches[0];
$site_link = get_bloginfo('url');
if (strpos($link, 'rel') === false) {
$link = preg_replace("%(href=\S(?!$site_link))%i", 'rel="nofollow" $1', $link);
} elseif (preg_match("%href=\S(?!$site_link)%i", $link)) {
$link = preg_replace('/rel=\S(?!nofollow)\S*/i', 'rel="nofollow"', $link);
}
return $link;
}
Please, notice that some plugins could break this code and I’ve never tried this on newer WordPress versions.
One way you can do it is using JavaScript:
var hrefToCheck = "mysite.com" // change this string
noFollowExternalLinks(hrefToCheck); // call to run the function below
function noFollowExternalLinks(siteHref) {
document.querySelectorAll('a').forEach(function(link) {
if (!link.href.includes(siteHref)) {
link.setAttribute("rel", "nofollow");
}
})
}
So, you can change hrefToCheck variable to a piece of your site URL that will be checked inside each link inside the page.
The function will loop to check every link in the page and apply rel="nofollow" to all external links (that don't match the text in the variable).

WP REST API returns HTML instead of JSON

An endpoint of https://example.com/wp-json/wp/v2/pages/123 returns a combination of HTML and JSON format such as:
<div class="my-class">HTML Content</div>
{ "id" : 123, ... }
There is an excerpt filter in functions.php that set the outputted HTML, in this line:
function my_custom_filter(){
echo '<div class="my-class">HTML Content</div>';
}
add_filter ('the_excerpt', 'my_custom_filter' );
How to prevent such filter from intervening with the JSON response?
The real issue here is echoing content during a filter, with the assumption it is being output to the same place echo would. One should only ever modify filtered content. To correct that code:
function my_custom_filter($excerpt) {
return '<div class="my-class">HTML Content</div>' . "\n$excerpt";
}
add_filter ('the_excerpt', 'my_custom_filter');
In this case, OP probably still wants the conditional:
function my_custom_filter($excerpt) {
return !defined('REST_REQUEST') || !REST_REQUEST ?
'<div class="my-class">HTML Content</div>' . "\n$excerpt" :
$excerpt;
}
add_filter ('the_excerpt', 'my_custom_filter');
If you can modify the filter, then you can detect the REST API inside the filter function, like this:
function my_custom_filter(){
if (! defined("REST_REQUEST")) {
echo '<div class="my-class">HTML Content</div>';
}
}
add_filter ('the_excerpt', 'my_custom_filter' );
The constant REST_REQUEST is only defined inside a request that was handled by the REST API. So, with the code above, if a request is being handled by the normal WP request cycle, the constant will not be defined, so the if statement we added will evaluate as true and the echo will occur like normal. If the constant is defined, then the if will evaluate as false and the filter will not add any output to the response.
See https://wpseek.com/constant/rest_request/
UPDATE: while the code above will work, I think we can do better.
The problem is that if you have many filters, every one of them needs to duplicate the if statement and your code become littered with checks for REST_REQUEST, which
is repetitive (not DRY)
not the concern of the filters themselves
untidy
Instead, we can introduce the REST_REQUEST checks earlier in the cycle by making it a concern of the code that adds the filter.
This would look like:
function my_custom_filter(){
echo '<div class="my-class">HTML Content</div>';
}
if (! defined("REST_REQUEST")) {
add_filter ('the_excerpt', 'my_custom_filter' );
//add other HTML filters here
} else {
//attach REST API actions
}
This seems like a small change (if you catch it early, when the number of filters is small), but it potentially brings some big advantages:
it keeps the REST_REQUEST checks all in one place: maintainability
the filters don't need to know or care about whether it's a REST_REQUEST or not - if they get called, they're good to go. Keeping it simple.
you can potentially do other things like logging or role-based access checks in that place: extensibility
It's a relatively easy change to make early on in your application, but becomes more difficult and error prone as you add further filters and actions. Always better to do it right the first time!

Wordpress: do something only for new posts and not for updated ones

Good morning,
I need a hook or something to call to separate new posts from updated ones.
I want to execute some code only for new posts... something like that:
add_action('publish_post', myfunction);
function myfunction( $post_id ){
if <new post> {
/* do something */
}
else {
/* do nothing */
}
}
I'm using WP v3.5
Any suggestion?
First, you want to use the draft_to_publish hook:
add_action('draft_to_published', 'my_function');
However, this can fire multiple times if a post is published, unpublished, and then published again. To ensure that your action only fires one time, set a custom meta element for the post at the end of your function. At the start of your function, check if you meta element is set and skip the action if it is.
--- Added ---
Here is a sample on how the repeat action protection would work (untested code - so might need a little work):
function my_function() {
$already_done = get_post_meta($post_id, 'do_once', true);
if(!$already_done) {
// Do what you need to do here
add_post_meta($post_id, 'do_once', 1, true);
}
}

Custom post type functions.php if statement on action

I am using developing a child theme for Woothemes' Canvas.
I am trying to use functions.php in the child theme to only use actions on my custom post type.
This code doesn't seem to be working:
add_action( 'woo_post_inside_after', 'my_geo_mashup' );
function my_geo_mashup() {
echo GeoMashup::map();
if ($post->post_type == 'listings') {
//My function
}
}
add_action( 'woo_post_inside_before', 'listings_nivo' );
function listings_nivo() {
echo do_shortcode('[nivo source="current-post" ]');
if ($post->post_type == 'listings') {
//My function
}
}
So, I'm unsure how to get the above to work properly and only show these items on the custom post type, or only for the custom post type template single-listings.php (as I only want the map and slider to show on the actual post, not on the blog page (archive.php)
Rather than making the entire $post object global, you can just make $post_type global instead. Ex below.
I'm not exactly sure where that function is being loaded, but make sure you hook somewhere within the post. If the action is before, as far as I know and from experience, the post variable will be null.
Just as a test, try running the action in wp_footer Ex. add_action( 'wp_footer', 'listings_nivo' );
See if that yeilds any results.
if echoing var_dump($post) is still null, well, not sure where to go from there.
So you can try running the below, then run the action in the appropriate place if it works:
function listings_nivo() {
echo do_shortcode('[nivo source="current-post" ]');
global $post_type;
// Diagnostic purposes
echo var_dump($post_type);
if ($post_type == 'listings') {
//My function
}
}
add_action( 'wp_footer', 'listings_nivo' );
Check your error log or turn wp_debug to true in your wp-config.php file if nothing else to see if anything else is going on.
Best of luck!
Inside your function, try adding global $post;. Then to see what you are getting with $post->post_type echo it out to the screen. As long as this gives you "listings", your code should work. If not, there's probably another issue at play.

Resources