Dynamic shortcodes and functions in WordPress - wordpress

I am having a bit of an issue with autogenerating shortcodes, based on database entries.
I am able to get a normal shortcode working i.e:
function route_sc5() {
return "<div>Route 5</div>";
}
add_shortcode('route 5','route_sc');
and the following shortcode to activate it would be [route 5]
This works. But what I need is the shortcode to be produced for each database entry. something like:
$routes = $wpdb->get_results( $wpdb->prepare("SELECT * FROM wp_routes") );
foreach($routes as $route)
{
function route_sc$route->id () {
return "<div>Route $route->id</div>";
}
add_shortcode('route $route->id','route_sc$route->id');
}
The above is just an example of how I want it to work. Not literally the code I am using. How would I go about achieving this? ):
Thanks.

Here's an example of dynamic shortcode callbacks using PHP 5.3 anonymous functions:
for( $i = 1; $i <= 5; $i++ ) {
$cb = function() use ($i) {
return "<div>Route $i</div>";
};
add_shortcode( "route $i", $cb );
}
I have to ask, though: can you just accomplish what you need to do using shortcode arguments? ie. [route num=3]. Then you could just have one handle_route() function and one [route] shortcode, which may simplify things.
Also, while technically you can include a shortcode with a space in the name, I think it creates a confusing ambiguity. If you decide you need specific shortcodes for each route, I would recommend "route5" or "route-5" rather than "route 5."

Thanks guys, finally got it working. here is the code for any1 who may need it in the future:
function route_sc($atts, $content = null) {
extract(shortcode_atts(array(
'num' => '',
'bg' => '',
'text' => '',
), $atts));
global $wpdb;
$bus = $wpdb->get_row( $wpdb->prepare("SELECT * FROM wp_route WHERE id = '$num'") );
return "<div class='".$bus->text_colour."' style='background-color:".$bus->bg_colour."'>".$bus->route_id."</div></div>";
}
add_shortcode('route','route_sc');
with the shortcode at [route num="5a"]

Dynamic function names are not possible in PHP.
But you could try eval.
eval('function route_sc'.$route->id.' () { return "<div>Route '.$route->id.'</div>"; }');

Go about it a different way: Shortcodes can take parameters. So instead of [route 5] do [route rt="5"]. This way your shortcode processing function stays generic and the part that changes is meant to be dynamic. It also means that if an unexpected shortcode is encountered during the page load you can handle it properly instead of WordPress just stripping the code and replacing it with nothing.
See here for more info: http://codex.wordpress.org/Shortcode_API

Related

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

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;
} );

Wordpress / Woocommerce Hook into a Function

I appreciate questions similar to this have been asked before but the answers I've tried aren't doing what I need.
Basically,
I have this file in the woocommerce plugin folder structure:
wp-content\plugins\woocommerce\includes\wc-coupon-functions.php
Inside the file is the following function:
function wc_get_cart_coupon_types() {
return (array) apply_filters( 'woocommerce_cart_coupon_types', array( 'fixed_cart') );
}
I need to override it so it returns an additional item in the array but nothing I've tried has worked. I've tried:
Creating the same file in my custom theme file
hooking the function in my functions file with the following code:
function woocommerce_coupon_get_cart_coupon_types()
{
return (array) apply_filters( 'woocommerce_cart_coupon_types', array( 'fixed_cart', 'custom_discount' ) );
}
add_filter('wc_get_cart_coupon_types', 'woocommerce_coupon_get_cart_coupon_types',10, 1);
Any other suggestions would be greatly appreciated, also..... I've made the change directly in the file and it definitely works. Thanks
Your #2 approach is sort of how to do it, but you're essentially caught in a loop situation the way you did it.
You need to do it this way:
function wpso59974749_woocommerce_coupon_get_cart_coupon_types( $data ) {
$data[ 'your_new_key_here' ] = 'your new value here';
return $data;
}
add_filter('woocommerce_cart_coupon_types','wpso59974749_woocommerce_coupon_get_cart_coupon_types',10, 1);
You shouldn't add the apply_filter back in your function, as it would get stuck in a loop - essentially refiltering itself over and over.
I prefixed your function so if there is another woocommerce_coupon_get_cart_coupon_types function, it won't conflict.

Wordpress Plugin: Show html only on standard page and not in admin area

I'm writing a plugin and I need to display a piece of text in the WP page, but not in the admin area. How can I do so?
I tried this in the construct:
add_action( 'init', array( $this, 'initPage' ) )
and then:
public function initPage() {
echo 'hello';
}
but the text is displayed also in the admin area. Is there a way to do this? It would be the opposite of the action admin_init I assume.
Proper way to handle it: is_admin()
http://codex.wordpress.org/Function_Reference/is_admin
if(is_admin()) { // do nothing } else {
// function you want to execute.
}
I solved this by adding it to a shortcode action. Like this:
add_shortcode( 'myPlugin', array( $this, 'shortcode' ) );
and:
public function shortcode( $atts ) {
return 'hello';
}
With the above code, 'hello' will only display on the front-end. Not sure if that's the cleaner way to do it, but does the job.
There is no "front-end-only" version of init, however you probably don't want to be doing any output at the init action anyway.
What exactly are you trying to do? Usually, you use an action hook for specific types of things, and causing output very early at something like "init" is rare and weird.

How to deactivate past shortcodes in Wordpress

Recently I changed the theme of my site, and I found many of my articles use a shortcode like this
[box]
....
[/box]
My new theme does not support it and I actually don't need this shortcode to function. I thought I could just write a empty function for the shortcode in function.php, like this
function shortcode_box() {
return "";
}
add_shortcode('box', 'shortcode_box');
but it's not working.
Do you know any method to deactivate this short code?
So, you want to leave the [box] bits in the posts and/or pages, but have them not do anything? Try a shortcode that passes through the content unchanged:
function shortcode_box( $atts, $content = null ) {
return $content;
}
add_shortcode( 'box', 'shortcode_box' );
(For enclosing shortcodes, the return value of the function is used to replace the entire shortcode.)
Use remove_shortcode()
remove_shortcode('box');
Reference: http://codex.wordpress.org/Function_Reference/remove_shortcode

Date-based archives for custom post_types (wordpress 3.0)

If I have a custom post type named 'my_type', how can I get Wordpress to make date-based archives, for example:
mysite.com/my_type/2010/
mysite.com/my_type/2010/07/
mysite.com/my_type/2010/07/28/
I'm looking for tips both on creating the rewrite rules and on linking the urls to templates.
Thanks!
Update:
I've tried the following in my functions.php (EVENT_TYPE_SLUG is a constant defined elsewhere):
function event_rewrite_rules() {
global $wp_rewrite;
$new_rules = array(
EVENT_TYPE_SLUG."/([0-9]{4})/([0-9]{1,2})/$" => 'index.php?post_type=event&year='.$wp_rewrite->preg_index(1).'&monthnum='.$wp_rewrite->preg_index(2),
EVENT_TYPE_SLUG."/([0-9]{4})/$" => 'index.php?post_type=event&year='.$wp_rewrite->preg_index(1),
);
$wp_rewrite->rules = $new_rules + $wp_rewrite->rules;
}
add_action('generate_rewrite_rules', 'event_rewrite_rules');
the rewrite rules show up in $wp_rewrite-> rules, but when I navigate to those pages I get a 404 error. If I manually navigate to mysite.com/index.php?post_type=event&year=2010, I get redirected to mysite.com/2010?post_type=event
Benj I think WordPress automatically creates archives for custom post type
OK ... took some time but I figured this out (there may be more elegant solutions out there).
1) Create a standard wordpress page to serve as the archive page (and to give me access to the template). I defined the page's slug as a constant (EVENT_ARCHIVE_BASE) so that it's just hard-coded in one place and referenced everywhere else.
2) Make custom rewrite rules that catch that page's urls and redirect them to the page:
function event_date_queryvars($qvars) {
array_push($qvars, 'eyear', 'emonth');
return $qvars;
}
add_filter('query_vars', 'event_date_queryvars');
function event_date_rewrite_rules() {
// Adds rewrite rules for date-based event archives
global $wp_rewrite;
$new_rules = array(
EVENT_ARCHIVE_BASE."/([0-9]{4})/([0-9]{1,2})/?$" =>
"index.php?pagename=".EVENT_ARCHIVE_BASE."&eyear=".$wp_rewrite->preg_index(1)."&emonth=".$wp_rewrite->preg_index(2),
EVENT_ARCHIVE_BASE."/([0-9]{4})/?$" => "index.php?pagename=".EVENT_ARCHIVE_BASE."&eyear=".$wp_rewrite->preg_index(1),
);
$wp_rewrite->rules = $new_rules + $wp_rewrite->rules;
}
add_action('generate_rewrite_rules', 'event_date_rewrite_rules');
3) At the top of my page template, I check for the custom query vars and build the page accordingly. (I should have been able to use the built-in year and monthnum, but couldn't get them to work properly. Future troubleshooting):
// Was the page called with a specific year, month, or just plain?
$year = get_query_var('eyear');
$monthnum = sprintf('%02d', get_query_var('emonth'));
if ($monthnum) {
$list_type = 'Month';
$monthname = $month[$monthnum];
$heading = "Events from $monthname, $year";
} elseif ($year) {
$list_type = 'Year';
$heading = "Events from $year";
} else {
$list_type = 'AllPast';
$heading = get_the_title();
}
Thanks for the help, and hope this is useful for someone else! Let me know if you have a simpler/built-in way to do this.
I managed to find a more elegant/built-in solution.
The trick is in the "post_type=article" parameter.
If you create a rewrite like so:
'(articles)/([0-9]{4})' => 'index.php?post_type=article&eyear=' . $wp_rewrite->preg_index(2)
This will then keep your URL structure, but go through the default 'archive' template.
Then you steal the template away from the default to your own archive template. (for instance 'archive-articles.php')
This function accomplishes this (works for me):
add_action("template_redirect", 'my_template_redirect');
// Template selection
function my_template_redirect()
{
global $wp;
global $wp_query;
if ($wp->query_vars["post_type"] == "article")
{
if (have_posts())
{
include(TEMPLATEPATH . '/archive-articles.php');
die();
}
else
{
$wp_query->is_404 = true;
}
}
}
You'll still need to create your own methods to handle the get_query_var()'s for the year/month/day, but at least you don't need to create a custom page template.

Resources