+ /**
+ * Get the ExternalStoreMedium for a given URL
+ *
+ * $url is either of the form:
+ * - a) "<proto>://<location>/<path>", for retrieval, or
+ * - b) "<proto>://<location>", for storage
+ *
+ * @param string $url
+ * @param array $params Map of ExternalStoreMedium::__construct context parameters
+ * @return ExternalStoreMedium
+ * @throws ExternalStoreException When the protocol is missing or not recognized
+ * @since 1.34
+ */
+ public function getStoreForUrl( $url, array $params = [] ) {
+ list( $proto, $path ) = self::splitStorageUrl( $url );
+ if ( $path == '' ) { // bad URL
+ throw new ExternalStoreException( "Invalid URL '$url'" );
+ }
+
+ return $this->getStore( $proto, $params );
+ }
+
+ /**
+ * Get the location within the appropriate store for a given a URL
+ *
+ * @param string $url
+ * @return string
+ * @throws ExternalStoreException
+ * @since 1.34
+ */
+ public function getStoreLocationFromUrl( $url ) {
+ list( , $location ) = self::splitStorageUrl( $url );
+ if ( $location == '' ) { // bad URL
+ throw new ExternalStoreException( "Invalid URL '$url'" );
+ }
+
+ return $location;
+ }
+
+ /**
+ * @param string[] $urls
+ * @return array[] Map of (protocol => list of URLs)
+ * @throws ExternalStoreException
+ * @since 1.34
+ */
+ public function getUrlsByProtocol( array $urls ) {
+ $urlsByProtocol = [];
+ foreach ( $urls as $url ) {
+ list( $proto, ) = self::splitStorageUrl( $url );
+ $urlsByProtocol[$proto][] = $url;
+ }
+
+ return $urlsByProtocol;
+ }
+
+ /**
+ * @param string $storeUrl
+ * @return string[] (protocol, store location or location-qualified path)
+ * @throws ExternalStoreException
+ */
+ private static function splitStorageUrl( $storeUrl ) {
+ $parts = explode( '://', $storeUrl );
+ if ( count( $parts ) != 2 || $parts[0] === '' || $parts[1] === '' ) {
+ throw new ExternalStoreException( "Invalid storage URL '$storeUrl'" );
+ }
+
+ return $parts;
+ }