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. #phpworld Who Am I ? Self-employed, Independent Consultant Creator PHPCheatsheets

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

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

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

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

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

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

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

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

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

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

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