├── README.md ├── performance ├── migrations.md ├── uncached-functions.md └── wp-query-tips.md ├── security ├── README.md ├── checking-capabilities.md ├── escaping-output.md ├── securing-input.md └── verifying-intent.md └── tutorials └── photon-local-development.md /README.md: -------------------------------------------------------------------------------- 1 | WordPress Developer Documentation 2 | =========== 3 | 4 | Community-powered reference material for developers building with WordPress. Pull requests welcome! 5 | 6 | ## Contents 7 | 8 | **Security:** 9 | * [Introduction](security/README.md) 10 | * [Securing Input](security/securing-input.md) 11 | * [Escaping Output](security/escaping-output.md) 12 | * [Checking Capabilities](security/checking-capabilities.md) 13 | * [Verifying Intent](security/verifying-intent.md) 14 | 15 | **Performance:** 16 | * [WP_Query tips](performance/wp-query-tips.md) 17 | * [Uncached Functions](performance/uncached-functions.md) 18 | * [Migrations](performance/migrations.md) - Speed makes migrations much more enjoyable. 19 | -------------------------------------------------------------------------------- /performance/migrations.md: -------------------------------------------------------------------------------- 1 | Migrations 2 | ========== 3 | 4 | As a developer, migrations can be nightmares — or they can be tasks you look forward to. Because you'll likely need to run the migration many times as you make minor corrections, you'll want to optimize for speed. Doing so will make the project much more enjoyable. 5 | 6 | ## Use WP-CLI as your framework 7 | 8 | It goes without saying that you should use [WP-CLI](http://wp-cli.org/) as the framework for your migration script. WP-CLI is easy to extend with [custom commands](https://github.com/wp-cli/wp-cli/wiki/Commands-Cookbook). It also includes many reusable functional and presentational components. 9 | 10 | ### Put default arguments in wp-cli.local.yml 11 | 12 | With commands you'll be using regularly, you can store your standard arguments in your `wp-cli.local.yml` file. 13 | 14 | Given a `wp-cli.local.yml` file like: 15 | 16 | ``` 17 | core install: 18 | title: WordPress Trunk 19 | url: http://wordpress-trunk.dev 20 | admin_user: daniel 21 | admin_password: daniel 22 | admin_email: daniel@handbuilt.co 23 | ``` 24 | 25 | ... a command like this: 26 | 27 | `wp core install --title="WordPress Trunk" --url=http://wordpress-trunk.dev --admin_user=daniel --admin_password=daniel --admin_email=daniel@handbuilt.co` 28 | 29 | ... becomes as easy as this: 30 | 31 | `wp core install` 32 | 33 | When you want to quickly reset your WordPress instance, it becomes a two-liner: `wp db reset --yes; wp core install` 34 | 35 | ## Pre-fetch remote assets 36 | 37 | If your migration script is downloading assets on each run, you'll be spending too much time on redundant remote requests. Solve this problem by "pre-fetching" your remote assets. 38 | 39 | Given a script to loop through all of your remote assets, you can use these helper methods to cache a remote file if the file doesn't exist: 40 | 41 | ``` 42 | /** 43 | * Get the file path for the cached image 44 | * 45 | * @param string $image_url 46 | * @return string 47 | */ 48 | private function get_cache_image_path( $image_url ) { 49 | 50 | $upload_dir = wp_upload_dir(); 51 | return $upload_dir['basedir'] . parse_url( $image_url, PHP_URL_PATH ); 52 | } 53 | 54 | /** 55 | * Cache a remote image locally 56 | * 57 | * @param string $remote_image_src 58 | * @param string $cached_image_path 59 | */ 60 | private function cache_remote_image( $remote_image_src, $cache_remote_image ) { 61 | 62 | // Make sure we aren't killing the server with `wget` usage 63 | while ( trim( shell_exec( 'ps aux | grep wget | wc -l' ) ) > 20 ) { 64 | sleep( 1 ); 65 | } 66 | 67 | // Launch a wget process but don't wait for it to complete 68 | shell_exec( "wget -O {$cache_remote_image} -T 5 {$remote_image_src} > /dev/null 2>/dev/null &" ); 69 | 70 | } 71 | ``` 72 | 73 | ## Disable thumbnail generation 74 | 75 | Especially if you're running the import early in the site rebuild process, you'll want to disable thumbnail generation. Generating multiple sizes of each image has a severe performance penalty. 76 | 77 | Thumbnail generation can be disabled with: 78 | 79 | ``` 80 | add_filter( 'intermediate_image_sizes_advanced', '__return_false' ); 81 | ``` 82 | 83 | Later on, when you're close to launch and all of the theme's image sizes have been nailed down, you can generate all of your necessary thumbnails with [wp media regenerate](http://wp-cli.org/commands/media/regenerate/). 84 | 85 | ## Execution Pro Tips 86 | 87 | Once you're ready to commit to your migration script, you'll be happy you knew the following tips. 88 | 89 | ### Run the script using screen 90 | 91 | `screen` is an [awesome utility for running your scripts](http://www.mattcutts.com/blog/a-quick-tutorial-on-screen/) even when the terminal window closes. Why does this matter? You probably don't want your script to die when you shut your laptop to go home for the evening. Before starting your script, make sure to launch a screen session. 92 | 93 | ### Pipe verbosity to a log file 94 | 95 | Make sure your script produces output for the changes it's making to the database. WP-CLI has a couple utilities you can use: `WP_CLI::line()` and `WP_CLI::warning()`. 96 | 97 | Then, when you're running your script against production data, pipe the verbosity to a log file: 98 | 99 | ``` 100 | wp custom-command my-migration-command &> logfile.txt 101 | ``` 102 | 103 | The log of what happened during the migration can be tremendously useful post-migration, as you debug where your script did the unexpected. [ack](http://beyondgrep.com/) is a useful tool for searching through your log file. 104 | -------------------------------------------------------------------------------- /performance/uncached-functions.md: -------------------------------------------------------------------------------- 1 | Uncached Functions 2 | ================== 3 | 4 | Some commonly-used internal WordPress functions aren't cached by default. If you plan to be using them frequently, it's helpeful to wrap caching around them. 5 | 6 | ## `wp_nav_menu()` 7 | 8 | `wp_nav_menu()` has a high likelihood of running a substantial number of duplicate queries (fetching each item in the menu) on each page load. The following wrapper function caches `wp_nav_menu()` data on the first use, and intelligently purges when nav menus are updated. 9 | 10 | ``` 11 | /** 12 | * Wrapper function around wp_nav_menu() that will cache the wp_nav_menu 13 | */ 14 | function wpdd_cached_nav_menu( $args = array(), $prime_cache = false ) { 15 | global $wp_query; 16 | 17 | $queried_object_id = empty( $wp_query->queried_object_id ) ? 0 : (int) $wp_query->queried_object_id; 18 | 19 | $last_edit = get_option( 'nav_menu_last_edit', 0 ); 20 | 21 | $nav_menu_key = 'nav-menu-' . md5( serialize( $args ) . '-' . $queried_object_id . '-' . $last_edit ); 22 | $my_args = wp_parse_args( $args ); 23 | $my_args = apply_filters( 'wp_nav_menu_args', $my_args ); 24 | $my_args = (object) $my_args; 25 | 26 | if ( ( isset( $my_args->echo ) && true === $my_args->echo ) || !isset( $my_args->echo ) ) { 27 | $echo = true; 28 | } else { 29 | $echo = false; 30 | } 31 | 32 | if ( true === $prime_cache || false === ( $nav_menu = get_transient( $nav_menu_key ) ) ) { 33 | if ( false === $echo ) { 34 | $nav_menu = wp_nav_menu( $args ); 35 | } else { 36 | ob_start(); 37 | wp_nav_menu( $args ); 38 | $nav_menu = ob_get_clean(); 39 | } 40 | 41 | set_transient( $nav_menu_key, $nav_menu, MINUTE_IN_SECONDS * 15 ); 42 | } 43 | if ( true === $echo ) { 44 | echo $nav_menu; 45 | } else { 46 | return $nav_menu; 47 | } 48 | } 49 | 50 | /** 51 | * Tracking when any nav menus were last edited 52 | * makes cache purging much easier 53 | */ 54 | function _wpdd_action_wp_update_nav_menu() { 55 | update_option( 'nav_menu_last_edit', time() ); 56 | } 57 | add_action( 'wp_update_nav_menu', '_wpdd_action_wp_update_nav_menu' ); 58 | ``` 59 | -------------------------------------------------------------------------------- /performance/wp-query-tips.md: -------------------------------------------------------------------------------- 1 | WP_Query tips 2 | ========================= 3 | 4 | A series of performance tips to keep in mind when you're using `WP_Query` 5 | 6 | ### "posts_per_page" => -1 7 | 8 | `WP_Query`, and its sibling functions `get_posts()` and `query_posts()`, is useful in that it allows you to grab an unlimited set of results with `"posts_per_page" => -1`. This feature isn't without its downside, however. Another feature of `WP_Query` is that it will *pre-fetch* all of the post data associated with your query — post meta, taxonomy terms, etc. 9 | 10 | When `"posts_per_page" => -1` (or greater than 500), `WP_Query` [won't pre-fetch post data](https://core.trac.wordpress.org/browser/tags/3.8.2/src/wp-includes/query.php#L2927), and instead run a number of queries to fetch the full representation of each post. 11 | 12 | If you need a lot of posts, you should instead set a high upper bound like "100". 13 | -------------------------------------------------------------------------------- /security/README.md: -------------------------------------------------------------------------------- 1 | Security: An Introduction 2 | ========================= 3 | 4 | Your code works, but is it safe? Will it make your users love your plugin or theme, or hate it when it lets their site get hacked? The best WordPress projects keep their users safe. 5 | 6 | When writing code to run across hundreds if not thousands of websites, you should be extra cautious of how your code is written. In particular, when building a settings page for your theme, creating and manipulating shortcodes, or saving and rendering extra data associated with a post. 7 | 8 | You're in luck though! There are common patterns you can follow to ensure the security of your code. We’ll break them into three key ideas: 9 | 10 | **[Securing Input](securing-input.md):** Every time a user submits data to WordPress, or data is ingested from an external feed, or data comes into WordPress, you should make sure it's safe to handle. You can do so by validating and sanitizing the data. 11 | 12 | **[Escaping Output](escaping-output.md):** Every time a post title, post meta value, or some other data from the database is rendered to the user, we need to make sure it’s properly escaped. This helps prevent issues like cross-site scripting. 13 | 14 | **[Checking Capabilities](checking-capabilities.md)**: To keep sneaky evildoers from changing your plugin's settings, it's important to check that a given user has permission to make the change they want to make. 15 | 16 | **[Verifying Intent](verifying-intent.md)**: Even though a given user might have the capability to perform the action you're checking, they might not have initiated it. Nonces are WordPress' way of verifying the user actually initiated the action. 17 | 18 | Ready to make your plugin secure? [Let’s get started](securing-input.md). 19 | -------------------------------------------------------------------------------- /security/checking-capabilities.md: -------------------------------------------------------------------------------- 1 | This page has been migrated to https://handbuilt.co/tip/checking-capabilities/ 2 | -------------------------------------------------------------------------------- /security/escaping-output.md: -------------------------------------------------------------------------------- 1 | This page has been migrated to: https://handbuilt.co/tip/escaping-output/ 2 | -------------------------------------------------------------------------------- /security/securing-input.md: -------------------------------------------------------------------------------- 1 | This page has been migrated to https://handbuilt.co/tip/securing-input/ 2 | -------------------------------------------------------------------------------- /security/verifying-intent.md: -------------------------------------------------------------------------------- 1 | This page has been migrated: https://handbuilt.co/tip/verifying-intent/ 2 | -------------------------------------------------------------------------------- /tutorials/photon-local-development.md: -------------------------------------------------------------------------------- 1 | # Using Photon in local development 2 | 3 | This page is deprecated in favor of https://handbuilt.co/tip/photon-local-development/ 4 | --------------------------------------------------------------------------------