response[ $c['id'] ] ) ) { return null; } $u = (array) $t->response[ $c['id'] ]; if ( empty( $u['new_version'] ) ) { return null; } return array( 'version' => (string) $u['new_version'], 'package' => isset( $u['package'] ) ? (string) $u['package'] : '', 'requires_php' => isset( $u['requires_php'] ) ? (string) $u['requires_php'] : '', ); } /** True or false when the update check says whether WordPress.org hosts it, null when no check has run yet. */ public static function on_wporg( array $c ) { $t = self::transient( $c ); if ( ! $t ) { return null; } return ! empty( $t->response[ $c['id'] ] ) || ! empty( $t->no_update[ $c['id'] ] ); } public static function downloadable( $url ) { return 'https' === wp_parse_url( (string) $url, PHP_URL_SCHEME ) && 'downloads.wordpress.org' === wp_parse_url( (string) $url, PHP_URL_HOST ); } /** Downloads and unpacks an update into a private temp folder, returns its root; nothing is installed or run. */ public static function download( array $c, $job_id ) { if ( ! self::downloadable( $c['package'] ) ) { return new \WP_Error( 'leaplens_package', __( 'The update is not hosted on WordPress.org.', 'leaplens' ) ); } require_once ABSPATH . 'wp-admin/includes/file.php'; if ( 'direct' !== get_filesystem_method() || ! WP_Filesystem() ) { return new \WP_Error( 'leaplens_fs', __( 'Checking the update needs direct file access on this server.', 'leaplens' ) ); } $zip = apply_filters( 'leaplens_pre_download', null, $c['package'] ); if ( null === $zip ) { $zip = download_url( $c['package'], 60 ); } if ( is_wp_error( $zip ) ) { return $zip; } $dest = self::temp_base() . md5( $job_id . $c['key'] . wp_rand() ); $done = unzip_file( $zip, $dest ); wp_delete_file( $zip ); if ( is_wp_error( $done ) ) { self::remove( $dest ); return $done; } $top = glob( $dest . '/*', GLOB_ONLYDIR ); return 1 === count( (array) $top ) ? wp_normalize_path( $top[0] ) : $dest; } /** Temp folder prefix of this site, so installs sharing /tmp never touch each other's downloads. */ public static function temp_base() { $site = substr( md5( get_current_blog_id() . '|' . ABSPATH . '|' . home_url() ), 0, 10 ); return wp_normalize_path( trailingslashit( get_temp_dir() ) . self::TEMP_PREFIX . $site . '-' ); } public static function remove( $root ) { global $wp_filesystem; $base = self::temp_base(); $root = wp_normalize_path( $root ); if ( 0 !== strpos( $root, $base ) ) { return; } $name = strtok( substr( $root, strlen( $base ) ), '/' ); $dir = $base . $name; // A link in a shared temp folder may point anywhere: never delete through it. if ( false === $name || '' === $name || is_link( $dir ) || ! is_dir( $dir ) ) { return; } require_once ABSPATH . 'wp-admin/includes/file.php'; if ( WP_Filesystem() && $wp_filesystem ) { $wp_filesystem->delete( $dir, true ); } } /** Removes this site's update folders a crashed step left behind; only one scan runs per site, so none is in use. */ public static function sweep() { foreach ( (array) glob( self::temp_base() . '*', GLOB_ONLYDIR ) as $dir ) { self::remove( $dir ); } } /** Directory facts for a component with errors and no update: found, closed, last_updated, support. Cached for 12 hours. */ public static function directory( array $c ) { if ( false === $c['wporg'] ) { return array( 'found' => false ); } $cache = 'leaplens_dir_' . md5( $c['type'] . ':' . $c['slug'] ); $info = get_transient( $cache ); if ( is_array( $info ) ) { return $info; } $api = 'plugin' === $c['type'] ? 'plugins' : 'themes'; $url = add_query_arg( array( 'action' => 'plugin' === $c['type'] ? 'plugin_information' : 'theme_information', 'request[slug]' => $c['slug'], 'request[fields][sections]' => 0, 'request[fields][versions]' => 0, 'request[fields][screenshots]' => 0, ), "https://api.wordpress.org/{$api}/info/1.2/" ); $res = wp_remote_get( $url, array( 'timeout' => 15 ) ); $body = is_wp_error( $res ) ? null : json_decode( wp_remote_retrieve_body( $res ), true ); if ( ! is_array( $body ) ) { return null; } $closed = ! empty( $body['closed'] ) || ( isset( $body['error'] ) && 'closed' === $body['error'] ); $found = $closed || empty( $body['error'] ); $info = array( 'found' => $found, 'closed' => $closed, 'last_updated' => $found && ! empty( $body['last_updated'] ) ? gmdate( 'Y-m-d', (int) strtotime( (string) $body['last_updated'] ) ) : '', 'support' => $found ? "https://wordpress.org/support/{$c['type']}/{$c['slug']}/" : '', ); set_transient( $cache, $info, 12 * HOUR_IN_SECONDS ); return $info; } }