WordPress Plugins

Write a plugin which adds a text to the footer

Description: Create a plugin called Footer Credit that prints Built with WordPress at the end of every page.

Hint:

  • The plugin needs a header comment with Plugin Name.
  • Use add_action( 'wp_footer', ... ).

Solution

<?php
/**
 * Plugin Name: Footer Credit
 */
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

add_action( 'wp_footer', function () {
	echo '<p class="credit">Built with WordPress</p>';
} );
Terminal output

$ wp plugin activate tc-footer-credit
Plugin 'tc-footer-credit' activated.
Success: Activated 1 of 1 plugins.
$ wp plugin list –name=tc-footer-credit –fields=name,status
name status
tc-footer-credit active
$ curl -s http://example.com/ | grep -o '<p class="credit">[^<]*</p>'
<p class="credit">Built with WordPress</p>

How it works

  • WordPress reads the header comment and shows the plugin in the list.
  • After activation, the file is loaded on every request, and the function waits for the wp_footer action.

Read Create a WordPress Plugin.

Create a [today] shortcode with a date format

Description: Make a shortcode [today] which prints the date in the format d F Y. It should also accept an attribute: [today format="Y-m-d"].

Hint:

  • Use add_shortcode() and shortcode_atts().
  • Return the text, do not echo it. Use wp_date() for the date.

Solution

add_shortcode( 'today', function ( $atts ) {
	$a = shortcode_atts( array( 'format' => 'd F Y' ), $atts, 'today' );
	return esc_html( wp_date( $a['format'] ) );
} );

echo 'Default: ' . ( strlen( do_shortcode( '[today]' ) ) > 0 ? 'works' : 'empty' ) . "\n";
echo 'Format Y-m-d has the right shape: ' . ( preg_match( '/^\d{4}-\d{2}-\d{2}$/', do_shortcode( '[today format="Y-m-d"]' ) ) ? 'yes' : 'no' );
Output

Default: works
Format Y-m-d has the right shape: yes

How it works

  • shortcode_atts() merges the given attributes with the defaults.
  • wp_date() prints the date in the timezone of the site.
  • The output changes every day, so the test checks its shape, not the exact text.

Read Shortcode API.

Register a Movie custom post type

Description: Register a post type movie with the label Movies, an archive at /movies/ and REST support. Add a movie Inception and check its URL.

Hint:

  • Use register_post_type() on the init hook.
  • Flush the rewrite rules once after registering the type.

Solution

<?php
/**
 * Plugin Name: Movies
 */
add_action( 'init', function () {
	register_post_type( 'movie', array(
		'label'        => 'Movies',
		'public'       => true,
		'has_archive'  => true,
		'rewrite'      => array( 'slug' => 'movies' ),
		'show_in_rest' => true,
		'supports'     => array( 'title', 'editor', 'thumbnail' ),
	) );
} );
Terminal output

$ wp plugin activate tc-movies
Plugin 'tc-movies' activated.
Success: Activated 1 of 1 plugins.
$ wp rewrite flush
Success: Rewrite rules flushed.
$ wp post create –post_type=movie –post_title="Inception" –post_status=publish –porcelain
5
$ wp eval 'echo get_permalink( 5 );'
http://example.com/movies/inception/
$ curl -s -o /dev/null -w "archive: %{http_code}\n" http://example.com/movies/
archive: 200
$ curl -s -o /dev/null -w "single: %{http_code}\n" http://example.com/movies/inception/
single: 200

How it works

  • The type is saved in wp_posts with post_type = movie.
  • has_archive gives the list page, and rewrite sets the URL base.
  • Without the flush, the new URLs can show a 404 error.

Read Custom Post Types.

Add a hierarchical Level taxonomy to a Course post type

Description: Register a post type course and a hierarchical taxonomy level. Add the terms Beginner and Advanced. Create a course WordPress Basics in the level Beginner, and list the courses of that level with WP_Query.

Hint:

  • register_taxonomy( 'level', 'course', array( 'hierarchical' => true ) ).
  • Use a tax_query for the list.

Solution

<?php
/**
 * Plugin Name: Courses
 */
add_action( 'init', function () {
	register_post_type( 'course', array( 'label' => 'Courses', 'public' => true, 'show_in_rest' => true ) );
	register_taxonomy( 'level', 'course', array( 'label' => 'Levels', 'hierarchical' => true, 'show_in_rest' => true ) );
} );
Terminal output

$ wp plugin activate tc-courses
Plugin 'tc-courses' activated.
Success: Activated 1 of 1 plugins.
$ wp term create level "Beginner" –slug=beginner –porcelain
2
$ wp term create level "Advanced" –slug=advanced –porcelain
3
$ wp post create –post_type=course –post_title="WordPress Basics" –post_status=publish –porcelain
5
$ wp post create –post_type=course –post_title="Plugin Security" –post_status=publish –porcelain
6
$ wp post term add 5 level beginner
Success: Added term.
$ wp post term add 6 level advanced
Success: Added term.

$q = new WP_Query( array(
	'post_type' => 'course',
	'tax_query' => array( array( 'taxonomy' => 'level', 'field' => 'slug', 'terms' => 'beginner' ) ),
) );
foreach ( $q->posts as $p ) {
	echo $p->post_title . "\n";
}
Output

WordPress Basics

How it works

  • A taxonomy is registered for a post type, and its terms are connected to the posts through wp_term_relationships.
  • The tax_query asks only for the posts which have the term beginner.

Read Custom Taxonomies.

Save a plugin setting with a sanitize callback

Description: Register an option site_slogan in a plugin, which cleans the value with sanitize_text_field. Save the value <b>Learn</b> fast and print what is stored.

Hint:

  • register_setting() with sanitize_callback runs a filter every time update_option() saves the option.

Solution

register_setting( 'my_group', 'site_slogan', array(
	'type'              => 'string',
	'sanitize_callback' => 'sanitize_text_field',
) );

update_option( 'site_slogan', '<b>Learn</b> fast' );
var_dump( get_option( 'site_slogan' ) );
Output

string(10) "Learn fast"

How it works

  • register_setting() connects the sanitize function to the option name.
  • The tags are removed before the value goes to the database, so the stored value is plain text.
  • In a real plugin you call register_setting() on the admin_init hook.

Read Plugin Settings Page.

Create a custom table and insert a row with $wpdb

Description: Create a table wp_tc_visits with the columns id, page and hits. Insert the row (home, 10), and read it back with a prepared query.

Hint:

  • Use $wpdb->prefix, dbDelta(), $wpdb->insert() and $wpdb->prepare().

Solution

global $wpdb;
require_once ABSPATH . 'wp-admin/includes/upgrade.php';

$table   = $wpdb->prefix . 'tc_visits';
$charset = $wpdb->get_charset_collate();

dbDelta( "CREATE TABLE $table (
	id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
	page varchar(100) NOT NULL,
	hits int(11) NOT NULL DEFAULT 0,
	PRIMARY KEY  (id)
) $charset;" );

$wpdb->insert( $table, array( 'page' => 'home', 'hits' => 10 ), array( '%s', '%d' ) );

$row = $wpdb->get_row( $wpdb->prepare( "SELECT page, hits FROM $table WHERE page = %s", 'home' ) );
echo $row->page . ' => ' . $row->hits;
Output

home => 10

How it works

  • dbDelta() creates the table (and it would update it if it existed).
  • insert() writes the row and prepares the values with the formats %s and %d.
  • prepare() protects the select query from SQL injection.

Read WordPress $wpdb.