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

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.

Juliette Reinders Folmer

October 19, 2015
Tweet

More Decks by Juliette Reinders Folmer

Other Decks in Programming

Transcript

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

    phpcheatsheets.com Frequent Contributor to Open Source Projects
  2. #zendcon Page footer Admin Bar Page content (loop) Page header

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

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

    Themes  Languages  Js Libraries Content  Post Types  Taxonomies  Widgets  Users  Meta data/custom fields  Options  Shortcodes  OEmbeds
  5. #zendcon Hooks See:  WordPress Codex & Developer Reference 

    Hooks database: http://adambrown.info/p/wp_hooks  Debug Bar – Action & filter hooks plugin
  6. #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 );
  7. #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
  8. #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
  9. #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
  10. #zendcon The Loop <?php if ( have_posts() ) : while

    ( have_posts() ) : the_post(); // // Post Content here // the_title() // the_content() // the_permalink() // ... endwhile; endif;
  11. #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
  12. #zendcon Doing It Wrong function wrap_content( $content ) { echo

    '<div class="my-content">' . $content . '</div>'; } add_filter( 'the_content', 'wrap_content' );
  13. #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' );
  14. #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' );
  15. #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' );
  16. #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' );
  17. #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' );
  18. #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' );
  19. #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 );
  20. #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 );
  21. #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' ) );
  22. #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' );
  23. #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' );
  24. #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 );
  25. #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 );
  26. #zendcon Doing It Wrong function schedule_my_cron_job() { wp_schedule_event( time(), 'hourly',

    'my_cron_function‘ ); } add_action( 'init', 'schedule_my_cron_job' );
  27. #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' );
  28. #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
  29. #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!)
  30. #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
  31. #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
  32. #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()
  33. #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' );
  34. #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' );
  35. #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 )
  36. #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 );
  37. #zendcon Useful Plugins Debug Bar + Extensions Developer Pig Latin

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

    Framework Varying Vagrant Vagrants WP CLI Generate WP WP Gear WPackagist vs TGMPA
  39. #zendcon Useful Resources Codex Developer Reference Source code (collection of

    best practices) Hook database Demo Quotes plugin (including handbooks!)
  40. #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/
  41. #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.