diff --git a/includes/Wpup/Cache.php b/includes/Wpup/Cache.php index 6a3535b..ade9ee1 100644 --- a/includes/Wpup/Cache.php +++ b/includes/Wpup/Cache.php @@ -20,4 +20,12 @@ function get($key); * @return void */ function set($key, $value, $expiration = 0); + + /** + * Clear a cache + * + * @param string $key Cache key. + * @return void + */ + function clear($key); } diff --git a/includes/Wpup/FileCache.php b/includes/Wpup/FileCache.php index 11bd33f..3a165a5 100644 --- a/includes/Wpup/FileCache.php +++ b/includes/Wpup/FileCache.php @@ -59,4 +59,18 @@ public function set($key, $value, $expiration = 0) { protected function getCacheFilename($key) { return $this->cacheDirectory . '/' . $key . '.txt'; } + + + /** + * Clear a cache. + * + * @param string $key Cache key. + * @return void + */ + public function clear($key) { + $file = $this->getCacheFilename($key); + if ( is_file($file) ) { + unlink($file); + } + } } \ No newline at end of file diff --git a/includes/Wpup/Package.php b/includes/Wpup/Package.php index 8d4617c..4b97c84 100644 --- a/includes/Wpup/Package.php +++ b/includes/Wpup/Package.php @@ -9,6 +9,13 @@ * file and store the actual download elsewhere - or even generate it on the fly. */ class Wpup_Package { + + /** + * @var int $cacheTime How long the package metadata should be cached in seconds. + * Defaults to 1 week ( 7 * 24 * 60 * 60 ). + */ + public static $cacheTime = 604800; + /** @var string Path to the Zip archive that contains the plugin or theme. */ protected $filename; @@ -79,14 +86,14 @@ public static function fromArchive($filename, $slug = null, Wpup_Cache $cache = if ( !isset($metadata) || !is_array($metadata) ) { $metadata = self::extractMetadata($filename); if ( $metadata === null ) { - throw new Wpup_InvalidPackageException('The specified file does not contain a valid WordPress plugin or theme.'); + throw new Wpup_InvalidPackageException( sprintf('The specified file %s does not contain a valid WordPress plugin or theme.', $filename)); } $metadata['last_updated'] = gmdate('Y-m-d H:i:s', $modified); } //Update cache. if ( isset($cache) ) { - $cache->set($cacheKey, $metadata, 7 * 24 * 3600); + $cache->set($cacheKey, $metadata, self::$cacheTime); } if ( $slug === null ) { $slug = $metadata['slug']; @@ -116,12 +123,14 @@ public static function extractMetadata($zipFilename){ if ( isset($packageInfo['header']) && !empty($packageInfo['header']) ){ $mapping = array( 'Name' => 'name', - 'Version' => 'version', - 'PluginURI' => 'homepage', - 'ThemeURI' => 'homepage', - 'Author' => 'author', - 'AuthorURI' => 'author_homepage', - 'DetailsURI' => 'details_url', //Only for themes. + 'Version' => 'version', + 'PluginURI' => 'homepage', + 'ThemeURI' => 'homepage', + 'Author' => 'author', + 'AuthorURI' => 'author_homepage', + 'DetailsURI' => 'details_url', //Only for themes. + 'Depends' => 'depends', // plugin-dependencies plugin + 'Provides' => 'provides', // plugin-dependencies plugin ); foreach($mapping as $headerField => $metaField){ if ( array_key_exists($headerField, $packageInfo['header']) && !empty($packageInfo['header'][$headerField]) ){ @@ -138,7 +147,10 @@ public static function extractMetadata($zipFilename){ } if ( !empty($packageInfo['readme']) ){ - $mapping = array('requires', 'tested'); + $mapping = array( + 'requires', + 'tested', + ); foreach($mapping as $readmeField){ if ( !empty($packageInfo['readme'][$readmeField]) ){ $meta[$readmeField] = $packageInfo['readme'][$readmeField]; diff --git a/includes/Wpup/UpdateServer.php b/includes/Wpup/UpdateServer.php index 2cb56eb..ea6d333 100644 --- a/includes/Wpup/UpdateServer.php +++ b/includes/Wpup/UpdateServer.php @@ -11,13 +11,7 @@ public function __construct($serverUrl = null, $serverDirectory = null) { $serverDirectory = realpath(__DIR__ . '/../..'); } if ( $serverUrl === null ) { - //Default to the current URL minus the query and "index.php". - $serverUrl = 'http://' . $_SERVER['HTTP_HOST']; - $path = $_SERVER['SCRIPT_NAME']; - if ( basename($path) === 'index.php' ) { - $path = dirname($path) . '/'; - } - $serverUrl .= $path; + $serverUrl = self::guessServerUrl(); } $this->serverUrl = $serverUrl; @@ -26,10 +20,60 @@ public function __construct($serverUrl = null, $serverDirectory = null) { $this->cache = new Wpup_FileCache($serverDirectory . '/cache'); } + /** + * Guess the Server Url based on the current request. + * + * Defaults to the current URL minus the query and "index.php". + * + * @static + * + * @return string Url + */ + public static function guessServerUrl() { + $serverUrl = ( self::isSsl() ? 'https' : 'http' ); + $serverUrl .= '://' . $_SERVER['HTTP_HOST']; + $path = $_SERVER['SCRIPT_NAME']; + + if ( basename($path) === 'index.php' ) { + $dir = dirname($path); + if ( DIRECTORY_SEPARATOR === '/' ) { + $path = $dir . '/'; + } else { + // Fix Windows + $path = str_replace('\\', '/', $dir); + //Make sure there's a trailing slash. + if ( substr($path, -1) !== '/' ) { + $path .= '/'; + } + } + } + + $serverUrl .= $path; + return $serverUrl; + } + + /** + * Determine if ssl is used. + * + * @see WP core - wp-includes/functions.php + * + * @return bool True if SSL, false if not used. + */ + public static function isSsl() { + if ( isset($_SERVER['HTTPS']) ) { + if ( $_SERVER['HTTPS'] == '1' || strtolower($_SERVER['HTTPS']) === 'on' ) { + return true; + } + } elseif ( isset($_SERVER['SERVER_PORT']) && ( '443' == $_SERVER['SERVER_PORT'] ) ) { + return true; + } + return false; + } + /** * Process an update API request. * - * @param array|null $query Query parameters. Defaults to the current request parameters (GET + POST). + * @param array|null $query Query parameters. Defaults to the current GET request parameters. */ public function handleRequest($query = null) { $this->startTime = microtime(true); @@ -214,13 +258,15 @@ protected function logRequest($query) { $columns = array( isset($_SERVER['REMOTE_ADDR']) ? str_pad($_SERVER['REMOTE_ADDR'], 15, ' ') : '-', - isset($query['action']) ? $query['action'] : '-', - isset($query['slug']) ? $query['slug'] : '-', + isset($_SERVER['REQUEST_METHOD']) ? str_pad($_SERVER['REQUEST_METHOD'], 4, ' ') : '-', + isset($query['action']) ? $query['action'] : '-', + isset($query['slug']) ? $query['slug'] : '-', isset($query['installed_version']) ? $query['installed_version'] : '-', isset($wpVersion) ? $wpVersion : '-', isset($wpSiteUrl) ? $wpSiteUrl : '-', http_build_query($query, '', '&') ); + $columns = $this->filterLogInfo($columns); //Set the time zone to whatever the default is to avoid PHP notices. //Will default to UTC if it's not set properly in php.ini. @@ -235,6 +281,17 @@ protected function logRequest($query) { fclose($handle); } } + + /** + * Adjust information that will be logged. + * Intended to be overridden in child classes. + * + * @param array $columns List of columns in the log entry. + * @return array + */ + protected function filterLogInfo($columns) { + return $columns; + } /** * Output something as JSON. @@ -243,9 +300,13 @@ protected function logRequest($query) { */ protected function outputAsJson($response) { header('Content-Type: application/json'); - $output = json_encode($response); - if ( function_exists('wsh_pretty_json') ) { - $output = wsh_pretty_json($output); + $output = ''; + if ( defined('JSON_PRETTY_PRINT') ) { + $output = json_encode($response, JSON_PRETTY_PRINT); + } elseif ( function_exists('wsh_pretty_json') ) { + $output = wsh_pretty_json(json_encode($response)); + } else { + $output = json_encode($response); } echo $output; } @@ -256,7 +317,7 @@ protected function outputAsJson($response) { * @param string $message Error message. * @param int $httpStatus Optional HTTP status code. Defaults to 500 (Internal Server Error). */ - protected function exitWithError($message, $httpStatus = 500) { + protected function exitWithError($message = '', $httpStatus = 500) { $statusMessages = array( // This is not a full list of HTTP status messages. We only need the errors. // [Client Error 4xx] @@ -286,15 +347,25 @@ protected function exitWithError($message, $httpStatus = 500) { 504 => '504 Gateway Timeout', 505 => '505 HTTP Version Not Supported' ); + + if ( !isset($_SERVER['SERVER_PROTOCOL']) || $_SERVER['SERVER_PROTOCOL'] === '' ) { + $protocol = 'HTTP/1.1'; + } else { + $protocol = $_SERVER['SERVER_PROTOCOL']; + } //Output a HTTP status header. if ( isset($statusMessages[$httpStatus]) ) { - header('HTTP/1.1 ' . $statusMessages[$httpStatus]); + header($protocol . ' ' . $statusMessages[$httpStatus]); $title = $statusMessages[$httpStatus]; } else { header('X-Ws-Update-Server-Error: ' . $httpStatus, true, $httpStatus); $title = 'HTTP ' . $httpStatus; } + + if ( $message === '' ) { + $message = $title; + } //And a basic HTML error message. printf( @@ -312,10 +383,13 @@ protected function exitWithError($message, $httpStatus = 500) { * You can also set an argument to NULL to remove it. * * @param array $args An associative array of query arguments. - * @param string $url The old URL. + * @param string $url The old URL. Optional, defaults to the request url without query arguments. * @return string New URL. */ - protected static function addQueryArg($args, $url) { + protected static function addQueryArg($args, $url = null ) { + if ( !isset($url) ) { + $url = self::guessServerUrl(); + } if ( strpos($url, '?') !== false ) { $parts = explode('?', $url, 2); $base = $parts[0] . '?'; diff --git a/includes/extension-meta/extension-meta.php b/includes/extension-meta/extension-meta.php index fd30bda..f88d165 100644 --- a/includes/extension-meta/extension-meta.php +++ b/includes/extension-meta/extension-meta.php @@ -262,6 +262,9 @@ public static function getPluginHeaders($fileContents) { 'TextDomain' => 'Text Domain', 'DomainPath' => 'Domain Path', 'Network' => 'Network', + 'Depends' => 'Depends', + 'Provides' => 'Provides', + //Site Wide Only is deprecated in favor of Network. '_sitewide' => 'Site Wide Only', ); @@ -277,6 +280,16 @@ public static function getPluginHeaders($fileContents) { //For backward compatibility by default Title is the same as Name. $headers['Title'] = $headers['Name']; + + //"Depends" is a comma-separated list. Convert it to an array. + if ( !empty($headers['Depends']) ){ + $headers['Depends'] = array_map('trim', explode(',', $headers['Depends'])); + } + + //Same for "Provides" + if ( !empty($headers['Provides']) ){ + $headers['Provides'] = array_map('trim', explode(',', $headers['Provides'])); + } //If it doesn't have a name, it's probably not a plugin. if ( empty($headers['Name']) ){