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"; ?>