Upgrade to Pro — share decks privately, control downloads, hide ads and more …

WordPress for the Win! - PHPWorld

WordPress for the Win! - PHPWorld

Presented on November 18th 2015 at PHPWorld, Washington, D.C., USA.
https://world.phparch.com/
---------------------------------------------------------------
WordPress nowadays powers more than 20% of all websites, and with its extensive plugin infrastructure, it is a serious contender in the CMS market. The learning curve for PHP developers to work with WP is very shallow compared to, for instance, Drupal or Typo3. In this talk, which is intended for experienced PHP developers, I will introduce you to essential WordPress concepts and functionality, which will allow you to start rapid development using WordPress. Learn how to develop for WordPress like a pro, and take WordPress to the next level.
---------------------------------------------------------------

If you download the slides, the links should be clickable. Unfortunately they don't seem to be in the online deck, so here's a list of relevant & interesting links in semi-random order:

The Basics:
------------------------------
https://codex.wordpress.org/
https://developer.wordpress.org/reference/
http://adambrown.info/p/wp_hooks
https://codex.wordpress.org/Plugin_API
https://codex.wordpress.org/Plugin_API/Action_Reference
https://codex.wordpress.org/Plugin_API/Filter_Reference
https://codex.wordpress.org/The_Loop

WordPress in Your language:
------------------------------
https://make.wordpress.org/polyglots/handbook/tools/glotpress-translate-wordpress-org/
https://translate.wordpress.org/

wp-config.php Tips & Tricks
------------------------------
https://codex.wordpress.org/Editing_wp-config.php

WP Cron
------------------------------
https://developer.wordpress.org/plugins/cron/
https://wordpress.org/plugins/wp-cron-control/

Theming your Site
------------------------------
https://wordpress.org/themes
https://codex.wordpress.org/Child_Themes
http://wphierarchy.com/
https://make.wordpress.org/themes/

Functions.php
------------------------------
https://codex.wordpress.org/Functions_File_Explained
https://codex.wordpress.org/Function_Reference/add_theme_support
https://codex.wordpress.org/Function_Reference/register_nav_menu
https://codex.wordpress.org/Function_Reference/register_sidebar
https://codex.wordpress.org/Theme_Customization_API

Extending WordPress
------------------------------
http://wordpress.org/plugins/
https://codex.wordpress.org/Must_Use_Plugins

Create a Plugin
------------------------------
https://make.wordpress.org/plugins/
https://codex.wordpress.org/Writing_a_Plugin
https://codex.wordpress.org/Shortcode_API
https://codex.wordpress.org/Embeds
https://codex.wordpress.org/Function_Reference/register_post_type
https://codex.wordpress.org/Function_Reference/register_taxonomy
https://codex.wordpress.org/Settings_API
https://codex.wordpress.org/Widgets_API
https://codex.wordpress.org/Function_Reference/wp_enqueue_style
https://codex.wordpress.org/Function_Reference/wp_enqueue_script
https://codex.wordpress.org/Function_Reference/wp_localize_script
https://developer.wordpress.org/plugins/the-basics/activation-deactivation-hooks/
https://developer.wordpress.org/plugins/the-basics/uninstall-methods/

Doing Things the WordPress Way
------------------------------
https://codex.wordpress.org/WordPress_APIs
http://codex.wordpress.org/Function_Reference

Developing & Debugging
------------------------------
https://wordpress.org/plugins/search.php?q=debug+bar
https://wordpress.org/plugins/developer/
https://wordpress.org/plugins/piglatin/
https://wordpress.org/plugins/user-switching/
https://wordpress.org/plugins/log-deprecated-notices/
https://wordpress.org/plugins/whats-running/
https://wordpress.org/plugins/demo-data-creator/
https://wordpress.org/plugins/theme-check/
https://wordpress.org/plugins/rtl-tester/

https://make.wordpress.org/core/handbook/best-practices/coding-standards/
https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards
https://make.wordpress.org/core/handbook/testing/automated-testing/phpunit/
https://github.com/Varying-Vagrant-Vagrants/VVV
http://wp-cli.org/
http://wpcligui.com/
https://generatewp.com/
http://wpgear.org/
http://wpackagist.org/
http://tgmpluginactivation.com/

Closing
------------------------------
http://twitter.com/jrf_nl
http://profiles.wordpress.org/jrf

Bonus (re: question from the audience)
------------------------------
Moving WP - DB search & replace tool:
https://interconnectit.com/products/search-and-replace-for-wordpress-databases/

Juliette Reinders Folmer

November 18, 2015
Tweet

More Decks by Juliette Reinders Folmer

Other Decks in Programming

Transcript

  1. WordPress For The Win!
    Juliette Reinders Folmer / @jrf_nl

    View full-size slide

  2. #phpworld
    Who Am I?
    Self-employed,
    Independent
    Consultant
    Creator
    PHPCheatsheets
    phpcheatsheets.com
    Frequent
    Contributor to
    Open Source
    Projects

    View full-size slide

  3. #phpworld
     Themes
     Plugins
     SEO
     Ease of use
     Scalability
     Support community
     Regular updates
     Learning curve
     Setup costs
     Maintenance costs
     Hacker target

    View full-size slide

  4. #phpworld
    Anatomy of WordPress

    View full-size slide

  5. #phpworld
    Page
    footer
    Admin
    Bar
    Page
    content
    (loop)
    Page
    header
    (Invisible) HTML
    Sidebar
    containing
    widgets
    (Main)
    menu

    View full-size slide

  6. #phpworld
    Admin Bar
    (Invisible) HTML
    Admin
    footer
    Admin
    Menu
    1. Post
    types
    2. Custom-
    izations
    3. Extras
    Admin
    page
    (with
    dashboard
    widgets)

    View full-size slide

  7. #phpworld
    Anatomy of WordPress
    Functionality
     Core
     Plugins
     Themes
     Languages
     Js Libraries
    Content
     Post Types
     Taxonomies
     Widgets
     Users
     Meta data/custom fields
     Options
     Shortcodes
     OEmbeds

    View full-size slide

  8. #phpworld
    Hooks

    View full-size slide

  9. #phpworld
    Hooks
    See:
     WordPress Codex & Developer Reference
     Hooks database: http://adambrown.info/p/wp_hooks
     Debug Bar – Action & filter hooks plugin

    View full-size slide

  10. #phpworld
    Hooking into WP
    apply_filter( 'hook_name', 'function_name',
    $priority = 10, $accepted_args = 1 );
    add_action( 'hook_name', 'function_name',
    $priority = 10, $accepted_args = 1 );
    add_action( 'hook_name', array( $this, 'method_name' ),
    $priority = 10, $accepted_args = 1 );
    add_action( 'hook_name',
    array( __CLASS__, 'static_method_name' ),
    $priority = 10, $accepted_args = 1 );

    View full-size slide

  11. #phpworld
    Action Hooks Front-end
    muplugins
    _loaded
    plugins
    _loaded
    setup
    _theme
    set_current
    _user
    after_setup
    _theme
    init wp_loaded
    parse
    _request
    posts
    _selection
    wp
    wp_head the_post wp_meta wp_footer
    admin_bar
    _menu

    View full-size slide

  12. #phpworld
    Action Hooks Back-end
    muplugins
    _loaded
    plugins
    _loaded
    setup
    _theme
    set_current
    _user
    after_setup
    _theme
    init wp_loaded
    admin
    _menu
    admin_init
    current
    _screen
    load-
    {page}
    posts
    _selection
    wp
    admin
    _head
    admin_bar
    _menu
    admin
    _notices
    the_post
    admin
    _footer

    View full-size slide

  13. #phpworld
    Action Hooks Back-end
    muplugins
    _loaded
    plugins
    _loaded
    setup
    _theme
    set_current
    _user
    after_setup
    _theme
    init wp_loaded
    admin
    _menu
    admin_init
    current
    _screen
    load-
    {page}
    posts
    _selection
    wp
    admin
    _head
    admin_bar
    _menu
    admin
    _notices
    the_post
    admin
    _footer

    View full-size slide

  14. #phpworld
    The Loop

    View full-size slide

  15. #phpworld
    The Loop
    if ( have_posts() ) :
    while ( have_posts() ) : the_post();
    //
    // Post Content here
    // the_title()
    // the_content()
    // the_permalink()
    // ...
    endwhile;
    endif;

    View full-size slide

  16. #phpworld
    WordPress in Practice

    View full-size slide

  17. WordPress in Your Language

    View full-size slide

  18. #phpworld
    Localized
     ~60 languages
    + ~30 > 50%
     GlotPress
    translate.wordpress.org

    View full-size slide

  19. wp-config.php
    Tips & Tricks

    View full-size slide

  20. #phpworld
    Manage Saved Data
    /* Change how WP deals with post revisions, trash
    and auto-saves */
    define( 'WP_POST_REVISIONS', 20 );
    define( 'AUTOSAVE_INTERVAL', 60 ); // seconds
    define( 'EMPTY_TRASH_DAYS', 30 ); // days
    define( 'MEDIA_TRASH', true );

    View full-size slide

  21. #phpworld
    Performance
    /* Enable Compression, concatenation and cache */
    define( 'COMPRESS_CSS', true );
    define( 'COMPRESS_SCRIPTS', true );
    define( 'CONCATENATE_SCRIPTS', true );
    define( 'ENFORCE_GZIP', true );
    define( 'WP_CACHE', true );

    View full-size slide

  22. #phpworld
    Multisite
    /* Set up WordPress as Multi-site */
    define( 'WP_ALLOW_MULTISITE', true );
    define( 'NOBLOGREDIRECT', 'http://mywp.com' );
    /* After setup - will be provided by WP */
    define( 'MULTISITE', true );
    define( 'SUBDOMAIN_INSTALL', true );
    define( 'DOMAIN_CURRENT_SITE', 'mywp.com' );
    define( 'PATH_CURRENT_SITE', '/' );
    define( 'SITE_ID_CURRENT_SITE', 1 );
    define( 'BLOG_ID_CURRENT_SITE', 1 );

    View full-size slide

  23. #phpworld
    Custom User Tables
    /* Overrule the WP User tables */
    define( 'CUSTOM_USER_TABLE',
    $table_prefix . 'my_users' );
    define( 'CUSTOM_USER_META_TABLE',
    $table_prefix . 'my_usermeta' );

    View full-size slide

  24. #phpworld
    SSL
    /* Force secure logins and admin sessions */
    define( 'FORCE_SSL_LOGIN', true );
    define( 'FORCE_SSL_ADMIN', true );

    View full-size slide

  25. #phpworld
    PHP Memory Limit
    /* Try to increase PHP Memory */
    define( 'WP_MEMORY_LIMIT', '128M' );
    /* Potentially increase it even more in the
    admin area */
    define( 'WP_MAX_MEMORY_LIMIT', '256M' );

    View full-size slide

  26. #phpworld
    No File Editing
    /* Disable Plugin and Theme Update and Installation
    and/or plugin/theme editor */
    define( 'DISALLOW_FILE_EDIT', true );
    define( 'DISALLOW_FILE_MODS', true );

    View full-size slide

  27. #phpworld
    Limit External URL Access
    /* Block WP from making external calls */
    define( 'WP_HTTP_BLOCK_EXTERNAL', true );
    /* Limit allowed external hosts */
    define( 'WP_ACCESSIBLE_HOSTS',
    'api.wordpress.org,*.github.com' );

    View full-size slide

  28. #phpworld
    Custom Directory Paths [1]
    /* Overrule some directory and url paths.
    Mind: no traling slashes! */
    define( 'WP_CONTENT_DIR',
    dirname( __FILE__ ) . '/blog/wp-content' );
    define( 'WP_CONTENT_URL',
    'http://mywp.com/blog/wp-content' );
    define( 'UPLOADS',
    'blog/wp-content/uploads' );
    // Relative to ABSPATH

    View full-size slide

  29. #phpworld
    Custom Directory Paths [2]
    /* Overrule some directory and url paths.
    Mind: no traling slashes! */
    define( 'WP_PLUGIN_DIR',
    dirname( __FILE__ ) . '/blog/wp-content/plugins' );
    define( 'WP_PLUGIN_URL',
    'http://mywp.com/blog/wp-content/plugins' );
    define( 'WPMU_PLUGIN_DIR',
    dirname( __FILE__ ) . '/blog/wp-content/mu-plugins' );
    define( 'WPMU_PLUGIN_URL',
    'http://mywp.com/blog/wp-content/mu-plugins');

    View full-size slide

  30. #phpworld
    Adjust Auto-Updating
    /* Disable the auto-updater (WP 3.7+) */
    define( 'AUTOMATIC_UPDATER_DISABLED', true );
    /* Use true/false to change the default behavior */
    define( 'WP_AUTO_UPDATE_CORE', 'minor' );
    /* Prevent the table upgrade function being run in
    favor of manually running it */
    define( 'DO_NOT_UPGRADE_GLOBAL_TABLES', true );

    View full-size slide

  31. #phpworld
    FTP Credentials
    /* FTP settings */
    define( 'FTP_HOST', 'hostname:port' );
    define( 'FTP_USER', 'username' );
    define( 'FTP_PASS', 'password' );
    define( 'FTP_SSL', true );

    View full-size slide

  32. #phpworld
    Debugging
    /* Enable debugging */
    define( 'WP_DEBUG', true);
    define( 'WP_DEBUG_DISPLAY', true);
    /* Error logging to /wp-content/debug.log */
    define( 'WP_DEBUG_LOG', true);
    /* Load non-minified versions of all scripts, css,
    disables compression and concatenation */
    define( 'SCRIPT_DEBUG', true);

    View full-size slide

  33. #phpworld
    Basic Query Debugging
    /* Remember the DB queries run for the current
    page. */
    define( 'SAVEQUERIES', true );

    View full-size slide

  34. #phpworld
    WP Cron

    View full-size slide

  35. #phpworld
    Adjust WP-Cron
    /* Prevent Cron running too often (plugins) */
    define( 'WP_CRON_LOCK_TIMEOUT', 60 ); // seconds
    /* Run wp-cron with a redirect */
    define('ALTERNATE_WP_CRON', true);

    View full-size slide

  36. #phpworld
    Disable WP-Cron
    /* Disable WP-cron in favor of real cron */
    define( 'DISABLE_WP_CRON', true );

    View full-size slide

  37. #phpworld
    Real Cron with Cron Control
    */5 * * * *
    /usr/local/bin/php -q /path/to/wp/wp-content/
    plugins/wp-cron-control/wp-cron-control.php
    http://mywp.com 38a37a2d5b94bc02ce0472a238d493df
    wget -q 'http://mywp.com/wp-cron.php?doing_wp_cron
    &38a37a2d5b94bc02ce0472a238d493df'

    View full-size slide

  38. Theming Your Site

    View full-size slide

  39. Child Themes

    View full-size slide

  40. #phpworld
    Creating a Child Theme

    View full-size slide

  41. #phpworld
    Child Theme – style.css
    /*
    Theme Name: Twenty Fifteen Child
    Theme URI: http://localhost/
    Template: twentyfifteen
    Author: Advies en zo
    Author URI: http://adviesenzo.nl/
    Description: My Child theme
    Text Domain: twentyfifteen-child
    Version: 1.0.0
    */

    View full-size slide

  42. #phpworld
    Template
    Hierarchy
    Source:
    http://wphierarchy.com/

    View full-size slide

  43. #phpworld
    Example singular.php


    if ( is_singular( array( 'employee' ) ) ) {
    get_template_part( 'template-parts/content',
    'employee' );
    } else {
    get_template_part( 'template-parts/content',
    'singular' );
    }
    endwhile; ?>


    View full-size slide

  44. Functions.php

    View full-size slide

  45. #phpworld
    Typical Theme Setup Functions
    // Hook 'after_setup_theme'
    add_theme_support( 'post-formats', $formats );
    add_theme_support( 'post-thumbnails', $post_types );
    add_theme_support( 'custom-background', $defaults );
    add_theme_support( 'custom-header', $defaults );
    add_theme_support( 'automatic-feed-links' ); // RSS
    add_theme_support( 'html5', $locations );
    add_theme_support( 'title-tag' ); // WP 4.1+

    View full-size slide

  46. #phpworld
    Typical Theme Setup Functions
    // Hook 'init'
    register_nav_menus();
    // Hook 'widget_init'
    register_sidebars();

    View full-size slide

  47. #phpworld
    Theme Customizer
    class MyTheme_Customizer {
    public static function register( $customizer ) {
    $customizer->add_section( 'my_section', $args );
    $customizer->add_setting( 'my_setting', $args );
    $customizer->add_control( $id, $args );
    }
    public static function header_output() {
    // Customizer CSS
    }
    }
    add_action( 'customize_register',
    array( 'MyTheme_Customizer', 'register' ) );
    add_action( 'wp_head',
    array( 'MyTheme_Customizer', 'header_output' ) );

    View full-size slide

  48. #phpworld
    Theme Customizer

    View full-size slide

  49. #phpworld
    functions.php
    function add_qr_code( $content ) {
    $url = 'http://api.qrserver.com/v1/create- qr-code/
    ?size=100x100&data=' .
    urlencode( get_the_permalink() );
    $esc_alt = the_title_attribute( array(
    'before' => 'QR Code for',
    'echo' => false ) );
    return '
    alt="' . $esc_alt . '"/>
    ' . $content;
    }
    add_filter( 'the_content', 'add_qr_code' );

    View full-size slide

  50. #phpworld
    functions.php
    /* Change admin page footer Branding */
    function my_admin_footer_text( $left ) {
    $left = '/>' . esc_html__( 'Webdevelopment, design and
    consultancy:', 'text-domain' ) .
    'Advies en zo';
    return $left;
    }
    add_filter( 'admin_footer_text', my_admin_footer_text' );

    View full-size slide

  51. Extending WordPress

    View full-size slide

  52. #phpworld
    Must-use Plugins

    View full-size slide

  53. #phpworld
    Loading Order
    wp-config.php
    Must-use plugins
    Plugins
    • [MS] Network-activated plugins
    • Site-activated plugins
    Theme
    • Child-theme functions.php
    • Parent-theme functions.php

    View full-size slide

  54. Create a Plugin

    View full-size slide

  55. #phpworld
    Starting a Plugin
    /**
    * Plugin Name: Demo Quotes Plugin
    * Plugin URI: https://github.com/jrfnl/wp-plugin-best-practices-demo
    * Description: Demo plugin - Best Practices Tutorial
    * Version: 1.0
    * Author: Juliette Reinders Folmer
    * Author URI: http://adviesenzo.nl/
    * Text Domain: demo-quotes-plugin
    * Domain Path: /languages
    * License: GPL v3
    */

    View full-size slide

  56. #phpworld
    What Every Plugin Needs
     Hook your functionality onto actions and filters
     Loading your localization files
     Do something
     Load CSS
     Load JS
     Add admin page or add to another admin page
     Help information
     Activation/ Upgrade routines
     Uninstall routines
     ...

    View full-size slide

  57. #phpworld
    Add a shortcode
    function display_call_to_action( $atts ) {
    $atts = shortcode_atts( array(
    'user' => 'office', // Default
    ), $atts, 'calltoaction' );
    $user = get_user_by( 'login', $atts['user'] );
    $html = '';
    if( $user ) {
    $html = '';
    }
    return $html;
    }
    add_shortcode( 'calltoaction', 'display_call_to_action' );

    View full-size slide

  58. #phpworld
    Add Oembed Provider
    // Register Speakerdeck as an oembed provider.
    function add_speakerdeck_oembed() {
    wp_oembed_add_provider(
    // URL format
    '`http[s]?://(www\.)?speakerdeck\.com/[^/]*/.*`i',
    // Endpoint
    'https://speakerdeck.com/oembed.json',
    true // Is format a regex ?
    );
    }
    add_action( 'init', 'add_speakerdeck_oembed' );

    View full-size slide

  59. #phpworld
    Add Custom Content Types &
    Taxonomies
    register_post_type(
    $post_type_name, // Post type name. Max of 20 chars.
    Uppercase and spaces not allowed.
    $args // Arguments for post type.
    );
    register_taxonomy(
    $taxonomy_name, // Taxonomy internal name. Max 32 chars.
    Uppercase and spaces not allowed.
    array( $post_type_name ), // Post types to register this
    taxonomy for.
    $args // Arguments for taxonomy.
    );

    View full-size slide

  60. #phpworld
    Add a Settings Page
    register_setting( $option_group, $option_name,
    $sanitize_callback );
    get/update/delete_option( $option_name );
    add_submenu_page( $parent_slug, $page_title, $menu_title,
    $capability, $page_slug, $call_back );
    add_settings_section( $section_id, $title, $callback,
    $page_slug );
    add_settings_field( $id, $title, $callback, $page_slug,
    $section_id, $args );
    settings_fields( $option_group )
    do_settings_sections( $page_slug );
    do_settings_fields( $page_slug, $section_id );

    View full-size slide

  61. #phpworld
    Add a Widget
    class My_Widget extends WP_Widget {
    // widget actual processes
    public function __construct() {}
    // outputs the content of the widget
    public function widget( $args, $instance ) {}
    // outputs the options form on admin
    public function form( $instance ) {}
    // processes widget options to be saved
    public function update( $new_instance, $old_instance ) {}
    }
    add_action( 'widgets_init',
    create_function( '', 'return register_widget( "My_Widget" );' )
    );

    View full-size slide

  62. #phpworld
    Enqueue Scripts & Styles
    wp_register_style( 'handle',
    plugins_url( 'css/style' . $suffix . '.css', __FILE__ ),
    array(), VERSION, 'all' );
    wp_enqueue_style( 'handle' );
    wp_register_script( 'handle',
    plugins_url( 'js/file' . $suffix . '.js', __FILE__ ),
    array( 'jquery', 'wp-ajax-response' ),
    VERSION, true ); // load in footer
    wp_enqueue_script( 'handle' );
    wp_localize_script( 'handle', 'i18n_js_objectname',
    array() );

    View full-size slide

  63. #phpworld
    (De-)Activation, Upgrade, Uninstall
     (De-)Activation:
     Upgrading -> save plugin version nr & compare
     Uninstall -> uninstall.php file
    /* Set up the (de-)activation actions */
    register_activation_hook( __FILE__,
    array( 'Plugin_Class', 'activate' ) );
    register_deactivation_hook( __FILE__,
    array( 'Plugin_Class', 'deactivate' ) );

    View full-size slide

  64. Doing Things the WordPress Way

    View full-size slide

  65. #phpworld
    Don’t Reinvent the Wheel
    Dashboard
    Widgets API
    Database API HTTP API
    File Header
    API
    Filesystem
    API
    Heartbeat API Metadata API Options API Plugin API Quicktags API
    REST API * Rewrite API Settings API Shortcode API
    Theme
    modification
    API
    Theme
    customization
    API
    Transients API Widgets API
    XML-RPC
    WordPress API
    * Expected in WP 4.4

    View full-size slide

  66. #phpworld
    Use WP Functions
    PHP
     mysqli_...()
     file_put_contents()
     json_encode()
     mail()
     unserialize()
     htmlspecialchars()
     add/stripslashes()
     strtotime() / date()
     http_build_query()
     $_SERVER['HTTPS']
    ...
    WP
     $wpdb->....()
     $wp_filesystem->put_contents()
     wp_json_encode()
     wp_mail()
     maybe_unserialize()
     esc_html()
     wp_unslash()
     mysql2date() / current_time()
     add_query_arg()
     is_ssl()
    ...
    (I mean it!)

    View full-size slide

  67. Developing & Debugging

    View full-size slide

  68. #phpworld
    Useful Plugins
    Debug Bar +
    Extensions
    Developer Pig Latin
    User Switching
    Log
    Deprecated
    Notices
    What’s
    Running
    Demo Data Theme Check RTL Tester

    View full-size slide

  69. #phpworld
    Tools

    View full-size slide

  70. #phpworld
    Helpful Infrastructure
    WordPress Coding Standards CS
    WP Unit Testing Framework
    Varying Vagrant Vagrants
    WP CLI
    Generate WP
    WP Gear
    WPackagist vs TGMPA

    View full-size slide

  71. #phpworld
    Photo Credits
     WordPress - mkhmarketing (crayons)
    http://www.flickr.com/photos/mkhmarketing/8469030267/
     Bridge - Glenn Euloth
    http://www.flickr.com/photos/eulothg/4956082108/
     WordPress - Jenn Vargas
    http://www.flickr.com/photos/foreverdigital/2768397344/
     Billie Holiday – William P. Gottlieb
    http://lcweb2.loc.gov/diglib/ihas/loc.natlib.gottlieb.04251/default.html
     Anatomy - Eva di Martino
    http://www.pureblacklove.com
     Hooks - Raul Lieberwirth
    http://www.flickr.com/photos/lanier67/185311136/
     Loop - Gabe Kinsman
    http://www.flickr.com/photos/auguris/5286612308/
     WordPress - Saad Irfan (core, plugins, themes)
    http://www.flickr.com/photos/saadirfan/5722057280/

    View full-size slide

  72. Thank You!
    Slides: http://speakerdeck.com/jrf
    Feedback: https://joind.in/14755
    Contact me: @jrf_nl
    jrf

    View full-size slide