Best practices for WordPress plugin development - Zendcon

Best practices for WordPress plugin development - Zendcon

Presented on October 19th 2015 at Zendcon, Las Vegas, Nevada, USA.
http://www.zendcon.com/
---------------------------------------------------------------
The WordPress plugin system allows you to add functionality to WordPress in a snap and turn it into much more than 'just a blogging platform'. Custom Post Types, Post Formats, Shortcodes, Custom Fields, Metaboxes are all ways to extend the functionality of WordPress. In this tutorial we'll look at a number of best practices for building WordPress plugins and some common mistakes made. Bring your favorite plugin to improve or start building your own.

2776198ea9584b6c0d4b494293b8d635?s=128

Juliette Reinders Folmer

October 19, 2015
Tweet

Transcript

  1. Best Practices for WordPress Plugin Development Tutorial @ ZendCon2015 Juliette

    Reinders Folmer / @jrf_nl
  2. #zendcon

  3. Who Are You ?

  4. #zendcon Who Am I ? Self-employed, Independent Consultant Creator PHPCheatsheets

    phpcheatsheets.com Frequent Contributor to Open Source Projects
  5. #zendcon Agenda Introducing Best Practices Applying Best Practices Closing

  6. #zendcon Agenda Introducing Best Practices

  7. WordPress Basics

  8. #zendcon Anatomy of WordPress

  9. #zendcon Page footer Admin Bar Page content (loop) Page header

    (Invisible) HTML <head> Sidebar containing widgets (Main) menu
  10. #zendcon Admin Bar (Invisible) HTML <head> Admin footer Admin Menu

    1. Post types 2. Custom- izations 3. Extras Admin page (with dashboard widgets)
  11. #zendcon Anatomy of WordPress Functionality  Core  Plugins 

    Themes  Languages  Js Libraries Content  Post Types  Taxonomies  Widgets  Users  Meta data/custom fields  Options  Shortcodes  OEmbeds
  12. #zendcon Hooks

  13. #zendcon Hooks See:  WordPress Codex & Developer Reference 

    Hooks database: http://adambrown.info/p/wp_hooks  Debug Bar – Action & filter hooks plugin
  14. #zendcon 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 );
  15. #zendcon 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
  16. #zendcon 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_select ion wp admin_head admin_bar _menu admin _notices the_post admin _footer
  17. #zendcon 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_select ion wp admin_head admin_bar _menu admin _notices the_post admin _footer
  18. #zendcon The Loop

  19. #zendcon The Loop <?php if ( have_posts() ) : while

    ( have_posts() ) : the_post(); // // Post Content here // the_title() // the_content() // the_permalink() // ... endwhile; endif;
  20. #zendcon Putting the Pieces Together

  21. #zendcon Putting the Pieces Together wp-config.php Must-use plugins Plugins •

    [MS] Network-activated plugins • Site-activated plugins Theme • Child-theme functions.php • Parent-theme functions.php
  22. Doing It Wrong ™

  23. #zendcon Doing It Wrong function wrap_content( $content ) { echo

    '<div class="my-content">' . $content . '</div>'; } add_filter( 'the_content', 'wrap_content' );
  24. #zendcon Doing It Right function my_prefix_wrap_content( $content ) { return

    '<div class="my-content">' . $content . '</div>'; } add_filter( 'the_content', 'my_prefix_wrap_content' );
  25. #zendcon Doing It Wrong function prefix_add_hooks() { add_action( 'init', 'prefix_my_init'

    ); add_action( 'admin_init', 'prefix_my_admin_init' ); } add_action( 'wp', 'prefix_add_hooks' );
  26. #zendcon Doing It Right function prefix_add_hooks() { add_action( 'init', 'prefix_my_init'

    ); add_action( 'admin_init', 'prefix_my_admin_init' ); } add_action( 'plugins_loaded', 'prefix_add_hooks' );
  27. #zendcon Doing It Wrong function unique_prefix_content( $content ) { return

    $content . '<p>Copyright Company 2010-' . date( 'Y' ) . '.</p>'; } add_filter( 'the_content', 'unique_prefix_content' );
  28. #zendcon Doing It Right function unique_prefix_content( $content ) { return

    $content . '<p>' . sprintf( /* Translators: 1: Company Name, 2: Year. */ esc_html__( 'Copyright %1$s %2$s.', 'text-domain' ), 'Company', date_i18n( 'Y', get_the_date( 'U' ) ) ) . '</p>'; } add_filter( 'the_content', 'unique_prefix_content' );
  29. #zendcon Doing It Wrong function unique_prefix_title( $title, $id ) {

    $post = get_post( $id ); if ( $post->post_type == 'my_cpt' ) { $title = sprintf( esc_html__( '%s (My Custom Type)', 'text-domain' ), $title ); } return $title; } add_filter( 'the_title', 'unique_prefix_title' );
  30. #zendcon Doing It Right function unique_prefix_title( $title, $id ) {

    $post = get_post( $id ); $type = $post->post_type; if ( ! empty( $type ) && 'my_cpt' === $type ) { $title = sprintf( esc_html__( '%s (My Custom Type)', 'text-domain' ), $title ); } return $title; } add_filter( 'the_title', 'unique_prefix_title', 10, 2 );
  31. #zendcon Doing It Wrong public function add_settings_link( $links, $file )

    { if ( plugin_basename( __FILE__ ) === $file ) { $mylinks = array( '<a href="' . $this->settings_url . '">Settings</a>', ); $links = array_merge( $links, $mylinks ); } return $links; } add_filter( 'plugin_action_links', array( $this, 'add_settings_link' ), 10, 2 );
  32. #zendcon Doing It Right public function add_settings_link( $links ) {

    if ( current_user_can( 'manage_options' ) ) { $links[] = sprintf( '<a href="%1$s" title="%2$s">%3$s</a>', esc_url( $this->settings_url ), esc_attr__( 'Plugin-name Settings', 'text-d..n'), esc_html__( 'Settings', 'text-domain' ) ); } return $links; } add_filter( 'plugin_action_links_' . plugin_basename( __FILE__ ), array( $this, 'add_settings_link' ) );
  33. #zendcon Doing It Wrong function my_prefix_scripts() { echo ' <script

    type="text/javascript“ src="http://ajax.googleapis.com/ajax/libs/jquery/1. 3.0/jquery.min.js"></script> <script type="text/javascript“ src="' . home_url( '/wp-content/plugins/my- plugin/js/my-jquery-dependant-script.js' ) . '"></script>'; } add_action( 'wp_head', 'my_prefix_scripts' );
  34. #zendcon Doing It Right function my_prefix_scripts() { $suffix = (

    ( defined( 'SCRIPT_DEBUG' ) && true === SCRIPT_DEBUG ) ? '' : '.min' ); wp_enqueue_script( 'my-plugin-js', // ID. plugins_url( 'js/my-script' . $suffix . '.js', __FILE__ ), // URL. array( 'jquery' ), // Dependants. PLUGIN_VERSION, // Version. true // Load in footer ? ); } add_action( 'wp_enqueue_scripts', 'my_prefix_scripts' );
  35. #zendcon Doing It Wrong public function filter_featured_image( $value, $object_id, $meta_key,

    $single ) { if ( $meta_key === '_thumbnail_id' && is_home() ) { $field = 'frontpageimage'; $meta_value = get_post_meta( $object_id, $field, $single ); $value = (int) $meta_value; } return $value; } add_filter( 'get_post_metadata', array( $this, 'filter_featured_image' ), 10, 4 );
  36. #zendcon Doing It Right public function filter_featured_image( $value, $object_id, $meta_key

    ) { if ( $meta_key === '_thumbnail_id' && is_home() ) { $field = 'frontpageimage'; remove_filter( 'get_post_metadata', array( $this, 'filter_featured_image' ), 10 ); $meta_value = get_post_meta( $object_id, $field, true ); add_filter( 'get_post_metadata', array( $this, 'filter_featured_image' ), 10, 3 ); if ( is_numeric( $meta_value ) && 0 < (int) $meta_value ) { $value = (int) $meta_value; } } return $value; } add_filter( 'get_post_metadata', array( $this, 'filter_featured_image' ), 10, 3 );
  37. #zendcon Doing It Wrong function schedule_my_cron_job() { wp_schedule_event( time(), 'hourly',

    'my_cron_function‘ ); } add_action( 'init', 'schedule_my_cron_job' );
  38. #zendcon Doing It Right function schedule_my_cron_job() { if ( !

    wp_next_scheduled( 'my_cron_function' ) ) { wp_schedule_event( time(), 'hourly', 'my_cron_function' ); } } register_activation_hook( __FILE__ , 'schedule_my_cron_job' );
  39. Best Practices

  40. #zendcon Know Your Hooks

  41. #zendcon Know Your Hooks Hook Order Actions vs Filters Parameters

    Priority
  42. #zendcon Don’t Reinvent the Wheel

  43. #zendcon 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
  44. #zendcon 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!)
  45. #zendcon Avoid Conflict

  46. #zendcon Avoid Conflict function_exists() class_exists() Jquery no conflicts mode Use

    bundled libraries
  47. #zendcon Libraries shipped with WP json2 /wp-includes/js/json2.js underscore /wp-includes/js/underscore.js backbone

    /wp-includes/js/backbone.js prototype // via googleapis.com scriptaculous-root // via googleapis.com scriptaculous-builder // via googleapis.com scriptaculous-dragdrop // via googleapis.com scriptaculous-effects // via googleapis.com scriptaculous-slider // via googleapis.com scriptaculous-sound // via googleapis.com scriptaculous-controls // via googleapis.com cropper /wp-includes/js/crop/cropper.js swfobject /wp-includes/js/swfobject.js plupload /wp-includes/js/plupload/plupload.full.min.js plupload-handlers /wp-includes/js/plupload/handlers.js wp-plupload /wp-includes/js/plupload/wp-plupload.js swfupload /wp-includes/js/swfupload/swfupload.js swfupload-swfobject /wp-includes/js/swfupload/plugins/swfupload.swfobject.js swfupload-queue /wp-includes/js/swfupload/plugins/swfupload.queue.js swfupload-speed /wp-includes/js/swfupload/plugins/swfupload.speed.js swfupload-handlers /wp-includes/js/swfupload/handlers.js comment-reply /wp-includes/js/comment-reply.js quicktags /wp-includes/js/quicktags.js colorpicker /wp-includes/js/colorpicker.js editor /wp-admin/js/editor.js wp-ajax-response /wp-includes/js/wp-ajax-response.js wp-util /wp-includes/js/wp-util.js wp-backbone /wp-includes/js/wp-backbone.js revisions /wp-admin/js/revisions.js imgareaselect /wp-includes/js/imgareaselect/jquery.imgareaselect.js mediaelement /wp-includes/js/mediaelement/mediaelement-....js wp-mediaelement /wp-includes/js/mediaelement/wp-mediaelement.js froogaloop /wp-includes/js/mediaelement/froogaloop.min.js wp-playlist /wp-includes/js/mediaelement/wp-playlist.js zxcvbn-async /wp-includes/js/zxcvbn-async.js password-strength-meter /wp-admin/js/password-strength-meter.js user-profile /wp-admin/js/user-profile.js language-chooser /wp-admin/js/language-chooser.js user-suggest /wp-admin/js/user-suggest.js admin-bar /wp-includes/js/admin-bar.js Wplink /wp-includes/js/wplink.js wpdialogs /wp-includes/js/wpdialog.js word-count /wp-admin/js/word-count.js jquery-core /wp-includes/js/jquery/jquery.js jquery-migrate /wp-includes/js/jquery/jquery-migrate.js jquery-ui-core /wp-includes/js/jquery/ui/core.js jquery-effects-core /wp-includes/js/jquery/ui/effect.js jquery-effects-blind /wp-includes/js/jquery/ui/effect-blind.js jquery-effects-bounce /wp-includes/js/jquery/ui/effect-bounce.js jquery-effects-clip /wp-includes/js/jquery/ui/effect-clip.js jquery-effects-drop /wp-includes/js/jquery/ui/effect-drop.js jquery-effects-explode /wp-includes/js/jquery/ui/effect-explode.js jquery-effects-fade /wp-includes/js/jquery/ui/effect-fade.js jquery-effects-fold /wp-includes/js/jquery/ui/effect-fold.js jquery-effects-highlight /wp-includes/js/jquery/ui/effect-highlight.js jquery-effects-puff /wp-includes/js/jquery/ui/effect-puff.js jquery-effects-pulsate /wp-includes/js/jquery/ui/effect-pulsate.js jquery-effects-scale /wp-includes/js/jquery/ui/effect-scale.js jquery-effects-shake /wp-includes/js/jquery/ui/effect-shake.js jquery-effects-size /wp-includes/js/jquery/ui/effect-size.js jquery-effects-slide /wp-includes/js/jquery/ui/effect-slide.js jquery-effects-transfer /wp-includes/js/jquery/ui/effect-transfer.js jquery-ui-accordion /wp-includes/js/jquery/ui/accordion.js jquery-ui-autocomplete /wp-includes/js/jquery/ui/autocomplete.js jquery-ui-button /wp-includes/js/jquery/ui/button.js jquery-ui-datepicker /wp-includes/js/jquery/ui/datepicker.js jquery-ui-dialog /wp-includes/js/jquery/ui/dialog.js jquery-ui-draggable /wp-includes/js/jquery/ui/draggable.js jquery-ui-droppable /wp-includes/js/jquery/ui/droppable.js jquery-ui-menu /wp-includes/js/jquery/ui/menu.js jquery-ui-mouse /wp-includes/js/jquery/ui/mouse.js jquery-ui-position /wp-includes/js/jquery/ui/position.js jquery-ui-progressbar /wp-includes/js/jquery/ui/progressbar.js jquery-ui-resizable /wp-includes/js/jquery/ui/resizable.js jquery-ui-selectable /wp-includes/js/jquery/ui/selectable.js jquery-ui-selectmenu /wp-includes/js/jquery/ui/selectmenu.js jquery-ui-slider /wp-includes/js/jquery/ui/slider.js jquery-ui-sortable /wp-includes/js/jquery/ui/sortable.js jquery-ui-spinner /wp-includes/js/jquery/ui/spinner.js jquery-ui-tabs /wp-includes/js/jquery/ui/tabs.js jquery-ui-tooltip /wp-includes/js/jquery/ui/tooltip.js jquery-ui-widget /wp-includes/js/jquery/ui/widget.js jquery-form /wp-includes/js/jquery/jquery.form.js jquery-color /wp-includes/js/jquery/jquery.color.min.js suggest /wp-includes/js/jquery/suggest.js schedule /wp-includes/js/jquery/jquery.schedule.js
  48. #zendcon Be Unique

  49. #zendcon Be Unique  PHP: • Classes • Functions •

    global vars • (global) constants  WP: • shortcodes • option(s) / meta fields • nonces • settings pages • custom post types • hooks  Filenames  HTML/CSS: • classes, ids  Javascript: • I18n object • functions  Multi-lingual • I18n text domain Choose your plugin name carefully & implement consistently
  50. #zendcon Be Lazy, Stay Lean

  51. #zendcon Be Lazy, Be Lean is_...() functions Conditional loading Hook

    in Include files css / js Minify
  52. #zendcon Be Safe

  53. #zendcon Be Safe Check early & check often current_user_can() Validation

    all input sanitize_text_field(), sanitize_title(), sanitize_meta(), sanitize_user() etc Prepare all queries $wpdb->prepare() Escape all output wp_kses(), esc_html(), esc_attr(), esc_url(), esc_textarea(), esc_js() etc Use wp_nonce wp_create_nonce(), wp_nonce_url(), wp_nonce_field(), wp_verify_nonce(), check_admin_referer(), check_ajax_referer()
  54. #zendcon Be Wordly

  55. #zendcon I18n Quiz Time http://is.gd/WRLP9C (https://developer.wordpress.com/2015/04/23/ wordpress-developers-test-your-i18n- internationalization-knowledge/)

  56. #zendcon Be Worldly  GetText load_plugin_textdomain( 'my_plugin', false, plugin_basename( dirname(

    __FILE__ ) ) . '/languages' ); wp_kses_post( sprintf( __( 'Post updated. <a href="%s">View post</a>', 'my_plugin' ), esc_url( get_permalink( $post_ID ) ) ) ); esc_html__( 'Custom field updated.', 'my_plugin' );
  57. #zendcon Be Worldly  GetText load_plugin_textdomain( 'my_plugin', false, plugin_basename( dirname(

    __FILE__ ) ) . '/languages' ); wp_kses_post( sprintf( __( 'Post updated. <a href="%s">View post</a>', 'my_plugin' ), esc_url( get_permalink( $post_ID ) ) ) ); esc_html__( 'Custom field updated.', 'my_plugin' );
  58. #zendcon Be Worldly  GetText  Not only UTF-8 seems_utf8(

    $str ) wp_check_invalid_utf8( $string, $strip = false ) utf8_uri_encode( $utf8_string, $length = 0 ) convert_chars( $content )
  59. #zendcon Let Others Hook In

  60. #zendcon Let Others Hook In do_action( 'my_unique_action_hook', $var_to_pass ); $filtered_var

    = apply_filters( 'my_unique_filter_hook', $var_to_pass, $context_var, $another_context_var );
  61. #zendcon Make it Beautiful

  62. #zendcon

  63. Developing & Debugging

  64. #zendcon Don’t Get Discouraged WP_DEBUG Error logging! JS console logging

    set_ transient()
  65. #zendcon Useful Plugins Debug Bar + Extensions Developer Pig Latin

    User Switching Log Deprecated Notices What’s Running Demo Data Theme Check RTL Tester
  66. #zendcon Helpful Infrastructure WordPress Coding Standards CS WP Unit Testing

    Framework Varying Vagrant Vagrants WP CLI Generate WP WP Gear WPackagist vs TGMPA
  67. #zendcon

  68. #zendcon Agenda Applying Best Practices

  69. Let’s Make Things Better

  70. #zendcon “ ” To be prepared is half the victory.

    - Miguel de Cervantes
  71. #zendcon Let’s Make Things Better Copyrighted Post Easy Bootstrap Shortcode

    Login Logout Search Meter
  72. #zendcon Useful Resources Codex Developer Reference Source code (collection of

    best practices) Hook database Demo Quotes plugin (including handbooks!)
  73. #zendcon Agenda Closing

  74. Stay Involved

  75. #zendcon Help someone who’s struggling

  76. #zendcon Find an abandoned project

  77. #zendcon Have fun!

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

  79. #zendcon Image Credits  WordPress - mkhmarketing (crayons) http://www.flickr.com/photos/mkhmarketing/8469030267/ 

    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/  Dominos – George Hodan http://www.publicdomainpictures.net/view-image.php?image=30522&picture=domino  Hooks - Melissa Maples http://www.flickr.com/photos/melissamaples/3093966940/  Wheel - Pauline Mak http://www.flickr.com/photos/__my__photos/5025541044/  Conflict - Asaf Antman http://www.flickr.com/photos/asafantman/5134136997/  Unique - Luca Volpi (leafs) http://www.flickr.com/photos/luca_volpi/2974346674/  Lazy - Kevin Cauchi http://www.flickr.com/photos/kpcauchi/5376768095/
  80. #zendcon Image Credits  Security – kismihok http://www.flickr.com/photos/kismihok/9686252463/  World

    - Kenneth Lu http://www.flickr.com/photos/toasty/1540997910/  Hooks – Macroman (red background) http://www.flickr.com/photos/macroman/34644959/  Daisies - Steve Wall http://www.flickr.com/photos/stevewall/4780035332/  Alone – Jon http://www.flickr.com/photos/jb-london/3594171841/  Breaktime - Iryna Yeroshko https://www.flickr.com/photos/mandarina94/6389984357/  Help - Green Kozi http://www.flickr.com/photos/themacinator/3445776069/  Bike - Pauline Mak http://www.flickr.com/photos/__my__photos/6399028713/  Fun - Justin Beckley http://www.flickr.com/photos/justinbeckleyphotography/8452437969/  Logos used are the property of and may be trademarked by their respective organizations.