mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
ENHANCEMENT Allow vendor modules with url rewriting
API Introduce ModuleResource feature
This commit is contained in:
parent
91943c03e5
commit
fa57deeba4
2
.gitignore
vendored
2
.gitignore
vendored
@ -8,6 +8,6 @@ node_modules/
|
||||
coverage/
|
||||
/**/*.js.map
|
||||
/**/*.css.map
|
||||
vendor/
|
||||
/vendor/
|
||||
composer.lock
|
||||
silverstripe-cache/
|
||||
|
@ -1,11 +1,6 @@
|
||||
---
|
||||
Name: coreconfig
|
||||
---
|
||||
SilverStripe\Core\Injector\Injector:
|
||||
SilverStripe\Core\Manifest\ResourceURLGenerator:
|
||||
class: SilverStripe\Control\SimpleResourceURLGenerator
|
||||
properties:
|
||||
NonceStyle: mtime
|
||||
SilverStripe\Control\HTTP:
|
||||
cache_control:
|
||||
max-age: 0
|
||||
|
8
_config/resources.yml
Normal file
8
_config/resources.yml
Normal file
@ -0,0 +1,8 @@
|
||||
---
|
||||
Name: coreresources
|
||||
---
|
||||
SilverStripe\Core\Injector\Injector:
|
||||
SilverStripe\Core\Manifest\ResourceURLGenerator:
|
||||
class: SilverStripe\Control\SimpleResourceURLGenerator
|
||||
properties:
|
||||
NonceStyle: mtime
|
@ -29,6 +29,8 @@ As you've added new files to your SilverStripe installation, make sure you clear
|
||||
`?flush=1` to your website URL (e.g http://yoursite.com/?flush=1).
|
||||
</div>
|
||||
|
||||
### Configuring themes
|
||||
|
||||
After installing the files through either method, update the current theme in SilverStripe. This can be done by
|
||||
either altering the `SSViewer.themes` setting in a [config.yml](../configuration) or by changing the current theme in
|
||||
the Site Configuration panel (http://yoursite.com/admin/settings)
|
||||
@ -42,6 +44,31 @@ SilverStripe\View\SSViewer:
|
||||
- '$default'
|
||||
```
|
||||
|
||||
There are a variety of ways in which you can specify a theme. The below describe the three
|
||||
main styles of syntax:
|
||||
|
||||
1. You can use the following to point to a theme or path within your root project:
|
||||
|
||||
- `themename` -> A simple name with no slash represents a theme in the `/themes` directory
|
||||
- `/some/path/to/theme` - Any `/` prefixed string will be treated as a direct filesystem path to a theme root.
|
||||
- `$themeset` - Any `$` prefixed name will refer to a set of themes. By default only `$default` set is configured,
|
||||
which represents all module roots with a `templates` directory.
|
||||
|
||||
2. Using the `:` syntax you can also specify themes relative to the given module:
|
||||
|
||||
- `myvendor/mymodule:sometheme` - This will specify a standard theme within the given module.
|
||||
This will lookup the theme in the `themes` subfolder within this module. E.g.
|
||||
`/vendor/myvendor/mymodule/themes/sometheme`.
|
||||
Note: This syntax also works without the vendor prefix (`mymodule:sometheme`)
|
||||
- `myvendor/mymodule:/some/path` - Rather than looking in the themes subdir, look in the
|
||||
exact path within the root of the given module.
|
||||
|
||||
3. You can also specify a module root folder directly.
|
||||
|
||||
- `myvendor/mymodule` - Points to the base folder of the given module.
|
||||
- `mymodule:` - Also points to the base folder of the given module, but without a vendor.
|
||||
The `:` is necessary to distinguish this from a non-module theme.
|
||||
|
||||
### Manually
|
||||
|
||||
Unpack the contents of the zip file you download into the `themes` directory in your SilverStripe installation. The
|
||||
|
@ -17,39 +17,87 @@ this:
|
||||
|
||||
**mycustommodule/composer.json**
|
||||
|
||||
```js
|
||||
|
||||
{
|
||||
"name": "your-vendor-name/module-name",
|
||||
"description": "One-liner describing your module",
|
||||
"type": "silverstripe-module",
|
||||
"homepage": "http://github.com/your-vendor-name/module-name",
|
||||
"keywords": ["silverstripe", "some-tag", "some-other-tag"],
|
||||
"license": "BSD-3-Clause",
|
||||
"authors": [
|
||||
{"name": "Your Name","email": "your@email.com"}
|
||||
],
|
||||
"support": {
|
||||
"issues": "http://github.com/your-vendor-name/module-name/issues"
|
||||
},
|
||||
"require": {
|
||||
"silverstripe/cms": "~3.1",
|
||||
"silverstripe/framework": "~3.1"
|
||||
},
|
||||
"extra": {
|
||||
"installer-name": "module-name",
|
||||
"screenshots": [
|
||||
"relative/path/screenshot1.png",
|
||||
"http://myhost.com/screenshot2.png"
|
||||
]
|
||||
}
|
||||
}
|
||||
```json
|
||||
{
|
||||
"name": "your-vendor-name/module-name",
|
||||
"description": "One-liner describing your module",
|
||||
"type": "silverstripe-module",
|
||||
"homepage": "http://github.com/your-vendor-name/module-name",
|
||||
"keywords": ["silverstripe", "some-tag", "some-other-tag"],
|
||||
"license": "BSD-3-Clause",
|
||||
"authors": [
|
||||
{"name": "Your Name","email": "your@email.com"}
|
||||
],
|
||||
"support": {
|
||||
"issues": "http://github.com/your-vendor-name/module-name/issues"
|
||||
},
|
||||
"require": {
|
||||
"silverstripe/cms": "^4",
|
||||
"silverstripe/framework": "^4"
|
||||
},
|
||||
"extra": {
|
||||
"installer-name": "module-name",
|
||||
"screenshots": [
|
||||
"relative/path/screenshot1.png",
|
||||
"http://myhost.com/screenshot2.png"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Once your module is published online with a service like Github.com or Bitbucket.com, submit the repository to
|
||||
[Packagist](https://packagist.org/) to have the module accessible to developers. It'll automatically get picked
|
||||
up by [addons.silverstripe.org](http://addons.silverstripe.org/) website.
|
||||
|
||||
## Vendor modules
|
||||
|
||||
By default `silverstripe-module` type libraries are installed to the root web folder, however a new type
|
||||
`silverstripe-vendormodule` allows you to publish your module to the vendor directory.
|
||||
|
||||
The below is an example of a vendor module composer.json:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "tractorcow/test-vendor-module",
|
||||
"description": "Test module for silverstripe/vendor-plugin",
|
||||
"type": "silverstripe-vendormodule",
|
||||
"require": {
|
||||
"silverstripe/vendor-plugin": "^1.0",
|
||||
"silverstripe/cms": "^4.0"
|
||||
},
|
||||
"license": "BSD-3-Clause",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"TractorCow\\TestVendorModule\\": "src/"
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"expose": [
|
||||
"client"
|
||||
]
|
||||
},
|
||||
"minimum-stability": "dev"
|
||||
}
|
||||
```
|
||||
|
||||
Note that these modules have the following distinct characteristics:
|
||||
|
||||
- Library type is `silverstripe-vendormodule`
|
||||
- Any folder which should be exposed to the public webroot must be declared in the `extra.expose` config.
|
||||
These paths will be automatically rewritten to public urls which don't directly serve files from the `vendor`
|
||||
folder. For instance, `vendor/tractorcow/test-vendor-module/client` will be rewritten to
|
||||
`resources/tractorcow/test-vendor-module/client`.
|
||||
- Any module which uses the folder expose feature must require `silverstripe/vendor-plugin` in order to
|
||||
support automatic rewriting and linking. For more information on this plugin you can see the
|
||||
[silverstripe/vendor-plugin github page](https://github.com/silverstripe/vendor-plugin).
|
||||
|
||||
Linking to resources in vendor modules uses exactly the same syntax as non-vendor modules. For example,
|
||||
this is how you would require a script in this module:
|
||||
|
||||
```php
|
||||
Requirements::javascript('tractorcow/test-vendor-module:client/js/script.js');
|
||||
```
|
||||
|
||||
## Releasing versions
|
||||
|
||||
Over time you may have to release new versions of your module to continue to work with newer versions of SilverStripe.
|
||||
@ -76,4 +124,4 @@ Here's some common values for your `require` section
|
||||
* `3.0.*`: Version `3.0`, including `3.0.1`, `3.0.2` etc, excluding `3.1`
|
||||
* `~3.0`: Version `3.0` or higher, including `3.0.1` and `3.1` etc, excluding `4.0`
|
||||
* `~3.0,<3.2`: Version `3.0` or higher, up until `3.2`, which is excluded
|
||||
* `~3.0,>3.0.4`: Version `3.0` or higher, starting with `3.0.4`
|
||||
* `~3.0,>3.0.4`: Version `3.0` or higher, starting with `3.0.4`
|
||||
|
@ -64,6 +64,8 @@ guide developers in preparing existing 3.x code for compatibility with 4.0
|
||||
[intervention/image](https://github.com/intervention/image) library to power manipualations.
|
||||
* Dependencies can managed via [recipe-plugin](https://github.com/silverstripe/recipe-plugin). See [recipe-core](https://github.com/silverstripe/recipe-core) and [recipe-cms](https://github.com/silverstripe/recipe-cms) as examples.
|
||||
* Authentication has been upgraded to a modular approach using re-usable interfaces and easier to hook in to LoginHandlers.
|
||||
* Support for modules installed in vendor folder. See [/developer_guides/extending/how_tos/publish_a_module](the
|
||||
module publishing guide) for more information.
|
||||
|
||||
## <a name="upgrading"></a>Upgrading
|
||||
|
||||
|
@ -3,6 +3,8 @@
|
||||
namespace SilverStripe\Control;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use SilverStripe\Core\Config\Config;
|
||||
use SilverStripe\Core\Manifest\ModuleResource;
|
||||
use SilverStripe\Core\Manifest\ResourceURLGenerator;
|
||||
|
||||
/**
|
||||
@ -11,6 +13,18 @@ use SilverStripe\Core\Manifest\ResourceURLGenerator;
|
||||
*/
|
||||
class SimpleResourceURLGenerator implements ResourceURLGenerator
|
||||
{
|
||||
/**
|
||||
* Rewrites applied after generating url.
|
||||
* Note: requires either silverstripe/vendor-plugin-helper or silverstripe/vendor-plugin
|
||||
* to ensure the file is available.
|
||||
*
|
||||
* @config
|
||||
* @var array
|
||||
*/
|
||||
private static $url_rewrites = [
|
||||
'#^vendor/#i' => 'resources/',
|
||||
];
|
||||
|
||||
/*
|
||||
* @var string
|
||||
*/
|
||||
@ -45,18 +59,34 @@ class SimpleResourceURLGenerator implements ResourceURLGenerator
|
||||
/**
|
||||
* Return the URL for a resource, prefixing with Director::baseURL() and suffixing with a nonce
|
||||
*
|
||||
* @param string $relativePath File or directory path relative to BASE_PATH
|
||||
* @param string|ModuleResource $relativePath File or directory path relative to BASE_PATH
|
||||
* @return string Doman-relative URL
|
||||
* @throws InvalidArgumentException If the resource doesn't exist
|
||||
*/
|
||||
public function urlForResource($relativePath)
|
||||
{
|
||||
$absolutePath = preg_replace('/\?.*/', '', Director::baseFolder() . '/' . $relativePath);
|
||||
|
||||
if (!file_exists($absolutePath)) {
|
||||
if ($relativePath instanceof ModuleResource) {
|
||||
// Load from module resource
|
||||
$resource = $relativePath;
|
||||
$relativePath = $resource->getRelativePath();
|
||||
$exists = $resource->exists();
|
||||
$absolutePath = $resource->getPath();
|
||||
} else {
|
||||
// Use normal string
|
||||
$absolutePath = preg_replace('/\?.*/', '', Director::baseFolder() . '/' . $relativePath);
|
||||
$exists = file_exists($absolutePath);
|
||||
}
|
||||
if (!$exists) {
|
||||
throw new InvalidArgumentException("File {$relativePath} does not exist");
|
||||
}
|
||||
|
||||
// Apply url rewrites
|
||||
$rules = Config::inst()->get(static::class, 'url_rewrites') ?: [];
|
||||
foreach ($rules as $from => $to) {
|
||||
$relativePath = preg_replace($from, $to, $relativePath);
|
||||
}
|
||||
|
||||
// Apply nonce
|
||||
$nonce = '';
|
||||
// Don't add nonce to directories
|
||||
if ($this->nonceStyle && is_file($absolutePath)) {
|
||||
|
@ -444,7 +444,7 @@ class ClassManifest
|
||||
'name_regex' => '/^[^_].*\\.php$/',
|
||||
'ignore_files' => array('index.php', 'main.php', 'cli-script.php'),
|
||||
'ignore_tests' => !$includeTests,
|
||||
'file_callback' => function ($basename, $pathname) use ($includeTests) {
|
||||
'file_callback' => function ($basename, $pathname, $depth) use ($includeTests, $finder) {
|
||||
$this->handleFile($basename, $pathname, $includeTests);
|
||||
},
|
||||
));
|
||||
|
@ -16,63 +16,230 @@ use SilverStripe\Assets\FileFinder;
|
||||
class ManifestFileFinder extends FileFinder
|
||||
{
|
||||
|
||||
const CONFIG_FILE = '_config.php';
|
||||
const CONFIG_DIR = '_config';
|
||||
const CONFIG_FILE = '_config.php';
|
||||
const CONFIG_DIR = '_config';
|
||||
const EXCLUDE_FILE = '_manifest_exclude';
|
||||
const LANG_DIR = 'lang';
|
||||
const TESTS_DIR = 'tests';
|
||||
const LANG_DIR = 'lang';
|
||||
const TESTS_DIR = 'tests';
|
||||
const VENDOR_DIR = 'vendor';
|
||||
const RESOURCES_DIR = 'resources';
|
||||
|
||||
protected static $default_options = array(
|
||||
'include_themes' => false,
|
||||
'ignore_tests' => true,
|
||||
'min_depth' => 1,
|
||||
'ignore_dirs' => array('node_modules')
|
||||
'ignore_tests' => true,
|
||||
'min_depth' => 1,
|
||||
'ignore_dirs' => ['node_modules']
|
||||
);
|
||||
|
||||
public function acceptDir($basename, $pathname, $depth)
|
||||
{
|
||||
// Skip over the assets directory in the site root.
|
||||
if ($depth == 1 && $basename == ASSETS_DIR) {
|
||||
// Skip if ignored
|
||||
if ($this->isInsideIgnored($basename, $pathname, $depth)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip over any lang directories in the top level of the module.
|
||||
if ($depth == 2 && $basename == self::LANG_DIR) {
|
||||
return false;
|
||||
// Keep searching inside vendor
|
||||
$inVendor = $this->isInsideVendor($basename, $pathname, $depth);
|
||||
if ($inVendor) {
|
||||
// Keep searching if we could have a subdir module
|
||||
if ($depth < 3) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Stop searching if we are in a non-module library
|
||||
$libraryPath = $this->upLevels($pathname, $depth - 3);
|
||||
$libraryBase = basename($libraryPath);
|
||||
if (!$this->isDirectoryModule($libraryBase, $libraryPath, 3)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Skip over the vendor directories
|
||||
if (($depth == 1 || $depth == 2) && $basename == 'vendor') {
|
||||
return false;
|
||||
// Include themes
|
||||
if ($this->getOption('include_themes') && $this->isInsideThemes($basename, $pathname, $depth)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If we're not in testing mode, then skip over any tests directories.
|
||||
if ($this->getOption('ignore_tests') && $basename == self::TESTS_DIR) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ignore any directories which contain a _manifest_exclude file.
|
||||
if (file_exists($pathname . '/' . self::EXCLUDE_FILE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only include top level module directories which have a configuration
|
||||
// _config.php file. However, if we're in themes mode then include
|
||||
// the themes dir without a config file.
|
||||
$lackingConfig = (
|
||||
$depth == 1
|
||||
&& !($this->getOption('include_themes') && $basename == THEMES_DIR)
|
||||
&& !file_exists($pathname . '/' . self::CONFIG_FILE)
|
||||
&& !file_exists($pathname . '/' . self::CONFIG_DIR)
|
||||
&& $basename !== self::CONFIG_DIR // include a root config dir
|
||||
&& !file_exists("$pathname/../" . self::CONFIG_DIR) // include all paths if a root config dir exists
|
||||
&& !file_exists("$pathname/../" . self::CONFIG_FILE)// include all paths if a root config file exists
|
||||
);
|
||||
|
||||
if ($lackingConfig) {
|
||||
// Skip if not in module
|
||||
if (!$this->isInsideModule($basename, $pathname, $depth)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return parent::acceptDir($basename, $pathname, $depth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given dir is, or is inside the vendor folder
|
||||
*
|
||||
* @param string $basename
|
||||
* @param string $pathname
|
||||
* @param int $depth
|
||||
* @return bool
|
||||
*/
|
||||
public function isInsideVendor($basename, $pathname, $depth)
|
||||
{
|
||||
$base = basename($this->upLevels($pathname, $depth - 1));
|
||||
return $base === self::VENDOR_DIR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given dir is, or is inside the themes folder
|
||||
*
|
||||
* @param string $basename
|
||||
* @param string $pathname
|
||||
* @param int $depth
|
||||
* @return bool
|
||||
*/
|
||||
public function isInsideThemes($basename, $pathname, $depth)
|
||||
{
|
||||
$base = basename($this->upLevels($pathname, $depth - 1));
|
||||
return $base === THEMES_DIR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this folder or any parent is ignored
|
||||
*
|
||||
* @param string $basename
|
||||
* @param string $pathname
|
||||
* @param int $depth
|
||||
* @return bool
|
||||
*/
|
||||
public function isInsideIgnored($basename, $pathname, $depth)
|
||||
{
|
||||
return $this->anyParents($basename, $pathname, $depth, function ($basename, $pathname, $depth) {
|
||||
return $this->isDirectoryIgnored($basename, $pathname, $depth);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this folder is inside any module
|
||||
*
|
||||
* @param string $basename
|
||||
* @param string $pathname
|
||||
* @param int $depth
|
||||
* @return bool
|
||||
*/
|
||||
public function isInsideModule($basename, $pathname, $depth)
|
||||
{
|
||||
return $this->anyParents($basename, $pathname, $depth, function ($basename, $pathname, $depth) {
|
||||
return $this->isDirectoryModule($basename, $pathname, $depth);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if any parents match the given callback
|
||||
*
|
||||
* @param string $basename
|
||||
* @param string $pathname
|
||||
* @param int $depth
|
||||
* @param callable $callback
|
||||
* @return bool
|
||||
*/
|
||||
protected function anyParents($basename, $pathname, $depth, $callback)
|
||||
{
|
||||
// Check all ignored dir up the path
|
||||
while ($depth >= 0) {
|
||||
$ignored = $callback($basename, $pathname, $depth);
|
||||
if ($ignored) {
|
||||
return true;
|
||||
}
|
||||
$pathname = dirname($pathname);
|
||||
$basename = basename($pathname);
|
||||
$depth--;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given dir is a module root (not a subdir)
|
||||
*
|
||||
* @param string $basename
|
||||
* @param string $pathname
|
||||
* @param string $depth
|
||||
* @return bool
|
||||
*/
|
||||
public function isDirectoryModule($basename, $pathname, $depth)
|
||||
{
|
||||
// Depth can either be 0, 1, or 3 (if and only if inside vendor)
|
||||
$inVendor = $this->isInsideVendor($basename, $pathname, $depth);
|
||||
if ($depth > 0 && $depth !== ($inVendor ? 3 : 1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// True if config file exists
|
||||
if (file_exists($pathname . '/' . self::CONFIG_FILE)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// True if config dir exists
|
||||
if (file_exists($pathname . '/' . self::CONFIG_DIR)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a parent path the given levels above
|
||||
*
|
||||
* @param string $pathname
|
||||
* @param int $depth Number of parents to rise
|
||||
* @return string
|
||||
*/
|
||||
protected function upLevels($pathname, $depth)
|
||||
{
|
||||
if ($depth < 0) {
|
||||
return null;
|
||||
}
|
||||
while ($depth--) {
|
||||
$pathname = dirname($pathname);
|
||||
}
|
||||
return $pathname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all ignored directories
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getIgnoredDirs()
|
||||
{
|
||||
$ignored = [self::LANG_DIR, 'node_modules'];
|
||||
if ($this->getOption('ignore_tests')) {
|
||||
$ignored[] = self::TESTS_DIR;
|
||||
}
|
||||
return $ignored;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given directory is ignored
|
||||
* @param string $basename
|
||||
* @param string $pathname
|
||||
* @param string $depth
|
||||
* @return bool
|
||||
*/
|
||||
public function isDirectoryIgnored($basename, $pathname, $depth)
|
||||
{
|
||||
// Don't ignore root
|
||||
if ($depth === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if manifest-ignored is present
|
||||
if (file_exists($pathname . '/' . self::EXCLUDE_FILE)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if directory name is ignored
|
||||
$ignored = $this->getIgnoredDirs();
|
||||
if (in_array($basename, $ignored)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ignore these dirs in the root only
|
||||
if ($depth === 1 && in_array($basename, [ASSETS_DIR, self::RESOURCES_DIR])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -4,19 +4,21 @@ namespace SilverStripe\Core\Manifest;
|
||||
|
||||
use Exception;
|
||||
use Serializable;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Dev\Deprecation;
|
||||
|
||||
class Module implements Serializable
|
||||
{
|
||||
const TRIM_CHARS = '/\\';
|
||||
|
||||
/**
|
||||
* Directory
|
||||
* Full directory path to this module with no trailing slash
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $path = null;
|
||||
|
||||
/**
|
||||
* Base folder of application
|
||||
* Base folder of application with no trailing slash
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
@ -29,10 +31,23 @@ class Module implements Serializable
|
||||
*/
|
||||
protected $composerData = null;
|
||||
|
||||
/**
|
||||
* Loaded resources for this module
|
||||
*
|
||||
* @var ModuleResource[]
|
||||
*/
|
||||
protected $resources = [];
|
||||
|
||||
/**
|
||||
* Construct a module
|
||||
*
|
||||
* @param string $path Absolute filesystem path to this module
|
||||
* @param string $base base url for the application this module is installed in
|
||||
*/
|
||||
public function __construct($path, $base)
|
||||
{
|
||||
$this->path = rtrim($path, '/\\');
|
||||
$this->basePath = rtrim($base, '/\\');
|
||||
$this->path = rtrim($path, self::TRIM_CHARS);
|
||||
$this->basePath = rtrim($base, self::TRIM_CHARS);
|
||||
$this->loadComposer();
|
||||
}
|
||||
|
||||
@ -62,6 +77,19 @@ class Module implements Serializable
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of folders that need to be made available
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getExposedFolders()
|
||||
{
|
||||
if (isset($this->composerData['extra']['expose'])) {
|
||||
return $this->composerData['extra']['expose'];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets "short" name of this module. This is the base directory this module
|
||||
* is installed in.
|
||||
@ -94,7 +122,7 @@ class Module implements Serializable
|
||||
/**
|
||||
* Get base path for this module
|
||||
*
|
||||
* @return string
|
||||
* @return string Path with no trailing slash E.g. /var/www/module
|
||||
*/
|
||||
public function getPath()
|
||||
{
|
||||
@ -105,11 +133,11 @@ class Module implements Serializable
|
||||
* Get path relative to base dir.
|
||||
* If module path is base this will be empty string
|
||||
*
|
||||
* @return string
|
||||
* @return string Path with trimmed slashes. E.g. vendor/silverstripe/module.
|
||||
*/
|
||||
public function getRelativePath()
|
||||
{
|
||||
return ltrim(substr($this->path, strlen($this->basePath)), '/\\');
|
||||
return trim(substr($this->path, strlen($this->basePath)), self::TRIM_CHARS);
|
||||
}
|
||||
|
||||
public function serialize()
|
||||
@ -120,6 +148,7 @@ class Module implements Serializable
|
||||
public function unserialize($serialized)
|
||||
{
|
||||
list($this->path, $this->basePath, $this->composerData) = json_decode($serialized, true);
|
||||
$this->resources = [];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -152,63 +181,69 @@ class Module implements Serializable
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets path to physical file resource relative to base directory.
|
||||
* Directories included
|
||||
* Get resource for this module
|
||||
*
|
||||
* This method makes no distinction between public / local resources,
|
||||
* which may change in the near future.
|
||||
*
|
||||
* @internal Experimental API and may change
|
||||
* @param string $path File or directory path relative to module directory
|
||||
* @return string Path relative to base directory
|
||||
* @param string $path
|
||||
* @return ModuleResource
|
||||
*/
|
||||
public function getResource($path)
|
||||
{
|
||||
$path = trim($path, '/\\');
|
||||
if (isset($this->resources[$path])) {
|
||||
return $this->resources[$path];
|
||||
}
|
||||
return $this->resources[$path] = new ModuleResource($this, $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 4.0...5.0 Use getResource($path)->getRelativePath() instead
|
||||
* @param string $path
|
||||
* @return string
|
||||
*/
|
||||
public function getRelativeResourcePath($path)
|
||||
{
|
||||
$base = trim($this->getRelativePath(), '/\\');
|
||||
$path = trim($path, '/\\');
|
||||
return trim("{$base}/{$path}", '/\\');
|
||||
Deprecation::notice('5.0', 'Use getResource($path)->getRelativePath() instead');
|
||||
return $this
|
||||
->getResource($path)
|
||||
->getRelativePath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets path to physical file resource relative to base directory.
|
||||
* Directories included
|
||||
*
|
||||
* This method makes no distinction between public / local resources,
|
||||
* which may change in the near future.
|
||||
*
|
||||
* @internal Experimental API and may change
|
||||
* @param string $path File or directory path relative to module directory
|
||||
* @return string Path relative to base directory
|
||||
* @deprecated 4.0...5.0 Use ->getResource($path)->getPath() instead
|
||||
* @param string $path
|
||||
* @return string
|
||||
*/
|
||||
public function getResourcePath($path)
|
||||
{
|
||||
return $this->basePath . '/' . $this->getRelativeResourcePath($path);
|
||||
Deprecation::notice('5.0', 'Use getResource($path)->getPath() instead');
|
||||
return $this
|
||||
->getResource($path)
|
||||
->getPath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the URL for a given resource.
|
||||
* Relies on the ModuleURLGenerator Injector service to do the heavy lifting
|
||||
*
|
||||
* @internal Experimental API and may change
|
||||
* @param string $path File or directory path relative to module directory
|
||||
* @return string URL, either domain-relative (starting with /) or absolute
|
||||
* @deprecated 4.0...5.0 Use ->getResource($path)->getURL() instead
|
||||
* @param string $path
|
||||
* @return string
|
||||
*/
|
||||
public function getResourceURL($path)
|
||||
{
|
||||
return Injector::inst()
|
||||
->get(ResourceURLGenerator::class)
|
||||
->urlForResource($this->getRelativeResourcePath($path));
|
||||
Deprecation::notice('5.0', 'Use getResource($path)->getURL() instead');
|
||||
return $this
|
||||
->getResource($path)
|
||||
->getURL();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this module has a given resource
|
||||
*
|
||||
* @internal Experimental API and may change
|
||||
* @deprecated 4.0...5.0 Use ->getResource($path)->exists() instead
|
||||
* @param string $path
|
||||
* @return bool
|
||||
* @return string
|
||||
*/
|
||||
public function hasResource($path)
|
||||
{
|
||||
return file_exists($this->getResourcePath($path));
|
||||
Deprecation::notice('5.0', 'Use getResource($path)->exists() instead');
|
||||
return $this
|
||||
->getResource($path)
|
||||
->exists();
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ namespace SilverStripe\Core\Manifest;
|
||||
use LogicException;
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
use SilverStripe\Core\Cache\CacheFactory;
|
||||
use SilverStripe\Core\Config\Config;
|
||||
use SilverStripe\Core\Config\Configurable;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
|
||||
@ -171,52 +170,25 @@ class ModuleManifest
|
||||
$finder = new ManifestFileFinder();
|
||||
$finder->setOptions(array(
|
||||
'min_depth' => 0,
|
||||
'name_regex' => '/(^|[\/\\\\])_config.php$/',
|
||||
'ignore_tests' => !$includeTests,
|
||||
'file_callback' => array($this, 'addSourceConfigFile'),
|
||||
// Cannot be max_depth: 1 due to "/framework/admin/_config.php"
|
||||
'max_depth' => 2
|
||||
'dir_callback' => function ($basename, $pathname, $depth) use ($finder) {
|
||||
if ($finder->isDirectoryModule($basename, $pathname, $depth)) {
|
||||
$this->addModule($pathname);
|
||||
}
|
||||
}
|
||||
));
|
||||
$finder->find($this->base);
|
||||
|
||||
$finder = new ManifestFileFinder();
|
||||
$finder->setOptions(array(
|
||||
'name_regex' => '/\.ya?ml$/',
|
||||
'ignore_tests' => !$includeTests,
|
||||
'file_callback' => array($this, 'addYAMLConfigFile'),
|
||||
'max_depth' => 2
|
||||
));
|
||||
$finder->find($this->base);
|
||||
// Include root itself if module
|
||||
if ($finder->isDirectoryModule(basename($this->base), $this->base, 0)) {
|
||||
$this->addModule($this->base);
|
||||
}
|
||||
|
||||
if ($this->cache) {
|
||||
$this->cache->set($this->cacheKey, $this->modules);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Record finding of _config.php file
|
||||
*
|
||||
* @param string $basename
|
||||
* @param string $pathname
|
||||
*/
|
||||
public function addSourceConfigFile($basename, $pathname)
|
||||
{
|
||||
$this->addModule(dirname($pathname));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle lookup of _config/*.yml file
|
||||
*
|
||||
* @param string $basename
|
||||
* @param string $pathname
|
||||
*/
|
||||
public function addYAMLConfigFile($basename, $pathname)
|
||||
{
|
||||
if (preg_match('{/([^/]+)/_config/}', $pathname, $match)) {
|
||||
$this->addModule(dirname(dirname($pathname)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get module by name
|
||||
*
|
||||
|
121
src/Core/Manifest/ModuleResource.php
Normal file
121
src/Core/Manifest/ModuleResource.php
Normal file
@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Core\Manifest;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
|
||||
/**
|
||||
* This object represents a single resource file attached to a module, and can be used
|
||||
* as a reference to this to be later turned into either a URL or file path.
|
||||
*/
|
||||
class ModuleResource
|
||||
{
|
||||
/**
|
||||
* @var Module
|
||||
*/
|
||||
protected $module = null;
|
||||
|
||||
/**
|
||||
* Path to this resource relative to the module (no leading slash)
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $relativePath = null;
|
||||
|
||||
/**
|
||||
* ModuleResource constructor.
|
||||
*
|
||||
* @param Module $module
|
||||
* @param string $relativePath
|
||||
*/
|
||||
public function __construct(Module $module, $relativePath)
|
||||
{
|
||||
$this->module = $module;
|
||||
$this->relativePath = ltrim($relativePath, Module::TRIM_CHARS);
|
||||
if (empty($this->relativePath)) {
|
||||
throw new InvalidArgumentException("Resource cannot have empty path");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the full filesystem path to this resource.
|
||||
*
|
||||
* Note: In the case that this resource is mapped to the `resources` folder, this will
|
||||
* return the original rather than the copy / symlink.
|
||||
*
|
||||
* @return string Path with no trailing slash E.g. /var/www/module
|
||||
*/
|
||||
public function getPath()
|
||||
{
|
||||
return $this->module->getPath() . '/' . $this->relativePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path of this resource relative to the base path.
|
||||
*
|
||||
* Note: In the case that this resource is mapped to the `resources` folder, this will
|
||||
* return the original rather than the copy / symlink.
|
||||
*
|
||||
* @return string Relative path (no leading /)
|
||||
*/
|
||||
public function getRelativePath()
|
||||
{
|
||||
$path = $this->module->getRelativePath() . '/' . $this->relativePath;
|
||||
return ltrim($path, Module::TRIM_CHARS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Public URL to this resource.
|
||||
* Note: May be either absolute url, or root-relative url
|
||||
*
|
||||
* In the case that this resource is mapped to the `resources` folder this
|
||||
* will be the mapped url rather than the original path.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getURL()
|
||||
{
|
||||
/** @var ResourceURLGenerator $generator */
|
||||
$generator = Injector::inst()->get(ResourceURLGenerator::class);
|
||||
return $generator->urlForResource($this->getRelativePath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Synonym for getURL() for APIs that expect a Link method
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function Link()
|
||||
{
|
||||
return $this->getURL();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this resource exists
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function exists()
|
||||
{
|
||||
return file_exists($this->getPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get relative path
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->getRelativePath();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Module
|
||||
*/
|
||||
public function getModule()
|
||||
{
|
||||
return $this->module;
|
||||
}
|
||||
}
|
@ -15,7 +15,7 @@ interface ResourceURLGenerator
|
||||
* As well as returning the URL, this method may also perform any changes needed to ensure that this
|
||||
* URL will resolve, for example, by copying files to another location
|
||||
*
|
||||
* @param string $resource File or directory path relative to BASE_PATH
|
||||
* @param string|ModuleResource $resource File or directory path relative to BASE_PATH, or ModuleResource instance
|
||||
* @return string URL, either domain-relative (starting with /) or absolute
|
||||
* @throws InvalidArgumentException If the resource doesn't exist or can't be sent to the browser
|
||||
*/
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
namespace SilverStripe\View;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
use SilverStripe\Core\Manifest\ModuleLoader;
|
||||
|
||||
/**
|
||||
@ -95,56 +97,59 @@ class ThemeResourceLoader
|
||||
public function getPath($identifier)
|
||||
{
|
||||
$slashPos = strpos($identifier, '/');
|
||||
$parts = explode(':', $identifier, 2);
|
||||
|
||||
// If identifier starts with "/", it's a path from root
|
||||
if ($slashPos === 0) {
|
||||
return substr($identifier, 1);
|
||||
} // Otherwise if there is a "/", identifier is a vendor'ed module
|
||||
elseif ($slashPos !== false) {
|
||||
// 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.
|
||||
$parts = explode(':', $identifier, 2);
|
||||
|
||||
if (count($parts) > 1) {
|
||||
$theme = $parts[1];
|
||||
// "module/vendor:/sub/path"
|
||||
if ($theme[0] === '/') {
|
||||
$subpath = $theme;
|
||||
|
||||
// "module/vendor:subtheme"
|
||||
} else {
|
||||
$subpath = '/themes/' . $theme;
|
||||
}
|
||||
|
||||
// "module/vendor"
|
||||
} else {
|
||||
$subpath = '';
|
||||
throw new InvalidArgumentException("Invalid theme identifier {$identifier}");
|
||||
}
|
||||
return substr($identifier, 1);
|
||||
}
|
||||
|
||||
$package = $parts[0];
|
||||
|
||||
// Find matching module for this package
|
||||
$module = ModuleLoader::inst()->getManifest()->getModule($package);
|
||||
if ($module) {
|
||||
$modulePath = $module->getRelativePath();
|
||||
} else {
|
||||
// fall back to dirname
|
||||
list(, $modulePath) = explode('/', $parts[0], 2);
|
||||
|
||||
// If the module is in the themes/<module>/ prefer that
|
||||
if (is_dir(THEMES_PATH . '/' .$modulePath)) {
|
||||
$modulePath = THEMES_DIR . '/' . $$modulePath;
|
||||
}
|
||||
}
|
||||
|
||||
return ltrim($modulePath . $subpath, '/');
|
||||
} // Otherwise it's a (deprecated) old-style "theme" identifier
|
||||
else {
|
||||
// If there is no slash / colon it's a legacy theme
|
||||
if ($slashPos === false && count($parts) === 1) {
|
||||
return 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 $modulePath . $subpath;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -120,7 +120,7 @@ class ConfigContext implements Context
|
||||
$project = ModuleManifest::config()->get('project') ?: 'mysite';
|
||||
$mysite = ModuleLoader::getModule($project);
|
||||
assertNotNull($mysite, 'Project exists');
|
||||
$destPath = $mysite->getResourcePath("_config/{$filename}");
|
||||
$destPath = $mysite->getResource("_config/{$filename}")->getPath();
|
||||
assertFileNotExists($destPath, "Config file {$filename} hasn't aleady been loaded");
|
||||
|
||||
// Load
|
||||
|
67
tests/php/Control/SimpleResourceURLGeneratorTest.php
Normal file
67
tests/php/Control/SimpleResourceURLGeneratorTest.php
Normal file
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Control\Tests;
|
||||
|
||||
use SilverStripe\Control\Director;
|
||||
use SilverStripe\Control\SimpleResourceURLGenerator;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Core\Manifest\Module;
|
||||
use SilverStripe\Core\Manifest\ResourceURLGenerator;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
|
||||
class SimpleResourceURLGeneratorTest extends SapphireTest
|
||||
{
|
||||
protected function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
Director::config()->set(
|
||||
'alternate_base_folder',
|
||||
__DIR__ .'/SimpleResourceURLGeneratorTest/_fakewebroot'
|
||||
);
|
||||
Director::config()->set(
|
||||
'alternate_base_url',
|
||||
'http://www.mysite.com/'
|
||||
);
|
||||
}
|
||||
|
||||
public function testAddMTime()
|
||||
{
|
||||
/** @var SimpleResourceURLGenerator $generator */
|
||||
$generator = Injector::inst()->get(ResourceURLGenerator::class);
|
||||
$mtime = filemtime(__DIR__ .'/SimpleResourceURLGeneratorTest/_fakewebroot/basemodule/client/file.js');
|
||||
$this->assertEquals(
|
||||
'/basemodule/client/file.js?m='.$mtime,
|
||||
$generator->urlForResource('basemodule/client/file.js')
|
||||
);
|
||||
}
|
||||
|
||||
public function testVendorResource()
|
||||
{
|
||||
/** @var SimpleResourceURLGenerator $generator */
|
||||
$generator = Injector::inst()->get(ResourceURLGenerator::class);
|
||||
$mtime = filemtime(
|
||||
__DIR__ .'/SimpleResourceURLGeneratorTest/_fakewebroot/vendor/silverstripe/mymodule/client/style.css'
|
||||
);
|
||||
$this->assertEquals(
|
||||
'/resources/silverstripe/mymodule/client/style.css?m='.$mtime,
|
||||
$generator->urlForResource('vendor/silverstripe/mymodule/client/style.css')
|
||||
);
|
||||
}
|
||||
|
||||
public function testModuleResource()
|
||||
{
|
||||
/** @var SimpleResourceURLGenerator $generator */
|
||||
$generator = Injector::inst()->get(ResourceURLGenerator::class);
|
||||
$module = new Module(
|
||||
__DIR__ .'/SimpleResourceURLGeneratorTest/_fakewebroot/vendor/silverstripe/mymodule/',
|
||||
__DIR__ .'/SimpleResourceURLGeneratorTest/_fakewebroot/'
|
||||
);
|
||||
$mtime = filemtime(
|
||||
__DIR__ .'/SimpleResourceURLGeneratorTest/_fakewebroot/vendor/silverstripe/mymodule/client/style.css'
|
||||
);
|
||||
$this->assertEquals(
|
||||
'/resources/silverstripe/mymodule/client/style.css?m='.$mtime,
|
||||
$generator->urlForResource($module->getResource('client/style.css'))
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
/* basemodule/file.js */
|
@ -0,0 +1,2 @@
|
||||
/* basemodule/style.css */
|
||||
body {}
|
@ -0,0 +1 @@
|
||||
/* mymodule/file.js */
|
@ -0,0 +1,2 @@
|
||||
/* mymodule/style.css */
|
||||
body {}
|
@ -32,39 +32,51 @@ class ClassManifestTest extends SapphireTest
|
||||
parent::setUp();
|
||||
|
||||
$this->base = dirname(__FILE__) . '/fixtures/classmanifest';
|
||||
$this->manifest = new ClassManifest($this->base);
|
||||
$this->manifest = new ClassManifest($this->base);
|
||||
$this->manifest->init(false);
|
||||
$this->manifestTests = new ClassManifest($this->base);
|
||||
$this->manifestTests->init(true);
|
||||
}
|
||||
|
||||
public function testGetItemPath()
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function providerTestGetItemPath()
|
||||
{
|
||||
$expect = array(
|
||||
'CLASSA' => 'module/classes/ClassA.php',
|
||||
'ClassA' => 'module/classes/ClassA.php',
|
||||
'classa' => 'module/classes/ClassA.php',
|
||||
'INTERFACEA' => 'module/interfaces/InterfaceA.php',
|
||||
'InterfaceA' => 'module/interfaces/InterfaceA.php',
|
||||
'interfacea' => 'module/interfaces/InterfaceA.php',
|
||||
'TestTraitA' => 'module/traits/TestTraitA.php',
|
||||
'TestNamespace\Testing\TestTraitB' => 'module/traits/TestTraitB.php'
|
||||
);
|
||||
return [
|
||||
['CLASSA', 'module/classes/ClassA.php'],
|
||||
['ClassA', 'module/classes/ClassA.php'],
|
||||
['classa', 'module/classes/ClassA.php'],
|
||||
['INTERFACEA', 'module/interfaces/InterfaceA.php'],
|
||||
['InterfaceA', 'module/interfaces/InterfaceA.php'],
|
||||
['interfacea', 'module/interfaces/InterfaceA.php'],
|
||||
['TestTraitA', 'module/traits/TestTraitA.php'],
|
||||
['TestNamespace\\Testing\\TestTraitB', 'module/traits/TestTraitB.php'],
|
||||
['VendorClassA', 'vendor/silverstripe/modulec/code/VendorClassA.php'],
|
||||
['VendorTraitA', 'vendor/silverstripe/modulec/code/VendorTraitA.php'],
|
||||
];
|
||||
}
|
||||
|
||||
foreach ($expect as $name => $path) {
|
||||
$this->assertEquals("{$this->base}/$path", $this->manifest->getItemPath($name));
|
||||
}
|
||||
/**
|
||||
* @dataProvider providerTestGetItemPath
|
||||
* @param string $name
|
||||
* @param string $path
|
||||
*/
|
||||
public function testGetItemPath($name, $path)
|
||||
{
|
||||
$this->assertEquals("{$this->base}/$path", $this->manifest->getItemPath($name));
|
||||
}
|
||||
|
||||
public function testGetClasses()
|
||||
{
|
||||
$expect = array(
|
||||
'classa' => "{$this->base}/module/classes/ClassA.php",
|
||||
'classb' => "{$this->base}/module/classes/ClassB.php",
|
||||
'classc' => "{$this->base}/module/classes/ClassC.php",
|
||||
'classd' => "{$this->base}/module/classes/ClassD.php",
|
||||
'classe' => "{$this->base}/module/classes/ClassE.php",
|
||||
);
|
||||
$expect = [
|
||||
'classa' => "{$this->base}/module/classes/ClassA.php",
|
||||
'classb' => "{$this->base}/module/classes/ClassB.php",
|
||||
'classc' => "{$this->base}/module/classes/ClassC.php",
|
||||
'classd' => "{$this->base}/module/classes/ClassD.php",
|
||||
'classe' => "{$this->base}/module/classes/ClassE.php",
|
||||
'vendorclassa' => "{$this->base}/vendor/silverstripe/modulec/code/VendorClassA.php",
|
||||
];
|
||||
$this->assertEquals($expect, $this->manifest->getClasses());
|
||||
}
|
||||
|
||||
@ -77,6 +89,7 @@ class ClassManifestTest extends SapphireTest
|
||||
'classc' => 'ClassC',
|
||||
'classd' => 'ClassD',
|
||||
'classe' => 'ClassE',
|
||||
'vendorclassa' => 'VendorClassA',
|
||||
],
|
||||
$this->manifest->getClassNames()
|
||||
);
|
||||
@ -85,10 +98,11 @@ class ClassManifestTest extends SapphireTest
|
||||
public function testGetTraitNames()
|
||||
{
|
||||
$this->assertEquals(
|
||||
array(
|
||||
[
|
||||
'testtraita' => 'TestTraitA',
|
||||
'testnamespace\testing\testtraitb' => 'TestNamespace\Testing\TestTraitB',
|
||||
),
|
||||
'testnamespace\\testing\\testtraitb' => 'TestNamespace\\Testing\\TestTraitB',
|
||||
'vendortraita' => 'VendorTraitA',
|
||||
],
|
||||
$this->manifest->getTraitNames()
|
||||
);
|
||||
}
|
||||
|
@ -10,16 +10,23 @@ use SilverStripe\Dev\SapphireTest;
|
||||
*/
|
||||
class ManifestFileFinderTest extends SapphireTest
|
||||
{
|
||||
|
||||
protected $base;
|
||||
protected $defaultBase;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->defaultBase = dirname(__FILE__) . '/fixtures/manifestfilefinder';
|
||||
$this->defaultBase = __DIR__ . '/fixtures/manifestfilefinder';
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function assertFinderFinds($finder, $base, $expect, $message = null)
|
||||
/**
|
||||
* Test that the finder can find the given files
|
||||
*
|
||||
* @param ManifestFileFinder $finder
|
||||
* @param string $base
|
||||
* @param array $expect
|
||||
* @param string $message
|
||||
*/
|
||||
public function assertFinderFinds(ManifestFileFinder $finder, $base, $expect, $message = null)
|
||||
{
|
||||
if (!$base) {
|
||||
$base = $this->defaultBase;
|
||||
@ -45,9 +52,10 @@ class ManifestFileFinderTest extends SapphireTest
|
||||
$this->assertFinderFinds(
|
||||
$finder,
|
||||
null,
|
||||
array(
|
||||
'module/module.txt'
|
||||
)
|
||||
[
|
||||
'module/module.txt',
|
||||
'vendor/myvendor/thismodule/module.txt',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@ -60,11 +68,14 @@ class ManifestFileFinderTest extends SapphireTest
|
||||
$this->assertFinderFinds(
|
||||
$finder,
|
||||
null,
|
||||
array(
|
||||
'module/module.txt',
|
||||
'module/tests/tests.txt',
|
||||
'module/code/tests/tests2.txt'
|
||||
)
|
||||
[
|
||||
'module/module.txt',
|
||||
'module/tests/tests.txt',
|
||||
'module/code/tests/tests2.txt',
|
||||
'vendor/myvendor/thismodule/module.txt',
|
||||
'vendor/myvendor/thismodule/tests/tests.txt',
|
||||
'vendor/myvendor/thismodule/code/tests/tests2.txt',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@ -77,10 +88,11 @@ class ManifestFileFinderTest extends SapphireTest
|
||||
$this->assertFinderFinds(
|
||||
$finder,
|
||||
null,
|
||||
array(
|
||||
'module/module.txt',
|
||||
'themes/themes.txt'
|
||||
)
|
||||
[
|
||||
'module/module.txt',
|
||||
'themes/themes.txt',
|
||||
'vendor/myvendor/thismodule/module.txt',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@ -90,10 +102,8 @@ class ManifestFileFinderTest extends SapphireTest
|
||||
|
||||
$this->assertFinderFinds(
|
||||
$finder,
|
||||
dirname(__FILE__) . '/fixtures/manifestfilefinder_rootconfigfile',
|
||||
array(
|
||||
'code/code.txt',
|
||||
)
|
||||
__DIR__ . '/fixtures/manifestfilefinder_rootconfigfile',
|
||||
[ 'code/code.txt' ]
|
||||
);
|
||||
}
|
||||
|
||||
@ -103,11 +113,11 @@ class ManifestFileFinderTest extends SapphireTest
|
||||
|
||||
$this->assertFinderFinds(
|
||||
$finder,
|
||||
dirname(__FILE__) . '/fixtures/manifestfilefinder_rootconfigfolder',
|
||||
array(
|
||||
__DIR__ . '/fixtures/manifestfilefinder_rootconfigfolder',
|
||||
[
|
||||
'_config/config.yml',
|
||||
'code/code.txt',
|
||||
)
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -31,9 +31,10 @@ class ModuleManifestTest extends SapphireTest
|
||||
$modules = $this->manifest->getModules();
|
||||
$this->assertEquals(
|
||||
[
|
||||
'silverstripe/root-module',
|
||||
'module',
|
||||
'silverstripe/awesome-module',
|
||||
'silverstripe/modulec',
|
||||
'silverstripe/root-module',
|
||||
],
|
||||
array_keys($modules)
|
||||
);
|
||||
@ -71,32 +72,37 @@ class ModuleManifestTest extends SapphireTest
|
||||
$this->assertEquals('moduleb', $module->getRelativePath());
|
||||
}
|
||||
|
||||
/*
|
||||
* Note: Tests experimental API
|
||||
* @internal
|
||||
*/
|
||||
public function testGetResourcePath()
|
||||
{
|
||||
$module = $this->manifest->getModule('moduleb');
|
||||
$this->assertTrue($module->hasResource('composer.json'));
|
||||
$this->assertFalse($module->hasResource('package.json'));
|
||||
// Root module
|
||||
$moduleb = $this->manifest->getModule('moduleb');
|
||||
$this->assertTrue($moduleb->getResource('composer.json')->exists());
|
||||
$this->assertFalse($moduleb->getResource('package.json')->exists());
|
||||
$this->assertEquals(
|
||||
'moduleb/composer.json',
|
||||
$module->getRelativeResourcePath('composer.json')
|
||||
$moduleb->getResource('composer.json')->getRelativePath()
|
||||
);
|
||||
}
|
||||
|
||||
public function testGetResourcePathsInVendor()
|
||||
{
|
||||
// Vendor module
|
||||
$modulec = $this->manifest->getModule('silverstripe/modulec');
|
||||
$this->assertTrue($modulec->getResource('composer.json')->exists());
|
||||
$this->assertFalse($modulec->getResource('package.json')->exists());
|
||||
$this->assertEquals(
|
||||
'vendor/silverstripe/modulec/composer.json',
|
||||
$modulec->getResource('composer.json')->getRelativePath()
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* Note: Tests experimental API
|
||||
* @internal
|
||||
*/
|
||||
public function testGetResourcePathOnRoot()
|
||||
{
|
||||
$module = $this->manifest->getModule('silverstripe/root-module');
|
||||
$this->assertTrue($module->hasResource('composer.json'));
|
||||
$this->assertTrue($module->getResource('composer.json')->exists());
|
||||
$this->assertEquals(
|
||||
'composer.json',
|
||||
$module->getRelativeResourcePath('composer.json')
|
||||
$module->getResource('composer.json')->getRelativePath()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace SilverStripe\Core\Tests\Manifest;
|
||||
|
||||
use SilverStripe\Control\Director;
|
||||
use SilverStripe\Core\Manifest\ModuleLoader;
|
||||
use SilverStripe\View\ThemeResourceLoader;
|
||||
use SilverStripe\View\ThemeManifest;
|
||||
@ -37,6 +38,7 @@ class ThemeResourceLoaderTest extends SapphireTest
|
||||
|
||||
// Fake project root
|
||||
$this->base = dirname(__FILE__) . '/fixtures/templatemanifest';
|
||||
Director::config()->set('alternate_base_folder', $this->base);
|
||||
ModuleManifest::config()->set('module_priority', ['$project', '$other_modules']);
|
||||
ModuleManifest::config()->set('project', 'myproject');
|
||||
|
||||
@ -87,8 +89,8 @@ class ThemeResourceLoaderTest extends SapphireTest
|
||||
$this->loader->findTemplate(
|
||||
'NestedThemePage',
|
||||
[
|
||||
'silverstripe/module:subtheme',
|
||||
'$default'
|
||||
'silverstripe/module:subtheme',
|
||||
'$default'
|
||||
]
|
||||
)
|
||||
);
|
||||
@ -99,7 +101,7 @@ class ThemeResourceLoaderTest extends SapphireTest
|
||||
$this->loader->findTemplate(
|
||||
'NestedThemePage',
|
||||
[
|
||||
'silverstripe/module:subtheme',
|
||||
'silverstripe/module:subtheme',
|
||||
]
|
||||
)
|
||||
);
|
||||
@ -166,8 +168,8 @@ class ThemeResourceLoaderTest extends SapphireTest
|
||||
"$this->base/themes/theme/templates/Page.ss",
|
||||
$this->loader->findTemplate(
|
||||
[
|
||||
"$this->base/themes/theme/templates/Page.ss",
|
||||
"Page"
|
||||
"$this->base/themes/theme/templates/Page.ss",
|
||||
"Page"
|
||||
],
|
||||
['theme']
|
||||
)
|
||||
@ -178,8 +180,8 @@ class ThemeResourceLoaderTest extends SapphireTest
|
||||
"$this->base/themes/theme/templates/Page.ss",
|
||||
$this->loader->findTemplate(
|
||||
[
|
||||
"$this->base/themes/theme/templates/NotAPage.ss",
|
||||
"$this->base/themes/theme/templates/Page.ss",
|
||||
"$this->base/themes/theme/templates/NotAPage.ss",
|
||||
"$this->base/themes/theme/templates/Page.ss",
|
||||
],
|
||||
['theme']
|
||||
)
|
||||
@ -310,4 +312,69 @@ class ThemeResourceLoaderTest extends SapphireTest
|
||||
unlink($template);
|
||||
}
|
||||
}
|
||||
|
||||
public function providerTestGetPath()
|
||||
{
|
||||
return [
|
||||
// Legacy theme
|
||||
[
|
||||
'theme',
|
||||
'themes/theme',
|
||||
],
|
||||
// Module themes
|
||||
[
|
||||
'silverstripe/vendormodule:vendortheme',
|
||||
'vendor/silverstripe/vendormodule/themes/vendortheme',
|
||||
],
|
||||
[
|
||||
'module:subtheme',
|
||||
'module/themes/subtheme',
|
||||
],
|
||||
// Module absolute paths
|
||||
[
|
||||
'silverstripe/vendormodule:/themes/vendortheme',
|
||||
'vendor/silverstripe/vendormodule/themes/vendortheme',
|
||||
],
|
||||
[
|
||||
'module:/themes/subtheme',
|
||||
'module/themes/subtheme',
|
||||
],
|
||||
// Module root directory
|
||||
[
|
||||
'silverstripe/vendormodule:/',
|
||||
'vendor/silverstripe/vendormodule',
|
||||
],
|
||||
[
|
||||
'silverstripe/vendormodule:',
|
||||
'vendor/silverstripe/vendormodule',
|
||||
],
|
||||
[
|
||||
'silverstripe/vendormodule',
|
||||
'vendor/silverstripe/vendormodule',
|
||||
],
|
||||
[
|
||||
'module:',
|
||||
'module',
|
||||
],
|
||||
// Absolute paths
|
||||
[
|
||||
'/vendor/silverstripe/vendormodule/themes/vendortheme',
|
||||
'vendor/silverstripe/vendormodule/themes/vendortheme',
|
||||
],
|
||||
[
|
||||
'/module/themes/subtheme',
|
||||
'module/themes/subtheme'
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerTestGetPath
|
||||
* @param string $name Theme identifier
|
||||
* @param string $path Path to theme
|
||||
*/
|
||||
public function testGetPath($name, $path)
|
||||
{
|
||||
$this->assertEquals($path, $this->loader->getPath($name));
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
Name: blankconfig
|
||||
---
|
||||
{}
|
@ -0,0 +1,6 @@
|
||||
<?php
|
||||
|
||||
class VendorClassA
|
||||
{
|
||||
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
<?php
|
||||
|
||||
trait VendorTraitA
|
||||
{
|
||||
|
||||
}
|
5
tests/php/Core/Manifest/fixtures/classmanifest/vendor/silverstripe/modulec/composer.json
vendored
Normal file
5
tests/php/Core/Manifest/fixtures/classmanifest/vendor/silverstripe/modulec/composer.json
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "silverstripe/modulec",
|
||||
"description": "dummy test module",
|
||||
"require": {}
|
||||
}
|
1
tests/php/Core/Manifest/fixtures/manifestfilefinder/vendor/myvendor/thismodule/_config.php
vendored
Normal file
1
tests/php/Core/Manifest/fixtures/manifestfilefinder/vendor/myvendor/thismodule/_config.php
vendored
Normal file
@ -0,0 +1 @@
|
||||
<?php
|
0
tests/php/Core/Manifest/fixtures/manifestfilefinder/vendor/myvendor/thismodule/lang/lang.txt
vendored
Normal file
0
tests/php/Core/Manifest/fixtures/manifestfilefinder/vendor/myvendor/thismodule/lang/lang.txt
vendored
Normal file
0
tests/php/Core/Manifest/fixtures/manifestfilefinder/vendor/myvendor/thismodule/module.txt
vendored
Normal file
0
tests/php/Core/Manifest/fixtures/manifestfilefinder/vendor/myvendor/thismodule/module.txt
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "silverstripe/module",
|
||||
"type": "silverstripe-module"
|
||||
}
|
@ -0,0 +1 @@
|
||||
<?php
|
@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "silverstripe/vendormodule",
|
||||
"type": "silverstripe-vendormodule"
|
||||
}
|
@ -712,11 +712,11 @@ PHP;
|
||||
$collector = new Collector();
|
||||
$modules = ModuleLoader::inst()->getManifest()->getModules();
|
||||
$this->assertEquals(
|
||||
array(
|
||||
[
|
||||
'i18nnonstandardmodule',
|
||||
'i18nothermodule',
|
||||
'i18ntestmodule',
|
||||
'i18nothermodule'
|
||||
),
|
||||
],
|
||||
array_keys($modules)
|
||||
);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user