Dump WordPress rewrite rules

Couldn’t find this… Dump the complete mess of WordPress rewrite rules:

global $wp_rewrite;

error_log(print_r($wp_rewrite->wp_rewrite_rules(), true));
Better yet, dump to a terminal with Kint, error_log and tail -f:
global $wp_rewrite;

\Kint::$mode_default=\Kint::MODE_CLI;
error_log(@d($wp_rewrite->wp_rewrite_rules()));
\Kint::$mode_default = \Kint::MODE_RICH;

Call from an action hook like init to be sure WordPress has had time to populate the list.

Here are the default rules from a fresh install of WordPress 5.2.3:


<?php
array (
'^wp-json/?$' => 'index.php?rest_route=/',
'^wp-json/(.*)?' => 'index.php?rest_route=/$matches[1]',
'^index.php/wp-json/?$' => 'index.php?rest_route=/',
'^index.php/wp-json/(.*)?' => 'index.php?rest_route=/$matches[1]',
'robots\\.txt$' => 'index.php?robots=1',
'.*wp-(atom|rdf|rss|rss2|feed|commentsrss2)\\.php$' => 'index.php?feed=old',
'.*wp-app\\.php(/.*)?$' => 'index.php?error=403',
'.*wp-register.php$' => 'index.php?register=true',
'feed/(feed|rdf|rss|rss2|atom)/?$' => 'index.php?&feed=$matches[1]',
'(feed|rdf|rss|rss2|atom)/?$' => 'index.php?&feed=$matches[1]',
'embed/?$' => 'index.php?&embed=true',
'page/?([0-9]{1,})/?$' => 'index.php?&paged=$matches[1]',
'comments/feed/(feed|rdf|rss|rss2|atom)/?$' => 'index.php?&feed=$matches[1]&withcomments=1',
'comments/(feed|rdf|rss|rss2|atom)/?$' => 'index.php?&feed=$matches[1]&withcomments=1',
'comments/embed/?$' => 'index.php?&embed=true',
'search/(.+)/feed/(feed|rdf|rss|rss2|atom)/?$' => 'index.php?s=$matches[1]&feed=$matches[2]',
'search/(.+)/(feed|rdf|rss|rss2|atom)/?$' => 'index.php?s=$matches[1]&feed=$matches[2]',
'search/(.+)/embed/?$' => 'index.php?s=$matches[1]&embed=true',
'search/(.+)/page/?([0-9]{1,})/?$' => 'index.php?s=$matches[1]&paged=$matches[2]',
'search/(.+)/?$' => 'index.php?s=$matches[1]',
'author/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$' => 'index.php?author_name=$matches[1]&feed=$matches[2]',
'author/([^/]+)/(feed|rdf|rss|rss2|atom)/?$' => 'index.php?author_name=$matches[1]&feed=$matches[2]',
'author/([^/]+)/embed/?$' => 'index.php?author_name=$matches[1]&embed=true',
'author/([^/]+)/page/?([0-9]{1,})/?$' => 'index.php?author_name=$matches[1]&paged=$matches[2]',
'author/([^/]+)/?$' => 'index.php?author_name=$matches[1]',
'([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/feed/(feed|rdf|rss|rss2|atom)/?$' => 'index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&feed=$matches[4]',
'([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/(feed|rdf|rss|rss2|atom)/?$' => 'index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&feed=$matches[4]',
'([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/embed/?$' => 'index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&embed=true',
'([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/page/?([0-9]{1,})/?$' => 'index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&paged=$matches[4]',
'([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/?$' => 'index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]',
'([0-9]{4})/([0-9]{1,2})/feed/(feed|rdf|rss|rss2|atom)/?$' => 'index.php?year=$matches[1]&monthnum=$matches[2]&feed=$matches[3]',
'([0-9]{4})/([0-9]{1,2})/(feed|rdf|rss|rss2|atom)/?$' => 'index.php?year=$matches[1]&monthnum=$matches[2]&feed=$matches[3]',
'([0-9]{4})/([0-9]{1,2})/embed/?$' => 'index.php?year=$matches[1]&monthnum=$matches[2]&embed=true',
'([0-9]{4})/([0-9]{1,2})/page/?([0-9]{1,})/?$' => 'index.php?year=$matches[1]&monthnum=$matches[2]&paged=$matches[3]',
'([0-9]{4})/([0-9]{1,2})/?$' => 'index.php?year=$matches[1]&monthnum=$matches[2]',
'([0-9]{4})/feed/(feed|rdf|rss|rss2|atom)/?$' => 'index.php?year=$matches[1]&feed=$matches[2]',
'([0-9]{4})/(feed|rdf|rss|rss2|atom)/?$' => 'index.php?year=$matches[1]&feed=$matches[2]',
'([0-9]{4})/embed/?$' => 'index.php?year=$matches[1]&embed=true',
'([0-9]{4})/page/?([0-9]{1,})/?$' => 'index.php?year=$matches[1]&paged=$matches[2]',
'([0-9]{4})/?$' => 'index.php?year=$matches[1]',
'[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}/[^/]+/attachment/([^/]+)/?$' => 'index.php?attachment=$matches[1]',
'[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}/[^/]+/attachment/([^/]+)/trackback/?$' => 'index.php?attachment=$matches[1]&tb=1',
'[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}/[^/]+/attachment/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$' => 'index.php?attachment=$matches[1]&feed=$matches[2]',
'[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}/[^/]+/attachment/([^/]+)/(feed|rdf|rss|rss2|atom)/?$' => 'index.php?attachment=$matches[1]&feed=$matches[2]',
'[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}/[^/]+/attachment/([^/]+)/comment-page-([0-9]{1,})/?$' => 'index.php?attachment=$matches[1]&cpage=$matches[2]',
'[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}/[^/]+/attachment/([^/]+)/embed/?$' => 'index.php?attachment=$matches[1]&embed=true',
'([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/([^/]+)/embed/?$' => 'index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&name=$matches[4]&embed=true',
'([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/([^/]+)/trackback/?$' => 'index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&name=$matches[4]&tb=1',
'([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$' => 'index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&name=$matches[4]&feed=$matches[5]',
'([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/([^/]+)/(feed|rdf|rss|rss2|atom)/?$' => 'index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&name=$matches[4]&feed=$matches[5]',
'([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/([^/]+)/page/?([0-9]{1,})/?$' => 'index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&name=$matches[4]&paged=$matches[5]',
'([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/([^/]+)/comment-page-([0-9]{1,})/?$' => 'index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&name=$matches[4]&cpage=$matches[5]',
'([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/([^/]+)(?:/([0-9]+))?/?$' => 'index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&name=$matches[4]&page=$matches[5]',
'[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}/[^/]+/([^/]+)/?$' => 'index.php?attachment=$matches[1]',
'[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}/[^/]+/([^/]+)/trackback/?$' => 'index.php?attachment=$matches[1]&tb=1',
'[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}/[^/]+/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$' => 'index.php?attachment=$matches[1]&feed=$matches[2]',
'[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}/[^/]+/([^/]+)/(feed|rdf|rss|rss2|atom)/?$' => 'index.php?attachment=$matches[1]&feed=$matches[2]',
'[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}/[^/]+/([^/]+)/comment-page-([0-9]{1,})/?$' => 'index.php?attachment=$matches[1]&cpage=$matches[2]',
'[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}/[^/]+/([^/]+)/embed/?$' => 'index.php?attachment=$matches[1]&embed=true',
'([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/comment-page-([0-9]{1,})/?$' => 'index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&cpage=$matches[4]',
'([0-9]{4})/([0-9]{1,2})/comment-page-([0-9]{1,})/?$' => 'index.php?year=$matches[1]&monthnum=$matches[2]&cpage=$matches[3]',
'([0-9]{4})/comment-page-([0-9]{1,})/?$' => 'index.php?year=$matches[1]&cpage=$matches[2]',
'.?.+?/attachment/([^/]+)/?$' => 'index.php?attachment=$matches[1]',
'.?.+?/attachment/([^/]+)/trackback/?$' => 'index.php?attachment=$matches[1]&tb=1',
'.?.+?/attachment/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$' => 'index.php?attachment=$matches[1]&feed=$matches[2]',
'.?.+?/attachment/([^/]+)/(feed|rdf|rss|rss2|atom)/?$' => 'index.php?attachment=$matches[1]&feed=$matches[2]',
'.?.+?/attachment/([^/]+)/comment-page-([0-9]{1,})/?$' => 'index.php?attachment=$matches[1]&cpage=$matches[2]',
'.?.+?/attachment/([^/]+)/embed/?$' => 'index.php?attachment=$matches[1]&embed=true',
'(.?.+?)/embed/?$' => 'index.php?pagename=$matches[1]&embed=true',
'(.?.+?)/trackback/?$' => 'index.php?pagename=$matches[1]&tb=1',
'(.?.+?)/feed/(feed|rdf|rss|rss2|atom)/?$' => 'index.php?pagename=$matches[1]&feed=$matches[2]',
'(.?.+?)/(feed|rdf|rss|rss2|atom)/?$' => 'index.php?pagename=$matches[1]&feed=$matches[2]',
'(.?.+?)/page/?([0-9]{1,})/?$' => 'index.php?pagename=$matches[1]&paged=$matches[2]',
'(.?.+?)/comment-page-([0-9]{1,})/?$' => 'index.php?pagename=$matches[1]&cpage=$matches[2]',
'(.?.+?)(?:/([0-9]+))?/?$' => 'index.php?pagename=$matches[1]&page=$matches[2]',
)

Removing WordPress Endpoints

One of my gripes about WordPress is how out-of-control routing can be. It’s difficult to anticipate all the various endpoints WordPress will happy throw responses at.

While I don’t have a solution for that, an old post on Better WP shows how to fully disable some common WordPress endpoints.

Here’s a simple example snippet to disable search results:

function kill_templates() {
    global $wp_query, $post;
    if (is_search()) {
        $wp_query->set_404();
    }
}
add_action('template_redirect', __NAMESPACE__ . '\kill_templates');

Composer autoload_static.php error under PHP 5.5

Summary (tl;dr)

Composer v1.1 generates a static autoloader for PHP v5.6+ which fails PHP 5.5 syntax checks.

Using an earlier version of Composer or omitting autoload_static.php is an effective workaround until WP Engine’s servers are updated to PHP 5.6 or 7.

Problem

On May 10th, Composer’s v1.1.0 release introduced a faster static autoloader for PHP 5.6 and above. The new code exists in a separate composer/autoload_static.php file which loads conditionally based on PHP version.

In most cases this should be fine, but on Git push, WP Engine syntax-checks every PHP file against PHP 5.5. The autoload_static.php defines object properties with concatenated strings, something that wasn’t possible until PHP 5.6 and so the push fails with this error:

PHP Parse error: syntax error, unexpected '.', expecting ')' in - on line 19

Below are lines 16-20 from autoload_static.php, the concatenated strings in the array values are invalid in PHP 5.5. IOP\ is our project’s namespace.

public static $prefixDirsPsr4 = array (
    'IOP\' => 
    array (
        0 => __DIR__ . '/../../../../..' . '/wp-content/themes/iop',
        1 => __DIR__ . '/../../../../..' . '/wp-content/themes/iop/inc',

Solutions & Workarounds

This problem only exists under PHP 5.5, whose end-of-life date is July 10th, 2016. Once WP Engine updates their servers to PHP 5.6 or 7, the updated autoloaders will validate correctly.

In the meantime, either the autoload_static.php file can be blocked in .gitignore, or a version of Composer prior to 1.1.0 can be used. Composer’s Downloads page documents its --version flag for installing previous versions.

Note that the PHP version used to generate the autoload files does not appear to matter, Composer generated identical files running under PHP 5.5.30 and PHP 7.0.6.

References

Avoiding WordPress theme fragility with ACF

Bill Erickson explains how to avoid front-end fragility by using WordPress’ built-in get_post_meta() instead of Advanced Custom Fields’ convenience functions. Those functions are easy to use, but while they simplify development in the short term, the delivered theme (and site) can be totally broken by ticking a single checkbox.

From Using Advanced Custom Fields without frontend dependency:

There is no reason to use an ACF function like get_field() when you can use a WordPress core function like get_post_meta(). Using these ACF functions introduces what I’m calling “frontend dependency”.

If you use an ACF field and the ACF plugin is removed or deactivated (because clients…), visitors to the website will see the white screen of death instead of the website. If you go to the bother of wrapping all the ACF functions in function_exists(), the site will load but none of your metadata will be shown.

ACF could be deployed via mu-plugins, but that breaks automatic updates and adds an additional, manual upkeep requirement to the delivered site.