How to Rename a Shortcode Programmatically
When we change mind on the name of a shortcode become almost ubiquitous amongst our Posts, the only reasonable way of renaming all its occurrences is programmatically.
It does happen to change mind. But when for some reason we change mind on the name of a shortcode become almost ubiquitous amongst our Posts, Pages and Custom Posts, the only reasonable way of renaming all its occurrences is programmatically — writing scripts all the monotonous work can be delegated to is a habit that, trust me, pays off over time.
The snippet of code that follows is a generalisation of a simpler script I wrote for the migration of data from SiteTree to The Permalinks Cascade, it is basically an implementation of the find-and-replace task iterated over all the posts showing a glimpse of the shortcode we want to rename. Most of its magic is condensed in the regular expressions, used to spot the occurrences of the shortcode which have not been escaped with double square brackets. To accomplish this, the two regex use a negative lookaround, a zero-length assertion you can learn everything about on the Regular-Expressions.info website.
1$old_shortcode_name = 'my-shortcode';
2$new_shortcode_name = 'my-new-shortcode';
3
4// We do a preliminary sorting of the posts by using
5// the search feature built into the WP_Query class,
6// so as to retrieve from the database only the posts
7// that might contain the shortcode to rename.
8$arguments = array(
9 'posts_per_page' => -1,
10 'post_type' => 'any',
11 's' => "[{$old_shortcode_name}"
12);
13
14$query = new WP_Query( $arguments );
15
16// The negative look-behind and the negative look-ahead in
17// the regular expressions ensure that shortcodes escaped
18// with double square brackets are not matched.
19// Also, the capturing group in the first regex is
20// used to capture the list of attributes that
21// the shortcode might include.
22$patterns = array(
23 "#(?<!\[)\[{$old_shortcode_name}( [^\]]+)?\]#",
24 "#\[/{$old_shortcode_name}\](?!\])#"
25);
26
27// The match variable '$1' will be replaced either with
28// an empty string or a list of shortcode attributes.
29$replacements = array(
30 "[{$new_shortcode_name}\$1]",
31 "[/{$new_shortcode_name}]"
32);
33
34while ( $query->have_posts() ) {
35 $query->the_post();
36
37 $the_content = get_the_content();
38
39 // If $the_content doesn't contain non-escaped
40 // occurrences of the shortcode we want to rename,
41 // we just go on to analyse the next post.
42 if (! preg_match( $patterns[0], $the_content ) ) {
43 continue;
44 }
45
46 // We retrieve the current post
47 // as an associative array.
48 $the_post = get_post( null, ARRAY_A );
49
50 if (! $the_post ) {
51 continue;
52 }
53
54 $the_post['post_content'] = preg_replace( $patterns,
55 $replacements,
56 $the_content );
57
58 // We let WordPress automatically update
59 // the post's modification date.
60 unset( $the_post['post_modified'], $the_post['post_modified_gmt'] );
61
62 // The last parameter hinders WordPress
63 // from triggering the after-insert hooks.
64 wp_update_post( $the_post, false, false );
65}
66
67// Mandatory when we create
68// a secondary loop through WP_Query.
69wp_reset_postdata();
The Edge Case
The presence of nested shortcodes in one or more of your posts is certainly not such an improbability that it should be tagged as edge case, but an escaped shortcode that wraps a very occurrence of the shortcode to rename is without doubt an edge case. So, if somewhere in your website you have written something like that...
1[[my-outer-shortcode]
2 [my-shortcode arg="value"]
3[/my-outer-shortcode]]
...it would be better to preventively exclude from the query the posts containing such pseudo-code. You just have to pass to the constructor of WP_Query
a list of IDs as the value of the post__not_in
argument.
Running the code: Where and When
Given a generic context, you can simply put the code in the functions.php
file of your active theme, and delete it once the renaming is complete. As a precaution however, I would suggest to let WordPress run it when the init
hook fires, like so:
1function rename_my_shortcode() {
2 // The snippet goes here.
3}
4add_action( 'init', 'rename_my_shortcode' );
Running the code: A Final Note
Running the snippet only once is more than enough, however, in the event you let the snippet run more than once, you don't have to worry, because the conditional statements it comes with will act as a deterrent to unexpected results.