Jaime Martínez | @jmslbam | #wpmeetup010 | 10 feb 2014
Start here
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
FireFox and Web Developer + FireBug 
Chrome and Web Developer + Chrome tools
Internet Explorer 
+ developer toolbar
 or 
modern.ie || ievms
    
Online hosting
    
    
Otherwise
Local with Homebrew and php-osx #
    
Recommended ++
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 );
}
display_errors = On;
error_reporting = E_ALL | E_STRICT; Also install Xdebug

wp-content/themes/* Themes consist of a collection of HTML, CSS, PHP and probably JavaScript files
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
*/ don't put everything in this file... organize it!

    
 
Too enqueue scripts and styles (see last slides)
// Most important call in header.php
wp_head();// Most important call in  footer.php
wp_footer();The Template file used to render the Archive Index page for a Custom Post Type
archive-{post_type}.php – If the post type is product, WordPress will look for archive-product.php.archive.php
    index.php
    Only for pages, needs plugin for CPT's
<?php
/*
Template Name: Contact
*/
?>The Template file used to render a static page (page post-type)
Custom template file – The Page Template assigned to the Page. See get_page_templates().page-{slug}.php – If the page slug is recent-news, WordPress will look to use page-recent-news.php
page-{id}.php – If the page ID is 6, WordPress will look to use page-6.php
page.php
index.php
    
         Please don't use 2 & 3  
    
get_template_part
get_template_part
get_template_part
get_template_part
get_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>
<?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>


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>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, ' -  <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 } ?><?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>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;

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' );
}
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 ); ?>" />
Child themes are themes that piggy-back on another (full) theme.
It only overrides the bare necessary
Updates!
One theme to rule them all...
Payed theme builders don't take care backwards compatibility
woocommerce/templates/cart/cart.phpto
your-theme/woocommerce/cart/cart.php
Or use hooks to add / move content

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' );
} //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(); 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 );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' );
Prevent XSS attacks, escape your data!
or stuff like
absint( $int )
sanitize_title( $title )
$wpdb->prepare(...)
wp_safe_redirect($location, $status = 302)