Category Archives: Programming

Cloudflare Logo

Use CloudFlare to Secure WordPress by Country Codes

Firstly, check that you have the IP Geolocation option enabled on CloudFlare.

The most efficient way to do this with PHP would be to place the code below in the top of your wp-login.php, but WordPress will overwrite this file when it updates. The next best position is at the top of wp-config.php. If you follow the way WordPress loads, wp-login.php will require wp-load.php first, then after 4 minor lines of code, it will then get wp-config.php.

/**
 * CloudFlare - Connecting IP - for wp-config.php.
 */
if ( !empty( $_SERVER['REMOTE_ADDR'] ) && !empty( $_SERVER['HTTP_CF_CONNECTING_IP'] ) )
    $_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_CF_CONNECTING_IP'];

/**
 * CloudFlare - Limit WordPress Login to Australia with Whitelist - for wp-config.php.
 * It is best to bypass for IPv6 unfortunately, unreliable country code from CloudFlare at the moment.
 */
$ip_whitelist = array( '::1', '127.0.0.1' );

if ( in_array( $_SERVER['PHP_SELF'], array( '/wp-login.php' ) ) && !in_array( $_SERVER['HTTP_CF_IPCOUNTRY'], array( 'AU' ) ) )
{
    if ( !in_array( $_SERVER['REMOTE_ADDR'], $ip_whitelist ) && !preg_match( '/^([0-9a-f\.\/:]+)$/', $_SERVER['REMOTE_ADDR'] ) )
    {
        header( 'Location: /' );
        exit;
    }
}

This is just a different interpretation of my friends script. My version does not allow WordPress to waste CPU before booting the IP from the login page. It also allows for an IP whitelist to bypass this for trusted IP addresses.

It is best to pair this with a plugin like Simple Login Lockdown. There are also some useful .htaccess rules you can use, but I won’t go into that here.

WordPress Logo

Stay logged in with WordPress on Subdomains

A clients site performs some magic to show different content per subdomain. By default each subdomain asks you to login, which is a bit annoying. It would be convenient if admins of a single WordPress installation could stay logged in when they jump between subdomains.

I found that all we needed to do was set the cookie domain and path in wp-config.php. At first it seemed only COOKIE_DOMAIN and COOKIEPATH would be needed, but it did not behave until COOKIEHASH was also set. You could probably set it to anything you like, I just had a defined variable already.

/**
 * Set cookie properties to allow persistent login across subdomains.
 */
define('MY_DOMAIN', 'thatstevensguy.com');
define('COOKIE_DOMAIN', MY_DOMAIN);
define('COOKIEPATH', '/');
define('COOKIEHASH', md5(MY_DOMAIN) );

This may also work between WordPress installations across subdomains. I haven’t tested this theory. Both installations would require the same login details and this config. It is working with WordPress versions 3.8+.

WordPress Logo

How to Improve FeedWordpress

FeedWordPress is the best free plugin to syndicate posts from WordPress RSS feeds. Syndication is not the best practice, but it suits the needs of a client and their site network.

However, there is some minor issues. There is no built in duplicate post detection. This can create a mess when syndicating multiple feeds that may contain the same posts. Also, a default category is not assigned to posts if there is no matching category on your site. Place the below filter in your themes functions.php file to correct these issues.

/**
 * FeedWordPress - Detect duplicates and prepare the syndicated post.
 *
 * @param array $post
 * @param SyndicatedPost $syndicatedpost
 * @return array|null
 */
add_filter('syndicated_post', function (array $post, SyndicatedPost $syndicatedpost) {

    // --- Duplicate Detection

    if (!$syndicatedpost->fresh_content_is_update()) {
        global $wpdb;

        $duplicate_post = $wpdb->get_row($wpdb->prepare("
            SELECT ID FROM $wpdb->posts
            WHERE post_status = 'publish' AND post_type = 'post' AND post_title = %s
            AND ( post_date BETWEEN DATE_SUB( %s, INTERVAL 2 HOUR ) AND DATE_ADD( %s, INTERVAL 1 HOUR ) )
            LIMIT 1
        ", [
            $post[ 'post_title' ],
            $post[ 'post_date' ],
            $post[ 'post_date' ]
        ]));

        // Is it a duplicate post?
        if (!empty($duplicate_post)) {
            return null;
        }
    }

    // --- Prepare Syndicated Post

    // Assign the default category when a post has no categories.
    // No idea why FeedWordPress doesn't do this by default.
    if (empty($post[ 'tax_input' ][ 'category' ])) {
        $post[ 'tax_input' ][ 'category' ][] = intval(get_option('default_category'));
    }

    // Syndicated posts will include a 'read more' from the source site at the
    // end of their excerpts. Strip that out.
    $post[ 'post_excerpt' ] = preg_replace("/(&hellip;|&#8230;) <a href=.+<\/a>/", '', $post[ 'post_excerpt' ]);
    $post[ 'post_excerpt' ] = preg_replace("/\\[&hellip;\\]/", '', $post[ 'post_excerpt' ]);
    $post[ 'post_excerpt' ] = trim($post[ 'post_excerpt' ]);

    return $post;
}, 10, 2);

Lastly, we don’t want search engines to index these articles, they aren’t original content on our site. I also make use of Yoast WordPress SEO, we’ll use that to our advantage.

/**
 * Yoast WordPress SEO - Hide Syndicated Posts, and Sign Up form from Search Engines.
 *
 * @return void
 */
add_action('template_redirect', function (): void {
    if (!function_exists('YoastSEO')) {
        return;
    }

    $noindex = false;

    // Don't index Syndicated posts from FeedWordpress
    $is_syndicated = false;
    if (function_exists('is_syndicated')) {
        $is_syndicated = is_syndicated();
    }

    if (is_single() && $is_syndicated) {
        $noindex = true;
    }

    if ($noindex) {
        add_filter('wpseo_robots_array', [ YoastSEO()->helpers->robots, 'set_robots_no_index' ], 10, 2);
    }
});