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

Best practices for WordPress plugin development - PHPWorld

Best practices for WordPress plugin development - PHPWorld

Presented on November 17th 2015 at PHPWorld, Washington D.C., USA.
https://world.phparch.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, and 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.

Juliette Reinders Folmer

November 17, 2015
Tweet

More Decks by Juliette Reinders Folmer

Other Decks in Programming

Transcript

  1. Best Practices for WordPress Plugin Development Tutorial by Juliette Reinders

    Folmer / @jrf_nl
  2. #phpworld

  3. Who Are You ?

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

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

  6. #phpworld Agenda Introducing Best Practices

  7. WordPress Basics

  8. #phpworld Anatomy of WordPress

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

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

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

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

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

    Hooks database: http://adambrown.info/p/wp_hooks  Debug Bar – Action & filter hooks plugin
  14. #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 );
  15. #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
  16. #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
  17. #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
  18. #phpworld The Loop

  19. #phpworld The Loop if ( have_posts() ) : while (

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

  21. #phpworld 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. #phpworld Doing It Wrong function wrap_content( $content ) { echo

    '<div class="my-content">' . $content . '</div>'; } add_filter( 'the_content', 'wrap_content' );
  24. #phpworld 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. #phpworld 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. #phpworld 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. #phpworld 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. #phpworld 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. #phpworld 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. #phpworld Doing It Right function unique_prefix_title( $title, $id ) {

    $post = get_post( $id ); if ( ! empty( $post->post_type ) && 'my_cpt' === $post->post_type ) { $title = sprintf( esc_html__( '%s (My Custom Type)', 'text-domain' ), $title ); } return $title; } add_filter( 'the_title', 'unique_prefix_title', 10, 2 );
  31. #phpworld 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. #phpworld 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. #phpworld 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. #phpworld 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. #phpworld 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. #phpworld 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. #phpworld Doing It Wrong function schedule_my_cron_job() { wp_schedule_event( time(), 'hourly',

    'my_cron_function‘ ); } add_action( 'init', 'schedule_my_cron_job' );
  38. #phpworld 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. #phpworld Know Your Hooks

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

    Priority
  42. #phpworld Don’t Reinvent the Wheel

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

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

    bundled libraries
  47. #phpworld 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 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
  48. #phpworld Be Unique

  49. #phpworld 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. #phpworld Be Lazy, Be Lean

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

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

  53. #phpworld 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. #phpworld Be Wordly

  55. #phpworld 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' );
  56. #phpworld 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. #phpworld 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 )
  58. #phpworld I18n Quiz Time http://is.gd/WRLP9C (https://developer.wordpress.com/2015/04/23/ wordpress-developers-test-your-i18n- internationalization-knowledge/)

  59. #phpworld Let Others Hook In

  60. #phpworld 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. #phpworld Make it Beautiful

  62. #phpworld

  63. Developing & Debugging

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

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

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

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

  68. #phpworld Agenda Applying Best Practices

  69. Let’s Make Things Better

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

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

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

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

  74. Stay Involved

  75. #phpworld Help someone who’s struggling

  76. #phpworld Find an abandoned project

  77. #phpworld Have fun!

  78. #phpworld 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/
  79. #phpworld 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.
  80. #phpworld

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