How to check WordPress versions on your server are up to date

By | April 22, 2015

Original script at https://www.howtoforge.com/how-to-find-outdated-wordpress-versions-on-your-server-to-reduce-the-risk-of-being-hacked

This script has been improved to automatically detect latest WordPress version by sending a HTTP HEAD request to http://wordpress.org/latest and parsing the Content-Disposition: attachment; filename=wordpress-A.B.C.tar.gz result as suggested at https://wordpress.org/support/topic/programmatically-check-latest-wp-release

<?php
/**
 * find outdated wordpress versions
 * (c) 2014 howtoforge.com (M. Cramer) <m.cramer@pixcept.de>
 * Appended by Steve Scotter www.stephen-scotter.net
 */

if(!isset($argv[1])) die("Please start this program with " . $argv[0] . " <web pase bath> [<csv file>]\n");
// set the base path
define('BASE_PATH', $argv[1]);
define('OUTFILE', (isset($argv[2]) ? $argv[2] : false));
// check that provided path exists
if(!is_dir(BASE_PATH)) {
	die(BASE_PATH . " is no valid path.\n");
}
// define array to store the wordpress installation paths
$wp_inst = array();

/**/
function detech_latest_wordpress_version() {
	$pattern = "/Content-Disposition: attachment; filename=wordpress-(.*).tar.gz/";
	$context  = stream_context_create(array('http' =>array('method'=>'HEAD')));
	$fd = fopen('http://wordpress.org/latest', 'rb', false, $context);
	$data = stream_get_meta_data($fd);
	fclose($fd);

	$WP_VERSION = '';

	foreach ($data['wrapper_data'] as $key => $value)
	   {
   	   if (preg_match($pattern, $value, $matches) == 1) {
      	   if (count($matches) == 2) {
         	      $WP_VERSION = $matches[1];
         	}
      	}
   	}

	return $WP_VERSION;
}

/* main function to loop through paths recursively */
function path_loop($path) {
	global $wp_inst;

	// make sure path ends with a slash
	if(substr($path, -1) !== '/') $path .= '/';

	// open dir
	$dir = opendir($path);
	if(!$dir) {
		print "[WARN] Could not access " . BASE_PATH . "\n";
		return false;
	}
	// loop through everything this dir contains
	while($cur = readdir($dir)) {
		// we only want to read paths, not files
		if($cur === '.' || $cur === '..' || is_link($path . $cur) || !is_dir($path . $cur)) continue;

		if(($cur === 'wp-content' || $cur == 'wp-admin' || $cur == 'wp-includes') && array_key_exists($path, $wp_inst) == false) {
			// this seems to be a wordpress installation path
			// check for the version file now
			$versionfile = $path . 'wp-includes/version.php';
			if(!file_exists($versionfile) || !is_readable($versionfile)) continue; // cannot read the file

			// we don't simply include the file for security reasons.
			// so store it in a variable
			$cont = file_get_contents($versionfile);

			// search for the version string
			$found = preg_match('/\$wp_version\s*=\s*(["\'])([0-9\.]+)\\1;/', $cont, $match);
			if(!$found) continue; // we found no version string in the file... strange.

			$wp_inst[$path] = $match[2];
			print '[INFO] found wp version ' . $match[2] . ' in ' . $path . "\n";
		}

		path_loop($path . $cur); // we dive into the dir even if we found wp here.
	}

	// free resource
	closedir($dir);
}

define('LATEST_VERSION', detech_latest_wordpress_version());

if (empty(LATEST_VERSION)) {
	die("Unable to detect latest version of WordPress available");
}
else {
	print "[INFO] Latest version of wordpress detected as " . LATEST_VERSION . "\n";
}

// start the loop process
path_loop(BASE_PATH);
// some statistic variables
$current = 0;
$outdated = 0;
if(OUTFILE) $fp = fopen(OUTFILE, 'w');
// loop through all found versions
foreach($wp_inst as $path => $version) {
	// is the found version lower than latest one?
	if(version_compare($version, LATEST_VERSION, '<')) {
		$outdated++;
		print '[WARN] outdated wordpress version ' . $version;
	} else {
		$current++;
		print '[OK] current wordpress version ' . $version;
	}
	print ' in ' . $path . "\n";

	if(OUTFILE) fputcsv($fp, array($path, $version, LATEST_VERSION), ';', '"');
}
if(OUTFILE) fclose($fp);
// print summary
print "We found " . count($wp_inst) . " wordpress installations, of which " . $outdated . " are outdated and " . $current . " are up to date.\n";
?>