Create a Theme Plugin

One of the basics I would often find myself doing is creating an advanced “latest posts” plugin, mainly so I could style the latest posts widgets a little nicer. This tutorial will show you how to create this plugin, and will give you some ideas to styling the sidebar.

First, navigate to your plugins folder and create a new folder, I’m calling it a generic “site-code”. If you are following this tutorial exactly and are using Bedrock skip down to the yellow note below.

Folder for plugin 1

Now, we create a php file, mine is called sitecode-plugin.php

Folder for plugin

Note: If you are using Bedrock

wp-content/plugins is not the correct path for your new plugin. If you are following my tutorial and using Bedrock then you will be adding the plugin here: app/bedrock/web/app/plugins

In this file, we need some important code so WordPress knows this is a plugin, copy this and paste it into this new file :

<?php
/*
Plugin Name: Theme Code
Description:  Bundle of Theme Widgets and various site code.
*/

This will now show up in the plugins folder :

Available Plugins

We now need it to do something! for this tutorial, we will create a new widget, but this plugin is going to be used to put various site-specific code into as we build the theme. Even though the PHP code is going to be added to the plugin, I will be adding the CSS code into _global.scss, I have no need to create plugin-specific style sheets as these are all made on a per-site basis. To make the plugin more reusable we would need to enqueue its own style sheet – but if we build these widgets to be site-specific we are able to avoid a lot of wasted network requests for stylesheets and often lots of CSS code that doesn’t get used. For example, the code we write here for the cards can also be used on our category pages.

The purpose of this tutorial is the workflow of Bedrock/Sage, not so much how to make plugins or widgets, but we still want to end up with a nice looking basic theme, so without explanation here is the plugin code :

<?php
/*
Plugin Name: Theme Code
Description:  Bundle of Theme Widgets and various site code.
*/

// Creating the widget
class newpost_widget extends WP_Widget
{
    public function __construct()
    {
        parent::__construct(
'newpost_widget',

__('Advanced New Posts Widget', 'newpost_widget_domain'),

['description' => __('New Posts', 'newpost_widget_domain')]
);
    }

    public function widget($args, $instance)
    {
        $title = apply_filters('widget_title', $instance['title']);

        echo $args['before_widget'];
        if (!empty($title)) {
            echo $args['before_title'].$title.$args['after_title'];
        } ?>

	<div class="cards">
    <?php
    $excludeid = ''; // make an array if you need more featured spots.
        $recent_posts = wp_get_recent_posts([
        'numberposts' => 1, // Number of recent posts thumbnails to display
        'post_status' => 'publish', // Show only the published posts
        'meta_key' => '_thumbnail_id', // only with images to keep consistant display
    ]);
        foreach ($recent_posts as $post_item) { ?>

 <?php $image = wp_get_attachment_image_src(get_post_thumbnail_id(($post_item['ID'])), 'single-post-thumbnail'); ?>
 <?php $excludeid = $post_item['ID']; ?>
	<a class="card" href="<?php echo get_permalink($post_item['ID']); ?>">
			<span class="card-header" style="background-image: url(<?php echo $image[0]; ?>);">
				<span class="card-title">
					<h3>  <?php echo esc_html($post_item['post_title']); ?></h3>
				</span>
			</span>
			<span class="card-summary">
		<?php echo wp_strip_all_tags(get_the_excerpt($post_item['ID']), true); ?>
			</span>
			<span class="card-meta">
		<?php echo esc_html($post_item['post_date']); ?>
			</span>
		</a>
    <?php wp_reset_query(); } ?>

<ul> <div class="card">
<?php
     $recent_posts = wp_get_recent_posts([
        'numberposts' => 5, // Number of recent posts thumbnails to display
        'post_status' => 'publish', // Show only the published posts
    ]);

        foreach ($recent_posts as $recent) {
            if ($recent['ID'] == $excludeid) {
                // do nothing
            } else {
                echo '<li class="altposts"><a href="'.get_permalink($recent['ID']).'">'.$recent['post_title'].'</a> </li> ';
            }
        }
        wp_reset_query(); ?>
</div></ul>
    
    </div>


<?php
    }

    // Widget Backend
    public function form($instance)
    {
        if (isset($instance['title'])) {
            $title = $instance['title'];
        } else {
            $title = __('New title', 'newpost_widget_domain');
        } ?>
<p>
<label for="<?php echo $this->get_field_id('title'); ?>"><?php _e('Title:'); ?></label> 
<input class="widefat" id="<?php echo $this->get_field_id('title'); ?>" name="<?php echo $this->get_field_name('title'); ?>" type="text" value="<?php echo esc_attr($title); ?>" />
</p>
<?php
    }

    public function update($new_instance, $old_instance)
    {
        $instance = [];
        $instance['title'] = (!empty($new_instance['title'])) ? strip_tags($new_instance['title']) : '';

        return $instance;
    }
}

// Register and load the widget
function load_widget()
{
    register_widget('newpost_widget');
}
add_action('widgets_init', 'load_widget');

and _global.scss now looks like this :

.widget-item {
  padding: 10px;
  margin-bottom: 20px;
}

.widget h3 {
  border-bottom-width: 3px;
  border-bottom-style: solid;
  border-bottom-color: lightgray;
}

.line {
  border-bottom-width: 3px;
  border-bottom-style: solid;
  border-bottom-color: theme-color("primary");
  display: inline-block;
  margin: 0 0 -3px 0;
  padding: 10px 3px;
}

.card {
  position: relative;
  margin-bottom: 20px;
  background: #fefff9;
  color: #363636;
  text-decoration: none;
  -moz-box-shadow: rgba(0, 0, 0, 0.19) 0 0 8px 0;
  -webkit-box-shadow: rgba(0, 0, 0, 0.19) 0 0 8px 0;
  box-shadow: rgba(0, 0, 0, 0.19) 0 0 8px 0;
  -moz-border-radius: 4px;
  -webkit-border-radius: 4px;
  border-radius: 4px;
}

.card span {
  display: block;
}

.card .card-summary {
  padding: 5% 5% 15% 5%;
}

.card .card-header {
  position: relative;
  height: 175px;
  overflow: hidden;
  background-repeat: no-repeat;
  background-size: cover;
  background-position: center;
  background-color: rgba(255, 255, 255, 0.15);
  background-blend-mode: overlay;
  -moz-border-radius: 4px 4px 0 0;
  -webkit-border-radius: 4px;
  border-radius: 4px 4px 0 0;
}

.card .card-title {
  background: theme-color("primary-dark");
  padding: 3.5% 0 2.5% 0;
  color: white;
  text-transform: uppercase;
  position: absolute;
  bottom: 0;
  width: 100%;
}

.card .card-title h3 {
  font-size: 1.2em;
  line-height: 1.2;
  padding: 0 3.5%;
  margin: 0;
}

.card .card-meta {
  max-height: 0;
  overflow: hidden;
  color: #666;
  font-size: 0.78em;
  text-transform: uppercase;
  position: absolute;
  bottom: 5%;
  padding: 0 5%;
  -moz-transition-property: max-height;
  -o-transition-property: max-height;
  -webkit-transition-property: max-height;
  transition-property: max-height;
  -moz-transition-duration: 0.4s;
  -o-transition-duration: 0.4s;
  -webkit-transition-duration: 0.4s;
  transition-duration: 0.4s;
  -moz-transition-timing-function: ease-in-out;
  -o-transition-timing-function: ease-in-out;
  -webkit-transition-timing-function: ease-in-out;
  transition-timing-function: ease-in-out;
}

.card:hover,
.card:focus {
  background: white;
  -moz-box-shadow: rgba(0, 0, 0, 0.45) 0 0 20 0;
  -webkit-box-shadow: rgba(0, 0, 0, 0.45) 0 0 20 0;
  box-shadow: rgba(0, 0, 0, 0.45) 0 0 20 0;
}

.card:hover .card-meta,
.card:focus .card-meta {
  max-height: 1em;
}

.card-header:hover > .card-title {
  background: theme-color("primary");
}

.card-header:hover {
  transform: scale(1.04) rotate(-2deg);
  transition: all 0.6s ease;
  box-shadow: 2px 2px 16px rgba(0, 0, 0, 5%);
}

.cards ul {
  list-style: none;
  padding-left: 0;
}

.altposts {
  background: #f0f0f0;
  padding: 5px;
}

.altposts:nth-child(odd) {
  background: #fefff9;
}

If we activate the plugin and add the new widget to the sidebar we will now have this :

Our new sidebar widget

It features the last post with a featured image then displays the next 4 posts underneath as a list (or 5 if you have 5 new posts without featured images). In our next post, we will be using a CSS grid to put widgets in the footer!

Soil and Demo Content

We should do a tiny bit more setup just to make things smoother as we progress. First I want you to install wp-cli – this is the “WordPress – Comand Line Interface”. I won’t be using it much, but you will find it’s often used in documentation so I found it useful to have.

curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar

Now, lets move it from whatever directory you just put it in, and make it accessible everywhere

chmod +x wp-cli.phar
sudo mv wp-cli.phar /usr/local/bin/wp

Install Soil

Read about the advantages and cleaner markup using soil on the official sage soil page here You will notice it costs money, however, it is released under the MIT license which means we can install it and use it free of charge. They do great work and the support given from paying for this plugin is small compared to the benefits of bedrock/sage/soil. So use the plugin now to try it out but once the site is launched circle back around and pay for the license.

composer require roots/soil

Back in your theme folder (bedrock/web/app/themes/name) reload everything

npm run start

Go to your admin panel and activate the Soil plugin.

app/setup.php

Take a look in the app/setup.php file, this one is important and you will likely use it a lot, you add theme support, menus and image sizes here. Lets just add a new image size for now, around line 50 you will find this :

    /*
     * Enable post thumbnails
     * @link https://developer.wordpress.org/themes/functionality/featured-images-post-thumbnails/
     */
    add_theme_support('post-thumbnails');

underneath, lets add our new image size

 add_image_size('post-featured', 1280, 628, true);

We will come back to this file as we develop our theme. Our next tutorial will show you how to tackle the menu, but before we do this let’s install the Theme Unit Test dummy content. On the linked page, you will find an XML file to download. This will add a bunch of test content, including items we can add to our menu. Once you have it downloaded, go to your admin panel and Tools > Import and install the importer :

Lastly, once the install has finished, click “Run Importer” and then choose the downloaded XML file and import some dummy data using the setting below

All set for tackling the menu!