Session 6.8 – WordPress Development
Module 6: Advanced Web Technologies | Duration: 1 hr
Learning Objectives
By the end of this session, students will be able to:
- Understand WordPress architecture and file structure
- Create custom WordPress themes from scratch
- Develop WordPress plugins with custom functionality
- Work with WordPress hooks (actions and filters)
- Use the WordPress database ($wpdb) class
- Apply WordPress development best practices
Introduction to WordPress Development
WordPress powers over 43% of all websites on the internet. Understanding WordPress development opens opportunities for creating custom themes, plugins, and complete web solutions.
WordPress Overview
- Core: WordPress core system (don't modify)
- Themes: Control website appearance and layout
- Plugins: Extend functionality
- Content: Posts, pages, custom post types
- Database: MySQL database for content storage
WordPress Directory Structure
wordpress/
├── wp-admin/ # Admin panel files
├── wp-content/ # Customizable content
│ ├── themes/ # Your themes here
│ ├── plugins/ # Your plugins here
│ └── uploads/ # Media files
├── wp-includes/ # Core WordPress files
├── wp-config.php # Configuration file
└── index.php # Entry point
WordPress Theme Development
Theme Structure
mytheme/
├── style.css # Required: Theme stylesheet & metadata
├── index.php # Required: Main template
├── functions.php # Theme functions and features
├── header.php # Header template
├── footer.php # Footer template
├── sidebar.php # Sidebar template
├── single.php # Single post template
├── page.php # Page template
├── archive.php # Archive template
├── 404.php # 404 error template
├── searchform.php # Search form template
├── screenshot.png # Theme thumbnail
└── assets/
├── css/
├── js/
└── images/
style.css (Theme Header)
/*
Theme Name: My Custom Theme
Theme URI: https://example.com/my-theme
Author: Your Name
Author URI: https://example.com
Description: A custom WordPress theme
Version: 1.0.0
License: GNU General Public License v2 or later
License URI: http://www.gnu.org/licenses/gpl-2.0.html
Text Domain: mytheme
*/
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 15px;
}
functions.php
<?php
// Theme setup
function mytheme_setup() {
// Add theme support
add_theme_support('title-tag');
add_theme_support('post-thumbnails');
add_theme_support('html5', array('search-form', 'comment-form', 'comment-list'));
add_theme_support('custom-logo');
// Register navigation menus
register_nav_menus(array(
'primary' => __('Primary Menu', 'mytheme'),
'footer' => __('Footer Menu', 'mytheme')
));
// Add image sizes
add_image_size('mytheme-featured', 800, 400, true);
add_image_size('mytheme-thumbnail', 300, 200, true);
}
add_action('after_setup_theme', 'mytheme_setup');
// Enqueue scripts and styles
function mytheme_scripts() {
// Enqueue stylesheet
wp_enqueue_style('mytheme-style', get_stylesheet_uri(), array(), '1.0.0');
// Enqueue custom CSS
wp_enqueue_style('mytheme-custom', get_template_directory_uri() . '/assets/css/custom.css', array(), '1.0.0');
// Enqueue JavaScript
wp_enqueue_script('mytheme-script', get_template_directory_uri() . '/assets/js/custom.js', array('jquery'), '1.0.0', true);
// Enqueue comment reply script
if (is_singular() && comments_open() && get_option('thread_comments')) {
wp_enqueue_script('comment-reply');
}
}
add_action('wp_enqueue_scripts', 'mytheme_scripts');
// Register widget areas
function mytheme_widgets_init() {
register_sidebar(array(
'name' => __('Sidebar', 'mytheme'),
'id' => 'sidebar-1',
'description' => __('Add widgets here.', 'mytheme'),
'before_widget' => '<section class="widget">',
'after_widget' => '</section>',
'before_title' => '<h2 class="widget-title">',
'after_title' => '</h2>',
));
register_sidebar(array(
'name' => __('Footer', 'mytheme'),
'id' => 'footer-1',
'description' => __('Footer widget area.', 'mytheme'),
'before_widget' => '<div class="footer-widget">',
'after_widget' => '</div>',
'before_title' => '<h3>',
'after_title' => '</h3>',
));
}
add_action('widgets_init', 'mytheme_widgets_init');
?>
header.php
<!DOCTYPE html>
<html <?php language_attributes(); ?>>
<head>
<meta charset="<?php bloginfo('charset'); ?>">
<meta name="viewport" content="width=device-width, initial-scale=1">
<?php wp_head(); ?>
</head>
<body <?php body_class(); ?>>
<?php wp_body_open(); ?>
<header class="site-header">
<div class="container">
<div class="site-branding">
<?php if (has_custom_logo()) : ?>
<?php the_custom_logo(); ?>
<?php else : ?>
<h1 class="site-title">
<a href="<?php echo esc_url(home_url('/')); ?>">
<?php bloginfo('name'); ?>
</a>
</h1>
<p class="site-description"><?php bloginfo('description'); ?></p>
<?php endif; ?>
</div>
<nav class="main-navigation">
<?php
wp_nav_menu(array(
'theme_location' => 'primary',
'menu_class' => 'primary-menu',
'fallback_cb' => false,
));
?>
</nav>
</div>
</header>
index.php (The Loop)
<?php get_header(); ?>
<main class="site-main">
<div class="container">
<?php if (have_posts()) : ?>
<?php while (have_posts()) : the_post(); ?>
<article id="post-<?php the_ID(); ?>" <?php post_class(); ?>>
<header class="entry-header">
<h2 class="entry-title">
<a href="<?php the_permalink(); ?>">
<?php the_title(); ?>
</a>
</h2>
<div class="entry-meta">
<span class="posted-on">
<?php echo get_the_date(); ?>
</span>
<span class="byline">
by <?php the_author(); ?>
</span>
</div>
</header>
<?php if (has_post_thumbnail()) : ?>
<div class="post-thumbnail">
<?php the_post_thumbnail('mytheme-featured'); ?>
</div>
<?php endif; ?>
<div class="entry-content">
<?php the_excerpt(); ?>
<a href="<?php the_permalink(); ?>" class="read-more">Read More</a>
</div>
<footer class="entry-footer">
<?php
$categories = get_the_category();
if ($categories) {
echo '<span class="cat-links">';
echo 'Posted in ';
echo get_the_category_list(', ');
echo '</span>';
}
$tags = get_the_tags();
if ($tags) {
echo '<span class="tags-links">';
echo 'Tagged ';
echo get_the_tag_list('', ', ');
echo '</span>';
}
?>
</footer>
</article>
<?php endwhile; ?>
<!-- Pagination -->
<div class="pagination">
<?php
the_posts_pagination(array(
'mid_size' => 2,
'prev_text' => __('« Previous', 'mytheme'),
'next_text' => __('Next »', 'mytheme'),
));
?>
</div>
<?php else : ?>
<p><?php _e('Sorry, no posts found.', 'mytheme'); ?></p>
<?php endif; ?>
</div>
</main>
<?php get_sidebar(); ?>
<?php get_footer(); ?>
WordPress Plugin Development
Basic Plugin Structure
<?php
/*
Plugin Name: My Custom Plugin
Plugin URI: https://example.com/my-plugin
Description: A custom WordPress plugin
Version: 1.0.0
Author: Your Name
Author URI: https://example.com
License: GPL v2 or later
Text Domain: my-custom-plugin
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
// Define plugin constants
define('MCP_VERSION', '1.0.0');
define('MCP_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('MCP_PLUGIN_URL', plugin_dir_url(__FILE__));
// Plugin activation
function mcp_activate() {
// Create custom database table
global $wpdb;
$table_name = $wpdb->prefix . 'custom_data';
$charset_collate = $wpdb->get_charset_collate();
$sql = "CREATE TABLE $table_name (
id mediumint(9) NOT NULL AUTO_INCREMENT,
name varchar(100) NOT NULL,
data text NOT NULL,
created_at datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id)
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($sql);
// Set default options
add_option('mcp_option_name', 'default_value');
}
register_activation_hook(__FILE__, 'mcp_activate');
// Plugin deactivation
function mcp_deactivate() {
// Cleanup tasks
flush_rewrite_rules();
}
register_deactivation_hook(__FILE__, 'mcp_deactivate');
// Add admin menu
function mcp_admin_menu() {
add_menu_page(
'My Plugin Settings', // Page title
'My Plugin', // Menu title
'manage_options', // Capability
'my-custom-plugin', // Menu slug
'mcp_settings_page', // Callback function
'dashicons-admin-generic', // Icon
30 // Position
);
}
add_action('admin_menu', 'mcp_admin_menu');
// Settings page callback
function mcp_settings_page() {
?>
<div class="wrap">
<h1>My Plugin Settings</h1>
<form method="post" action="options.php">
<?php
settings_fields('mcp_settings');
do_settings_sections('my-custom-plugin');
submit_button();
?>
</form>
</div>
<?php
}
// Register settings
function mcp_register_settings() {
register_setting('mcp_settings', 'mcp_option_name');
add_settings_section(
'mcp_main_section',
'Main Settings',
'mcp_section_callback',
'my-custom-plugin'
);
add_settings_field(
'mcp_option_field',
'Option Name',
'mcp_option_field_callback',
'my-custom-plugin',
'mcp_main_section'
);
}
add_action('admin_init', 'mcp_register_settings');
function mcp_section_callback() {
echo 'Configure your plugin settings below:';
}
function mcp_option_field_callback() {
$value = get_option('mcp_option_name');
echo '<input type="text" name="mcp_option_name" value="' . esc_attr($value) . '" />';
}
?>
Shortcode Example
<?php
// Create a shortcode
function mcp_contact_form_shortcode($atts) {
// Extract attributes
$atts = shortcode_atts(array(
'title' => 'Contact Us',
'button_text' => 'Submit'
), $atts);
ob_start();
?>
<div class="mcp-contact-form">
<h3><?php echo esc_html($atts['title']); ?></h3>
<form method="post" action="<?php echo admin_url('admin-ajax.php'); ?>">
<input type="hidden" name="action" value="mcp_submit_contact">
<?php wp_nonce_field('mcp_contact_nonce', 'mcp_nonce'); ?>
<div class="form-group">
<label>Name:</label>
<input type="text" name="name" required>
</div>
<div class="form-group">
<label>Email:</label>
<input type="email" name="email" required>
</div>
<div class="form-group">
<label>Message:</label>
<textarea name="message" required></textarea>
</div>
<button type="submit"><?php echo esc_html($atts['button_text']); ?></button>
</form>
</div>
<?php
return ob_get_clean();
}
add_shortcode('contact_form', 'mcp_contact_form_shortcode');
// Usage: [contact_form title="Get in Touch" button_text="Send Message"]
// Handle form submission via AJAX
function mcp_submit_contact() {
// Verify nonce
if (!isset($_POST['mcp_nonce']) || !wp_verify_nonce($_POST['mcp_nonce'], 'mcp_contact_nonce')) {
wp_send_json_error('Invalid nonce');
}
// Sanitize input
$name = sanitize_text_field($_POST['name']);
$email = sanitize_email($_POST['email']);
$message = sanitize_textarea_field($_POST['message']);
// Validate
if (empty($name) || empty($email) || empty($message)) {
wp_send_json_error('All fields are required');
}
// Send email
$to = get_option('admin_email');
$subject = 'Contact Form Submission from ' . $name;
$body = "Name: $name\nEmail: $email\n\nMessage:\n$message";
$headers = array('Content-Type: text/plain; charset=UTF-8');
if (wp_mail($to, $subject, $body, $headers)) {
wp_send_json_success('Message sent successfully');
} else {
wp_send_json_error('Failed to send message');
}
}
add_action('wp_ajax_mcp_submit_contact', 'mcp_submit_contact');
add_action('wp_ajax_nopriv_mcp_submit_contact', 'mcp_submit_contact');
?>
WordPress Hooks (Actions & Filters)
Actions
Execute code at specific points in WordPress execution
// Add action
add_action('init', 'my_function');
function my_function() {
// Your code here
}
// Do action
do_action('custom_hook');
Filters
Modify data before it's displayed or saved
// Add filter
add_filter('the_content', 'my_filter');
function my_filter($content) {
return $content . 'Added text';
}
// Apply filter
apply_filters('custom_filter', $data);
Common Hooks Examples
<?php
// Modify post content
function add_copyright_notice($content) {
if (is_single()) {
$content .= '<p>© ' . date('Y') . ' ' . get_bloginfo('name') . '</p>';
}
return $content;
}
add_filter('the_content', 'add_copyright_notice');
// Add custom body class
function add_custom_body_class($classes) {
if (is_page('about')) {
$classes[] = 'about-page';
}
return $classes;
}
add_filter('body_class', 'add_custom_body_class');
// Modify excerpt length
function custom_excerpt_length($length) {
return 30; // 30 words
}
add_filter('excerpt_length', 'custom_excerpt_length');
// Add content before post
function add_content_before_post($content) {
if (is_single()) {
$before = '<div class="before-post">Read time: 5 minutes</div>';
$content = $before . $content;
}
return $content;
}
add_filter('the_content', 'add_content_before_post');
// Disable WordPress auto-updates
add_filter('auto_update_core', '__return_false');
// Custom login logo
function custom_login_logo() {
?>
<style type="text/css">
#login h1 a {
background-image: url(<?php echo get_template_directory_uri(); ?>/images/logo.png);
background-size: contain;
width: 320px;
}
</style>
<?php
}
add_action('login_enqueue_scripts', 'custom_login_logo');
?>
WordPress Database ($wpdb)
Using $wpdb Class
<?php
global $wpdb;
// INSERT
$wpdb->insert(
$wpdb->prefix . 'custom_table',
array(
'name' => 'John Doe',
'email' => 'john@example.com',
'status' => 'active'
),
array('%s', '%s', '%s')
);
$insert_id = $wpdb->insert_id;
// SELECT (get_results)
$results = $wpdb->get_results("SELECT * FROM {$wpdb->prefix}custom_table WHERE status = 'active'");
foreach ($results as $row) {
echo $row->name;
}
// SELECT (get_row)
$user = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}custom_table WHERE id = %d",
$user_id
));
// SELECT (get_var) - single value
$count = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}custom_table");
// SELECT (get_col) - single column
$emails = $wpdb->get_col("SELECT email FROM {$wpdb->prefix}custom_table");
// UPDATE
$wpdb->update(
$wpdb->prefix . 'custom_table',
array('status' => 'inactive'), // Data to update
array('id' => 1), // WHERE clause
array('%s'), // Data format
array('%d') // WHERE format
);
$updated_rows = $wpdb->rows_affected;
// DELETE
$wpdb->delete(
$wpdb->prefix . 'custom_table',
array('id' => 1),
array('%d')
);
// Prepared statements (security)
$safe_query = $wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}custom_table WHERE name = %s AND status = %s",
$name,
$status
);
$results = $wpdb->get_results($safe_query);
// Error handling
if ($wpdb->last_error) {
error_log('Database error: ' . $wpdb->last_error);
}
?>
Custom Post Types
<?php
// Register custom post type
function register_portfolio_post_type() {
$labels = array(
'name' => 'Portfolio',
'singular_name' => 'Portfolio Item',
'add_new' => 'Add New',
'add_new_item' => 'Add New Portfolio Item',
'edit_item' => 'Edit Portfolio Item',
'new_item' => 'New Portfolio Item',
'view_item' => 'View Portfolio Item',
'search_items' => 'Search Portfolio',
'not_found' => 'No portfolio items found',
'not_found_in_trash' => 'No portfolio items found in trash'
);
$args = array(
'labels' => $labels,
'public' => true,
'has_archive' => true,
'publicly_queryable' => true,
'query_var' => true,
'rewrite' => array('slug' => 'portfolio'),
'capability_type' => 'post',
'hierarchical' => false,
'supports' => array('title', 'editor', 'thumbnail', 'excerpt'),
'menu_icon' => 'dashicons-portfolio',
'show_in_rest' => true // Gutenberg support
);
register_post_type('portfolio', $args);
}
add_action('init', 'register_portfolio_post_type');
// Register custom taxonomy
function register_portfolio_taxonomy() {
$labels = array(
'name' => 'Portfolio Categories',
'singular_name' => 'Portfolio Category',
'search_items' => 'Search Categories',
'all_items' => 'All Categories',
'parent_item' => 'Parent Category',
'parent_item_colon' => 'Parent Category:',
'edit_item' => 'Edit Category',
'update_item' => 'Update Category',
'add_new_item' => 'Add New Category',
'new_item_name' => 'New Category Name',
'menu_name' => 'Categories'
);
$args = array(
'labels' => $labels,
'hierarchical' => true, // Like categories
'public' => true,
'show_ui' => true,
'show_admin_column' => true,
'query_var' => true,
'rewrite' => array('slug' => 'portfolio-category'),
'show_in_rest' => true
);
register_taxonomy('portfolio_category', array('portfolio'), $args);
}
add_action('init', 'register_portfolio_taxonomy');
?>
WordPress Development Best Practices
Do's
- Use WordPress coding standards
- Sanitize all input data
- Escape all output
- Use nonces for security
- Enqueue scripts and styles properly
- Use translation functions
- Follow the template hierarchy
- Use child themes for customization
- Implement proper error handling
- Document your code
Don'ts
- Don't modify WordPress core files
- Don't hardcode URLs or paths
- Don't use deprecated functions
- Don't ignore security best practices
- Don't use direct database queries without $wpdb
- Don't forget to validate and sanitize
- Don't create security vulnerabilities
- Don't use too many plugins
- Don't ignore performance optimization
- Don't forget about mobile responsiveness
Security Best Practices
<?php
// 1. Sanitize input
$name = sanitize_text_field($_POST['name']);
$email = sanitize_email($_POST['email']);
$content = wp_kses_post($_POST['content']);
// 2. Escape output
echo esc_html($user_input);
echo esc_url($url);
echo esc_attr($attribute);
// 3. Use nonces
wp_nonce_field('my_action_name', 'my_nonce_field');
if (!wp_verify_nonce($_POST['my_nonce_field'], 'my_action_name')) {
die('Security check failed');
}
// 4. Check capabilities
if (!current_user_can('manage_options')) {
wp_die('You do not have sufficient permissions');
}
// 5. Prepared statements
$wpdb->prepare("SELECT * FROM {$wpdb->prefix}table WHERE id = %d", $id);
?>
Session Summary
Key Points
- WordPress themes control appearance; plugins extend functionality
- style.css and functions.php are essential theme files
- The Loop is the core of WordPress content display
- Template hierarchy determines which template file loads
- Hooks (actions and filters) enable customization
- $wpdb class provides safe database interactions
- Always sanitize input and escape output
- Use nonces for security validation
- Follow WordPress coding standards
- Custom post types extend content capabilities
Course Complete!
Congratulations on completing SWE4001 - Internet and Web Technologies! You've learned the fundamentals of web development, from HTML and CSS to advanced PHP, databases, and content management systems. Continue practicing and building projects to master these skills.