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/
|
coverage/
|
||||||
/**/*.js.map
|
/**/*.js.map
|
||||||
/**/*.css.map
|
/**/*.css.map
|
||||||
vendor/
|
/vendor/
|
||||||
composer.lock
|
composer.lock
|
||||||
silverstripe-cache/
|
silverstripe-cache/
|
||||||
|
@ -1,11 +1,6 @@
|
|||||||
---
|
---
|
||||||
Name: coreconfig
|
Name: coreconfig
|
||||||
---
|
---
|
||||||
SilverStripe\Core\Injector\Injector:
|
|
||||||
SilverStripe\Core\Manifest\ResourceURLGenerator:
|
|
||||||
class: SilverStripe\Control\SimpleResourceURLGenerator
|
|
||||||
properties:
|
|
||||||
NonceStyle: mtime
|
|
||||||
SilverStripe\Control\HTTP:
|
SilverStripe\Control\HTTP:
|
||||||
cache_control:
|
cache_control:
|
||||||
max-age: 0
|
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).
|
`?flush=1` to your website URL (e.g http://yoursite.com/?flush=1).
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
### Configuring themes
|
||||||
|
|
||||||
After installing the files through either method, update the current theme in SilverStripe. This can be done by
|
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
|
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)
|
the Site Configuration panel (http://yoursite.com/admin/settings)
|
||||||
@ -42,6 +44,31 @@ SilverStripe\View\SSViewer:
|
|||||||
- '$default'
|
- '$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
|
### Manually
|
||||||
|
|
||||||
Unpack the contents of the zip file you download into the `themes` directory in your SilverStripe installation. The
|
Unpack the contents of the zip file you download into the `themes` directory in your SilverStripe installation. The
|
||||||
|
@ -17,9 +17,8 @@ this:
|
|||||||
|
|
||||||
**mycustommodule/composer.json**
|
**mycustommodule/composer.json**
|
||||||
|
|
||||||
```js
|
```json
|
||||||
|
{
|
||||||
{
|
|
||||||
"name": "your-vendor-name/module-name",
|
"name": "your-vendor-name/module-name",
|
||||||
"description": "One-liner describing your module",
|
"description": "One-liner describing your module",
|
||||||
"type": "silverstripe-module",
|
"type": "silverstripe-module",
|
||||||
@ -33,8 +32,8 @@ this:
|
|||||||
"issues": "http://github.com/your-vendor-name/module-name/issues"
|
"issues": "http://github.com/your-vendor-name/module-name/issues"
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"silverstripe/cms": "~3.1",
|
"silverstripe/cms": "^4",
|
||||||
"silverstripe/framework": "~3.1"
|
"silverstripe/framework": "^4"
|
||||||
},
|
},
|
||||||
"extra": {
|
"extra": {
|
||||||
"installer-name": "module-name",
|
"installer-name": "module-name",
|
||||||
@ -43,13 +42,62 @@ this:
|
|||||||
"http://myhost.com/screenshot2.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
|
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
|
[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.
|
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
|
## Releasing versions
|
||||||
|
|
||||||
Over time you may have to release new versions of your module to continue to work with newer versions of SilverStripe.
|
Over time you may have to release new versions of your module to continue to work with newer versions of SilverStripe.
|
||||||
|
@ -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.
|
[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.
|
* 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.
|
* 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
|
## <a name="upgrading"></a>Upgrading
|
||||||
|
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
namespace SilverStripe\Control;
|
namespace SilverStripe\Control;
|
||||||
|
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
|
use SilverStripe\Core\Config\Config;
|
||||||
|
use SilverStripe\Core\Manifest\ModuleResource;
|
||||||
use SilverStripe\Core\Manifest\ResourceURLGenerator;
|
use SilverStripe\Core\Manifest\ResourceURLGenerator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -11,6 +13,18 @@ use SilverStripe\Core\Manifest\ResourceURLGenerator;
|
|||||||
*/
|
*/
|
||||||
class SimpleResourceURLGenerator implements 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
|
* @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
|
* 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
|
* @return string Doman-relative URL
|
||||||
* @throws InvalidArgumentException If the resource doesn't exist
|
* @throws InvalidArgumentException If the resource doesn't exist
|
||||||
*/
|
*/
|
||||||
public function urlForResource($relativePath)
|
public function urlForResource($relativePath)
|
||||||
{
|
{
|
||||||
|
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);
|
$absolutePath = preg_replace('/\?.*/', '', Director::baseFolder() . '/' . $relativePath);
|
||||||
|
$exists = file_exists($absolutePath);
|
||||||
if (!file_exists($absolutePath)) {
|
}
|
||||||
|
if (!$exists) {
|
||||||
throw new InvalidArgumentException("File {$relativePath} does not exist");
|
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 = '';
|
$nonce = '';
|
||||||
// Don't add nonce to directories
|
// Don't add nonce to directories
|
||||||
if ($this->nonceStyle && is_file($absolutePath)) {
|
if ($this->nonceStyle && is_file($absolutePath)) {
|
||||||
|
@ -444,7 +444,7 @@ class ClassManifest
|
|||||||
'name_regex' => '/^[^_].*\\.php$/',
|
'name_regex' => '/^[^_].*\\.php$/',
|
||||||
'ignore_files' => array('index.php', 'main.php', 'cli-script.php'),
|
'ignore_files' => array('index.php', 'main.php', 'cli-script.php'),
|
||||||
'ignore_tests' => !$includeTests,
|
'ignore_tests' => !$includeTests,
|
||||||
'file_callback' => function ($basename, $pathname) use ($includeTests) {
|
'file_callback' => function ($basename, $pathname, $depth) use ($includeTests, $finder) {
|
||||||
$this->handleFile($basename, $pathname, $includeTests);
|
$this->handleFile($basename, $pathname, $includeTests);
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
|
@ -21,58 +21,225 @@ class ManifestFileFinder extends FileFinder
|
|||||||
const EXCLUDE_FILE = '_manifest_exclude';
|
const EXCLUDE_FILE = '_manifest_exclude';
|
||||||
const LANG_DIR = 'lang';
|
const LANG_DIR = 'lang';
|
||||||
const TESTS_DIR = 'tests';
|
const TESTS_DIR = 'tests';
|
||||||
|
const VENDOR_DIR = 'vendor';
|
||||||
|
const RESOURCES_DIR = 'resources';
|
||||||
|
|
||||||
protected static $default_options = array(
|
protected static $default_options = array(
|
||||||
'include_themes' => false,
|
'include_themes' => false,
|
||||||
'ignore_tests' => true,
|
'ignore_tests' => true,
|
||||||
'min_depth' => 1,
|
'min_depth' => 1,
|
||||||
'ignore_dirs' => array('node_modules')
|
'ignore_dirs' => ['node_modules']
|
||||||
);
|
);
|
||||||
|
|
||||||
public function acceptDir($basename, $pathname, $depth)
|
public function acceptDir($basename, $pathname, $depth)
|
||||||
{
|
{
|
||||||
// Skip over the assets directory in the site root.
|
// Skip if ignored
|
||||||
if ($depth == 1 && $basename == ASSETS_DIR) {
|
if ($this->isInsideIgnored($basename, $pathname, $depth)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip over any lang directories in the top level of the module.
|
// Keep searching inside vendor
|
||||||
if ($depth == 2 && $basename == self::LANG_DIR) {
|
$inVendor = $this->isInsideVendor($basename, $pathname, $depth);
|
||||||
return false;
|
if ($inVendor) {
|
||||||
|
// Keep searching if we could have a subdir module
|
||||||
|
if ($depth < 3) {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip over the vendor directories
|
// Stop searching if we are in a non-module library
|
||||||
if (($depth == 1 || $depth == 2) && $basename == 'vendor') {
|
$libraryPath = $this->upLevels($pathname, $depth - 3);
|
||||||
|
$libraryBase = basename($libraryPath);
|
||||||
|
if (!$this->isDirectoryModule($libraryBase, $libraryPath, 3)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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.
|
// Include themes
|
||||||
if (file_exists($pathname . '/' . self::EXCLUDE_FILE)) {
|
if ($this->getOption('include_themes') && $this->isInsideThemes($basename, $pathname, $depth)) {
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only include top level module directories which have a configuration
|
// Skip if not in module
|
||||||
// _config.php file. However, if we're in themes mode then include
|
if (!$this->isInsideModule($basename, $pathname, $depth)) {
|
||||||
// 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) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return parent::acceptDir($basename, $pathname, $depth);
|
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 Exception;
|
||||||
use Serializable;
|
use Serializable;
|
||||||
use SilverStripe\Core\Injector\Injector;
|
use SilverStripe\Dev\Deprecation;
|
||||||
|
|
||||||
class Module implements Serializable
|
class Module implements Serializable
|
||||||
{
|
{
|
||||||
|
const TRIM_CHARS = '/\\';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Directory
|
* Full directory path to this module with no trailing slash
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $path = null;
|
protected $path = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base folder of application
|
* Base folder of application with no trailing slash
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
@ -29,10 +31,23 @@ class Module implements Serializable
|
|||||||
*/
|
*/
|
||||||
protected $composerData = null;
|
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)
|
public function __construct($path, $base)
|
||||||
{
|
{
|
||||||
$this->path = rtrim($path, '/\\');
|
$this->path = rtrim($path, self::TRIM_CHARS);
|
||||||
$this->basePath = rtrim($base, '/\\');
|
$this->basePath = rtrim($base, self::TRIM_CHARS);
|
||||||
$this->loadComposer();
|
$this->loadComposer();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,6 +77,19 @@ class Module implements Serializable
|
|||||||
return null;
|
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
|
* Gets "short" name of this module. This is the base directory this module
|
||||||
* is installed in.
|
* is installed in.
|
||||||
@ -94,7 +122,7 @@ class Module implements Serializable
|
|||||||
/**
|
/**
|
||||||
* Get base path for this module
|
* Get base path for this module
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string Path with no trailing slash E.g. /var/www/module
|
||||||
*/
|
*/
|
||||||
public function getPath()
|
public function getPath()
|
||||||
{
|
{
|
||||||
@ -105,11 +133,11 @@ class Module implements Serializable
|
|||||||
* Get path relative to base dir.
|
* Get path relative to base dir.
|
||||||
* If module path is base this will be empty string
|
* 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()
|
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()
|
public function serialize()
|
||||||
@ -120,6 +148,7 @@ class Module implements Serializable
|
|||||||
public function unserialize($serialized)
|
public function unserialize($serialized)
|
||||||
{
|
{
|
||||||
list($this->path, $this->basePath, $this->composerData) = json_decode($serialized, true);
|
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.
|
* Get resource for this module
|
||||||
* Directories included
|
|
||||||
*
|
*
|
||||||
* This method makes no distinction between public / local resources,
|
* @param string $path
|
||||||
* which may change in the near future.
|
* @return ModuleResource
|
||||||
*
|
*/
|
||||||
* @internal Experimental API and may change
|
public function getResource($path)
|
||||||
* @param string $path File or directory path relative to module directory
|
{
|
||||||
* @return string Path relative to base directory
|
$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)
|
public function getRelativeResourcePath($path)
|
||||||
{
|
{
|
||||||
$base = trim($this->getRelativePath(), '/\\');
|
Deprecation::notice('5.0', 'Use getResource($path)->getRelativePath() instead');
|
||||||
$path = trim($path, '/\\');
|
return $this
|
||||||
return trim("{$base}/{$path}", '/\\');
|
->getResource($path)
|
||||||
|
->getRelativePath();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets path to physical file resource relative to base directory.
|
* @deprecated 4.0...5.0 Use ->getResource($path)->getPath() instead
|
||||||
* Directories included
|
* @param string $path
|
||||||
*
|
* @return string
|
||||||
* 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
|
|
||||||
*/
|
*/
|
||||||
public function getResourcePath($path)
|
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.
|
* @deprecated 4.0...5.0 Use ->getResource($path)->getURL() instead
|
||||||
* Relies on the ModuleURLGenerator Injector service to do the heavy lifting
|
* @param string $path
|
||||||
*
|
* @return string
|
||||||
* @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
|
|
||||||
*/
|
*/
|
||||||
public function getResourceURL($path)
|
public function getResourceURL($path)
|
||||||
{
|
{
|
||||||
return Injector::inst()
|
Deprecation::notice('5.0', 'Use getResource($path)->getURL() instead');
|
||||||
->get(ResourceURLGenerator::class)
|
return $this
|
||||||
->urlForResource($this->getRelativeResourcePath($path));
|
->getResource($path)
|
||||||
|
->getURL();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if this module has a given resource
|
* @deprecated 4.0...5.0 Use ->getResource($path)->exists() instead
|
||||||
*
|
|
||||||
* @internal Experimental API and may change
|
|
||||||
* @param string $path
|
* @param string $path
|
||||||
* @return bool
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function hasResource($path)
|
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 LogicException;
|
||||||
use Psr\SimpleCache\CacheInterface;
|
use Psr\SimpleCache\CacheInterface;
|
||||||
use SilverStripe\Core\Cache\CacheFactory;
|
use SilverStripe\Core\Cache\CacheFactory;
|
||||||
use SilverStripe\Core\Config\Config;
|
|
||||||
use SilverStripe\Core\Config\Configurable;
|
use SilverStripe\Core\Config\Configurable;
|
||||||
use SilverStripe\Core\Injector\Injector;
|
use SilverStripe\Core\Injector\Injector;
|
||||||
|
|
||||||
@ -171,52 +170,25 @@ class ModuleManifest
|
|||||||
$finder = new ManifestFileFinder();
|
$finder = new ManifestFileFinder();
|
||||||
$finder->setOptions(array(
|
$finder->setOptions(array(
|
||||||
'min_depth' => 0,
|
'min_depth' => 0,
|
||||||
'name_regex' => '/(^|[\/\\\\])_config.php$/',
|
|
||||||
'ignore_tests' => !$includeTests,
|
'ignore_tests' => !$includeTests,
|
||||||
'file_callback' => array($this, 'addSourceConfigFile'),
|
'dir_callback' => function ($basename, $pathname, $depth) use ($finder) {
|
||||||
// Cannot be max_depth: 1 due to "/framework/admin/_config.php"
|
if ($finder->isDirectoryModule($basename, $pathname, $depth)) {
|
||||||
'max_depth' => 2
|
$this->addModule($pathname);
|
||||||
|
}
|
||||||
|
}
|
||||||
));
|
));
|
||||||
$finder->find($this->base);
|
$finder->find($this->base);
|
||||||
|
|
||||||
$finder = new ManifestFileFinder();
|
// Include root itself if module
|
||||||
$finder->setOptions(array(
|
if ($finder->isDirectoryModule(basename($this->base), $this->base, 0)) {
|
||||||
'name_regex' => '/\.ya?ml$/',
|
$this->addModule($this->base);
|
||||||
'ignore_tests' => !$includeTests,
|
}
|
||||||
'file_callback' => array($this, 'addYAMLConfigFile'),
|
|
||||||
'max_depth' => 2
|
|
||||||
));
|
|
||||||
$finder->find($this->base);
|
|
||||||
|
|
||||||
if ($this->cache) {
|
if ($this->cache) {
|
||||||
$this->cache->set($this->cacheKey, $this->modules);
|
$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
|
* 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
|
* 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
|
* 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
|
* @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
|
* @throws InvalidArgumentException If the resource doesn't exist or can't be sent to the browser
|
||||||
*/
|
*/
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
namespace SilverStripe\View;
|
namespace SilverStripe\View;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use RuntimeException;
|
||||||
use SilverStripe\Core\Manifest\ModuleLoader;
|
use SilverStripe\Core\Manifest\ModuleLoader;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -95,56 +97,59 @@ class ThemeResourceLoader
|
|||||||
public function getPath($identifier)
|
public function getPath($identifier)
|
||||||
{
|
{
|
||||||
$slashPos = strpos($identifier, '/');
|
$slashPos = strpos($identifier, '/');
|
||||||
|
$parts = explode(':', $identifier, 2);
|
||||||
|
|
||||||
// If identifier starts with "/", it's a path from root
|
// If identifier starts with "/", it's a path from root
|
||||||
if ($slashPos === 0) {
|
if ($slashPos === 0) {
|
||||||
|
if (count($parts) > 1) {
|
||||||
|
throw new InvalidArgumentException("Invalid theme identifier {$identifier}");
|
||||||
|
}
|
||||||
return substr($identifier, 1);
|
return substr($identifier, 1);
|
||||||
} // Otherwise if there is a "/", identifier is a vendor'ed module
|
}
|
||||||
elseif ($slashPos !== false) {
|
|
||||||
|
// 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.
|
// Extract from <vendor>/<module>:<theme> format.
|
||||||
// <vendor> is optional, and if <theme> is omitted it defaults to the module root dir.
|
// <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/
|
// If <theme> is included, this is the name of the directory under moduleroot/themes/
|
||||||
// which contains the theme.
|
// which contains the theme.
|
||||||
// <module> is always the name of the install directory, not necessarily the composer name.
|
// <module> is always the name of the install directory, not necessarily the composer name.
|
||||||
$parts = explode(':', $identifier, 2);
|
|
||||||
|
|
||||||
if (count($parts) > 1) {
|
// Find module from first part
|
||||||
$theme = $parts[1];
|
$moduleName = $parts[0];
|
||||||
// "module/vendor:/sub/path"
|
$module = ModuleLoader::inst()->getManifest()->getModule($moduleName);
|
||||||
if ($theme[0] === '/') {
|
|
||||||
$subpath = $theme;
|
|
||||||
|
|
||||||
// "module/vendor:subtheme"
|
|
||||||
} else {
|
|
||||||
$subpath = '/themes/' . $theme;
|
|
||||||
}
|
|
||||||
|
|
||||||
// "module/vendor"
|
|
||||||
} else {
|
|
||||||
$subpath = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
$package = $parts[0];
|
|
||||||
|
|
||||||
// Find matching module for this package
|
|
||||||
$module = ModuleLoader::inst()->getManifest()->getModule($package);
|
|
||||||
if ($module) {
|
if ($module) {
|
||||||
$modulePath = $module->getRelativePath();
|
$modulePath = $module->getRelativePath();
|
||||||
} else {
|
} else {
|
||||||
// fall back to dirname
|
// If no module could be found, assume based on basename
|
||||||
|
// with a warning
|
||||||
|
if (strstr('/', $moduleName)) {
|
||||||
list(, $modulePath) = explode('/', $parts[0], 2);
|
list(, $modulePath) = explode('/', $parts[0], 2);
|
||||||
|
} else {
|
||||||
// If the module is in the themes/<module>/ prefer that
|
$modulePath = $moduleName;
|
||||||
if (is_dir(THEMES_PATH . '/' .$modulePath)) {
|
|
||||||
$modulePath = THEMES_DIR . '/' . $$modulePath;
|
|
||||||
}
|
}
|
||||||
|
trigger_error("No module named {$moduleName} found. Assuming path {$modulePath}", E_USER_WARNING);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ltrim($modulePath . $subpath, '/');
|
// Parse relative path for this theme within this module
|
||||||
} // Otherwise it's a (deprecated) old-style "theme" identifier
|
$theme = count($parts) > 1 ? $parts[1] : '';
|
||||||
else {
|
if (empty($theme)) {
|
||||||
return THEMES_DIR.'/'.$identifier;
|
// "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';
|
$project = ModuleManifest::config()->get('project') ?: 'mysite';
|
||||||
$mysite = ModuleLoader::getModule($project);
|
$mysite = ModuleLoader::getModule($project);
|
||||||
assertNotNull($mysite, 'Project exists');
|
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");
|
assertFileNotExists($destPath, "Config file {$filename} hasn't aleady been loaded");
|
||||||
|
|
||||||
// Load
|
// 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 {}
|
@ -38,33 +38,45 @@ class ClassManifestTest extends SapphireTest
|
|||||||
$this->manifestTests->init(true);
|
$this->manifestTests->init(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetItemPath()
|
/**
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function providerTestGetItemPath()
|
||||||
{
|
{
|
||||||
$expect = array(
|
return [
|
||||||
'CLASSA' => 'module/classes/ClassA.php',
|
['CLASSA', 'module/classes/ClassA.php'],
|
||||||
'ClassA' => 'module/classes/ClassA.php',
|
['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',
|
['InterfaceA', 'module/interfaces/InterfaceA.php'],
|
||||||
'interfacea' => 'module/interfaces/InterfaceA.php',
|
['interfacea', 'module/interfaces/InterfaceA.php'],
|
||||||
'TestTraitA' => 'module/traits/TestTraitA.php',
|
['TestTraitA', 'module/traits/TestTraitA.php'],
|
||||||
'TestNamespace\Testing\TestTraitB' => 'module/traits/TestTraitB.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()
|
public function testGetClasses()
|
||||||
{
|
{
|
||||||
$expect = array(
|
$expect = [
|
||||||
'classa' => "{$this->base}/module/classes/ClassA.php",
|
'classa' => "{$this->base}/module/classes/ClassA.php",
|
||||||
'classb' => "{$this->base}/module/classes/ClassB.php",
|
'classb' => "{$this->base}/module/classes/ClassB.php",
|
||||||
'classc' => "{$this->base}/module/classes/ClassC.php",
|
'classc' => "{$this->base}/module/classes/ClassC.php",
|
||||||
'classd' => "{$this->base}/module/classes/ClassD.php",
|
'classd' => "{$this->base}/module/classes/ClassD.php",
|
||||||
'classe' => "{$this->base}/module/classes/ClassE.php",
|
'classe' => "{$this->base}/module/classes/ClassE.php",
|
||||||
);
|
'vendorclassa' => "{$this->base}/vendor/silverstripe/modulec/code/VendorClassA.php",
|
||||||
|
];
|
||||||
$this->assertEquals($expect, $this->manifest->getClasses());
|
$this->assertEquals($expect, $this->manifest->getClasses());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,6 +89,7 @@ class ClassManifestTest extends SapphireTest
|
|||||||
'classc' => 'ClassC',
|
'classc' => 'ClassC',
|
||||||
'classd' => 'ClassD',
|
'classd' => 'ClassD',
|
||||||
'classe' => 'ClassE',
|
'classe' => 'ClassE',
|
||||||
|
'vendorclassa' => 'VendorClassA',
|
||||||
],
|
],
|
||||||
$this->manifest->getClassNames()
|
$this->manifest->getClassNames()
|
||||||
);
|
);
|
||||||
@ -85,10 +98,11 @@ class ClassManifestTest extends SapphireTest
|
|||||||
public function testGetTraitNames()
|
public function testGetTraitNames()
|
||||||
{
|
{
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
array(
|
[
|
||||||
'testtraita' => 'TestTraitA',
|
'testtraita' => 'TestTraitA',
|
||||||
'testnamespace\testing\testtraitb' => 'TestNamespace\Testing\TestTraitB',
|
'testnamespace\\testing\\testtraitb' => 'TestNamespace\\Testing\\TestTraitB',
|
||||||
),
|
'vendortraita' => 'VendorTraitA',
|
||||||
|
],
|
||||||
$this->manifest->getTraitNames()
|
$this->manifest->getTraitNames()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -10,16 +10,23 @@ use SilverStripe\Dev\SapphireTest;
|
|||||||
*/
|
*/
|
||||||
class ManifestFileFinderTest extends SapphireTest
|
class ManifestFileFinderTest extends SapphireTest
|
||||||
{
|
{
|
||||||
|
protected $defaultBase;
|
||||||
protected $base;
|
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->defaultBase = dirname(__FILE__) . '/fixtures/manifestfilefinder';
|
$this->defaultBase = __DIR__ . '/fixtures/manifestfilefinder';
|
||||||
parent::__construct();
|
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) {
|
if (!$base) {
|
||||||
$base = $this->defaultBase;
|
$base = $this->defaultBase;
|
||||||
@ -45,9 +52,10 @@ class ManifestFileFinderTest extends SapphireTest
|
|||||||
$this->assertFinderFinds(
|
$this->assertFinderFinds(
|
||||||
$finder,
|
$finder,
|
||||||
null,
|
null,
|
||||||
array(
|
[
|
||||||
'module/module.txt'
|
'module/module.txt',
|
||||||
)
|
'vendor/myvendor/thismodule/module.txt',
|
||||||
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,11 +68,14 @@ class ManifestFileFinderTest extends SapphireTest
|
|||||||
$this->assertFinderFinds(
|
$this->assertFinderFinds(
|
||||||
$finder,
|
$finder,
|
||||||
null,
|
null,
|
||||||
array(
|
[
|
||||||
'module/module.txt',
|
'module/module.txt',
|
||||||
'module/tests/tests.txt',
|
'module/tests/tests.txt',
|
||||||
'module/code/tests/tests2.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(
|
$this->assertFinderFinds(
|
||||||
$finder,
|
$finder,
|
||||||
null,
|
null,
|
||||||
array(
|
[
|
||||||
'module/module.txt',
|
'module/module.txt',
|
||||||
'themes/themes.txt'
|
'themes/themes.txt',
|
||||||
)
|
'vendor/myvendor/thismodule/module.txt',
|
||||||
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,10 +102,8 @@ class ManifestFileFinderTest extends SapphireTest
|
|||||||
|
|
||||||
$this->assertFinderFinds(
|
$this->assertFinderFinds(
|
||||||
$finder,
|
$finder,
|
||||||
dirname(__FILE__) . '/fixtures/manifestfilefinder_rootconfigfile',
|
__DIR__ . '/fixtures/manifestfilefinder_rootconfigfile',
|
||||||
array(
|
[ 'code/code.txt' ]
|
||||||
'code/code.txt',
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,11 +113,11 @@ class ManifestFileFinderTest extends SapphireTest
|
|||||||
|
|
||||||
$this->assertFinderFinds(
|
$this->assertFinderFinds(
|
||||||
$finder,
|
$finder,
|
||||||
dirname(__FILE__) . '/fixtures/manifestfilefinder_rootconfigfolder',
|
__DIR__ . '/fixtures/manifestfilefinder_rootconfigfolder',
|
||||||
array(
|
[
|
||||||
'_config/config.yml',
|
'_config/config.yml',
|
||||||
'code/code.txt',
|
'code/code.txt',
|
||||||
)
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,9 +31,10 @@ class ModuleManifestTest extends SapphireTest
|
|||||||
$modules = $this->manifest->getModules();
|
$modules = $this->manifest->getModules();
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
[
|
[
|
||||||
'silverstripe/root-module',
|
|
||||||
'module',
|
'module',
|
||||||
'silverstripe/awesome-module',
|
'silverstripe/awesome-module',
|
||||||
|
'silverstripe/modulec',
|
||||||
|
'silverstripe/root-module',
|
||||||
],
|
],
|
||||||
array_keys($modules)
|
array_keys($modules)
|
||||||
);
|
);
|
||||||
@ -71,32 +72,37 @@ class ModuleManifestTest extends SapphireTest
|
|||||||
$this->assertEquals('moduleb', $module->getRelativePath());
|
$this->assertEquals('moduleb', $module->getRelativePath());
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Note: Tests experimental API
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
public function testGetResourcePath()
|
public function testGetResourcePath()
|
||||||
{
|
{
|
||||||
$module = $this->manifest->getModule('moduleb');
|
// Root module
|
||||||
$this->assertTrue($module->hasResource('composer.json'));
|
$moduleb = $this->manifest->getModule('moduleb');
|
||||||
$this->assertFalse($module->hasResource('package.json'));
|
$this->assertTrue($moduleb->getResource('composer.json')->exists());
|
||||||
|
$this->assertFalse($moduleb->getResource('package.json')->exists());
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
'moduleb/composer.json',
|
'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()
|
public function testGetResourcePathOnRoot()
|
||||||
{
|
{
|
||||||
$module = $this->manifest->getModule('silverstripe/root-module');
|
$module = $this->manifest->getModule('silverstripe/root-module');
|
||||||
$this->assertTrue($module->hasResource('composer.json'));
|
$this->assertTrue($module->getResource('composer.json')->exists());
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
'composer.json',
|
'composer.json',
|
||||||
$module->getRelativeResourcePath('composer.json')
|
$module->getResource('composer.json')->getRelativePath()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace SilverStripe\Core\Tests\Manifest;
|
namespace SilverStripe\Core\Tests\Manifest;
|
||||||
|
|
||||||
|
use SilverStripe\Control\Director;
|
||||||
use SilverStripe\Core\Manifest\ModuleLoader;
|
use SilverStripe\Core\Manifest\ModuleLoader;
|
||||||
use SilverStripe\View\ThemeResourceLoader;
|
use SilverStripe\View\ThemeResourceLoader;
|
||||||
use SilverStripe\View\ThemeManifest;
|
use SilverStripe\View\ThemeManifest;
|
||||||
@ -37,6 +38,7 @@ class ThemeResourceLoaderTest extends SapphireTest
|
|||||||
|
|
||||||
// Fake project root
|
// Fake project root
|
||||||
$this->base = dirname(__FILE__) . '/fixtures/templatemanifest';
|
$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('module_priority', ['$project', '$other_modules']);
|
||||||
ModuleManifest::config()->set('project', 'myproject');
|
ModuleManifest::config()->set('project', 'myproject');
|
||||||
|
|
||||||
@ -310,4 +312,69 @@ class ThemeResourceLoaderTest extends SapphireTest
|
|||||||
unlink($template);
|
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();
|
$collector = new Collector();
|
||||||
$modules = ModuleLoader::inst()->getManifest()->getModules();
|
$modules = ModuleLoader::inst()->getManifest()->getModules();
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
array(
|
[
|
||||||
'i18nnonstandardmodule',
|
'i18nnonstandardmodule',
|
||||||
|
'i18nothermodule',
|
||||||
'i18ntestmodule',
|
'i18ntestmodule',
|
||||||
'i18nothermodule'
|
],
|
||||||
),
|
|
||||||
array_keys($modules)
|
array_keys($modules)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user