Index: src/wp-includes/ms-blogs.php
===================================================================
--- src/wp-includes/ms-blogs.php	(revision 41654)
+++ src/wp-includes/ms-blogs.php	(working copy)
@@ -538,6 +538,102 @@
 }
 
 /**
+ * Retrieves a site by given site data.
+ *
+ * @since 4.9.0
+ *
+ * @param string     $field      Name of a field to query against. Either 'id', 'slug' or 'url', or
+ *                               'domain' (only if a subdomain install) or 'path' (only if a subdirectory
+ *                               install).
+ * @param string|int $value      The search value for $field.
+ * @param int|null   $network_id Optional. ID of the network. Default is the current network.
+ * @return WP_Site|null The site object or null if not found.
+ */
+function get_site_by( $field, $value, $network_id = null ) {
+	$args = array();
+
+	switch ( $field ) {
+		case 'id':
+			return get_site( $value );
+		case 'slug':
+			$network = get_network( $network_id );
+			if ( ! $network ) {
+				return null;
+			}
+
+			if ( is_subdomain_install() ) {
+				$args['domain'] = trim( $value, '/' ) . '.' . preg_replace( '|^www\.|', '', $network->domain );
+				$args['path'] = $network->path;
+			} else {
+				$args['domain'] = $network->domain;
+				$args['path'] = $network->path . trim( $value, '/' ) . '/';
+			}
+			break;
+		case 'url':
+			if ( 0 !== strpos( $value, 'http://' ) && 0 !== strpos( $value, 'https://' ) ) {
+				$value = 'http://' . $value;
+			}
+
+			$parts = wp_parse_url( $value );
+			if ( ! $parts ) {
+				return null;
+			}
+
+			$args['domain'] = $parts['host'];
+			if ( ! empty( $parts['path'] ) ) {
+				$args['path'] = '/' . trim( $parts['path'], '/' ) . '/';
+			} else {
+				$args['path'] = '/';
+			}
+			break;
+		case 'domain':
+			if ( ! is_subdomain_install() ) {
+				return null;
+			}
+
+			$args['domain'] = $value;
+			break;
+		case 'path':
+			if ( is_subdomain_install() ) {
+				return null;
+			}
+
+			$args['path'] = '/' . trim( $value, '/' ) . '/';
+			break;
+		default:
+			return null;
+	}
+
+	$args['number'] = 1;
+
+	if ( isset( $args['domain'] ) && substr( $args['domain'], 0, 4 ) === 'www.' ) {
+		$nowww = substr( $args['domain'], 4 );
+
+		$args['domain__in'] = array( $nowww, $args['domain'] );
+		unset( $args['domain'] );
+
+		$args['orderby'] = 'domain_length';
+		$args['order']   = 'DESC';
+	}
+
+	if ( isset( $args['path'] ) ) {
+		$args['path'] = str_replace( '//', '/', $args['path'] );
+	}
+
+	if ( ! empty( $network_id ) ) {
+		$args['network_id'] = (int) $network_id;
+	}
+
+	$sites = get_sites( $args );
+
+	if ( empty( $sites ) ) {
+		return null;
+	}
+
+	return array_shift( $sites );
+}
+
+/**
  * Adds any sites from the given ids to the cache that do not already exist in cache.
  *
  * @since 4.6.0
Index: tests/phpunit/tests/multisite/getSiteBy.php
===================================================================
--- tests/phpunit/tests/multisite/getSiteBy.php	(nonexistent)
+++ tests/phpunit/tests/multisite/getSiteBy.php	(working copy)
@@ -0,0 +1,220 @@
+<?php
+
+if ( is_multisite() ) :
+/**
+ * Test get_site_by() in multisite.
+ *
+ * @ticket 40180
+ * @group ms-site
+ * @group multisite
+ */
+class Tests_Multisite_Get_Site_By extends WP_UnitTestCase {
+	protected static $network_ids;
+	protected static $site_ids;
+
+	public static function wpSetUpBeforeClass( $factory ) {
+		self::$network_ids = array(
+			'wordpress.org/'         => array( 'domain' => 'wordpress.org',     'path' => '/' ),
+			'www.wordpress.net/'     => array( 'domain' => 'www.wordpress.net', 'path' => '/' ),
+		);
+
+		foreach ( self::$network_ids as &$id ) {
+			$id = $factory->network->create( $id );
+		}
+		unset( $id );
+
+		self::$site_ids = array(
+			'wordpress.org/'              => array( 'domain' => 'wordpress.org',     'path' => '/',     'site_id' => self::$network_ids['wordpress.org/'] ),
+			'foo.wordpress.org/'          => array( 'domain' => 'foo.wordpress.org', 'path' => '/',     'site_id' => self::$network_ids['wordpress.org/'] ),
+			'wordpress.org/foo/'          => array( 'domain' => 'wordpress.org',     'path' => '/foo/', 'site_id' => self::$network_ids['wordpress.org/'] ),
+			'www.wordpress.org/'          => array( 'domain' => 'www.wordpress.org', 'path' => '/',     'site_id' => self::$network_ids['wordpress.org/'] ),
+			'www.wordpress.net/'          => array( 'domain' => 'www.wordpress.net', 'path' => '/',     'site_id' => self::$network_ids['www.wordpress.net/'] ),
+			'foo.wordpress.net/'          => array( 'domain' => 'foo.wordpress.net', 'path' => '/',     'site_id' => self::$network_ids['www.wordpress.net/'] ),
+			'www.wordpress.net/foo/'      => array( 'domain' => 'www.wordpress.net', 'path' => '/foo/', 'site_id' => self::$network_ids['www.wordpress.net/'] ),
+		);
+
+		foreach ( self::$site_ids as &$id ) {
+			$id = $factory->blog->create( $id );
+		}
+		unset( $id );
+	}
+
+	public static function wpTearDownAfterClass() {
+		global $wpdb;
+
+		foreach( self::$site_ids as $id ) {
+			wpmu_delete_blog( $id, true );
+		}
+
+		foreach( self::$network_ids as $id ) {
+			$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->sitemeta} WHERE site_id = %d", $id ) );
+			$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->site} WHERE id= %d", $id ) );
+		}
+
+		wp_update_network_site_counts();
+	}
+
+	public function test_get_site_by_id() {
+		$result = get_site_by( 'id', self::$site_ids['wordpress.org/'] );
+
+		$this->assertEquals( self::$site_ids['wordpress.org/'], $result->id );
+	}
+
+	public function test_get_site_by_current_id() {
+		$result = get_site_by( 'id', null );
+
+		$this->assertEquals( get_current_blog_id(), $result->id );
+	}
+
+	public function test_get_site_by_slug_subdomain() {
+		if ( ! is_subdomain_install() ) {
+			$this->markTestSkipped( 'This test is only valid in a subdomain configuration.' );
+		}
+
+		$result = get_site_by( 'slug', 'foo', self::$network_ids['wordpress.org/'] );
+
+		$this->assertEquals( self::$site_ids['foo.wordpress.org/'], $result->id );
+	}
+
+	public function test_get_site_by_slug_with_www_subdomain() {
+		if ( ! is_subdomain_install() ) {
+			$this->markTestSkipped( 'This test is only valid in a subdomain configuration.' );
+		}
+
+		$result = get_site_by( 'slug', 'foo', self::$network_ids['www.wordpress.net/'] );
+
+		$this->assertEquals( self::$site_ids['foo.wordpress.net/'], $result->id );
+	}
+
+	public function test_get_site_by_slug_subdirectory() {
+		if ( is_subdomain_install() ) {
+			$this->markTestSkipped( 'This test is only valid in a subdirectory configuration.' );
+		}
+
+		$result = get_site_by( 'slug', 'foo', self::$network_ids['wordpress.org/'] );
+
+		$this->assertEquals( self::$site_ids['wordpress.org/foo/'], $result->id );
+	}
+
+	public function test_get_site_by_slug_with_www_subdirectory() {
+		if ( is_subdomain_install() ) {
+			$this->markTestSkipped( 'This test is only valid in a subdirectory configuration.' );
+		}
+
+		$result = get_site_by( 'slug', 'foo', self::$network_ids['www.wordpress.net/'] );
+
+		$this->assertEquals( self::$site_ids['www.wordpress.net/foo/'], $result->id );
+	}
+
+	public function test_get_site_by_slug_with_first_network() {
+		$result = get_site_by( 'slug', 'foo', self::$network_ids['wordpress.org/'] );
+
+		if ( is_subdomain_install() ) {
+			$this->assertEquals( self::$site_ids['foo.wordpress.org/'], $result->id );
+		} else {
+			$this->assertEquals( self::$site_ids['wordpress.org/foo/'], $result->id );
+		}
+	}
+
+	public function test_get_site_by_slug_with_second_network() {
+		$result = get_site_by( 'slug', 'foo', self::$network_ids['www.wordpress.net/'] );
+
+		if ( is_subdomain_install() ) {
+			$this->assertEquals( self::$site_ids['foo.wordpress.net/'], $result->id );
+		} else {
+			$this->assertEquals( self::$site_ids['www.wordpress.net/foo/'], $result->id );
+		}
+	}
+
+	public function test_get_site_by_slug_with_invalid_network() {
+		$result = get_site_by( 'slug', 'foo', 444 );
+
+		$this->assertNull( $result );
+	}
+
+	/**
+	 * @dataProvider data_get_site_by_url
+	 */
+	public function test_get_site_by_url( $url, $expected ) {
+		$result = get_site_by( 'url', $url );
+
+		$this->assertEquals( self::$site_ids[ $expected ], $result->id );
+	}
+
+	public function data_get_site_by_url() {
+		return array(
+			array(
+				'wordpress.org/foo/',
+				'wordpress.org/foo/',
+			),
+			array(
+				'wordpress.org/foo',
+				'wordpress.org/foo/',
+			),
+			array(
+				'foo.wordpress.org/',
+				'foo.wordpress.org/',
+			),
+			array(
+				'foo.wordpress.org',
+				'foo.wordpress.org/',
+			),
+			array(
+				'www.wordpress.net/',
+				'www.wordpress.net/',
+			),
+			array(
+				'www.wordpress.org/',
+				'www.wordpress.org/',
+			),
+		);
+	}
+
+	public function test_get_site_by_url_with_invalid_url() {
+		$result = get_site_by( 'url', 'not a url' );
+
+		$this->assertNull( $result );
+	}
+
+	public function test_get_site_by_domain_subdomain() {
+		if ( ! is_subdomain_install() ) {
+			$this->markTestSkipped( 'This test is only valid in a subdomain configuration.' );
+		}
+
+		$result = get_site_by( 'domain', 'foo.wordpress.org' );
+
+		$this->assertEquals( self::$site_ids['foo.wordpress.org/'], $result->id );
+	}
+
+	public function test_get_site_by_domain_subdirectory() {
+		if ( is_subdomain_install() ) {
+			$this->markTestSkipped( 'This test is only valid in a subdirectory configuration.' );
+		}
+
+		$result = get_site_by( 'domain', 'foo.wordpress.org' );
+
+		$this->assertNull( $result );
+	}
+
+	public function test_get_site_by_path_subdomain() {
+		if ( ! is_subdomain_install() ) {
+			$this->markTestSkipped( 'This test is only valid in a subdomain configuration.' );
+		}
+
+		$result = get_site_by( 'path', '/foo/' );
+
+		$this->assertNull( $result );
+	}
+
+	public function test_get_site_by_path_subdirectory() {
+		if ( is_subdomain_install() ) {
+			$this->markTestSkipped( 'This test is only valid in a subdirectory configuration.' );
+		}
+
+		$result = get_site_by( 'path', '/foo/' );
+
+		$this->assertEquals( self::$site_ids['wordpress.org/foo/'], $result->id );
+	}
+}
+
+endif;
