Contributing to open source

Want to contribute to an open source project but don’t know where to begin? Don’t worry — with a bit of effort and dedication, you too can contribute to open source.

Before you begin, it’s helpful to acknowledge the history of open source. Historically, the term “open source” refers to software published under an open source license. These days, “open source” refers to both the software license and the community around the project. The community can be large, or it can be small. The people could be involved on a daily basis, or weekly basis, or rarely. “Open source” is almost an incomplete term, because it can refer to a broad spectrum of projects.

What project should I contribute to?

You get to choose! Which brings us to the first rule: make yourself happy first.

Contributing to open source can be an emotionally fraught activity. You may getting a fight with someone about whether an idea is good or bad. You may spend hours working on a pull request or new piece of documentation, only to have it rejected. Or, you may go weeks without hearing a reply to that issue you wrote. While it’s easy to construe these experiences as “bad”, often they can occur as a matter of circumstance — not the fault of a specific person.

Going into the open source, know your own motivations: Why do you want to contribute? What do you hope to gain from the experience? How much time are you willing to commit?

Also know that it’s not about you. The scope of the project may include that feature you want to contribute, or it may not. In contributing to an open source project, you’re delegating final decision making authority to the project maintainer.

If you manage your own expectations accordingly, you can keep contributing a rewarding experience.

How do I contribute to a project?

Just like many cultures have their own customs and norms, open source projects can have a variety of ways to manage the contributing process.

One good place to start would be to see if the project has a CONTRIBUTING.md file in its repository. For example, here is the CONTRIBUTING.md for WP-CLI. Or, there may be an equivalent document on the project’s website. For example, here’s the contributing page for WordPress.org. The project will likely outline guidelines for contributing code, writing documentation, providing support, etc.

Another place to look would be the project’s issue tracker. Specifically, look for issues tagged “good first bug” (or some equivalent label). These are issues they have been identified as starting points for new contributors.

Please keep in mind, libre != gratis. The open source license grants you the freedom to use and modify, but not commitments of other people’s time. Most contributors are involved in the project on a volunteer basis (potentially even nights and weekends). Please be respectful, and set your expectations accordingly.

Add a rel=”nofollow” checkbox to WordPress link modal

With a little bit of JavaScript, you can add a rel="nofollow" checkbox to WordPress’ link editor, right below “Open link in a new tab”.

Customizing the WordPress link modal requires using JavaScript to first modify the form’s HTML, then overriding functions on the wpLink object to perform additional behavior.

Map a custom domain with Laravel Valet

Laravel Valet serves *.dev domains by default. But what if you need to host hack the production domain to your project? As it turns out, it’s easier than you’d expect it to be.

First, add the record for the domain to your /etc/hosts file:

sudo echo '127.0.0.1 my-project.com' >> /etc/hosts

Then, in your parked directory, create a symlink with the domain you want to serve:

$ cd ~/projects/
$ valet park
This directory has been added to Valet's paths.
$ mkdir my-project
# my-project.dev is now served by Valet
$ ln -s my-project my-project.com
# my-project.com is now served by Valet

Valet’s Nginx configuration handles all requests by default, so don’t bother creating a custom Nginx config file. Having it serve your specific domain is only a matter of making sure Valet knows where to find your project directory, given its auto-discovery behavior.

Fix PHP Parse error: syntax error, unexpected ‘?’ in phar:///usr/local/bin/wp/php/WP_CLI/Runner.php(941) : eval()’d code on line 1

Are you seeing this PHP parse error when trying to use WP-CLI?

PHP Parse error: syntax error, unexpected '?' in phar:///usr/local/bin/wp/php/WP_CLI/Runner.php(941) : eval()'d code on line 1

You may have a Byte Order Mark at the beginning of your wp-config.php file.

Run head -n3 wp-config.php | hexdump -C to see if there’s a ef bb bf pattern at the beginning of the file:

$ head -n3 wp-config.php | hexdump -C
00000000  ef bb bf 3c 3f 70 68 70  0d 0a 0a 0a              |...<?php....|
0000000c

The Byte Order Mark can be removed with vim:

$ vim wp-config.php
:set nobomb
:wq

Write a custom check to perform an arbitrary assertion

Because wp doctor checks are built on top of a foundational abstraction, it’s relatively straightforward for you to write your own custom check.

The basic requirement is that you create a class extending runcommand\Doctor\Checks\Check that implements a run() method. The run() must set a status and message based on whatever procedural logic

As an example, here’s an annotated custom check to assert Akismet is activated with a valid API key:

<?php

/**
 * Ensures Akismet is activated with the appropriate credentials.
 */
class Akismet_Activated extends runcommand\Doctor\Checks\Check {

	public function __construct( $options = array() ) {
		parent::__construct( $options );
		// Every check is to run on 'after_wp_load' by default.
		// You could instead use 'before_wp_load' or 'after_wp_config_load'
		$this->set_when( 'after_wp_load' );
	}

	public function run() {
		// If the Akismet isn't activated, bail early.
		if ( ! class_exists( 'Akismet' ) ) {
			$this->set_status( 'error' );
			$this->set_message( "Akismet doesn't appear to be activated." );
			return;
		}
		// Verify that the API exists.
		$api_key = Akismet::get_api_key();
		if ( empty( $api_key ) ) {
			$this->set_status( 'error' );
			$this->set_message( 'API key is missing.' );
			return;
		}
		// Verify that the API key is valid.
		$verification = Akismet::verify_key( $api_key );
		if ( 'failed' === $verification ) {
			$this->set_status( 'error' );
			$this->set_message( 'API key verification failed.' );
			return;
		}
		// Everything looks good, so report a success.
		$this->set_status( 'success' );
		$this->set_message( 'Akismet is activated with a verified API key.' );
	}

}

If the class were placed in an akismet-activated.php file, you could register it with:

plugin-akismet-activated:
  class: Akismet_Activated
  require: akismet-activated.php

Then, run the config file:

$ wp doctor check plugin-akismet-activated --config=doctor.yml
+--------------------------+--------+---------------------+
| name                     | status | message             |
+--------------------------+--------+---------------------+
| plugin-akismet-activated | error  | API key is missing. |
+--------------------------+--------+---------------------+

Use Jetpack’s Photon image resizing in local development

In order to dynamically resize an image, Photon needs to be able to fetch the image file over HTTP. Jetpack disables Photon when JETPACK_DEV_DEBUG is defined because the assumption is that your local development domain isn’t accessible from the public internet. But, if you configure Photon in your local environment, you can tell Jetpack to enable all of Photon’s features too.

Setup Photon

First, check out the Photon codebase to a location served by your VIP Quickstart or Salty WordPress Vagrant:

svn co https://code.svn.wordpress.org/photon/ photon.dev

You’ll need to set up a record within your web server (Nginx or Apache) to serve the domain. If you aren’t using dnsmasq for local development, you’ll need to edit /etc/hosts on your local machine too.

Install Gmagick

You probably don’t have Gmagick installed, which Photon uses to resize images, so you’ll need to install it. For me, this was:

apt-get install php5.6-gmagick
service php5.6-fpm restart

Add a couple of filters

The last step is to add a couple of filters to a local mu-plugin or similar:

add_action( 'jetpack_modules_loaded', function(){
include( Jetpack::get_module_path( 'photon' ) );
add_filter( 'jetpack_photon_development_mode', '__return_false' );
}, 11 );

add_filter( 'jetpack_photon_domain', function( $domain ){
return 'http://photon.dev';
});

Figure out why WordPress is slow with wp profile

wp profile is a WP-CLI command to help you quickly identify what’s slow with WordPress. It’s designed to work alongside Xdebug and New Relic because it’s easy to deploy to any server that has WP-CLI. With wp profile, you gain quick visibility into key performance metrics (execution time, query count, cache hit/miss ratio, etc.) to guide further debugging.

Dealing with a slow WordPress install you’ve never worked with before? Run wp profile stage to see metrics for each stage of the WordPress load process. Include the --url=<url> argument to mock the request as a specific URL.

$ wp profile stage --url=runcommand.io
+------------+---------+------------+-------------+-------------+------------+--------------+-----------+------------+--------------+---------------+
| stage      | time    | query_time | query_count | cache_ratio | cache_hits | cache_misses | hook_time | hook_count | request_time | request_count |
+------------+---------+------------+-------------+-------------+------------+--------------+-----------+------------+--------------+---------------+
| bootstrap  | 0.7597s | 0.0052s    | 14          | 93.21%      | 357        | 26           | 0.3328s   | 2717       | 0s           | 0             |
| main_query | 0.0131s | 0.0004s    | 3           | 94.29%      | 33         | 2            | 0.0065s   | 78         | 0s           | 0             |
| template   | 0.7041s | 0.0192s    | 147         | 92.16%      | 2350       | 200          | 0.6982s   | 6130       | 0s           | 0             |
+------------+---------+------------+-------------+-------------+------------+--------------+-----------+------------+--------------+---------------+
| total (3)  | 1.477s  | 0.0248s    | 164         | 93.22%      | 2740       | 228          | 1.0375s   | 8925       | 0s           | 0             |
+------------+---------+------------+-------------+-------------+------------+--------------+-----------+------------+--------------+---------------+

When WordPress handles a request from a browser, it’s essentially executing as one long PHP script. wp profile stage breaks the script into three stages:

  • bootstrap is where WordPress is setting itself up, loading plugins and the main theme, and firing the init hook.
  • main_query is how WordPress transforms the request (e.g. /2016/10/21/moms-birthday/) into the primary WP_Query.
  • template is where WordPress determines which theme template to render based on the main query, and renders it.

In the example from above, bootstrap seems a bit slow, so let’s dive into it further. Run wp profile stage bootstrap to dive into higher fidelity mode of a given stage. Use the --spotlight flag to filter out the zero-ish results.

$ wp profile stage bootstrap --url=runcommand.io --spotlight
+--------------------------+----------------+---------+------------+-------------+-------------+------------+--------------+--------------+---------------+
| hook                     | callback_count | time    | query_time | query_count | cache_ratio | cache_hits | cache_misses | request_time | request_count |
+--------------------------+----------------+---------+------------+-------------+-------------+------------+--------------+--------------+---------------+
| muplugins_loaded:before  |                | 0.1644s | 0.0017s    | 1           | 40%         | 2          | 3            | 0s           | 0             |
| muplugins_loaded         | 2              | 0.0005s | 0s         | 0           | 50%         | 1          | 1            | 0s           | 0             |
| plugins_loaded:before    |                | 0.1771s | 0.0008s    | 6           | 77.63%      | 59         | 17           | 0s           | 0             |
| plugins_loaded           | 14             | 0.0887s | 0s         | 0           | 100%        | 104        | 0            | 0s           | 0             |
| after_setup_theme:before |                | 0.043s  | 0s         | 0           | 100%        | 26         | 0            | 0s           | 0             |
| init                     | 82             | 0.1569s | 0.0018s    | 7           | 96.88%      | 155        | 5            | 0s           | 0             |
| wp_loaded:after          |                | 0.027s  | 0s         | 0           |             | 0          | 0            | 0s           | 0             |
+--------------------------+----------------+---------+------------+-------------+-------------+------------+--------------+--------------+---------------+
| total (7)                | 98             | 0.6575s | 0.0043s    | 14          | 77.42%      | 347        | 26           | 0s           | 0             |
+--------------------------+----------------+---------+------------+-------------+-------------+------------+--------------+--------------+---------------+

Each stage is further segmented by wp profile based on its primary actions. For the bootstrap stage, the primary actions include ‘muplugins_loaded’, ‘plugins_loaded’, and ‘init’. You can also see some intermediate actions like ‘plugins_loaded:before’ and ‘wp_loaded:after’. These intermediate actions correspond to script execution before (or after) actual WordPress hooks. They’re pseudo hooks in a sense.

When you’ve found a specific hook you’d like to assess, run wp profile hook <hook>. Include the --fields=<fields> argument to limit output to certain fields.

$ wp profile hook plugins_loaded --url=runcommand.io --fields=callback,time,location
+------------------------------------------------------------+---------+-----------------------------------------------------------------+
| callback                                                   | time    | location                                                        |
+------------------------------------------------------------+---------+-----------------------------------------------------------------+
| wp_maybe_load_widgets()                                    | 0.0046s | wp-includes/functions.php:3501                                  |
| wp_maybe_load_embeds()                                     | 0.0003s | wp-includes/embed.php:162                                       |
| VaultPress_Hotfixes->protect_jetpack_402_from_oembed_xss() | 0s      | vaultpress/class.vaultpress-hotfixes.php:124                    |
| _wp_customize_include()                                    | 0s      | wp-includes/theme.php:2052                                      |
| EasyRecipePlus->pluginsLoaded()                            | 0.0013s | easyrecipeplus/lib/EasyRecipePlus.php:125                       |
| Gamajo\GenesisHeaderNav\genesis_header_nav_i18n()          | 0.0007s | genesis-header-nav/genesis-header-nav.php:61                    |
| DS_Public_Post_Preview::init()                             | 0.0001s | public-post-preview/public-post-preview.php:52                  |
| wpseo_load_textdomain()                                    | 0.0004s | wordpress-seo-premium/wp-seo-main.php:222                       |
| load_yoast_notifications()                                 | 0.0016s | wordpress-seo-premium/wp-seo-main.php:381                       |
| wpseo_init()                                               | 0.0329s | wordpress-seo-premium/wp-seo-main.php:240                       |
| wpseo_premium_init()                                       | 0.0019s | wordpress-seo-premium/wp-seo-premium.php:79                     |
| wpseo_frontend_init()                                      | 0.0007s | wordpress-seo-premium/wp-seo-main.php:274                       |
| Black_Studio_TinyMCE_Plugin->load_compatibility()          | 0.0016s | black-studio-tinymce-widget/black-studio-tinymce-widget.php:206 |
| Jetpack::load_modules()                                    | 0.0564s | jetpack/class.jetpack.php:1672                                  |
+------------------------------------------------------------+---------+-----------------------------------------------------------------+
| total (14)                                                 | 0.1026s |                                                                 |
+------------------------------------------------------------+---------+-----------------------------------------------------------------+

Et voila! We’ve discovered that wpseo_init() and Jetpack::load_modules() are collectively contributing ~100ms to every page load.

With wp profile, discovering why WordPress is slow becomes the easy part.

Questions to ask when determining why a WordPress site is going down

Working on a WordPress site that’s crashing all of the time? Here are some qualifying questions you can use to determine why it might be going down:

  • When does the site go down? Is it a particular time of day that could be correlated to traffic patterns?
  • When the site goes down, what do you do to bring it back up?
  • When did the problem start happening? Is it closely related to some other change that was made to the site?
  • What have you done in previous attempts to fix the problem?
  • How are the servers configured (RAM, etc.)? Does adding more capacity remediate the problem?
  • What caching plugins are you using? Do you have other caching strategies in place (e.g. Varnish or Cloudflare)?
  • What plugins are you running? How customized is your theme?

Importantly, these qualifying questions should give you better visibility into the nature of the problem, to help guide further debugging.

Profile key performance metrics for a WP REST API response

Using the eval-file command in wp profile, you can profile key performance metrics for a WP REST API response by mocking, executing, and evaluating the request with a one-off file. These key performance metrics include execution time, query count, cache hit/miss ratio, and more.

For example, to mock a call to GET /wp/v2/posts?per_page=100, you’d create a get-posts.php file like this:

<?php
$request = new WP_REST_Request( 'GET', '/wp/v2/posts' );
$request->set_param( 'per_page', 100 );
$server = rest_get_server();
$server->dispatch( $request );

Then, profile get-posts.php by running wp profile eval-file get-posts.php:

$ wp profile eval-file get-posts.php
+---------+------------+-------------+-------------+------------+--------------+--------------+---------------+
| time    | query_time | query_count | cache_ratio | cache_hits | cache_misses | request_time | request_count |
+---------+------------+-------------+-------------+------------+--------------+--------------+---------------+
| 0.9429s | 0.002s     | 5           | 96.39%      | 7599       | 285          | 0s           | 0             |
+---------+------------+-------------+-------------+------------+--------------+--------------+---------------+

Write a check for verifying contents of WordPress files

One of the check types included in wp doctor is File_Contents, or the ability to check all or a selection of WordPress files for a given regex pattern. The check type is in use by a couple of the default diagnostic checks, and you can use the File_Contents check type in your custom doctor.yml configuration file.

The File_Contents check type is the most efficient way to check WordPress files because it only iterates the filesystem once, regardless of how many file checks you’ve registered.

As an example, here are two checks using File_Contents, one which ensures sessions aren’t used by plugins and the other which ensures $_SERVER['SERVER_NAME'] isn’t used in wp-config.php:

file-sessions:
  check: File_Contents
  options:
    regex: .*(session_start|\$_SESSION).*
    only_wp_content: true
file-server-name-wp-config:
  check: File_Contents
  options:
    regex: define\(.+WP_(HOME|SITEURL).+\$_SERVER.+SERVER_NAME
    path: wp-config.php

Run together, you might see:

$ wp doctor check --config=file-contents.yml --all
+----------------------------+---------+-----------------------------------------------------------------------------------------+
| name                       | status  | message                                                                                 |
+----------------------------+---------+-----------------------------------------------------------------------------------------+
| file-sessions              | success | All 'php' files passed check for '.*(session_start|\$_SESSION).*'.                      |
| file-server-name-wp-config | success | All 'php' files passed check for 'define\(.+WP_(HOME|SITEURL).+\$_SERVER.+SERVER_NAME'. |
+----------------------------+---------+-----------------------------------------------------------------------------------------+

The File_Contents check type accepts the following options:

  • ‘regex’: Regex pattern to check against each file’s contents.
  • ‘extension’. File extension to check. Defaults to ‘php’. Separate multiple file extensions with a ‘|’.
  • ‘path’. Check a specific file path. Value should be relative to ABSPATH (e.g. ‘wp-content’ or ‘wp-config.php’).
  • ‘only_wp_content’: Only check the wp-content directory.