Share
Embed
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 PRO
October 19, 2015
Transcript
80 slides
-
Best Practices for WordPress Plugin Development Tutorial @ ZendCon2015 Juliette
Reinders Folmer / @jrf_nl -
#zendcon Who Am I ? Self-employed, Independent Consultant Creator PHPCheatsheets
phpcheatsheets.com Frequent Contributor to Open Source Projects -
#zendcon Page footer Admin Bar Page content (loop) Page header
(Invisible) HTML <head> Sidebar containing widgets (Main) menu -
#zendcon Admin Bar (Invisible) HTML <head> Admin footer Admin Menu
1. Post types 2. Custom- izations 3. Extras Admin page (with dashboard widgets) -
#zendcon Anatomy of WordPress Functionality Core Plugins
Themes Languages Js Libraries Content Post Types Taxonomies Widgets Users Meta data/custom fields Options Shortcodes OEmbeds -
#zendcon Hooks See: WordPress Codex & Developer Reference
Hooks database: http://adambrown.info/p/wp_hooks Debug Bar – Action & filter hooks plugin -
#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 ); -
#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 -
#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 -
#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 -
#zendcon The Loop <?php if ( have_posts() ) : while
( have_posts() ) : the_post(); // // Post Content here // the_title() // the_content() // the_permalink() // ... endwhile; endif; -
#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 -
#zendcon Doing It Wrong function wrap_content( $content ) { echo
'<div class="my-content">' . $content . '</div>'; } add_filter( 'the_content', 'wrap_content' ); -
#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' ); -
#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' ); -
#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' ); -
#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' ); -
#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' ); -
#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' ); -
#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 ); -
#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 ); -
#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' ) ); -
#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' ); -
#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' ); -
#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 ); -
#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 ); -
#zendcon Doing It Wrong function schedule_my_cron_job() { wp_schedule_event( time(), 'hourly',
'my_cron_function‘ ); } add_action( 'init', 'schedule_my_cron_job' ); -
#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' ); -
#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 -
#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!) -
#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 -
#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 -
#zendcon Be Lazy, Be Lean is_...() functions Conditional loading Hook
in Include files css / js Minify -
#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() -
#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' ); -
#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' ); -
#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 ) -
#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 ); -
#zendcon Useful Plugins Debug Bar + Extensions Developer Pig Latin
User Switching Log Deprecated Notices What’s Running Demo Data Theme Check RTL Tester -
#zendcon Helpful Infrastructure WordPress Coding Standards CS WP Unit Testing
Framework Varying Vagrant Vagrants WP CLI Generate WP WP Gear WPackagist vs TGMPA -
#zendcon “ ” To be prepared is half the victory.
- Miguel de Cervantes -
#zendcon Let’s Make Things Better Copyrighted Post Easy Bootstrap Shortcode
Login Logout Search Meter -
#zendcon Useful Resources Codex Developer Reference Source code (collection of
best practices) Hook database Demo Quotes plugin (including handbooks!) -
#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/ -
#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.