Here is the anatomy of a shortcode, and how to add and then use them.
add_shortcode('my_custom_shortcode', function ($atts, $content = NULL, $tag = NULL) {
$defaults = ['a' => 'default A', 'b' => 'default B', 'c' => 'default C'];
$atts = shortcode_atts($defaults, $atts, $tag);
ob_start();
?>
<h2>This is shortcode "<?= $tag ?>"</h2>
<p><em>With the attributes of:</em></p>
<ul>
<?php foreach ($atts as $key => $value) : ?>
<li><strong><?= $key ?>:</strong> <?= $value ?></li>
<?php endforeach; ?>
</ul>
<p><em>With the content of:</em></p>
<?= $content ?: '(Content was not passed to this shortcode)' ?>
<?php
return ob_get_clean();
});
- In order to create a shortcode you need use the
add_shortcode
function
- It takes two arguments: a
$tag
and a $callback
(a callback can either be an anonymous function, as used in this example, a string that is the name of the callback function defined elsewhere, or an array of class and method).
$tag
can be whatever you want, just as long as no other shortcode is already using that name. I chose my_custom_shortcode
for this example
$callback
accepts three arguments $atts, $content = NULL, $tag = NULL
$atts
is an array of the arguments the user defined
$content
is any text the user defined inside of a shortcode ([my_custom_shortcode]This text right here (if applicable)[/my_custom_shortcode]
)
$tag
is the shortcode name, if you are using an anonymous function it will always be identical to the $tag name when adding the shortcode. It can be helpful because you can use the same (non-anonymous) callback function for different shortcodes and just change them slightly depending the the tag name.
- inside the callback you can optionally use the
shortcode_atts
function. It allows you to restrict/expect certain values from the user, set defaults (if not passed by the user), and drop/ignore params that are invalid (as decided by you). I typically will do this, but it's not required.
- you want to return a string in the callback. DON'T echo/display the contents directly to the buffer. That is a common mistake. You can use
ob_start()
to start capturing the output buffer and ob_get_clean()
to get then clear the buffer when you're done.
Here is how a user (or you) can call a shortcode in code:
// This is the most primitive form, no params or content is passed
// NOTE: just like html, if there is no content, you don't need to close the shortcode tag
echo do_shortcode('[my_custom_shortcode]');
// This example has one param, in this case `b`, passed and content
// NOTE: since you do want to pass content here, you do need to have a closing shortcode tag
echo do_shortcode('[my_custom_shortcode b="some user defined value for B"]The user contents[/my_custom_shortcode]');
If you want to call a shortcode from some WYSIWYG/Gutenberg type UI tool in WordPress, you just need the string, like this...
[my_custom_shortcode]
-or-
[my_custom_shortcode b="some user defined value for B"]The user contents[/my_custom_shortcode]
...and WordPress with call do_shortcode
behind the scenes.
So...
How would you use it? Well I don't know what's inside your template file you're using for this question, but what's in there would go inside your shortcode callback. Here is your HTML, optimized from the original question
<div class="col kb_landing_left">
<?= do_shortcode('[my_custom_shortcode post_id="56"]') ?>
</div>
<div class="col -sm-6">
<?php foreach ([1, 3, 2] as $post_id) : ?>
<div class="row">
<div class="col kb_landing_right">
<?= do_shortcode("[my_custom_shortcode post_id='{$post_id}']") ?>
</div>
</div>
<?php endforeach; ?>
</div>
That's it, I would not mess with changing the global $post, it will cause you more problems than it's worth.
Here is how you write the shortcode:
add_shortcode('my_custom_shortcode', function ($atts, $content = NULL, $tag = NULL) {
$atts = shortcode_atts(['post_id' => 0], $atts, $tag);
if (empty($atts['post_id']) || empty($post = get_post($atts['post_id']))) {
return ''; // return nothing, a proper post_id was not passed by the user
}
ob_start();
?>
<h2>Title: <?= $post->post_title ?></h2>
<p><?= $content ?: $post->post_excerpt ?></p>
<p><a href="<?= get_permalink($post->ID) ?>">Read More</a></p>
<?php
return ob_get_clean();
});
In my example, I'm creating a custom shortcode called my_custom_shortcode
. I'm only accepting a post_id param. If post_id is not passed, or is not a valid id for a post, I exit early with an empty string. If a valid post is found, then I display the Title, content (if passed by user or fallback to the post excerpt), then a Read More link that takes me to that post's url. Then catch the buffer and return it.
Let me know if you have questions!