src/View/ThemeResourceLoader.php
public function getPath($identifier)
    {
        $slashPos = strpos($identifier, '/');
        $parts = explode(':', $identifier, 2);

        // If identifier starts with "/", it's a path from root
        if ($slashPos === 0) {
            if (count($parts) > 1) {
                throw new InvalidArgumentException("Invalid theme identifier {$identifier}");
            }
            return Path::normalise($identifier, true);
        }

        // If there is no slash / colon it's a legacy theme
        if ($slashPos === false && count($parts) === 1) {
            return Path::join(THEMES_DIR, $identifier);
        }

        // Extract from <vendor>/<module>:<theme> format.
        // <vendor> is optional, and if <theme> is omitted it defaults to the module root dir.
        // If <theme> is included, this is the name of the directory under moduleroot/themes/
        // which contains the theme.
        // <module> is always the name of the install directory, not necessarily the composer name.

        // Find module from first part
        $moduleName = $parts[0];
        $module = ModuleLoader::inst()->getManifest()->getModule($moduleName);
        if ($module) {
            $modulePath = $module->getRelativePath();
        } else {
            // If no module could be found, assume based on basename
            // with a warning
            if (strstr('/', $moduleName)) {
                list(, $modulePath) = explode('/', $parts[0], 2);
            } else {
                $modulePath = $moduleName;
            }
            trigger_error("No module named {$moduleName} found. Assuming path {$modulePath}", E_USER_WARNING);
        }

        // Parse relative path for this theme within this module
        $theme = count($parts) > 1 ? $parts[1] : '';
        if (empty($theme)) {
            // "module/vendor:"
            // "module/vendor"
            $subpath = '';
        } elseif (strpos($theme, '/') === 0) {
            // "module/vendor:/sub/path"
            $subpath = rtrim($theme, '/');
        } else {
            // "module/vendor:subtheme"
            $subpath = '/themes/' . $theme;
        }

        // Join module with subpath
        return Path::normalise($modulePath . $subpath, true);
    }
public function getPath($identifier)
    {
        $slashPos = strpos($identifier, '/');
        $parts = explode(':', $identifier, 2);

        // If identifier starts with "/", it's a path from root
        if ($slashPos === 0) {
            if (count($parts) > 1) {
                throw new InvalidArgumentException("Invalid theme identifier {$identifier}");
            }
            return Path::normalise($identifier, true);
        }

        // If there is no slash / colon it's a legacy theme
        if ($slashPos === false && count($parts) === 1) {
            return Path::join(THEMES_DIR, $identifier);
        }

        // Extract from <vendor>/<module>:<theme> format.
        // <vendor> is optional, and if <theme> is omitted it defaults to the module root dir.
        // If <theme> is included, this is the name of the directory under moduleroot/themes/
        // which contains the theme.
        // <module> is always the name of the install directory, not necessarily the composer name.

        // Find module from first part
        $moduleName = $parts[0];
        $module = ModuleLoader::inst()->getManifest()->getModule($moduleName);
        if ($module) {
            $modulePath = $module->getRelativePath();
        } else {
            // If no module could be found, assume based on basename
            // with a warning
            if (strstr('/', $moduleName)) {
                list(, $modulePath) = explode('/', $parts[0], 2);
            } else {
                $modulePath = $moduleName;
            }
            trigger_error("No module named {$moduleName} found. Assuming path {$modulePath}", E_USER_WARNING);
        }

        // Parse relative path for this theme within this module
        $theme = count($parts) > 1 ? $parts[1] : '';
        if (empty($theme)) {
            // "module/vendor:"
            // "module/vendor"
            $subpath = '';
        } elseif (strpos($theme, '/') === 0) {
            // "module/vendor:/sub/path"
            $subpath = rtrim($theme, '/');
        } else {
            // "module/vendor:subtheme"
            $subpath = '/themes/' . $theme;
        }

        // Join module with subpath
        return Path::normalise($modulePath . $subpath, true);
    }
public function getPath ( $ identifier ) { $ slashPos = strpos ( $ identifier , '/' ) ; $ parts = explode ( ':' , $ identifier , 2 ) ; // If identifier starts with "/", it's a path from root if ( $ slashPos === 0 ) { if ( count ( $ parts ) > 1 ) { throw new InvalidArgumentException ( "Invalid theme identifier {$identifier}" ) ; } return Path :: normalise ( $ identifier , true ) ; } // If there is no slash / colon it's a legacy theme if ( $ slashPos === false && count ( $ parts ) === 1 ) { return Path :: join ( THEMES_DIR , $ identifier ) ; } // Extract from <vendor>/<module>:<theme> format. // <vendor> is optional, and if <theme> is omitted it defaults to the module root dir. // If <theme> is included, this is the name of the directory under moduleroot/themes/ // which contains the theme. // <module> is always the name of the install directory, not necessarily the composer name. // Find module from first part $ moduleName = $ parts [ 0 ] ; $ module = ModuleLoader :: inst ( ) -> getManifest ( ) -> getModule ( $ moduleName ) ; if ( $ module ) { $ modulePath = $ module -> getRelativePath ( ) ; } else { // If no module could be found, assume based on basename // with a warning if ( strstr ( '/' , $ moduleName ) ) { list ( , $ modulePath ) = explode ( '/' , $ parts [ 0 ] , 2 ) ; } else { $ modulePath = $ moduleName ; } trigger_error ( "No module named {$moduleName} found. Assuming path {$modulePath}" , E_USER_WARNING ) ; } // Parse relative path for this theme within this module $ theme = count ( $ parts ) > 1 ? $ parts [ 1 ] : '' ; if ( empty ( $ theme ) ) { // "module/vendor:" // "module/vendor" $ subpath = '' ; } elseif ( strpos ( $ theme , '/' ) === 0 ) { // "module/vendor:/sub/path" $ subpath = rtrim ( $ theme , '/' ) ; } else { // "module/vendor:subtheme" $ subpath = '/themes/' . $ theme ; } // Join module with subpath return Path :: normalise ( $ modulePath . $ subpath , true ) ; }
Given a theme identifier, determine the path from the root directory

The mapping from $identifier to path follows these rules:
- A simple theme name ('mytheme') which maps to the standard themes dir (/themes/mytheme)
- A theme path with a leading slash ('/mymodule/themes/mytheme') which maps directly to that path.
- or a vendored theme path. (vendor/mymodule:mytheme) which maps to the nested 'theme' within
that module. ('/mymodule/themes/mytheme').
- A vendored module with no nested theme (vendor/mymodule) which maps to the root directory
of that module. ('/mymodule').

@param string $identifier Theme identifier.
@return string Path from root, not including leading or trailing forward slash. E.g. themes/mytheme
Given a theme identifier determine the path from the root directory