Building Web Apps With WordPress - kylecoberly/knowledge GitHub Wiki

Folder Hierarchy

  • In the root directory, the only file you should touch is wp-config.php. Everything else could be rewritten with an upgrade.
  • /wp-admin is the admin dashboard, AJAX calls should all go through admin-ajax.php
  • /wp-includes is where the built-in functions are
  • /wp-content is where all of the customization is
    • /plugins
    • /themes
    • /uploads - Media files
    • /mu-plugins - "Must use" - plugins that are autoloaded and don't even show up on the dashboard

Database Structure

  • wp_options Table - Sitewide data, plugin settings
    • Functions are in /wp-includes/option.php
  • wp_users - User data, generally managed from dashboard
    • wp_usermeta - Extensible user data, use this to add extra attributes without adding more columns to the wp_users table
    • Functions are in /wp-includes/pluggable.php and /wp-includes/user.php
  • wp_posts - Data for posts, pages, menu items, etc. Type is determined by the post_type column.
    • wp_postmeta - Extra data for posts, better than adding more columns to wp_posts
    • Functions are in /wp-includes/post.php
  • wp_comments
    • wp_commentmeta - Extra data for comments, better than adding more columns to wp_comments
    • Functions are in /wp-includes/comment.php
  • wp_terms - Stores taxonomies- tags, categories, etc.
    • wp_termmeta - Extra data for terms, better than adding more columns to wp_terms
    • wp_term_taxonomy - Registers new taxonomies
    • wp_term_relationships - Stores taxonomy assignments
    • Functions are in /wp-includes/taxonomy.php

Hooks

Actions

When WP code has a do_action function, you can insert your code to run in it using the add_action function:

add_action('init', 'some_function_name')

Filters

Filters work on the output of functions, and give you a chance to modify data before it show up on the page or goes into the database:

apply_filters('the_name_of_the_hook, 'a_function_that_will_be_called_with_the_value')

Testing

PHPUnit

  • composer global require phpunit/phpunit:5.*
  • Test methods must be prefixed with test_, and the classes should be suffixed with Test
<?php 

class WP_Meta_VerifyTest extends WP_UnitTestCase
{
    public function setUp()
    {
        parent::setUp();

        $this->class_instance = new WP_Meta_Verify();
    }

    public function test_google_site_verification()
    {

    }

    public function test_bing_site_verification()
    {

    }
}

wp-cli

Makes integrating PHPUnit easier

curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar 
chmod +x wp-cli.phar 
sudo mv wp-cli.phar /usr/local/bin/wp

Gives you commands like:

wp scaffold plugin-tests name-of-plugin-here

Plugins

  • Frontmatter shows up on the dashboard
  • You can extend existing plugins with addons, which may use hooks and filters for the plugins
  • You should have one primary plugin for your app code
  • Any modular thing that could be useful in another project should be a separate plugin

Example:

<?php
/**
 * Plugin Name: My Plugin
 * Plugin URI: https://bwawwp.com/my-plugin/
 * Description: This thing rocks
 * Author: Kyle Coberly
 * Version: 1.0.0
 * Author URI: kylecoberly.com
 * License: GPL-2.0+
 * License URI: http://ww.gnu.org/licenses/gpl-2.0.txt
*/
function some_plugin(){
  echo "<h1>Can't stop; won't stop</h1>";
}

add_action('wp_footer', 'some_plugin')
?>

Suggested File Structure

adminpages/ (only backend)
classes/ (should be named class.ClassName.php)
css/
  |-admin.css (only what you would need with no theme at all)
  |-frontend.css (only what you would need with no theme at all)
js/
images/
includes/ (all PHP files needed by your plugin)
  |-lib/ (third-party)
  |-functions.php (helper code, not core logic)
pages/ (only frontend)
services/ (PHP code for AJAX calls)
scheduled/ (for crons or other intervals)
my-plugin.php (entrypoint)

Common Helpers

Loading CSS and JS:

function load_a_style(){
  wp_enqueue_style(
    'name-of-script',
    plugins_url('css/file-path.css, __FILE__),
    array(),
    VERSION_NUMBER_HERE,
    'screen'
  );
}
function load_a_script(){
  wp_enqueue_script(
    'name-of-script',
    plugins_url('js/file-path.js', __FILE__),
    array('dependent-script'),
    VERSION_NUMBER_HERE
  );
}
function add_a_menu_page()
  add_menu_page(
    'Page Title',
    'Menu Title',
    'capability_required_to_show_page', // eg. manage_options
    'menu-slug',
    'reports_page' // callable
  );
}
function reports_page(){
  require_once dirname( __FILE__ ) . "/adminpages/reports.php";
}

add_action('wp_enquque_scripts', 'load_a_script');
add_action('wp_enquque_scripts', 'load_a_style);

WordPress Loop:

global $post; // All post data
global $authordata; // Author of a post
global $wp_query // All post content for the page you're on
global $current_user

Queries

global $wpdb
$wpdb->query() // Query the DB directly, returns a bunch of different things
$wpdb->prepare($query, $params) // Prepared statements, uses %s, %f, %s, and the literal %%
$wpdb->result() 
$wpdb->insert_id()
$wpdb->get_results($query, $output_type) // number/associative, array/object
$wpdb->get_col($query, $column_offset) // Pluck, eg. ids
$wpdb->get_row($query, $output_type, 0) // Get first record
$wpdb->insert(
  $wpdb->my_table_name
  array("name" => $name, "id" => $id),
  array($s, $d)
) // Auto-escaped
$wpdb->replace($name, $data, $types) // Overwrites any row with same keys as $data
$wpdb->update($table, $data, $where)

Themes

  • You should have one primary theme for the frontend.
  • WordPress scans all of the .php files in your active theme's directory for templates. Any file with Template Name: Whatever in the frontmatter will be available as a template
  • If your users must activate a plugin for a theme to work, you should probably put the plugin related stuff in a parent theme and the view stuff in a child theme.
  • index.php is the fallback for all page loads, and with style.css is the only required file for a theme
  • You can make single- and archive- versions of any custom post type: single-<post-type>.php, archive-<post-type>.php
  • Templates are loaded after WordPress's init and wp actions have fired

Theme Files

  • index.php
  • 404.php
  • author.php
  • archive.php
  • attachment.php
  • comments.php
  • date.php
  • footer.php
  • front-page.php
  • functions.php
  • header.php
  • home.php
  • image.php
  • search.php
  • sidebar.php
  • category.php
  • tag.php
  • taxonomy.php
  • single.php
  • single-{post-type}.php
  • page.php

Loading Files

To load a file, use the get_{type}() method:

get_header(); // Loads `header.php`
get_header("alternate"); // Loads `header-alternate.php`

comments_template(); // Loads `comments.php`
get_search_form(); // Loads `searchform.php`

Functions

/includes/functions.php
/includes/settings.php
/includes/sidebars.php

Theme Frameworks

  • _s (Underscores) - Not a parent theme, but a starting place for building a parent theme
  • Memberlite - For member-driven sites, good for designers
  • Genesis - Parent theme, very abstracted, good for lite customization

Template Layouts

You can use the the_content hook to wrap content:

<?php
function template($content){
  ob_start();
  ?>
    <p>This will go after whatever</p>
  <?php
  $template_content = ob_get_contents();
  ob_end_clean();

  return $content . $template_content;
}
?>
  • You can achieve something similar with "short codes", which are simple syntax for embedding images, quizzes, API data, etc.
  • You can use locate_template to find something that may have been overridden by a user
  • You can also look up specific templates and send data to them:
get_template_part('templates/some_content', 'function_to_process_template')

Menus

  • Allows users to control items from dashboard
// Register
register_nav_menu($slug, $long_name);
register_nav_menus(array($slug => $long_name));

// Consume
wp_nav_menu(array('theme_location' => 'main'));

Styles

  • Loaded in a style.css file, which is automatically enqueued.
  • Version your CSS so the files can be fingerprinted
  • You can use wp_enqueue_style() to load up individual CSS files
  • You can use media queries for responsiveness, and also pass them into wp_enqueue_style

Looks for the following frontmatter:

/*
Theme Name: Name
Theme URI: https://uri
Author: Kyle Coberly
Author URI: https://kylecoberly.com
Description: Site
Version: 1.4
License: MIT
License URI: https://mitlicense.org
Text Domain: name
Tags: comma, separated, list
*/

Browser Detection

Built-in globals:

  • $is_lynx
  • $is_gecko
  • $is_winIE
  • $is_macIE
  • $is_opera
  • $is_NS4
  • $is_safari
  • $is_chrome
  • $is_iphone
  • $is_IE
  • $is_apache
  • $is_IIS
  • $is_iis7
  • wp_is_mobile()
  • get_browser() // Use with caution

Popular Plugins

  • Admin Columns - Manages what columns are displayed on posts, users, comments, media, and CPT
  • Advanced Custom Fields - Adds custom fields for any post type
  • BadgeOS/GamiPress - Adds achievements
  • Posts 2 Posts - M:N Relationships between posts
  • Members - User roles
  • W3 Total Cache - Caching for performance
  • Yoast SEO - Auto SEO optimization
  • Gravity Forms - Custom contact forms
  • BackupBuddy - Backup your WP instance
  • WP All Import - Import CSV or XML data into WP
  • BuddyPress - Social Media
⚠️ **GitHub.com Fallback** ⚠️