* * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * @file * @author Ori Livneh */ namespace Wikimedia; class RelPath { /** * Split a path into path components. * * @param string $path File path. * @return array Array of path components. */ public static function splitPath( $path ) { $fragments = []; while ( true ) { $cur = dirname( $path ); if ( $cur[0] === DIRECTORY_SEPARATOR ) { // dirname() on Windows sometimes returns a leading backslash, but other // times retains the leading forward slash. Slashes other than the leading one // are returned as-is, and therefore do not need to be touched. // Furthermore, don't break on *nix where \ is allowed in file/directory names. $cur[0] = '/'; } if ( $cur === $path || ( $cur === '.' && basename( $path ) === $path ) ) { break; } $fragment = trim( substr( $path, strlen( $cur ) ), '/' ); if ( !$fragments ) { $fragments[] = $fragment; } elseif ( $fragment === '..' && basename( $cur ) !== '..' ) { $cur = dirname( $cur ); } elseif ( $fragment !== '.' ) { $fragments[] = $fragment; } $path = $cur; } if ( $path !== '' ) { $fragments[] = trim( $path, '/' ); } return array_reverse( $fragments ); } /** * Return a relative filepath to path either from the current directory or from * an optional start directory. Both paths must be absolute. * * @param string $path File path. * @param string $start Start directory. Optional; if not specified, the current * working directory will be used. * @return string|bool Relative path, or false if input was invalid. */ public static function getRelativePath( $path, $start = null ) { if ( $start === null ) { // @codeCoverageIgnoreStart $start = getcwd(); } // @codeCoverageIgnoreEnd if ( substr( $path, 0, 1 ) !== '/' || substr( $start, 0, 1 ) !== '/' ) { return false; } $pathParts = self::splitPath( $path ); $countPathParts = count( $pathParts ); $startParts = self::splitPath( $start ); $countStartParts = count( $startParts ); $commonLength = min( $countPathParts, $countStartParts ); for ( $i = 0; $i < $commonLength; $i++ ) { if ( $startParts[$i] !== $pathParts[$i] ) { break; } } $relList = ( $countStartParts > $i ) ? array_fill( 0, $countStartParts - $i, '..' ) : []; $relList = array_merge( $relList, array_slice( $pathParts, $i ) ); return implode( '/', $relList ) ?: '.'; } /** * Join path components. * * @param string $base Base path. * @param string $path File path to join to base path. * @return string */ public static function joinPath( $base, $path ) { if ( substr( $path, 0, 1 ) === '/' ) { // $path is absolute. return $path; } if ( substr( $base, 0, 1 ) !== '/' ) { // $base is relative. return false; } $pathParts = self::splitPath( $path ); $resultParts = self::splitPath( $base ); while ( ( $part = array_shift( $pathParts ) ) !== null ) { switch ( $part ) { case '.': break; case '..': if ( count( $resultParts ) > 1 ) { array_pop( $resultParts ); } break; default: $resultParts[] = $part; break; } } return implode( '/', $resultParts ); } }