Theme development

Jaime Martínez | @jmslbam | #wpmeetup010 | 10 feb 2014

Hola

Jaime Martinez



Happy coder @ Level Level
btw, thank for the time & the drinks ;)

Feature contributor and tester WP-CLI

Long time WordPress enthousiast

And you are?


Developers?
Designers?
Account and projectmanager?
Site owners?
Shop owners?

Overall thought


DRY #

Don't repeat yourself

DRY results in flexible and maintainable software

Let me seduce you to the 
proper theming side

So new to WordPress and themes?


Start here

http://make.wordpress.org/docs/theme-developer-handbook/

kthxbye

What do you need?

Some knowledge of HTML & PHP comes in handy
and some developertools

Handbook *

Intro, just read this

http://codex.wordpress.org/WordPress_Semantics#Terminology_Related_to_Design


and then the handbook :)

http://make.wordpress.org/docs/theme-developer-handbook/


*Read back at home or the office

Tools and skills *

Editor

Sublime, PHPStorm



Browsers

FireFox and Web Developer + FireBug

Chrome and Web Developer + Chrome tools

Internet Explorer + developer toolbar or modern.ie || ievms


The setup *


Online hosting


MAMP


Otherwise

Local with Homebrew and php-osx #


Recommended ++

Varying Vagrant Vagrants or an equal

Learn to debug

Turn on debug info! #

wp-config.php

define( 'WP_DEBUG', true );

if ( WP_DEBUG ) {

	define( 'SCRIPT_DEBUG', true );

	define( 'WP_DEBUG_LOG', true );

	define( 'WP_DEBUG_DISPLAY', true );

	@ini_set( 'display_errors', 1 );

}

Turn on debug info!

php.ini
display_errors = On;

error_reporting = E_ALL | E_STRICT; 

Also install Xdebug


Thanks Barry!

Development plugins

Debug bar | more info


What the file | more info


So where's the party?

User proper themes

_s
Roots
Genesis Framework #


Bones
Hybrid
Thematic

Theme anatomy

wp-content/themes/* 

Themes consist of a collection of HTML, CSS, PHP and probably JavaScript files

  • style.css
  • functions.php
  • index.php
  • ...

style.css

Must have!

/*
Theme Name: Roots
Theme URI: http://roots.io/
Description: Roots is a WordPress starter theme based on HTML5 Boilerplate & Bootstrap.
Version: 6.5.2
Author: Ben Word
Author URI: http://benword.com/

License: MIT License
License URI: http://opensource.org/licenses/MIT
*/ 

functions.php

don't put everything in this file... organize it!


https://github.com/Automattic/_s/blob/master/functions.php

https://github.com/roots/roots/blob/master/functions.php

functions.php


Theme anatomy #

index.php calls


  • get_header();
  • The Loop
  • get_sidebar();
  • get_footer();

Theme anatomy

Theme anatomy

Too enqueue scripts and styles (see last slides)


header.php
// Most important call in header.php
wp_head();
footer.php
// Most important call in  footer.php
wp_footer();

Template hierarchy

Say WHUT? #

Template hierarchy
Custom Post Types #

The Template file used to render the Archive Index page for a Custom Post Type

  1. archive-{post_type}.php – If the post type is product, WordPress will look for archive-product.php.
  2. archive.php
  3. index.php

Custom template

Only for pages, needs plugin for CPT's

<?php
/*
Template Name: Contact
*/
?>

Template hierarchy Page #

The Template file used to render a static page (page post-type)

  1. Custom template file – The Page Template assigned to the Page. See get_page_templates().
  2. page-{slug}.php – If the page slug is recent-news, WordPress will look to use page-recent-news.php
  3. page-{id}.php – If the page ID is 6, WordPress will look to use page-6.php
  4. page.php
  5. index.php

 Please don't use 2 & 3 

Get a template part - #

DRY

get_template_part

get_template_part

get_template_part

get_template_part

get_template_part

get_template_part


Get a template part

get_template_part

<?php
/*
Template Name: Front page
*/
?>

<div class="main">
	<?php get_template_part('templates/post', 'highlighted'); // The same ?>
	<?php get_template_part('templates/post', 'latest'); ?>
</div>
<div class="sidebar">
	<?php get_template_part('templates/event', 'highlighted'); ?>
	<?php 
		// Remember that on the front_page the global query contains the latest x posts else use get_template_part('templates/post', 'latest');
		get_template_part('templates/event', 'latest');
	?>
</div>

get_template_part

get_template_part

<?php
/*
Template Name: News
*/
?>

<div class="main">
<?php get_template_part('templates/post', 'highlighted'); // The same ?>
</div>
<div class="sidebar">
<?php get_template_part('templates/post', 'latest'); ?>

<p class="list-view-all-link">
<a class="read-more" href="<?php echo $archive_page; ?>"><?php echo __("More news", "sharedstories"); ?></a>
</p>
<?php endif; ?>

</div>

get_template_part

get_template_part

templates/post-highlighted.php

<article <?php post_class('highlighted'); ?>>
	
	<header>
	  <?php get_template_part('templates/post', 'meta'); ?>
	  <h2 class="entry-title"><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h2>
	</header>

	<?php
	if ( has_post_thumbnail() ) :
    	the_post_thumbnail('medium'); // wrapper link added in custom.php for all post_thumbnail
	endif;
	?>

  <div class="entry-summary">
    <?php echo ll_excerpt_by_id( get_the_ID(), POST_EXCERPT_LENGTH , roots_excerpt_more( true ) ); ?>
  </div>
</article>

get_template_part

templates/post-meta.php

<?php if( is_front_page() ) { ?>
<?php get_template_part('templates/entry-meta', 'time'); ?>
<?php } else {
$post_taxonomies = get_object_taxonomies( $post ); ?> <p class="meta"> <?php get_template_part('templates/entry-meta', 'time'); ?>
<?php echo get_the_term_list( get_the_ID(), $post_taxonomies, '&nbsp;-&nbsp; <span class="lc">', '</span>, <span class="lc">', '</span>' ); ?> - <span class="byline author vcard"><a href="<?php echo get_author_posts_url(get_the_author_meta('ID')); ?>" rel="author" class="fn"><?php echo get_the_author(); ?></a></span> </p>
<?php } ?>

get_template_part

templates/entry-meta-time.php
<?php $d = "d M";?>
<span class="date">
    <time class="updated" datetime="<?php echo get_the_time('c'); ?>"><?php echo get_the_date( $d ); ?></time>
</span>

Render a template part ++

Like every proper template engine this lets you pass $args to the template file # 

/templates/contact-persons.php

$people = '';
foreach ($loc_people as $key => $person) {

$people .= ll_get_template_part('templates/contact-person', array( 'person' => (object)$person['ss_contact_person'], 'return' => true ), false ) ;

}
echo $people;

Even widgets can have templates

Templates in widgets


Templates in widgets #

Simply include the templates from plugins/your-plugin/widget.php

public function form( $instance ) {

	wp_enqueue_media();

	$defaults = array( 'image-id' => false, 'image-preview' => '', 'url' => false );

	$instance = wp_parse_args( $instance, $defaults );

	$image_preview = esc_url_raw( $instance['image-preview'] );
	$image_id = absint( $instance['image-id'] );
	
	$image_thumbnail = $this->get_image_preview( $image_id );

	$url = esc_url_raw( $instance['url'] );

	// Display the admin form
	include( plugin_dir_path(__FILE__) . 'views/admin.php' );
}

Templates in widgets

plugins/your-plugin/views/admint.php

<!-- This file is used to markup the administration form of the widget. -->
<p class="abn-sidebar-widget-image"><?php echo $image_thumbnail; ?></p> <p> <a class="ll-sidebar-widget-image-button button button-secondary" data-uploader_title="Select banner" >Select image</a> </p> <p> <label for="<?php echo $this->get_field_name('url'); ?>">Link</label> <input class="ll-sidebar-widget-url widefat" type="text" name="<?php echo $this->get_field_name('url'); ?>" value="<?php echo esc_attr($url); ?>" /> </p> <input class="ll-sidebar-widget-image-preview" type="hidden" name="<?php echo $this->get_field_name('image-preview'); ?>" value="<?php echo esc_attr( $image_preview ); ?>" /> <input class="ll-sidebar-widget-image-id" type="hidden" name="<?php echo $this->get_field_name('image-id'); ?>" value="<?php echo esc_attr( $image_id ); ?>" />

Overwriting stuff

Child theme #

Child themes are themes that piggy-back on another (full) theme.
It only overrides the bare necessary

  • functions.php loads before and in addition to it's parent them
  • No child theme limbo

Why child themes?

Updates!

One theme to rule them all...

Why not child themes?

Payed theme builders don't take care backwards compatibility

Overwriting WooCommerce

    Simply copy it into a directory within your theme
    named /woocommerce keeping the same file structure

    woocommerce/templates/cart/cart.php

    to

    your-theme/woocommerce/cart/cart.php


    Or use hooks to add / move content

    Overwriting WooCommerce

    Overwriting Woocommerce

    in your-child-theme/functions.php

    // First mimic WooCommerce style loading from theme-actions.php
    if ( ! is_admin() ) { add_action( 'wp_enqueue_scripts', 'woo_load_frontend_css', 20 ); }
    function woo_load_frontend_css () {
    
        // Mimic WooCommerce style loading - theme-actions.php
        wp_register_style( 'woo-theme-stylesheet', get_template_directory_uri() . '/style.css' );
        wp_register_style( 'woo-layout', get_template_directory_uri() . '/css/layout.css' );
        wp_enqueue_style( 'woo-theme-stylesheet' );
        wp_enqueue_style( 'woo-layout' );
       
        // Mimic Woocommerce style loading - theme-woocommerce.php
        wp_register_style( 'woocommerce', esc_url( get_template_directory_uri() . '/css/woocommerce.css' ) );
        wp_enqueue_style( 'woocommerce' );
    
        // Manualy load Fabulous Women style
        wp_enqueue_style('theme-stylesheet', get_stylesheet_directory_uri().'/assets/less/style.less');
        //wp_register_style( 'theme-stylesheet', get_stylesheet_uri() );
        wp_enqueue_style( 'theme-stylesheet' );
    } 

    Again DRY

    Reuse The Events Calendar #


    //Let's use all code from tri.be and no custom!
    $args	= array(
    'posts_per_page' => 3,
    ); $query = TribeEventsQuery::getEvents( $args, true ); if ( $query->tribe_is_event && $query->posts ) : //setup_postdata( $post ); $query->the_post(); echo '<article class="hentry tribe-event">'; tribe_get_template_part( 'list/single', 'event' ); // This way all views will be the same ;) echo "</article>"; endif; // Clean up $event = $query = $highlighted_event = false; wp_reset_postdata();

    Extra goodies

    Conditional Tags #

    Checks where you are on the site


    // function.php ;)

    function ll_conditional_custom_category_limit( $query ) { if ( is_admin() || ! $query->is_main_query() ) return; if ( is_archive('specific-term') ) { $query->set( 'posts_per_page', 1 ); return; } } add_action( 'pre_get_posts', 'll_conditional_custom_category_limit', 1 );

    Enqueue scripts and styles #

    Why?

    Caching plugins

    Minify plugins

    Unload stylesheets and scripts from plugins

    /**
     * Enqueue scripts and styles.
     */
    function _s_scripts() {
        wp_enqueue_style( '_s-style', get_stylesheet_uri() );
        
        wp_enqueue_script( '_s-navigation', get_template_directory_uri() . '/js/navigation.js', array(), '20120206', true );
        
        if ( is_singular() && comments_open() && get_option( 'thread_comments' ) ) {
            wp_enqueue_script( 'comment-reply' );
        }
    }
    add_action( 'wp_enqueue_scripts', '_s_scripts' );
    

    Security #

    Prevent XSS attacks, escape your data!

    • esc_url
    • esc_html
    • esc_attr
    • etc…


    or stuff like

    absint( $int )

    sanitize_title( $title )

    $wpdb->prepare(...)

    wp_safe_redirect($location, $status = 302)


    Tips and tricks *

    Use the WordPress API's #

    Coding Standards

    Analyse other peoples work

    Read some nice websites like http://www.poststat.us

    Follow the WordPress Devel Twitter account

    Follow developers like Nacin or ?

    Use at least SFTP



    Tools and skills II *

    GIT

    Bitbucket, GitHub


    CMD

    oh-my-zsh & off course WP-CLI  ^^


    rly kthxbye

    Theme development

    By Jaime Martinez

    Loading comments...

    More from Jaime Martinez