, needles: string[], rows: array[]} */ public static function build( array $src, array $dest, array $opts = array() ) { $opts = array_merge( array( 'www' => true, 'paths' => true, 'extra' => array(), ), $opts ); $pairs = array(); $rows = array(); // For the dry-run table: [search, replace, kind]. $urls = array(); foreach ( array( 'siteurl', 'home' ) as $key ) { $from = untrailingslashit( (string) ( $src[ $key ] ?? '' ) ); $to = untrailingslashit( (string) ( $dest[ $key ] ?? '' ) ); if ( '' !== $from && '' !== $to && $from !== $to ) { $urls[ $from ] = $to; } } foreach ( $urls as $from => $to ) { $variants = self::url_variants( $from, ! empty( $opts['www'] ) ); foreach ( $variants as $variant ) { self::add_url_pair( $pairs, $variant, $to ); } $rows[] = array( $from, $to, 'url' ); } if ( ! empty( $opts['paths'] ) ) { foreach ( array( 'uploads_dir', 'content_dir', 'abspath' ) as $key ) { $from = untrailingslashit( (string) ( $src[ $key ] ?? '' ) ); $to = untrailingslashit( (string) ( $dest[ $key ] ?? '' ) ); if ( strlen( $from ) > 4 && '' !== $to && $from !== $to && ! isset( $pairs[ $from ] ) ) { $pairs[ $from ] = $to; $pairs[ str_replace( '/', '\\/', $from ) ] = str_replace( '/', '\\/', $to ); $rows[] = array( $from, $to, 'path' ); } } } foreach ( (array) $opts['extra'] as $extra ) { if ( isset( $extra[0], $extra[1] ) && '' !== (string) $extra[0] && $extra[0] !== $extra[1] ) { $pairs[ (string) $extra[0] ] = (string) $extra[1]; $rows[] = array( (string) $extra[0], (string) $extra[1], 'custom' ); } } return array( 'pairs' => $pairs, 'needles' => self::needles( array_keys( $pairs ) ), 'rows' => $rows, ); } /** * Source URL spellings: scheme and www twins. * * @param string $url URL without trailing slash. * @param bool $www Include the www / non-www twin. * @return string[] */ private static function url_variants( $url, $www ) { $parts = wp_parse_url( $url ); if ( empty( $parts['host'] ) ) { return array( $url ); } $rest = ( isset( $parts['port'] ) ? ':' . $parts['port'] : '' ) . ( isset( $parts['path'] ) ? rtrim( $parts['path'], '/' ) : '' ); $hosts = array( $parts['host'] ); if ( $www ) { $hosts[] = 0 === strpos( $parts['host'], 'www.' ) ? substr( $parts['host'], 4 ) : 'www.' . $parts['host']; } $out = array(); foreach ( $hosts as $host ) { foreach ( array( 'https://', 'http://' ) as $scheme ) { $out[] = $scheme . $host . $rest; } } return $out; } /** * Adds a URL pair plus its protocol-relative, JSON-escaped and URL-encoded forms. * * @param array $pairs Pairs (by reference). * @param string $from Source URL. * @param string $to Destination URL. */ private static function add_url_pair( array &$pairs, $from, $to ) { $pairs[ $from ] = $to; $from_rel = preg_replace( '#^https?:#', '', $from ); $to_rel = preg_replace( '#^https?:#', '', $to ); $pairs[ $from_rel ] = $to_rel; $pairs[ str_replace( '/', '\\/', $from ) ] = str_replace( '/', '\\/', $to ); $pairs[ str_replace( '/', '\\/', $from_rel ) ] = str_replace( '/', '\\/', $to_rel ); $enc_from = rawurlencode( $from ); $enc_to = rawurlencode( $to ); $pairs[ $enc_from ] = $enc_to; $pairs[ str_replace( array( '%3A', '%2F' ), array( '%3a', '%2f' ), $enc_from ) ] = str_replace( array( '%3A', '%2F' ), array( '%3a', '%2f' ), $enc_to ); } /** * Pre-check needles: for each search string, its longest run without / \ % or :. * Every spelling of a URL (escaped, encoded) still contains that run (usually the host). * * @param string[] $searches Search strings. * @return string[] */ private static function needles( array $searches ) { $out = array(); foreach ( $searches as $search ) { $best = ''; foreach ( preg_split( '#[/\\\\%:]+#', $search ) as $run ) { if ( strlen( $run ) > strlen( $best ) ) { $best = $run; } } // Encoded forms (%2F...) leave fragments like "2Flocalhost"; the plain host is also added. if ( '' !== $best ) { $out[] = preg_replace( '/^(2F|2f|3A|3a)+/', '', $best ); } } return array_values( array_unique( array_filter( $out, 'strlen' ) ) ); } }