mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
Merge pull request #6706 from open-sausages/pulls/4.0/manifest-cache
API Build new ManifestCache based on PSR-16 SimpleCache
This commit is contained in:
commit
cfa6a36697
@ -4,9 +4,8 @@ Before: '/i18n'
|
||||
---
|
||||
SilverStripe\i18n\Data\Sources:
|
||||
module_priority:
|
||||
- admin
|
||||
- framework
|
||||
- sapphire
|
||||
- silverstripe\admin
|
||||
- silverstripe\framework
|
||||
---
|
||||
Name: defaulti18n
|
||||
---
|
||||
|
@ -85,6 +85,6 @@ SilverStripe core environment variables are listed here, though you're free to d
|
||||
| `SS_TRUSTED_PROXY_HOST_HEADER` | Used to define the proxy header to be used to determine the requested host name |
|
||||
| `SS_TRUSTED_PROXY_IPS` | IP address or CIDR range to trust proxy headers from |
|
||||
| `SS_ALLOWED_HOSTS` | A comma deliminated list of hostnames the site is allowed to respond to |
|
||||
| `SS_MANIFESTCACHE` | The manifest cache to use (defaults to file based caching) |
|
||||
| `SS_MANIFESTCACHE` | The manifest cache to use (defaults to file based caching). Must be a CacheInterface or CacheFactory class name |
|
||||
| `SS_IGNORE_DOT_ENV` | If set the .env file will be ignored. This is good for live to mitigate any performance implications of loading the .env file |
|
||||
| `SS_HOST` | The hostname to use when it isn't determinable by other means (eg: for CLI commands) |
|
||||
|
@ -11,17 +11,10 @@ Others store aggregate information like nested configuration graphs.
|
||||
|
||||
## Storage
|
||||
|
||||
By default, manifests are stored on the local filesystem through PHP's `serialize()` method.
|
||||
Combined with PHP opcode caching this provides fast access.
|
||||
In order to share manifests between servers, or centralise cache management,
|
||||
other storage adapters are available. These can be configured by a `SS_MANIFESTCACHE` constant,
|
||||
placed in your `.env`.
|
||||
|
||||
* `ManifestCache_File`: The default adapter using PHP's `serialize()`
|
||||
* `ManifestCache_File_PHP`: Using `var_export()`, which is faster when a PHP opcode cache is installed
|
||||
* `ManifestCache_APC`: Use PHP's [APC object cache](http://php.net/manual/en/book.apc.php)
|
||||
|
||||
You can write your own adapters by implementing the `ManifestCache` interface.
|
||||
By default, manifests are serialised and cached via a cache generated by the [api:ManifestCacheFactory].
|
||||
This can be customised via `SS_MANIFESTCACHE` environment variable to point to either another
|
||||
[api:CacheFactory] or [CacheInterface](https://github.com/php-fig/cache/blob/master/src/CacheItemInterface.php)
|
||||
implementor.
|
||||
|
||||
## Traversing the Filesystem
|
||||
|
||||
|
@ -1067,6 +1067,7 @@ now generally safer to use the default inherited config, where in the past you w
|
||||
* Falsey config values (null, 0, false, etc) can now replace non-falsey values.
|
||||
* Introduced new ModuleLoader manifest, which allows modules to be found via composer name.
|
||||
E.g. `$cms = ModuleLoader::instance()->getManifest()->getModule('silverstripe/cms')`
|
||||
* `ClassManifest::getOwnerModule()` now returns a `Module` object instance.
|
||||
* Certain methods have been moved from `Controller` to `RequestHandler`:
|
||||
* `Link`
|
||||
* `redirect`
|
||||
@ -1081,7 +1082,7 @@ now generally safer to use the default inherited config, where in the past you w
|
||||
#### <a name="overview-general-removed"></a>General and Core Removed API
|
||||
|
||||
* `CMSMain::buildbrokenlinks()` action is removed.
|
||||
* `SS_Log::add_writer()` method is removed.
|
||||
* `SS_Log` class has been removed. Use `Injector::inst()->get(LoggerInterface::class)` instead.
|
||||
* Removed `CMSBatchAction_Delete`
|
||||
* Removed `CMSBatchAction_DeleteFromLive`
|
||||
* Removed `CMSMain.enabled_legacy_actions` config.
|
||||
@ -1101,6 +1102,8 @@ now generally safer to use the default inherited config, where in the past you w
|
||||
* Removed `SilverStripeInjectionCreator`
|
||||
* Removed `i18n::get_translatable_modules` method.
|
||||
* Removed `i18nTextCollector_Writer_Php`
|
||||
* `i18nTextCollector` no longer collects from `themes/<theme>` root dir.
|
||||
Modules which provide themes via `<moduleName>/themes/<theme>` are now preferred.
|
||||
* Removed `i18nSSLegacyAdapter`
|
||||
* Removed `FunctionalTest::stat`
|
||||
* Removed `LeftAndMainMarkingFilter`
|
||||
@ -1116,6 +1119,11 @@ now generally safer to use the default inherited config, where in the past you w
|
||||
* Removed `Config::FIRST_SET` and `Config::INHERITED`
|
||||
* Removed `RequestHandler.require_allowed_actions`. This is now fixed to on and cannot be
|
||||
disabled.
|
||||
* Config or module searching methods have been removed from `ClassManifest`. Use `ModuleLoader`
|
||||
to get this information instead:
|
||||
- `getModules`
|
||||
- `getConfigDirs`
|
||||
- `getConfigs`
|
||||
|
||||
#### <a name="overview-general-deprecated"></a>General and Core Deprecated API
|
||||
|
||||
@ -1567,6 +1575,7 @@ New `TimeField` methods replace `getConfig()` / `setConfig()`
|
||||
* `i18n::get_language_name()` moved to `SilverStripe\i18n\Data\Locales::languageName()`
|
||||
* `i18n.module_priority` config moved to `SilverStripe\i18n\Data\Sources.module_priority`
|
||||
* `i18n::get_owner_module()` moved to `SilverStripe\Core\Manifest\ClassManifest::getOwnerModule()`
|
||||
This now returns a `Module` object instance instead of a string.
|
||||
* `i18n::get_existing_translations()` moved to `SilverStripe\i18n\Data\Sources::getKnownLocales()`
|
||||
|
||||
#### <a name="overview-i18n-removed"></a>i18n API Removed API
|
||||
@ -1592,6 +1601,7 @@ and have a slightly different API (e.g. `set()` instead of `save()`).
|
||||
|
||||
Before:
|
||||
|
||||
|
||||
:::php
|
||||
$cache = Cache::factory('myCache');
|
||||
|
||||
@ -1612,6 +1622,7 @@ Before:
|
||||
|
||||
After:
|
||||
|
||||
|
||||
:::php
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
$cache = Injector::inst()->get(CacheInterface::class . '.myCache');
|
||||
@ -1629,6 +1640,21 @@ After:
|
||||
|
||||
$cache->delete('myCacheKey');
|
||||
|
||||
|
||||
With the necessary minimal config in `_config/mycache.yml`
|
||||
|
||||
|
||||
:::yml
|
||||
---
|
||||
Name: mycache
|
||||
---
|
||||
SilverStripe\Core\Injector\Injector:
|
||||
Psr\SimpleCache\CacheInterface.myCache:
|
||||
factory: SilverStripe\Core\Cache\CacheFactory
|
||||
constructor:
|
||||
namespace: 'mycache'
|
||||
|
||||
|
||||
#### Configuration Changes
|
||||
|
||||
Caches are now configured through dependency injection services instead of PHP.
|
||||
|
91
src/Core/Cache/ManifestCacheFactory.php
Normal file
91
src/Core/Cache/ManifestCacheFactory.php
Normal file
@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Core\Cache;
|
||||
|
||||
use BadMethodCallException;
|
||||
use Monolog\Handler\ErrorLogHandler;
|
||||
use Monolog\Handler\StreamHandler;
|
||||
use Monolog\Logger;
|
||||
use Psr\Log\LoggerAwareInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
use ReflectionClass;
|
||||
use SilverStripe\Control\Director;
|
||||
|
||||
/**
|
||||
* Assists with building of manifest cache prior to config being available
|
||||
*/
|
||||
class ManifestCacheFactory extends DefaultCacheFactory
|
||||
{
|
||||
public function __construct(array $args = [], LoggerInterface $logger = null)
|
||||
{
|
||||
// Build default manifest logger
|
||||
if (!$logger) {
|
||||
$logger = new Logger("manifestcache-log");
|
||||
if (Director::isDev()) {
|
||||
$logger->pushHandler(new StreamHandler('php://output'));
|
||||
} else {
|
||||
$logger->pushHandler(new ErrorLogHandler());
|
||||
}
|
||||
}
|
||||
|
||||
parent::__construct($args, $logger);
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: While the returned object is used as a singleton (by the originating Injector->get() call),
|
||||
* this cache object shouldn't be a singleton itself - it has varying constructor args for the same service name.
|
||||
*
|
||||
* @param string $service The class name of the service.
|
||||
* @param array $params The constructor parameters.
|
||||
* @return CacheInterface
|
||||
*/
|
||||
public function create($service, array $params = array())
|
||||
{
|
||||
// Override default cache generation with SS_MANIFESTCACHE
|
||||
$cacheClass = getenv('SS_MANIFESTCACHE');
|
||||
if (!$cacheClass) {
|
||||
return parent::create($service, $params);
|
||||
}
|
||||
|
||||
// Check if SS_MANIFESTCACHE is a factory
|
||||
if (is_a($cacheClass, CacheFactory::class, true)) {
|
||||
/** @var CacheFactory $factory */
|
||||
$factory = new $cacheClass;
|
||||
return $factory->create($service, $params);
|
||||
}
|
||||
|
||||
// Check if SS_MANIFESTCACHE is a cache subclass
|
||||
if (is_a($cacheClass, CacheInterface::class, true)) {
|
||||
$args = array_merge($this->args, $params);
|
||||
$namespace = isset($args['namespace']) ? $args['namespace'] : '';
|
||||
return $this->createCache($cacheClass, [$namespace]);
|
||||
}
|
||||
|
||||
// Validate type
|
||||
throw new BadMethodCallException(
|
||||
'SS_MANIFESTCACHE is not a valid CacheInterface or CacheFactory class name'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create cache directly without config / injector
|
||||
*
|
||||
* @param string $class
|
||||
* @param array $args
|
||||
* @return CacheInterface
|
||||
*/
|
||||
public function createCache($class, $args)
|
||||
{
|
||||
/** @var CacheInterface $cache */
|
||||
$reflection = new ReflectionClass($class);
|
||||
$cache = $reflection->newInstanceArgs($args);
|
||||
|
||||
// Assign cache logger
|
||||
if ($this->logger && $cache instanceof LoggerAwareInterface) {
|
||||
$cache->setLogger($this->logger);
|
||||
}
|
||||
|
||||
return $cache;
|
||||
}
|
||||
}
|
@ -6,11 +6,13 @@ use Monolog\Handler\ErrorLogHandler;
|
||||
use Monolog\Handler\StreamHandler;
|
||||
use Monolog\Logger;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
use SilverStripe\Config\Collections\CachedConfigCollection;
|
||||
use SilverStripe\Config\Collections\MemoryConfigCollection;
|
||||
use SilverStripe\Config\Transformer\PrivateStaticTransformer;
|
||||
use SilverStripe\Config\Transformer\YamlTransformer;
|
||||
use SilverStripe\Control\Director;
|
||||
use SilverStripe\Core\Cache\CacheFactory;
|
||||
use SilverStripe\Core\Config\Middleware\ExtensionMiddleware;
|
||||
use SilverStripe\Core\Config\Middleware\InheritanceMiddleware;
|
||||
use SilverStripe\Core\Manifest\ClassLoader;
|
||||
@ -45,14 +47,18 @@ class CoreConfigFactory
|
||||
* which conditionally generates a nested "core" config.
|
||||
*
|
||||
* @param bool $flush
|
||||
* @param CacheFactory $cacheFactory
|
||||
* @return CachedConfigCollection
|
||||
*/
|
||||
public function createRoot($flush)
|
||||
public function createRoot($flush, CacheFactory $cacheFactory)
|
||||
{
|
||||
$instance = new CachedConfigCollection();
|
||||
|
||||
// Set root cache
|
||||
$instance->setPool($this->createPool());
|
||||
// Create config cache
|
||||
$cache = $cacheFactory->create(CacheInterface::class.'.configcache', [
|
||||
'namespace' => 'configcache'
|
||||
]);
|
||||
$instance->setCache($cache);
|
||||
$instance->setFlush($flush);
|
||||
|
||||
// Set collection creator
|
||||
@ -172,32 +178,4 @@ class CoreConfigFactory
|
||||
return ModuleLoader::instance()->getManifest()->moduleExists($module);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo Refactor bootstrapping of manifest caching into app object
|
||||
* @return FilesystemAdapter
|
||||
*/
|
||||
protected function createPool()
|
||||
{
|
||||
$cache = new FilesystemAdapter('configcache', 0, getTempFolder());
|
||||
$cache->setLogger($this->createLogger());
|
||||
return $cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create default error logger
|
||||
*
|
||||
* @todo Refactor bootstrapping of manifest logging into app object
|
||||
* @return LoggerInterface
|
||||
*/
|
||||
protected function createLogger()
|
||||
{
|
||||
$logger = new Logger("configcache-log");
|
||||
if (Director::isDev()) {
|
||||
$logger->pushHandler(new StreamHandler('php://output'));
|
||||
} else {
|
||||
$logger->pushHandler(new ErrorLogHandler());
|
||||
}
|
||||
return $logger;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
<?php
|
||||
|
||||
use SilverStripe\Core\Cache\ManifestCacheFactory;
|
||||
use SilverStripe\Core\Config\Config;
|
||||
use SilverStripe\Core\Config\CoreConfigFactory;
|
||||
use SilverStripe\Core\Config\ConfigLoader;
|
||||
@ -70,8 +71,14 @@ Injector::set_inst($injector);
|
||||
$requestURL = isset($_REQUEST['url']) ? trim($_REQUEST['url'], '/') : false;
|
||||
$flush = (isset($_GET['flush']) || $requestURL === trim(BASE_URL . '/dev/build', '/'));
|
||||
|
||||
global $manifest;
|
||||
$manifest = new ClassManifest(BASE_PATH, false, $flush);
|
||||
// Manifest cache factory
|
||||
$manifestCacheFactory = new ManifestCacheFactory([
|
||||
'namespace' => 'manifestcache',
|
||||
'directory' => getTempFolder(),
|
||||
]);
|
||||
|
||||
// Build class manifest
|
||||
$manifest = new ClassManifest(BASE_PATH, false, $flush, $manifestCacheFactory);
|
||||
|
||||
// Register SilverStripe's class map autoload
|
||||
$loader = ClassLoader::instance();
|
||||
@ -79,11 +86,11 @@ $loader->registerAutoloader();
|
||||
$loader->pushManifest($manifest);
|
||||
|
||||
// Init module manifest
|
||||
$moduleManifest = new ModuleManifest(BASE_PATH, false, $flush);
|
||||
$moduleManifest = new ModuleManifest(BASE_PATH, false, $flush, $manifestCacheFactory);
|
||||
ModuleLoader::instance()->pushManifest($moduleManifest);
|
||||
|
||||
// Build config manifest
|
||||
$configManifest = CoreConfigFactory::inst()->createRoot($flush);
|
||||
$configManifest = CoreConfigFactory::inst()->createRoot($flush, $manifestCacheFactory);
|
||||
ConfigLoader::instance()->pushManifest($configManifest);
|
||||
|
||||
// After loading config, boot _config.php files
|
||||
@ -94,7 +101,8 @@ SilverStripe\View\ThemeResourceLoader::instance()->addSet('$default', new Silver
|
||||
BASE_PATH,
|
||||
project(),
|
||||
false,
|
||||
$flush
|
||||
$flush,
|
||||
$manifestCacheFactory
|
||||
));
|
||||
|
||||
// If in live mode, ensure deprecation, strict and notices are not reported
|
||||
|
@ -6,56 +6,112 @@ use Exception;
|
||||
use PhpParser\Error;
|
||||
use PhpParser\NodeTraverser;
|
||||
use PhpParser\NodeVisitor\NameResolver;
|
||||
use PhpParser\Parser;
|
||||
use PhpParser\ParserFactory;
|
||||
use SilverStripe\Control\Director;
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
use SilverStripe\Core\Cache\CacheFactory;
|
||||
use SilverStripe\Dev\TestOnly;
|
||||
|
||||
/**
|
||||
* A utility class which builds a manifest of all classes, interfaces and some
|
||||
* additional items present in a directory, and caches it.
|
||||
* A utility class which builds a manifest of all classes, interfaces and caches it.
|
||||
*
|
||||
* It finds the following information:
|
||||
* - Class and interface names and paths.
|
||||
* - All direct and indirect descendants of a class.
|
||||
* - All implementors of an interface.
|
||||
* - All module configuration files.
|
||||
*/
|
||||
class ClassManifest
|
||||
{
|
||||
|
||||
const CONF_FILE = '_config.php';
|
||||
const CONF_DIR = '_config';
|
||||
|
||||
/**
|
||||
* base manifest directory
|
||||
* @var string
|
||||
*/
|
||||
protected $base;
|
||||
|
||||
/**
|
||||
* Set if including test classes
|
||||
*
|
||||
* @see TestOnly
|
||||
* @var bool
|
||||
*/
|
||||
protected $tests;
|
||||
|
||||
/**
|
||||
* @var ManifestCache
|
||||
* Cache to use, if caching.
|
||||
* Set to null if uncached.
|
||||
*
|
||||
* @var CacheInterface|null
|
||||
*/
|
||||
protected $cache;
|
||||
|
||||
/**
|
||||
* Key to use for the top level cache of all items
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $cacheKey;
|
||||
|
||||
/**
|
||||
* Map of classes to paths
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $classes = array();
|
||||
protected $roots = array();
|
||||
protected $children = array();
|
||||
protected $descendants = array();
|
||||
protected $interfaces = array();
|
||||
protected $implementors = array();
|
||||
protected $configs = array();
|
||||
protected $configDirs = array();
|
||||
protected $traits = array();
|
||||
|
||||
/**
|
||||
* @var \PhpParser\Parser
|
||||
* List of root classes with no parent class
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $roots = array();
|
||||
|
||||
/**
|
||||
* List of direct children for any class
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $children = array();
|
||||
|
||||
/**
|
||||
* List of descendents for any class (direct + indirect children)
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $descendants = array();
|
||||
|
||||
/**
|
||||
* List of interfaces and paths to those files
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $interfaces = array();
|
||||
|
||||
/**
|
||||
* List of direct implementors of any interface
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $implementors = array();
|
||||
|
||||
/**
|
||||
* Map of traits to paths
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $traits = array();
|
||||
|
||||
/**
|
||||
* PHP Parser for parsing found files
|
||||
*
|
||||
* @var Parser
|
||||
*/
|
||||
private $parser;
|
||||
|
||||
/**
|
||||
* @var NodeTraverser
|
||||
*/
|
||||
private $traverser;
|
||||
|
||||
/**
|
||||
* @var ClassManifestVisitor
|
||||
*/
|
||||
@ -66,33 +122,44 @@ class ClassManifest
|
||||
* from the cache or re-scanning for classes.
|
||||
*
|
||||
* @param string $base The manifest base path.
|
||||
* @param bool $includeTests Include the contents of "tests" directories.
|
||||
* @param bool $forceRegen Force the manifest to be regenerated.
|
||||
* @param bool $cache If the manifest is regenerated, cache it.
|
||||
* @param bool $includeTests Include the contents of "tests" directories.
|
||||
* @param bool $forceRegen Force the manifest to be regenerated.
|
||||
* @param CacheFactory $cacheFactory Optional cache to use. Set to null to not cache.
|
||||
*/
|
||||
public function __construct($base, $includeTests = false, $forceRegen = false, $cache = true)
|
||||
{
|
||||
$this->base = $base;
|
||||
public function __construct(
|
||||
$base,
|
||||
$includeTests = false,
|
||||
$forceRegen = false,
|
||||
CacheFactory $cacheFactory = null
|
||||
) {
|
||||
$this->base = $base;
|
||||
$this->tests = $includeTests;
|
||||
|
||||
$cacheClass = getenv('SS_MANIFESTCACHE') ?: 'SilverStripe\\Core\\Manifest\\ManifestCache_File';
|
||||
|
||||
$this->cache = new $cacheClass('classmanifest'.($includeTests ? '_tests' : ''));
|
||||
// build cache from factory
|
||||
if ($cacheFactory) {
|
||||
$this->cache = $cacheFactory->create(
|
||||
CacheInterface::class.'.classmanifest',
|
||||
[ 'namespace' => 'classmanifest' . ($includeTests ? '_tests' : '') ]
|
||||
);
|
||||
}
|
||||
$this->cacheKey = 'manifest';
|
||||
|
||||
if (!$forceRegen && $data = $this->cache->load($this->cacheKey)) {
|
||||
$this->classes = $data['classes'];
|
||||
$this->descendants = $data['descendants'];
|
||||
$this->interfaces = $data['interfaces'];
|
||||
if (!$forceRegen && $this->cache && ($data = $this->cache->get($this->cacheKey))) {
|
||||
$this->classes = $data['classes'];
|
||||
$this->descendants = $data['descendants'];
|
||||
$this->interfaces = $data['interfaces'];
|
||||
$this->implementors = $data['implementors'];
|
||||
$this->configs = $data['configs'];
|
||||
$this->configDirs = $data['configDirs'];
|
||||
$this->traits = $data['traits'];
|
||||
$this->traits = $data['traits'];
|
||||
} else {
|
||||
$this->regenerate($cache);
|
||||
$this->regenerate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create active parser
|
||||
*
|
||||
* @return Parser
|
||||
*/
|
||||
public function getParser()
|
||||
{
|
||||
if (!$this->parser) {
|
||||
@ -246,49 +313,11 @@ class ClassManifest
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of paths to module config files.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getConfigs()
|
||||
{
|
||||
return $this->configs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of module names mapped to their paths.
|
||||
*
|
||||
* "Modules" in SilverStripe are simply directories with a _config.php
|
||||
* file.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getModules()
|
||||
{
|
||||
$modules = array();
|
||||
|
||||
if ($this->configs) {
|
||||
foreach ($this->configs as $configPath) {
|
||||
$modules[basename(dirname($configPath))] = dirname($configPath);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->configDirs) {
|
||||
foreach ($this->configDirs as $configDir) {
|
||||
$path = preg_replace('/\/_config$/', '', dirname($configDir));
|
||||
$modules[basename($path)] = $path;
|
||||
}
|
||||
}
|
||||
|
||||
return $modules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get module that owns this class
|
||||
*
|
||||
* @param string $class Class name
|
||||
* @return string
|
||||
* @return Module
|
||||
*/
|
||||
public function getOwnerModule($class)
|
||||
{
|
||||
@ -297,29 +326,32 @@ class ClassManifest
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @var Module $rootModule */
|
||||
$rootModule = null;
|
||||
|
||||
// Find based on loaded modules
|
||||
foreach ($this->getModules() as $parent => $module) {
|
||||
if (stripos($path, realpath($parent)) === 0) {
|
||||
$modules = ModuleLoader::instance()->getManifest()->getModules();
|
||||
foreach ($modules as $module) {
|
||||
// Leave root module as fallback
|
||||
if (empty($module->getRelativePath())) {
|
||||
$rootModule = $module;
|
||||
} elseif (stripos($path, realpath($module->getPath())) === 0) {
|
||||
return $module;
|
||||
}
|
||||
}
|
||||
|
||||
// Assume top level folder is the module name
|
||||
$relativePath = substr($path, strlen(realpath(Director::baseFolder())));
|
||||
$parts = explode('/', trim($relativePath, '/'));
|
||||
return array_shift($parts);
|
||||
// Fall back to top level module
|
||||
return $rootModule;
|
||||
}
|
||||
|
||||
/**
|
||||
* Completely regenerates the manifest file.
|
||||
*
|
||||
* @param bool $cache Cache the result.
|
||||
*/
|
||||
public function regenerate($cache = true)
|
||||
public function regenerate()
|
||||
{
|
||||
$resets = array(
|
||||
'classes', 'roots', 'children', 'descendants', 'interfaces',
|
||||
'implementors', 'configs', 'configDirs', 'traits'
|
||||
'implementors', 'traits'
|
||||
);
|
||||
|
||||
// Reset the manifest so stale info doesn't cause errors.
|
||||
@ -329,11 +361,10 @@ class ClassManifest
|
||||
|
||||
$finder = new ManifestFileFinder();
|
||||
$finder->setOptions(array(
|
||||
'name_regex' => '/^((_config)|([^_].*))\\.php$/',
|
||||
'name_regex' => '/^[^_].*\\.php$/',
|
||||
'ignore_files' => array('index.php', 'main.php', 'cli-script.php'),
|
||||
'ignore_tests' => !$this->tests,
|
||||
'file_callback' => array($this, 'handleFile'),
|
||||
'dir_callback' => array($this, 'handleDir')
|
||||
));
|
||||
$finder->find($this->base);
|
||||
|
||||
@ -341,34 +372,20 @@ class ClassManifest
|
||||
$this->coalesceDescendants($root);
|
||||
}
|
||||
|
||||
if ($cache) {
|
||||
if ($this->cache) {
|
||||
$data = array(
|
||||
'classes' => $this->classes,
|
||||
'descendants' => $this->descendants,
|
||||
'interfaces' => $this->interfaces,
|
||||
'implementors' => $this->implementors,
|
||||
'configs' => $this->configs,
|
||||
'configDirs' => $this->configDirs,
|
||||
'traits' => $this->traits,
|
||||
);
|
||||
$this->cache->save($data, $this->cacheKey);
|
||||
$this->cache->set($this->cacheKey, $data);
|
||||
}
|
||||
}
|
||||
|
||||
public function handleDir($basename, $pathname, $depth)
|
||||
public function handleFile($basename, $pathname)
|
||||
{
|
||||
if ($basename == self::CONF_DIR) {
|
||||
$this->configDirs[] = $pathname;
|
||||
}
|
||||
}
|
||||
|
||||
public function handleFile($basename, $pathname, $depth)
|
||||
{
|
||||
if ($basename == self::CONF_FILE) {
|
||||
$this->configs[] = $pathname;
|
||||
return;
|
||||
}
|
||||
|
||||
$classes = null;
|
||||
$interfaces = null;
|
||||
$traits = null;
|
||||
@ -379,24 +396,16 @@ class ClassManifest
|
||||
// since just using the datetime lead to problems with upgrading.
|
||||
$key = preg_replace('/[^a-zA-Z0-9_]/', '_', $basename) . '_' . md5_file($pathname);
|
||||
|
||||
$valid = false;
|
||||
if ($data = $this->cache->load($key)) {
|
||||
$valid = (
|
||||
isset($data['classes']) && is_array($data['classes'])
|
||||
&& isset($data['interfaces'])
|
||||
&& is_array($data['interfaces'])
|
||||
&& isset($data['traits'])
|
||||
&& is_array($data['traits'])
|
||||
);
|
||||
|
||||
if ($valid) {
|
||||
$classes = $data['classes'];
|
||||
$interfaces = $data['interfaces'];
|
||||
$traits = $data['traits'];
|
||||
}
|
||||
}
|
||||
|
||||
if (!$valid) {
|
||||
// Attempt to load from cache
|
||||
if ($this->cache
|
||||
&& ($data = $this->cache->get($key))
|
||||
&& $this->validateItemCache($data)
|
||||
) {
|
||||
$classes = $data['classes'];
|
||||
$interfaces = $data['interfaces'];
|
||||
$traits = $data['traits'];
|
||||
} else {
|
||||
// Build from php file parser
|
||||
$fileContents = ClassContentRemover::remove_class_content($pathname);
|
||||
try {
|
||||
$stmts = $this->getParser()->parse($fileContents);
|
||||
@ -410,14 +419,18 @@ class ClassManifest
|
||||
$interfaces = $this->getVisitor()->getInterfaces();
|
||||
$traits = $this->getVisitor()->getTraits();
|
||||
|
||||
$cache = array(
|
||||
'classes' => $classes,
|
||||
'interfaces' => $interfaces,
|
||||
'traits' => $traits,
|
||||
);
|
||||
$this->cache->save($cache, $key);
|
||||
// Save back to cache if configured
|
||||
if ($this->cache) {
|
||||
$cache = array(
|
||||
'classes' => $classes,
|
||||
'interfaces' => $interfaces,
|
||||
'traits' => $traits,
|
||||
);
|
||||
$this->cache->set($key, $cache);
|
||||
}
|
||||
}
|
||||
|
||||
// Merge this data into the global list
|
||||
foreach ($classes as $className => $classInfo) {
|
||||
$extends = isset($classInfo['extends']) ? $classInfo['extends'] : null;
|
||||
$implements = isset($classInfo['interfaces']) ? $classInfo['interfaces'] : null;
|
||||
@ -496,4 +509,30 @@ class ClassManifest
|
||||
return array();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that cached data is valid for a single item
|
||||
*
|
||||
* @param array $data
|
||||
* @return bool
|
||||
*/
|
||||
protected function validateItemCache($data)
|
||||
{
|
||||
foreach (['classes', 'interfaces', 'traits'] as $key) {
|
||||
// Must be set
|
||||
if (!isset($data[$key])) {
|
||||
return false;
|
||||
}
|
||||
// and an array
|
||||
if (!is_array($data[$key])) {
|
||||
return false;
|
||||
}
|
||||
// Detect legacy cache keys (non-associative)
|
||||
$array = $data[$key];
|
||||
if (!empty($array) && is_numeric(key($array))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Core\Manifest;
|
||||
|
||||
/**
|
||||
* A basic caching interface that manifests use to store data.
|
||||
*/
|
||||
interface ManifestCache
|
||||
{
|
||||
public function __construct($name);
|
||||
public function load($key);
|
||||
public function save($data, $key);
|
||||
public function clear();
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Core\Manifest;
|
||||
|
||||
/**
|
||||
* Stores manifest data in APC.
|
||||
* Note: benchmarks seem to indicate this is not particularly faster than _File
|
||||
*/
|
||||
class ManifestCache_APC implements ManifestCache
|
||||
{
|
||||
protected $pre;
|
||||
|
||||
function __construct($name)
|
||||
{
|
||||
$this->pre = $name;
|
||||
}
|
||||
|
||||
function load($key)
|
||||
{
|
||||
return apc_fetch($this->pre . $key);
|
||||
}
|
||||
|
||||
function save($data, $key)
|
||||
{
|
||||
apc_store($this->pre . $key, $data);
|
||||
}
|
||||
|
||||
function clear()
|
||||
{
|
||||
}
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Core\Manifest;
|
||||
|
||||
/**
|
||||
* Stores manifest data in files in TEMP_DIR dir on filesystem
|
||||
*/
|
||||
class ManifestCache_File implements ManifestCache
|
||||
{
|
||||
|
||||
protected $folder = null;
|
||||
|
||||
function __construct($name)
|
||||
{
|
||||
$this->folder = TEMP_FOLDER . DIRECTORY_SEPARATOR . $name;
|
||||
if (!is_dir($this->folder)) {
|
||||
mkdir($this->folder);
|
||||
}
|
||||
}
|
||||
|
||||
function load($key)
|
||||
{
|
||||
$file = $this->folder . DIRECTORY_SEPARATOR . 'cache_' . $key;
|
||||
return file_exists($file) ? unserialize(file_get_contents($file)) : null;
|
||||
}
|
||||
|
||||
function save($data, $key)
|
||||
{
|
||||
$file = $this->folder . DIRECTORY_SEPARATOR . 'cache_' . $key;
|
||||
file_put_contents($file, serialize($data));
|
||||
}
|
||||
|
||||
function clear()
|
||||
{
|
||||
array_map('unlink', glob($this->folder . DIRECTORY_SEPARATOR . 'cache_*'));
|
||||
}
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Core\Manifest;
|
||||
|
||||
/**
|
||||
* Same as ManifestCache_File, but stores the data as valid PHP which gets included to load
|
||||
* This is a bit faster if you have an opcode cache installed, but slower otherwise
|
||||
*/
|
||||
class ManifestCache_File_PHP extends ManifestCache_File
|
||||
{
|
||||
function load($key)
|
||||
{
|
||||
global $loaded_manifest;
|
||||
$loaded_manifest = null;
|
||||
|
||||
$file = $this->folder . DIRECTORY_SEPARATOR . 'cache_' . $key;
|
||||
if (file_exists($file)) {
|
||||
include $file;
|
||||
}
|
||||
|
||||
return $loaded_manifest;
|
||||
}
|
||||
|
||||
function save($data, $key)
|
||||
{
|
||||
$file = $this->folder . DIRECTORY_SEPARATOR. 'cache_' . $key;
|
||||
file_put_contents($file, '<?php $loaded_manifest = ' . var_export($data, true) . ';');
|
||||
}
|
||||
}
|
@ -3,6 +3,8 @@
|
||||
namespace SilverStripe\Core\Manifest;
|
||||
|
||||
use LogicException;
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
use SilverStripe\Core\Cache\CacheFactory;
|
||||
|
||||
/**
|
||||
* A utility class which builds a manifest of configuration items
|
||||
@ -31,7 +33,7 @@ class ModuleManifest
|
||||
protected $includeTests;
|
||||
|
||||
/**
|
||||
* @var ManifestCache
|
||||
* @var CacheInterface
|
||||
*/
|
||||
protected $cache;
|
||||
|
||||
@ -87,37 +89,31 @@ class ModuleManifest
|
||||
* @param string $base The project base path.
|
||||
* @param bool $includeTests
|
||||
* @param bool $forceRegen Force the manifest to be regenerated.
|
||||
* @param CacheFactory $cacheFactory Cache factory to use
|
||||
*/
|
||||
public function __construct($base, $includeTests = false, $forceRegen = false)
|
||||
public function __construct($base, $includeTests = false, $forceRegen = false, CacheFactory $cacheFactory = null)
|
||||
{
|
||||
$this->base = $base;
|
||||
$this->cacheKey = sha1($base).'_modules';
|
||||
$this->includeTests = $includeTests;
|
||||
|
||||
$this->cache = $this->getCache($includeTests);
|
||||
// build cache from factory
|
||||
if ($cacheFactory) {
|
||||
$this->cache = $cacheFactory->create(
|
||||
CacheInterface::class.'.modulemanifest',
|
||||
[ 'namespace' => 'modulemanifest' . ($includeTests ? '_tests' : '') ]
|
||||
);
|
||||
}
|
||||
|
||||
// Unless we're forcing regen, try loading from cache
|
||||
if (!$forceRegen) {
|
||||
$this->modules = $this->cache->load($this->cacheKey) ?: [];
|
||||
if (!$forceRegen && $this->cache) {
|
||||
$this->modules = $this->cache->get($this->cacheKey) ?: [];
|
||||
}
|
||||
if (empty($this->modules)) {
|
||||
$this->regenerate($includeTests);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a hook for mock unit tests despite no DI
|
||||
*
|
||||
* @param bool $includeTests
|
||||
* @return ManifestCache
|
||||
*/
|
||||
protected function getCache($includeTests = false)
|
||||
{
|
||||
// Cache
|
||||
$cacheClass = getenv('SS_MANIFESTCACHE') ?: ManifestCache_File::class;
|
||||
return new $cacheClass('classmanifest'.($includeTests ? '_tests' : ''));
|
||||
}
|
||||
|
||||
/**
|
||||
* Includes all of the php _config.php files found by this manifest.
|
||||
*/
|
||||
@ -136,9 +132,8 @@ class ModuleManifest
|
||||
* Does _not_ build the actual variant
|
||||
*
|
||||
* @param bool $includeTests
|
||||
* @param bool $cache Cache the result.
|
||||
*/
|
||||
public function regenerate($includeTests = false, $cache = true)
|
||||
public function regenerate($includeTests = false)
|
||||
{
|
||||
$this->modules = [];
|
||||
|
||||
@ -162,8 +157,8 @@ class ModuleManifest
|
||||
));
|
||||
$finder->find($this->base);
|
||||
|
||||
if ($cache) {
|
||||
$this->cache->save($this->modules, $this->cacheKey);
|
||||
if ($this->cache) {
|
||||
$this->cache->set($this->cacheKey, $this->modules);
|
||||
}
|
||||
}
|
||||
|
||||
@ -172,9 +167,8 @@ class ModuleManifest
|
||||
*
|
||||
* @param string $basename
|
||||
* @param string $pathname
|
||||
* @param int $depth
|
||||
*/
|
||||
public function addSourceConfigFile($basename, $pathname, $depth)
|
||||
public function addSourceConfigFile($basename, $pathname)
|
||||
{
|
||||
$this->addModule(dirname($pathname));
|
||||
}
|
||||
@ -184,9 +178,8 @@ class ModuleManifest
|
||||
*
|
||||
* @param string $basename
|
||||
* @param string $pathname
|
||||
* @param int $depth
|
||||
*/
|
||||
public function addYAMLConfigFile($basename, $pathname, $depth)
|
||||
public function addYAMLConfigFile($basename, $pathname)
|
||||
{
|
||||
if (preg_match('{/([^/]+)/_config/}', $pathname, $match)) {
|
||||
$this->addModule(dirname(dirname($pathname)));
|
||||
|
@ -4,6 +4,8 @@ namespace SilverStripe\Dev;
|
||||
|
||||
use SilverStripe\Control\Director;
|
||||
use SilverStripe\Core\Manifest\ClassLoader;
|
||||
use SilverStripe\Core\Manifest\Module;
|
||||
use SilverStripe\Core\Manifest\ModuleLoader;
|
||||
|
||||
/**
|
||||
* Handles raising an notice when accessing a deprecated method
|
||||
@ -96,7 +98,7 @@ class Deprecation
|
||||
* #notice)
|
||||
*
|
||||
* @param array $backtrace A backtrace as returned from debug_backtrace
|
||||
* @return string The name of the module the call came from, or null if we can't determine
|
||||
* @return Module The module being called
|
||||
*/
|
||||
protected static function get_calling_module_from_trace($backtrace)
|
||||
{
|
||||
@ -106,10 +108,10 @@ class Deprecation
|
||||
|
||||
$callingfile = realpath($backtrace[1]['file']);
|
||||
|
||||
$manifest = ClassLoader::instance()->getManifest();
|
||||
foreach ($manifest->getModules() as $name => $path) {
|
||||
if (strpos($callingfile, realpath($path)) === 0) {
|
||||
return $name;
|
||||
$modules = ModuleLoader::instance()->getManifest()->getModules();
|
||||
foreach ($modules as $module) {
|
||||
if (strpos($callingfile, realpath($module->getPath())) === 0) {
|
||||
return $module;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@ -193,8 +195,16 @@ class Deprecation
|
||||
|
||||
if (self::$module_version_overrides) {
|
||||
$module = self::get_calling_module_from_trace($backtrace = debug_backtrace(0));
|
||||
if (isset(self::$module_version_overrides[$module])) {
|
||||
$checkVersion = self::$module_version_overrides[$module];
|
||||
if ($module) {
|
||||
if (($name = $module->getComposerName())
|
||||
&& isset(self::$module_version_overrides[$name])
|
||||
) {
|
||||
$checkVersion = self::$module_version_overrides[$name];
|
||||
} elseif (($name = $module->getShortName())
|
||||
&& isset(self::$module_version_overrides[$name])
|
||||
) {
|
||||
$checkVersion = self::$module_version_overrides[$name];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,61 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Logging;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Dev\Deprecation;
|
||||
|
||||
/**
|
||||
* Wrapper class for a logging handler like {@link Zend_Log}
|
||||
* which takes a message (or a map of context variables) and
|
||||
* sends it to one or more {@link Zend_Log_Writer_Abstract}
|
||||
* subclasses for output.
|
||||
*
|
||||
* These priorities are currently supported:
|
||||
* - Log::ERR
|
||||
* - Log::WARN
|
||||
* - Log::NOTICE
|
||||
* - Log::INFO
|
||||
* - Log::DEBUG
|
||||
*/
|
||||
class Log
|
||||
{
|
||||
|
||||
const ERR = 'error';
|
||||
const WARN = 'warning';
|
||||
const NOTICE = 'notice';
|
||||
const INFO = 'info';
|
||||
const DEBUG = 'debug';
|
||||
|
||||
/**
|
||||
* Get the logger currently in use, or create a new one if it doesn't exist.
|
||||
*
|
||||
* @deprecated 4.0..5.0
|
||||
* @return LoggerInterface
|
||||
*/
|
||||
public static function get_logger()
|
||||
{
|
||||
Deprecation::notice('5.0', 'Use Injector::inst()->get(LoggerInterface::class) instead');
|
||||
return Injector::inst()->get(LoggerInterface::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch a message by priority level.
|
||||
*
|
||||
* The message parameter can be either a string (a simple error
|
||||
* message), or an array of variables. The latter is useful for passing
|
||||
* along a list of debug information for the writer to handle, such as
|
||||
* error code, error line, error context (backtrace).
|
||||
*
|
||||
* @param mixed $message Exception object or array of error context variables
|
||||
* @param string $priority Priority. Possible values: Log::ERR, Log::WARN, Log::NOTICE, Log::INFO or Log::DEBUG
|
||||
*
|
||||
* @deprecated 4.0.0:5.0.0 Use Injector::inst()->get('Logger')->log($priority, $message) instead
|
||||
*/
|
||||
public static function log($message, $priority)
|
||||
{
|
||||
Deprecation::notice('5.0', 'Use Injector::inst()->get(LoggerInterface::class)->log($priority, $message) instead');
|
||||
Injector::inst()->get(LoggerInterface::class)->log($priority, $message);
|
||||
}
|
||||
}
|
@ -2,7 +2,8 @@
|
||||
|
||||
namespace SilverStripe\View;
|
||||
|
||||
use SilverStripe\Core\Manifest\ManifestCache;
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
use SilverStripe\Core\Cache\CacheFactory;
|
||||
use SilverStripe\Core\Manifest\ManifestFileFinder;
|
||||
|
||||
/**
|
||||
@ -37,7 +38,7 @@ class ThemeManifest implements ThemeList
|
||||
/**
|
||||
* Cache
|
||||
*
|
||||
* @var ManifestCache
|
||||
* @var CacheInterface
|
||||
*/
|
||||
protected $cache;
|
||||
|
||||
@ -51,7 +52,7 @@ class ThemeManifest implements ThemeList
|
||||
/**
|
||||
* List of theme root directories
|
||||
*
|
||||
* @var string
|
||||
* @var string[]
|
||||
*/
|
||||
protected $themes = null;
|
||||
|
||||
@ -64,18 +65,26 @@ class ThemeManifest implements ThemeList
|
||||
*
|
||||
* @param bool $includeTests Include tests in the manifest.
|
||||
* @param bool $forceRegen Force the manifest to be regenerated.
|
||||
* @param CacheFactory $cacheFactory Cache factory to generate backend cache with
|
||||
*/
|
||||
public function __construct($base, $project, $includeTests = false, $forceRegen = false)
|
||||
{
|
||||
public function __construct(
|
||||
$base,
|
||||
$project,
|
||||
$includeTests = false,
|
||||
$forceRegen = false,
|
||||
CacheFactory $cacheFactory = null
|
||||
) {
|
||||
$this->base = $base;
|
||||
$this->tests = $includeTests;
|
||||
|
||||
$this->project = $project;
|
||||
|
||||
$cacheClass = getenv('SS_MANIFESTCACHE')
|
||||
?: 'SilverStripe\\Core\\Manifest\\ManifestCache_File';
|
||||
|
||||
$this->cache = new $cacheClass('thememanifest'.($includeTests ? '_tests' : ''));
|
||||
// build cache from factory
|
||||
if ($cacheFactory) {
|
||||
$this->cache = $cacheFactory->create(
|
||||
CacheInterface::class.'.thememanifest',
|
||||
[ 'namespace' => 'thememanifest' . ($includeTests ? '_tests' : '') ]
|
||||
);
|
||||
}
|
||||
$this->cacheKey = $this->getCacheKey();
|
||||
|
||||
if ($forceRegen) {
|
||||
@ -117,10 +126,8 @@ class ThemeManifest implements ThemeList
|
||||
|
||||
/**
|
||||
* Regenerates the manifest by scanning the base path.
|
||||
*
|
||||
* @param bool $cache
|
||||
*/
|
||||
public function regenerate($cache = true)
|
||||
public function regenerate()
|
||||
{
|
||||
$finder = new ManifestFileFinder();
|
||||
$finder->setOptions(array(
|
||||
@ -133,8 +140,8 @@ class ThemeManifest implements ThemeList
|
||||
$this->themes = [];
|
||||
$finder->find($this->base);
|
||||
|
||||
if ($cache) {
|
||||
$this->cache->save($this->themes, $this->cacheKey);
|
||||
if ($this->cache) {
|
||||
$this->cache->set($this->cacheKey, $this->themes);
|
||||
}
|
||||
}
|
||||
|
||||
@ -171,7 +178,7 @@ class ThemeManifest implements ThemeList
|
||||
*/
|
||||
protected function init()
|
||||
{
|
||||
if ($data = $this->cache->load($this->cacheKey)) {
|
||||
if ($this->cache && ($data = $this->cache->get($this->cacheKey))) {
|
||||
$this->themes = $data;
|
||||
} else {
|
||||
$this->regenerate();
|
||||
|
@ -4,7 +4,7 @@ namespace SilverStripe\i18n\Data;
|
||||
|
||||
use SilverStripe\Core\Config\Configurable;
|
||||
use SilverStripe\Core\Injector\Injectable;
|
||||
use SilverStripe\Core\Manifest\ClassLoader;
|
||||
use SilverStripe\Core\Manifest\ModuleLoader;
|
||||
use SilverStripe\Core\Resettable;
|
||||
use SilverStripe\i18n\i18n;
|
||||
use SilverStripe\View\SSViewer;
|
||||
@ -35,7 +35,7 @@ class Sources implements Resettable
|
||||
public function getSortedModules()
|
||||
{
|
||||
// Get list of module => path pairs, and then just the names
|
||||
$modules = ClassLoader::instance()->getManifest()->getModules();
|
||||
$modules = ModuleLoader::instance()->getManifest()->getModules();
|
||||
$moduleNames = array_keys($modules);
|
||||
|
||||
// Remove the "project" module from the list - we'll add it back specially later if needed
|
||||
@ -63,14 +63,14 @@ class Sources implements Resettable
|
||||
$order[] = $project;
|
||||
}
|
||||
|
||||
$sortedModules = array();
|
||||
$sortedModulePaths = array();
|
||||
foreach ($order as $module) {
|
||||
if (isset($modules[$module])) {
|
||||
$sortedModules[$module] = $modules[$module];
|
||||
$sortedModulePaths[$module] = $modules[$module]->getPath();
|
||||
}
|
||||
}
|
||||
$sortedModules = array_reverse($sortedModules, true);
|
||||
return $sortedModules;
|
||||
$sortedModulePaths = array_reverse($sortedModulePaths, true);
|
||||
return $sortedModulePaths;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -5,6 +5,8 @@ namespace SilverStripe\i18n\TextCollection;
|
||||
use SilverStripe\Core\ClassInfo;
|
||||
use SilverStripe\Core\Injector\Injectable;
|
||||
use SilverStripe\Core\Manifest\ClassLoader;
|
||||
use SilverStripe\Core\Manifest\Module;
|
||||
use SilverStripe\Core\Manifest\ModuleLoader;
|
||||
use SilverStripe\Dev\Debug;
|
||||
use SilverStripe\Control\Director;
|
||||
use ReflectionClass;
|
||||
@ -172,7 +174,7 @@ class i18nTextCollector
|
||||
}
|
||||
|
||||
// Write each module language file
|
||||
foreach ($entitiesByModule as $module => $entities) {
|
||||
foreach ($entitiesByModule as $moduleName => $entities) {
|
||||
// Skip empty translations
|
||||
if (empty($entities)) {
|
||||
continue;
|
||||
@ -180,42 +182,11 @@ class i18nTextCollector
|
||||
|
||||
// Clean sorting prior to writing
|
||||
ksort($entities);
|
||||
$path = $this->baseSavePath . '/' . $module;
|
||||
$this->getWriter()->write($entities, $this->defaultLocale, $path);
|
||||
$module = ModuleLoader::instance()->getManifest()->getModule($moduleName);
|
||||
$this->write($module, $entities);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of modules in this installer
|
||||
*
|
||||
* @param string $directory Path to look in
|
||||
* @return array List of modules as paths relative to base
|
||||
*/
|
||||
protected function getModules($directory)
|
||||
{
|
||||
// Include self as head module
|
||||
$modules = array();
|
||||
|
||||
// Get all standard modules
|
||||
foreach (glob($directory."/*", GLOB_ONLYDIR) as $path) {
|
||||
// Check for _config
|
||||
if (!is_file("$path/_config.php") && !is_dir("$path/_config")) {
|
||||
continue;
|
||||
}
|
||||
$modules[] = basename($path);
|
||||
}
|
||||
|
||||
// Get all themes
|
||||
foreach (glob($directory."/themes/*", GLOB_ONLYDIR) as $path) {
|
||||
// Check for templates
|
||||
if (is_dir("$path/templates")) {
|
||||
$modules[] = 'themes/'.basename($path);
|
||||
}
|
||||
}
|
||||
|
||||
return $modules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract all strings from modules and return these grouped by module name
|
||||
*
|
||||
@ -237,7 +208,13 @@ class i18nTextCollector
|
||||
|
||||
// Restrict modules we update to just the specified ones (if any passed)
|
||||
if (!empty($restrictToModules)) {
|
||||
foreach (array_diff(array_keys($entitiesByModule), $restrictToModules) as $module) {
|
||||
// Normalise module names
|
||||
$modules = array_filter(array_map(function ($name) {
|
||||
$module = ModuleLoader::instance()->getManifest()->getModule($name);
|
||||
return $module ? $module->getName() : null;
|
||||
}, $restrictToModules));
|
||||
// Remove modules
|
||||
foreach (array_diff(array_keys($entitiesByModule), $modules) as $module) {
|
||||
unset($entitiesByModule[$module]);
|
||||
}
|
||||
}
|
||||
@ -350,9 +327,12 @@ class i18nTextCollector
|
||||
protected function findModuleForClass($class)
|
||||
{
|
||||
if (ClassInfo::exists($class)) {
|
||||
return ClassLoader::instance()
|
||||
$module = ClassLoader::instance()
|
||||
->getManifest()
|
||||
->getOwnerModule($class);
|
||||
if ($module) {
|
||||
return $module->getName();
|
||||
}
|
||||
}
|
||||
|
||||
// If we can't find a class, see if it needs to be fully qualified
|
||||
@ -368,7 +348,8 @@ class i18nTextCollector
|
||||
|
||||
// Find all modules for candidate classes
|
||||
$modules = array_unique(array_map(function ($class) {
|
||||
return ClassLoader::instance()->getManifest()->getOwnerModule($class);
|
||||
$module = ClassLoader::instance()->getManifest()->getOwnerModule($class);
|
||||
return $module ? $module->getName() : null;
|
||||
}, $classes));
|
||||
|
||||
if (count($modules) === 1) {
|
||||
@ -413,24 +394,32 @@ class i18nTextCollector
|
||||
{
|
||||
// A master string tables array (one mst per module)
|
||||
$entitiesByModule = array();
|
||||
$modules = $this->getModules($this->basePath);
|
||||
$modules = ModuleLoader::instance()->getManifest()->getModules();
|
||||
foreach ($modules as $module) {
|
||||
// we store the master string tables
|
||||
$processedEntities = $this->processModule($module);
|
||||
if (isset($entitiesByModule[$module])) {
|
||||
$entitiesByModule[$module] = array_merge_recursive($entitiesByModule[$module], $processedEntities);
|
||||
$moduleName = $module->getName();
|
||||
if (isset($entitiesByModule[$moduleName])) {
|
||||
$entitiesByModule[$moduleName] = array_merge_recursive(
|
||||
$entitiesByModule[$moduleName],
|
||||
$processedEntities
|
||||
);
|
||||
} else {
|
||||
$entitiesByModule[$module] = $processedEntities;
|
||||
$entitiesByModule[$moduleName] = $processedEntities;
|
||||
}
|
||||
|
||||
// Extract all entities for "foreign" modules ('module' key in array form)
|
||||
// @see CMSMenu::provideI18nEntities for an example usage
|
||||
foreach ($entitiesByModule[$module] as $fullName => $spec) {
|
||||
$specModule = $module;
|
||||
foreach ($entitiesByModule[$moduleName] as $fullName => $spec) {
|
||||
$specModuleName = $moduleName;
|
||||
|
||||
// Rewrite spec if module is specified
|
||||
if (is_array($spec) && isset($spec['module'])) {
|
||||
$specModule = $spec['module'];
|
||||
// Normalise name (in case non-composer name is specified)
|
||||
$specModule = ModuleLoader::instance()->getManifest()->getModule($spec['module']);
|
||||
if ($specModule) {
|
||||
$specModuleName = $specModule->getName();
|
||||
}
|
||||
unset($spec['module']);
|
||||
|
||||
// If only element is defalt, simplify
|
||||
@ -440,24 +429,34 @@ class i18nTextCollector
|
||||
}
|
||||
|
||||
// Remove from source module
|
||||
if ($specModule !== $module) {
|
||||
unset($entitiesByModule[$module][$fullName]);
|
||||
if ($specModuleName !== $moduleName) {
|
||||
unset($entitiesByModule[$moduleName][$fullName]);
|
||||
}
|
||||
|
||||
// Write to target module
|
||||
if (!isset($entitiesByModule[$specModule])) {
|
||||
$entitiesByModule[$specModule] = [];
|
||||
if (!isset($entitiesByModule[$specModuleName])) {
|
||||
$entitiesByModule[$specModuleName] = [];
|
||||
}
|
||||
$entitiesByModule[$specModule][$fullName] = $spec;
|
||||
$entitiesByModule[$specModuleName][$fullName] = $spec;
|
||||
}
|
||||
}
|
||||
return $entitiesByModule;
|
||||
}
|
||||
|
||||
|
||||
public function write($module, $entities)
|
||||
/**
|
||||
* Write entities to a module
|
||||
*
|
||||
* @param Module $module
|
||||
* @param array $entities
|
||||
* @return $this
|
||||
*/
|
||||
public function write(Module $module, $entities)
|
||||
{
|
||||
$this->getWriter()->write($entities, $this->defaultLocale, $this->baseSavePath . '/' . $module);
|
||||
$this->getWriter()->write(
|
||||
$entities,
|
||||
$this->defaultLocale,
|
||||
$this->baseSavePath . '/' . $module->getRelativePath()
|
||||
);
|
||||
return $this;
|
||||
}
|
||||
|
||||
@ -465,10 +464,10 @@ class i18nTextCollector
|
||||
* Builds a master string table from php and .ss template files for the module passed as the $module param
|
||||
* @see collectFromCode() and collectFromTemplate()
|
||||
*
|
||||
* @param string $module A module's name or just 'themes/<themename>'
|
||||
* @param Module $module Module instance
|
||||
* @return array An array of entities found in the files that comprise the module
|
||||
*/
|
||||
protected function processModule($module)
|
||||
protected function processModule(Module $module)
|
||||
{
|
||||
$entities = array();
|
||||
|
||||
@ -481,15 +480,14 @@ class i18nTextCollector
|
||||
if ($extension === 'php') {
|
||||
$entities = array_merge(
|
||||
$entities,
|
||||
$this->collectFromCode($content, $module),
|
||||
$this->collectFromCode($content, $filePath, $module),
|
||||
$this->collectFromEntityProviders($filePath, $module)
|
||||
);
|
||||
} elseif ($extension === 'ss') {
|
||||
// templates use their filename as a namespace
|
||||
$namespace = basename($filePath);
|
||||
$entities = array_merge(
|
||||
$entities,
|
||||
$this->collectFromTemplate($content, $module, $namespace)
|
||||
$this->collectFromTemplate($content, $filePath, $module)
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -503,25 +501,29 @@ class i18nTextCollector
|
||||
/**
|
||||
* Retrieves the list of files for this module
|
||||
*
|
||||
* @param string $module
|
||||
* @param Module $module Module instance
|
||||
* @return array List of files to parse
|
||||
*/
|
||||
protected function getFileListForModule($module)
|
||||
protected function getFileListForModule(Module $module)
|
||||
{
|
||||
$modulePath = "{$this->basePath}/{$module}";
|
||||
$modulePath = $module->getPath();
|
||||
|
||||
// Search all .ss files in themes
|
||||
if (stripos($module, 'themes/') === 0) {
|
||||
if (stripos($module->getRelativePath(), 'themes/') === 0) {
|
||||
return $this->getFilesRecursive($modulePath, null, 'ss');
|
||||
}
|
||||
|
||||
// If Framework or non-standard module structure, so we'll scan all subfolders
|
||||
if ($module === FRAMEWORK_DIR || !is_dir("{$modulePath}/code")) {
|
||||
// If non-standard module structure, search all root files
|
||||
if (!is_dir("{$modulePath}/code") && !is_dir("{$modulePath}/src")) {
|
||||
return $this->getFilesRecursive($modulePath);
|
||||
}
|
||||
|
||||
// Get code files
|
||||
$files = $this->getFilesRecursive("{$modulePath}/code", null, 'php');
|
||||
if (is_dir("{$modulePath}/src")) {
|
||||
$files = $this->getFilesRecursive("{$modulePath}/src", null, 'php');
|
||||
} else {
|
||||
$files = $this->getFilesRecursive("{$modulePath}/code", null, 'php');
|
||||
}
|
||||
|
||||
// Search for templates in this module
|
||||
if (is_dir("{$modulePath}/templates")) {
|
||||
@ -538,12 +540,15 @@ class i18nTextCollector
|
||||
* Note: Translations without default values are omitted.
|
||||
*
|
||||
* @param string $content The text content of a parsed template-file
|
||||
* @param string $module Module's name or 'themes'. Could also be a namespace
|
||||
* Generated by templates includes. E.g. 'UploadField.ss'
|
||||
* @param string $fileName Filename Optional filename
|
||||
* @param Module $module Module being collected
|
||||
* @return array Map of localised keys to default values provided for this code
|
||||
*/
|
||||
public function collectFromCode($content, $module)
|
||||
public function collectFromCode($content, $fileName, Module $module)
|
||||
{
|
||||
// Get namespace either from $fileName or $module fallback
|
||||
$namespace = $fileName ? basename($fileName) : $module->getName();
|
||||
|
||||
$entities = array();
|
||||
|
||||
$tokens = token_get_all("<?php\n" . $content);
|
||||
@ -713,7 +718,7 @@ class i18nTextCollector
|
||||
// Normalise all keys
|
||||
foreach ($entities as $key => $entity) {
|
||||
unset($entities[$key]);
|
||||
$entities[$this->normalizeEntity($key, $module)] = $entity;
|
||||
$entities[$this->normalizeEntity($key, $namespace)] = $entity;
|
||||
}
|
||||
ksort($entities);
|
||||
|
||||
@ -725,12 +730,15 @@ class i18nTextCollector
|
||||
*
|
||||
* @param string $content The text content of a parsed template-file
|
||||
* @param string $fileName The name of a template file when method is used in self-referencing mode
|
||||
* @param string $module Module's name or 'themes'
|
||||
* @param Module $module Module being collected
|
||||
* @param array $parsedFiles
|
||||
* @return array $entities An array of entities representing the extracted template function calls
|
||||
*/
|
||||
public function collectFromTemplate($content, $fileName, $module, &$parsedFiles = array())
|
||||
public function collectFromTemplate($content, $fileName, Module $module, &$parsedFiles = array())
|
||||
{
|
||||
// Get namespace either from $fileName or $module fallback
|
||||
$namespace = $fileName ? basename($fileName) : $module->getName();
|
||||
|
||||
// use parser to extract <%t style translatable entities
|
||||
$entities = Parser::getTranslatables($content, $this->getWarnOnEmptyDefault());
|
||||
|
||||
@ -738,13 +746,13 @@ class i18nTextCollector
|
||||
// Collect in actual template
|
||||
if (preg_match_all('/(_t\([^\)]*?\))/ms', $content, $matches)) {
|
||||
foreach ($matches[1] as $match) {
|
||||
$entities = array_merge($entities, $this->collectFromCode($match, $module));
|
||||
$entities = array_merge($entities, $this->collectFromCode($match, $fileName, $module));
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($entities as $entity => $spec) {
|
||||
unset($entities[$entity]);
|
||||
$entities[$this->normalizeEntity($entity, $module)] = $spec;
|
||||
$entities[$this->normalizeEntity($entity, $namespace)] = $spec;
|
||||
}
|
||||
ksort($entities);
|
||||
|
||||
@ -760,10 +768,10 @@ class i18nTextCollector
|
||||
*
|
||||
* @uses i18nEntityProvider
|
||||
* @param string $filePath
|
||||
* @param string $module
|
||||
* @param Module $module
|
||||
* @return array
|
||||
*/
|
||||
public function collectFromEntityProviders($filePath, $module = null)
|
||||
public function collectFromEntityProviders($filePath, Module $module = null)
|
||||
{
|
||||
$entities = array();
|
||||
$classes = ClassInfo::classes_for_file($filePath);
|
||||
|
@ -12,8 +12,25 @@ use SilverStripe\Dev\SapphireTest;
|
||||
class ClassLoaderTest extends SapphireTest
|
||||
{
|
||||
|
||||
protected $base;
|
||||
protected $manifest;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $baseManifest1;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $baseManifest2;
|
||||
|
||||
/**
|
||||
* @var ClassManifest
|
||||
*/
|
||||
protected $testManifest1;
|
||||
|
||||
/**
|
||||
* @var ClassManifest
|
||||
*/
|
||||
protected $testManifest2;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
@ -21,8 +38,8 @@ class ClassLoaderTest extends SapphireTest
|
||||
|
||||
$this->baseManifest1 = dirname(__FILE__) . '/fixtures/classmanifest';
|
||||
$this->baseManifest2 = dirname(__FILE__) . '/fixtures/classmanifest_other';
|
||||
$this->testManifest1 = new ClassManifest($this->baseManifest1, false, true, false);
|
||||
$this->testManifest2 = new ClassManifest($this->baseManifest2, false, true, false);
|
||||
$this->testManifest1 = new ClassManifest($this->baseManifest1, false);
|
||||
$this->testManifest2 = new ClassManifest($this->baseManifest2, false);
|
||||
}
|
||||
|
||||
public function testExclusive()
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace SilverStripe\Core\Tests\Manifest;
|
||||
|
||||
use Exception;
|
||||
use SilverStripe\Core\Manifest\ClassManifest;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
|
||||
@ -11,8 +12,19 @@ use SilverStripe\Dev\SapphireTest;
|
||||
class ClassManifestTest extends SapphireTest
|
||||
{
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $base;
|
||||
|
||||
/**
|
||||
* @var ClassManifest
|
||||
*/
|
||||
protected $manifest;
|
||||
|
||||
/**
|
||||
* @var ClassManifest
|
||||
*/
|
||||
protected $manifestTests;
|
||||
|
||||
public function setUp()
|
||||
@ -20,8 +32,8 @@ class ClassManifestTest extends SapphireTest
|
||||
parent::setUp();
|
||||
|
||||
$this->base = dirname(__FILE__) . '/fixtures/classmanifest';
|
||||
$this->manifest = new ClassManifest($this->base, false, true, false);
|
||||
$this->manifestTests = new ClassManifest($this->base, true, true, false);
|
||||
$this->manifest = new ClassManifest($this->base, false);
|
||||
$this->manifestTests = new ClassManifest($this->base, true);
|
||||
}
|
||||
|
||||
public function testGetItemPath()
|
||||
@ -125,23 +137,6 @@ class ClassManifestTest extends SapphireTest
|
||||
}
|
||||
}
|
||||
|
||||
public function testGetConfigs()
|
||||
{
|
||||
$expect = array("{$this->base}/module/_config.php");
|
||||
$this->assertEquals($expect, $this->manifest->getConfigs());
|
||||
$this->assertEquals($expect, $this->manifestTests->getConfigs());
|
||||
}
|
||||
|
||||
public function testGetModules()
|
||||
{
|
||||
$expect = array(
|
||||
"module" => "{$this->base}/module",
|
||||
"moduleb" => "{$this->base}/moduleb"
|
||||
);
|
||||
$this->assertEquals($expect, $this->manifest->getModules());
|
||||
$this->assertEquals($expect, $this->manifestTests->getModules());
|
||||
}
|
||||
|
||||
public function testTestManifestIncludesTestClasses()
|
||||
{
|
||||
$this->assertNotContains('testclassa', array_keys($this->manifest->getClasses()));
|
||||
@ -156,11 +151,10 @@ class ClassManifestTest extends SapphireTest
|
||||
/**
|
||||
* Assert that ClassManifest throws an exception when it encounters two files
|
||||
* which contain classes with the same name
|
||||
*
|
||||
* @expectedException Exception
|
||||
*/
|
||||
public function testManifestWarnsAboutDuplicateClasses()
|
||||
{
|
||||
$dummy = new ClassManifest(dirname(__FILE__) . '/fixtures/classmanifest_duplicates', false, true, false);
|
||||
$this->setExpectedException(Exception::class);
|
||||
new ClassManifest(dirname(__FILE__) . '/fixtures/classmanifest_duplicates', false);
|
||||
}
|
||||
}
|
||||
|
72
tests/php/Core/Manifest/ModuleManifestTest.php
Normal file
72
tests/php/Core/Manifest/ModuleManifestTest.php
Normal file
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace SilverStripe\Core\Tests\Manifest;
|
||||
|
||||
use SilverStripe\Core\Manifest\ModuleManifest;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
|
||||
class ModuleManifestTest extends SapphireTest
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $base;
|
||||
|
||||
/**
|
||||
* @var ModuleManifest
|
||||
*/
|
||||
protected $manifest;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->base = dirname(__FILE__) . '/fixtures/classmanifest';
|
||||
$this->manifest = new ModuleManifest($this->base, false);
|
||||
}
|
||||
|
||||
public function testGetModules()
|
||||
{
|
||||
$modules = $this->manifest->getModules();
|
||||
$this->assertEquals(
|
||||
[
|
||||
'module',
|
||||
'silverstripe/awesome-module',
|
||||
],
|
||||
array_keys($modules)
|
||||
);
|
||||
}
|
||||
|
||||
public function testGetLegacyModule()
|
||||
{
|
||||
$module = $this->manifest->getModule('module');
|
||||
$this->assertNotEmpty($module);
|
||||
$this->assertEquals('module', $module->getName());
|
||||
$this->assertEquals('module', $module->getShortName());
|
||||
$this->assertEquals('module', $module->getRelativePath());
|
||||
$this->assertEmpty($module->getComposerName());
|
||||
}
|
||||
|
||||
public function testGetComposerModule()
|
||||
{
|
||||
// Get by installer-name (folder)
|
||||
$moduleByShortName = $this->manifest->getModule('moduleb');
|
||||
$this->assertNotEmpty($moduleByShortName);
|
||||
|
||||
// Can also get this by full composer name
|
||||
$module = $this->manifest->getModule('silverstripe/awesome-module');
|
||||
$this->assertNotEmpty($module);
|
||||
$this->assertEquals($moduleByShortName->getPath(), $module->getPath());
|
||||
|
||||
// correctly respects vendor
|
||||
$this->assertEmpty($this->manifest->getModule('wrongvendor/awesome-module'));
|
||||
$this->assertEmpty($this->manifest->getModule('wrongvendor/moduleb'));
|
||||
|
||||
// Properties of module
|
||||
$this->assertEquals('silverstripe/awesome-module', $module->getName());
|
||||
$this->assertEquals('silverstripe/awesome-module', $module->getComposerName());
|
||||
$this->assertEquals('moduleb', $module->getShortName());
|
||||
$this->assertEquals('moduleb', $module->getRelativePath());
|
||||
}
|
||||
}
|
@ -13,7 +13,9 @@ use ReflectionMethod;
|
||||
*/
|
||||
class NamespacedClassManifestTest extends SapphireTest
|
||||
{
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $base;
|
||||
|
||||
/**
|
||||
@ -26,7 +28,7 @@ class NamespacedClassManifestTest extends SapphireTest
|
||||
parent::setUp();
|
||||
|
||||
$this->base = dirname(__FILE__) . '/fixtures/namespaced_classmanifest';
|
||||
$this->manifest = new ClassManifest($this->base, false, true, false);
|
||||
$this->manifest = new ClassManifest($this->base, false);
|
||||
ClassLoader::instance()->pushManifest($this->manifest, false);
|
||||
}
|
||||
|
||||
@ -40,7 +42,7 @@ class NamespacedClassManifestTest extends SapphireTest
|
||||
{
|
||||
$this->assertContains('SilverStripe\Framework\Tests\ClassI', ClassInfo::implementorsOf('SilverStripe\\Security\\PermissionProvider'));
|
||||
|
||||
//because we're using a nested manifest we have to "coalesce" the descendants again to correctly populate the
|
||||
// because we're using a nested manifest we have to "coalesce" the descendants again to correctly populate the
|
||||
// descendants of the core classes we want to test against - this is a limitation of the test manifest not
|
||||
// including all core classes
|
||||
$method = new ReflectionMethod($this->manifest, 'coalesceDescendants');
|
||||
@ -149,19 +151,4 @@ class NamespacedClassManifestTest extends SapphireTest
|
||||
$this->assertEquals($impl, $this->manifest->getImplementorsOf($interface));
|
||||
}
|
||||
}
|
||||
|
||||
public function testGetConfigs()
|
||||
{
|
||||
$expect = array("{$this->base}/module/_config.php");
|
||||
$this->assertEquals($expect, $this->manifest->getConfigs());
|
||||
}
|
||||
|
||||
public function testGetModules()
|
||||
{
|
||||
$expect = array(
|
||||
"module" => "{$this->base}/module",
|
||||
"moduleb" => "{$this->base}/moduleb"
|
||||
);
|
||||
$this->assertEquals($expect, $this->manifest->getModules());
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,9 @@ use SilverStripe\Dev\SapphireTest;
|
||||
*/
|
||||
class ThemeResourceLoaderTest extends SapphireTest
|
||||
{
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $base;
|
||||
|
||||
/**
|
||||
@ -34,7 +36,7 @@ class ThemeResourceLoaderTest extends SapphireTest
|
||||
// Fake project root
|
||||
$this->base = dirname(__FILE__) . '/fixtures/templatemanifest';
|
||||
// New ThemeManifest for that root
|
||||
$this->manifest = new ThemeManifest($this->base, 'myproject', false, true);
|
||||
$this->manifest = new ThemeManifest($this->base, 'myproject', false);
|
||||
// New Loader for that root
|
||||
$this->loader = new ThemeResourceLoader($this->base);
|
||||
$this->loader->addSet('$default', $this->manifest);
|
||||
|
@ -0,0 +1 @@
|
||||
module: {}
|
@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "silverstripe/awesome-module",
|
||||
"description": "dummy test module",
|
||||
"require": {},
|
||||
"extra": {
|
||||
"installer-name": "moduleb"
|
||||
}
|
||||
}
|
@ -116,7 +116,7 @@ class DeprecationTest extends SapphireTest
|
||||
|
||||
protected function callThatOriginatesFromFramework()
|
||||
{
|
||||
$this->assertEquals(TestDeprecation::get_module(), basename(FRAMEWORK_PATH));
|
||||
$this->assertEquals('silverstripe/framework', TestDeprecation::get_module()->getName());
|
||||
$this->assertNull(Deprecation::notice('2.0', 'Deprecation test passed'));
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1 @@
|
||||
<?php
|
@ -6,6 +6,8 @@ use SilverStripe\Control\Director;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Core\Manifest\ClassManifest;
|
||||
use SilverStripe\Core\Manifest\ClassLoader;
|
||||
use SilverStripe\Core\Manifest\ModuleLoader;
|
||||
use SilverStripe\Core\Manifest\ModuleManifest;
|
||||
use SilverStripe\i18n\i18n;
|
||||
use SilverStripe\i18n\Messages\MessageProvider;
|
||||
use SilverStripe\i18n\Messages\Symfony\ModuleYamlLoader;
|
||||
@ -41,6 +43,13 @@ trait i18nTestManifest
|
||||
*/
|
||||
protected $manifests = 0;
|
||||
|
||||
/**
|
||||
* Number of module manifests
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $moduleManifests = 0;
|
||||
|
||||
protected function getExtraDataObjects()
|
||||
{
|
||||
return [
|
||||
@ -72,17 +81,16 @@ trait i18nTestManifest
|
||||
$this->alternateBasePath = __DIR__ . $s . 'i18nTest' . $s . "_fakewebroot";
|
||||
Director::config()->update('alternate_base_folder', $this->alternateBasePath);
|
||||
|
||||
// New module manifest
|
||||
$moduleManifest = new ModuleManifest($this->alternateBasePath, false);
|
||||
$this->pushModuleManifest($moduleManifest);
|
||||
|
||||
// Replace old template loader with new one with alternate base path
|
||||
$this->oldThemeResourceLoader = ThemeResourceLoader::instance();
|
||||
ThemeResourceLoader::set_instance($loader = new ThemeResourceLoader($this->alternateBasePath));
|
||||
$loader->addSet(
|
||||
'$default',
|
||||
new ThemeManifest(
|
||||
$this->alternateBasePath,
|
||||
project(),
|
||||
false,
|
||||
true
|
||||
)
|
||||
new ThemeManifest($this->alternateBasePath, project(), false)
|
||||
);
|
||||
|
||||
SSViewer::set_themes([
|
||||
@ -94,7 +102,7 @@ trait i18nTestManifest
|
||||
i18n::set_locale('en_US');
|
||||
|
||||
// Set new manifest against the root
|
||||
$classManifest = new ClassManifest($this->alternateBasePath, true, true, false);
|
||||
$classManifest = new ClassManifest($this->alternateBasePath, true);
|
||||
$this->pushManifest($classManifest);
|
||||
|
||||
// Setup uncached translator
|
||||
@ -131,6 +139,12 @@ trait i18nTestManifest
|
||||
ClassLoader::instance()->pushManifest($manifest);
|
||||
}
|
||||
|
||||
protected function pushModuleManifest(ModuleManifest $manifest)
|
||||
{
|
||||
$this->moduleManifests++;
|
||||
ModuleLoader::instance()->pushManifest($manifest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pop off all extra manifests
|
||||
*/
|
||||
@ -141,5 +155,9 @@ trait i18nTestManifest
|
||||
ClassLoader::instance()->popManifest();
|
||||
$this->manifests--;
|
||||
}
|
||||
while ($this->moduleManifests > 0) {
|
||||
ModuleLoader::instance()->popManifest();
|
||||
$this->moduleManifests--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,12 +4,12 @@ namespace SilverStripe\i18n\Tests;
|
||||
|
||||
use PHPUnit_Framework_Error_Notice;
|
||||
use SilverStripe\Assets\Filesystem;
|
||||
use SilverStripe\Core\Manifest\ModuleLoader;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\i18n\i18n;
|
||||
use SilverStripe\i18n\TextCollection\i18nTextCollector;
|
||||
use SilverStripe\i18n\Messages\YamlWriter;
|
||||
use SilverStripe\i18n\Tests\i18nTextCollectorTest\Collector;
|
||||
use SilverStripe\View\SSViewer;
|
||||
|
||||
class i18nTextCollectorTest extends SapphireTest
|
||||
{
|
||||
@ -42,6 +42,7 @@ class i18nTextCollectorTest extends SapphireTest
|
||||
public function testConcatenationInEntityValues()
|
||||
{
|
||||
$c = i18nTextCollector::create();
|
||||
$module = ModuleLoader::instance()->getManifest()->getModule('i18ntestmodule');
|
||||
|
||||
$php = <<<PHP
|
||||
_t(
|
||||
@ -66,7 +67,7 @@ PHP;
|
||||
],
|
||||
'Test.CONCATENATED2' => "Line \"4\" and Line 5"
|
||||
),
|
||||
$c->collectFromCode($php, 'mymodule')
|
||||
$c->collectFromCode($php, null, $module)
|
||||
);
|
||||
}
|
||||
|
||||
@ -74,6 +75,7 @@ PHP;
|
||||
{
|
||||
$c = i18nTextCollector::create();
|
||||
$c->setWarnOnEmptyDefault(false);
|
||||
$mymodule = ModuleLoader::instance()->getManifest()->getModule('i18ntestmodule');
|
||||
|
||||
$html = <<<SS
|
||||
<% _t('Test.SINGLEQUOTE','Single Quote'); %>
|
||||
@ -85,7 +87,7 @@ PHP;
|
||||
<%t i18nTestModule.INJECTIONS_4 name=\$absoluteBaseURL greeting=\$get_locale goodbye="global calls" %>
|
||||
<%t i18nTestModule.INJECTIONS_9 "An item|{count} items" is "Test Pluralisation" count=4 %>
|
||||
SS;
|
||||
$c->collectFromTemplate($html, 'mymodule', 'Test');
|
||||
$c->collectFromTemplate($html, null, $mymodule);
|
||||
|
||||
$this->assertEquals(
|
||||
[
|
||||
@ -103,7 +105,7 @@ SS;
|
||||
'comment' => 'Test Pluralisation'
|
||||
],
|
||||
],
|
||||
$c->collectFromTemplate($html, 'mymodule', 'Test')
|
||||
$c->collectFromTemplate($html, null, $mymodule)
|
||||
);
|
||||
|
||||
// Test warning is raised on empty default
|
||||
@ -112,19 +114,20 @@ SS;
|
||||
PHPUnit_Framework_Error_Notice::class,
|
||||
'Missing localisation default for key i18nTestModule.INJECTIONS_3'
|
||||
);
|
||||
$c->collectFromTemplate($html, 'mymodule', 'Test');
|
||||
$c->collectFromTemplate($html, null, $mymodule);
|
||||
}
|
||||
|
||||
public function testCollectFromTemplateSimple()
|
||||
{
|
||||
$c = i18nTextCollector::create();
|
||||
$mymodule = ModuleLoader::instance()->getManifest()->getModule('i18ntestmodule');
|
||||
|
||||
$html = <<<SS
|
||||
<% _t('Test.SINGLEQUOTE','Single Quote'); %>
|
||||
SS;
|
||||
$this->assertEquals(
|
||||
[ 'Test.SINGLEQUOTE' => 'Single Quote' ],
|
||||
$c->collectFromTemplate($html, 'mymodule', 'Test')
|
||||
$c->collectFromTemplate($html, null, $mymodule)
|
||||
);
|
||||
|
||||
$html = <<<SS
|
||||
@ -132,7 +135,7 @@ SS;
|
||||
SS;
|
||||
$this->assertEquals(
|
||||
[ 'Test.DOUBLEQUOTE' => "Double Quote and Spaces" ],
|
||||
$c->collectFromTemplate($html, 'mymodule', 'Test')
|
||||
$c->collectFromTemplate($html, null, $mymodule)
|
||||
);
|
||||
|
||||
$html = <<<SS
|
||||
@ -140,7 +143,7 @@ SS;
|
||||
SS;
|
||||
$this->assertEquals(
|
||||
[ 'Test.NOSEMICOLON' => "No Semicolon" ],
|
||||
$c->collectFromTemplate($html, 'mymodule', 'Test')
|
||||
$c->collectFromTemplate($html, null, $mymodule)
|
||||
);
|
||||
}
|
||||
|
||||
@ -148,6 +151,7 @@ SS;
|
||||
{
|
||||
$c = i18nTextCollector::create();
|
||||
$c->setWarnOnEmptyDefault(false);
|
||||
$mymodule = ModuleLoader::instance()->getManifest()->getModule('i18ntestmodule');
|
||||
|
||||
$html = <<<SS
|
||||
<% _t(
|
||||
@ -157,7 +161,7 @@ SS;
|
||||
SS;
|
||||
$this->assertEquals(
|
||||
[ 'Test.NEWLINES' => "New Lines" ],
|
||||
$c->collectFromTemplate($html, 'mymodule', 'Test')
|
||||
$c->collectFromTemplate($html, 'Test', $mymodule)
|
||||
);
|
||||
|
||||
$html = <<<SS
|
||||
@ -172,7 +176,7 @@ SS;
|
||||
'default' => ' Prio and Value with "Double Quotes"',
|
||||
'comment' => 'Comment with "Double Quotes"',
|
||||
]],
|
||||
$c->collectFromTemplate($html, 'mymodule', 'Test')
|
||||
$c->collectFromTemplate($html, 'Test', $mymodule)
|
||||
);
|
||||
|
||||
$html = <<<SS
|
||||
@ -188,7 +192,7 @@ SS;
|
||||
'default' => " Prio and Value with 'Single Quotes'",
|
||||
'comment' => "Comment with 'Single Quotes'",
|
||||
]],
|
||||
$c->collectFromTemplate($html, 'mymodule', 'Test')
|
||||
$c->collectFromTemplate($html, 'Test', $mymodule)
|
||||
);
|
||||
|
||||
// Test empty
|
||||
@ -197,7 +201,7 @@ SS;
|
||||
SS;
|
||||
$this->assertEquals(
|
||||
[],
|
||||
$c->collectFromTemplate($html, 'mymodule', 'Test')
|
||||
$c->collectFromTemplate($html, null, $mymodule)
|
||||
);
|
||||
|
||||
// Test warning is raised on empty default
|
||||
@ -206,20 +210,21 @@ SS;
|
||||
PHPUnit_Framework_Error_Notice::class,
|
||||
'Missing localisation default for key Test.PRIOANDCOMMENT'
|
||||
);
|
||||
$c->collectFromTemplate($html, 'mymodule', 'Test');
|
||||
$c->collectFromTemplate($html, 'Test', $mymodule);
|
||||
}
|
||||
|
||||
|
||||
public function testCollectFromCodeSimple()
|
||||
{
|
||||
$c = i18nTextCollector::create();
|
||||
$mymodule = ModuleLoader::instance()->getManifest()->getModule('i18ntestmodule');
|
||||
|
||||
$php = <<<PHP
|
||||
_t('Test.SINGLEQUOTE','Single Quote');
|
||||
PHP;
|
||||
$this->assertEquals(
|
||||
[ 'Test.SINGLEQUOTE' => 'Single Quote' ],
|
||||
$c->collectFromCode($php, 'mymodule')
|
||||
$c->collectFromCode($php, null, $mymodule)
|
||||
);
|
||||
|
||||
$php = <<<PHP
|
||||
@ -227,13 +232,14 @@ _t( "Test.DOUBLEQUOTE", "Double Quote and Spaces" );
|
||||
PHP;
|
||||
$this->assertEquals(
|
||||
[ 'Test.DOUBLEQUOTE' => "Double Quote and Spaces" ],
|
||||
$c->collectFromCode($php, 'mymodule')
|
||||
$c->collectFromCode($php, null, $mymodule)
|
||||
);
|
||||
}
|
||||
|
||||
public function testCollectFromCodeAdvanced()
|
||||
{
|
||||
$c = i18nTextCollector::create();
|
||||
$mymodule = ModuleLoader::instance()->getManifest()->getModule('i18ntestmodule');
|
||||
|
||||
$php = <<<PHP
|
||||
_t(
|
||||
@ -243,7 +249,7 @@ _t(
|
||||
PHP;
|
||||
$this->assertEquals(
|
||||
[ 'Test.NEWLINES' => "New Lines" ],
|
||||
$c->collectFromCode($php, 'mymodule')
|
||||
$c->collectFromCode($php, null, $mymodule)
|
||||
);
|
||||
|
||||
$php = <<<PHP
|
||||
@ -261,7 +267,7 @@ PHP;
|
||||
'comment' => 'Comment with "Double Quotes"',
|
||||
]
|
||||
],
|
||||
$c->collectFromCode($php, 'mymodule')
|
||||
$c->collectFromCode($php, null, $mymodule)
|
||||
);
|
||||
|
||||
$php = <<<PHP
|
||||
@ -277,7 +283,7 @@ PHP;
|
||||
'default' => " Value with 'Single Quotes'",
|
||||
'comment' => "Comment with 'Single Quotes'"
|
||||
] ],
|
||||
$c->collectFromCode($php, 'mymodule')
|
||||
$c->collectFromCode($php, null, $mymodule)
|
||||
);
|
||||
|
||||
$php = <<<PHP
|
||||
@ -288,7 +294,7 @@ _t(
|
||||
PHP;
|
||||
$this->assertEquals(
|
||||
[ 'Test.PRIOANDCOMMENT' => "Value with 'Escaped Single Quotes'" ],
|
||||
$c->collectFromCode($php, 'mymodule')
|
||||
$c->collectFromCode($php, null, $mymodule)
|
||||
);
|
||||
|
||||
$php = <<<PHP
|
||||
@ -301,14 +307,14 @@ _t(
|
||||
PHP;
|
||||
$this->assertEquals(
|
||||
[ 'Test.PRIOANDCOMMENT' => "Doublequoted Value with 'Unescaped Single Quotes'"],
|
||||
$c->collectFromCode($php, 'mymodule')
|
||||
$c->collectFromCode($php, null, $mymodule)
|
||||
);
|
||||
}
|
||||
|
||||
public function testCollectFromCodeNamespace()
|
||||
{
|
||||
$c = i18nTextCollector::create();
|
||||
|
||||
$mymodule = ModuleLoader::instance()->getManifest()->getModule('i18ntestmodule');
|
||||
$php = <<<PHP
|
||||
<?php
|
||||
namespace SilverStripe\Framework\Core;
|
||||
@ -324,7 +330,7 @@ class MyClass extends Base implements SomeService {
|
||||
PHP;
|
||||
$this->assertEquals(
|
||||
[ 'SilverStripe\\Framework\\Core\\MyClass.NEWLINES' => "New Lines" ],
|
||||
$c->collectFromCode($php, 'mymodule')
|
||||
$c->collectFromCode($php, null, $mymodule)
|
||||
);
|
||||
}
|
||||
|
||||
@ -332,6 +338,7 @@ PHP;
|
||||
public function testNewlinesInEntityValues()
|
||||
{
|
||||
$c = i18nTextCollector::create();
|
||||
$mymodule = ModuleLoader::instance()->getManifest()->getModule('i18ntestmodule');
|
||||
|
||||
$php = <<<PHP
|
||||
_t(
|
||||
@ -344,7 +351,7 @@ PHP;
|
||||
$eol = PHP_EOL;
|
||||
$this->assertEquals(
|
||||
[ 'Test.NEWLINESINGLEQUOTE' => "Line 1{$eol}Line 2" ],
|
||||
$c->collectFromCode($php, 'mymodule')
|
||||
$c->collectFromCode($php, null, $mymodule)
|
||||
);
|
||||
|
||||
$php = <<<PHP
|
||||
@ -356,7 +363,7 @@ Line 2"
|
||||
PHP;
|
||||
$this->assertEquals(
|
||||
[ 'Test.NEWLINEDOUBLEQUOTE' => "Line 1{$eol}Line 2" ],
|
||||
$c->collectFromCode($php, 'mymodule')
|
||||
$c->collectFromCode($php, null, $mymodule)
|
||||
);
|
||||
}
|
||||
|
||||
@ -367,6 +374,7 @@ PHP;
|
||||
{
|
||||
$c = i18nTextCollector::create();
|
||||
$c->setWarnOnEmptyDefault(false); // Disable warnings for tests
|
||||
$mymodule = ModuleLoader::instance()->getManifest()->getModule('i18ntestmodule');
|
||||
|
||||
$php = <<<PHP
|
||||
_t('i18nTestModule.NEWMETHODSIG',"New _t method signature test");
|
||||
@ -385,7 +393,7 @@ _t('i18nTestModule.INJECTIONS8', ["name"=>"Cat", "greeting"=>"meow", "goodbye"=>
|
||||
_t('i18nTestModule.INJECTIONS9', "An item|{count} items", ['count' => 4], "Test Pluralisation");
|
||||
PHP;
|
||||
|
||||
$collectedTranslatables = $c->collectFromCode($php, 'mymodule');
|
||||
$collectedTranslatables = $c->collectFromCode($php, null, $mymodule);
|
||||
|
||||
$expectedArray = [
|
||||
'i18nTestModule.INJECTIONS2' => "Hello {name} {greeting}. But it is late, {goodbye}",
|
||||
@ -416,12 +424,13 @@ PHP;
|
||||
_t('i18nTestModule.INJECTIONS4', array("name"=>"Cat", "greeting"=>"meow", "goodbye"=>"meow"));
|
||||
PHP;
|
||||
$c->setWarnOnEmptyDefault(true);
|
||||
$c->collectFromCode($php, 'mymodule');
|
||||
$c->collectFromCode($php, null, $mymodule);
|
||||
}
|
||||
|
||||
public function testUncollectableCode()
|
||||
{
|
||||
$c = i18nTextCollector::create();
|
||||
$mymodule = ModuleLoader::instance()->getManifest()->getModule('i18ntestmodule');
|
||||
|
||||
$php = <<<PHP
|
||||
_t(static::class.'.KEY1', 'Default');
|
||||
@ -430,7 +439,7 @@ _t(__CLASS__.'.KEY3', 'Default');
|
||||
_t('Collectable.KEY4', 'Default');
|
||||
PHP;
|
||||
|
||||
$collectedTranslatables = $c->collectFromCode($php, 'mymodule');
|
||||
$collectedTranslatables = $c->collectFromCode($php, null, $mymodule);
|
||||
|
||||
// Only one item is collectable
|
||||
$expectedArray = [ 'Collectable.KEY4' => 'Default' ];
|
||||
@ -441,20 +450,21 @@ PHP;
|
||||
{
|
||||
$c = i18nTextCollector::create();
|
||||
$c->setWarnOnEmptyDefault(false); // Disable warnings for tests
|
||||
$mymodule = ModuleLoader::instance()->getManifest()->getModule('i18ntestmodule');
|
||||
|
||||
$templateFilePath = $this->alternateBasePath . '/i18ntestmodule/templates/Layout/i18nTestModule.ss';
|
||||
$html = file_get_contents($templateFilePath);
|
||||
$matches = $c->collectFromTemplate($html, 'mymodule', 'RandomNamespace');
|
||||
$matches = $c->collectFromTemplate($html, $templateFilePath, $mymodule);
|
||||
|
||||
$this->assertArrayHasKey('RandomNamespace.LAYOUTTEMPLATENONAMESPACE', $matches);
|
||||
$this->assertArrayHasKey('i18nTestModule.ss.LAYOUTTEMPLATENONAMESPACE', $matches);
|
||||
$this->assertEquals(
|
||||
'Layout Template no namespace',
|
||||
$matches['RandomNamespace.LAYOUTTEMPLATENONAMESPACE']
|
||||
$matches['i18nTestModule.ss.LAYOUTTEMPLATENONAMESPACE']
|
||||
);
|
||||
$this->assertArrayHasKey('RandomNamespace.SPRINTFNONAMESPACE', $matches);
|
||||
$this->assertArrayHasKey('i18nTestModule.ss.SPRINTFNONAMESPACE', $matches);
|
||||
$this->assertEquals(
|
||||
'My replacement no namespace: %s',
|
||||
$matches['RandomNamespace.SPRINTFNONAMESPACE']
|
||||
$matches['i18nTestModule.ss.SPRINTFNONAMESPACE']
|
||||
);
|
||||
$this->assertArrayHasKey('i18nTestModule.LAYOUTTEMPLATE', $matches);
|
||||
$this->assertEquals(
|
||||
@ -474,44 +484,6 @@ PHP;
|
||||
$this->assertArrayNotHasKey('i18nTestModuleInclude.ss.SPRINTFINCLUDENONAMESPACE', $matches);
|
||||
}
|
||||
|
||||
public function testCollectFromThemesTemplates()
|
||||
{
|
||||
$c = i18nTextCollector::create();
|
||||
SSViewer::set_themes([ 'testtheme1' ]);
|
||||
|
||||
// Collect from layout
|
||||
$layoutFilePath = $this->alternateBasePath . '/themes/testtheme1/templates/Layout/i18nTestTheme1.ss';
|
||||
$layoutHTML = file_get_contents($layoutFilePath);
|
||||
$layoutMatches = $c->collectFromTemplate($layoutHTML, 'themes/testtheme1', 'i18nTestTheme1.ss');
|
||||
|
||||
// all entities from i18nTestTheme1.ss
|
||||
$this->assertEquals(
|
||||
[
|
||||
'i18nTestTheme1.LAYOUTTEMPLATE' => 'Theme1 Layout Template',
|
||||
'i18nTestTheme1.SPRINTFNAMESPACE' => 'Theme1 My replacement: %s',
|
||||
'i18nTestTheme1.ss.LAYOUTTEMPLATENONAMESPACE' => 'Theme1 Layout Template no namespace',
|
||||
'i18nTestTheme1.ss.SPRINTFNONAMESPACE' => 'Theme1 My replacement no namespace: %s',
|
||||
],
|
||||
$layoutMatches
|
||||
);
|
||||
|
||||
// Collect from include
|
||||
$includeFilePath = $this->alternateBasePath . '/themes/testtheme1/templates/Includes/i18nTestTheme1Include.ss';
|
||||
$includeHTML = file_get_contents($includeFilePath);
|
||||
$includeMatches = $c->collectFromTemplate($includeHTML, 'themes/testtheme1', 'i18nTestTheme1Include.ss');
|
||||
|
||||
// all entities from i18nTestTheme1Include.ss
|
||||
$this->assertEquals(
|
||||
[
|
||||
'i18nTestTheme1Include.SPRINTFINCLUDENAMESPACE' => 'Theme1 My include replacement: %s',
|
||||
'i18nTestTheme1Include.WITHNAMESPACE' => 'Theme1 Include Entity with Namespace',
|
||||
'i18nTestTheme1Include.ss.NONAMESPACE' => 'Theme1 Include Entity without Namespace',
|
||||
'i18nTestTheme1Include.ss.SPRINTFINCLUDENONAMESPACE' => 'Theme1 My include replacement no namespace: %s'
|
||||
],
|
||||
$includeMatches
|
||||
);
|
||||
}
|
||||
|
||||
public function testCollectMergesWithExisting()
|
||||
{
|
||||
$c = i18nTextCollector::create();
|
||||
@ -608,63 +580,6 @@ PHP;
|
||||
" MAINTEMPLATE: 'Main Template Other Module'\n",
|
||||
$otherModuleLangFileContent
|
||||
);
|
||||
|
||||
// testtheme1
|
||||
$theme1LangFile = "{$this->alternateBaseSavePath}/themes/testtheme1/lang/" . $c->getDefaultLocale() . '.yml';
|
||||
$this->assertTrue(
|
||||
file_exists($theme1LangFile),
|
||||
'Master theme language file can be written to themes/testtheme1 /lang folder'
|
||||
);
|
||||
$theme1LangFileContent = file_get_contents($theme1LangFile);
|
||||
$this->assertContains(
|
||||
" MAINTEMPLATE: 'Theme1 Main Template'\n",
|
||||
$theme1LangFileContent
|
||||
);
|
||||
$this->assertContains(
|
||||
" LAYOUTTEMPLATE: 'Theme1 Layout Template'\n",
|
||||
$theme1LangFileContent
|
||||
);
|
||||
$this->assertContains(
|
||||
" SPRINTFNAMESPACE: 'Theme1 My replacement: %s'\n",
|
||||
$theme1LangFileContent
|
||||
);
|
||||
$this->assertContains(
|
||||
" LAYOUTTEMPLATENONAMESPACE: 'Theme1 Layout Template no namespace'\n",
|
||||
$theme1LangFileContent
|
||||
);
|
||||
$this->assertContains(
|
||||
" SPRINTFNONAMESPACE: 'Theme1 My replacement no namespace: %s'\n",
|
||||
$theme1LangFileContent
|
||||
);
|
||||
|
||||
$this->assertContains(
|
||||
" SPRINTFINCLUDENAMESPACE: 'Theme1 My include replacement: %s'\n",
|
||||
$theme1LangFileContent
|
||||
);
|
||||
$this->assertContains(
|
||||
" WITHNAMESPACE: 'Theme1 Include Entity with Namespace'\n",
|
||||
$theme1LangFileContent
|
||||
);
|
||||
$this->assertContains(
|
||||
" NONAMESPACE: 'Theme1 Include Entity without Namespace'\n",
|
||||
$theme1LangFileContent
|
||||
);
|
||||
$this->assertContains(
|
||||
" SPRINTFINCLUDENONAMESPACE: 'Theme1 My include replacement no namespace: %s'\n",
|
||||
$theme1LangFileContent
|
||||
);
|
||||
|
||||
// testtheme2
|
||||
$theme2LangFile = "{$this->alternateBaseSavePath}/themes/testtheme2/lang/" . $c->getDefaultLocale() . '.yml';
|
||||
$this->assertTrue(
|
||||
file_exists($theme2LangFile),
|
||||
'Master theme language file can be written to themes/testtheme2 /lang folder'
|
||||
);
|
||||
$theme2LangFileContent = file_get_contents($theme2LangFile);
|
||||
$this->assertContains(
|
||||
" MAINTEMPLATE: 'Theme2 Main Template'\n",
|
||||
$theme2LangFileContent
|
||||
);
|
||||
}
|
||||
|
||||
public function testCollectFromEntityProvidersInCustomObject()
|
||||
@ -793,16 +708,14 @@ PHP;
|
||||
public function testModuleDetection()
|
||||
{
|
||||
$collector = new Collector();
|
||||
$modules = $collector->getModules_Test($this->alternateBasePath);
|
||||
$modules = ModuleLoader::instance()->getManifest()->getModules();
|
||||
$this->assertEquals(
|
||||
array(
|
||||
'i18nnonstandardmodule',
|
||||
'i18nothermodule',
|
||||
'i18ntestmodule',
|
||||
'themes/testtheme1',
|
||||
'themes/testtheme2'
|
||||
'i18nothermodule'
|
||||
),
|
||||
$modules
|
||||
array_keys($modules)
|
||||
);
|
||||
|
||||
$this->assertEquals('i18ntestmodule', $collector->findModuleForClass_Test('i18nTestNamespacedClass'));
|
||||
@ -853,22 +766,5 @@ PHP;
|
||||
$this->assertArrayHasKey("{$otherRoot}/code/i18nProviderClass.php", $otherFiles);
|
||||
$this->assertArrayHasKey("{$otherRoot}/code/i18nTestModuleDecorator.php", $otherFiles);
|
||||
$this->assertArrayHasKey("{$otherRoot}/templates/i18nOtherModule.ss", $otherFiles);
|
||||
|
||||
// Themes should detect all ss files only
|
||||
$theme1Files = $collector->getFileListForModule_Test('themes/testtheme1');
|
||||
$theme1Root = $this->alternateBasePath . '/themes/testtheme1/templates';
|
||||
$this->assertEquals(3, count($theme1Files));
|
||||
// Find only ss files
|
||||
$this->assertArrayHasKey("{$theme1Root}/Includes/i18nTestTheme1Include.ss", $theme1Files);
|
||||
$this->assertArrayHasKey("{$theme1Root}/Layout/i18nTestTheme1.ss", $theme1Files);
|
||||
$this->assertArrayHasKey("{$theme1Root}/i18nTestTheme1Main.ss", $theme1Files);
|
||||
|
||||
// Only 1 file here
|
||||
$theme2Files = $collector->getFileListForModule_Test('themes/testtheme2');
|
||||
$this->assertEquals(1, count($theme2Files));
|
||||
$this->assertArrayHasKey(
|
||||
$this->alternateBasePath . '/themes/testtheme2/templates/i18nTestTheme2.ss',
|
||||
$theme2Files
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
namespace SilverStripe\i18n\Tests\i18nTextCollectorTest;
|
||||
|
||||
use SilverStripe\Core\Manifest\Module;
|
||||
use SilverStripe\Core\Manifest\ModuleLoader;
|
||||
use SilverStripe\Dev\TestOnly;
|
||||
use SilverStripe\i18n\TextCollection\i18nTextCollector;
|
||||
|
||||
@ -10,18 +12,17 @@ use SilverStripe\i18n\TextCollection\i18nTextCollector;
|
||||
*/
|
||||
class Collector extends i18nTextCollector implements TestOnly
|
||||
{
|
||||
public function getModules_Test($directory)
|
||||
{
|
||||
return $this->getModules($directory);
|
||||
}
|
||||
|
||||
public function resolveDuplicateConflicts_Test($entitiesByModule)
|
||||
{
|
||||
return $this->resolveDuplicateConflicts($entitiesByModule);
|
||||
}
|
||||
|
||||
public function getFileListForModule_Test($module)
|
||||
public function getFileListForModule_Test($modulename)
|
||||
{
|
||||
$module = ModuleLoader::instance()->getManifest()->getModule($modulename);
|
||||
if (!$module) {
|
||||
throw new \BadMethodCallException("No module named {$modulename}");
|
||||
}
|
||||
return $this->getFileListForModule($module);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user