2014-10-13 09:49:30 +02:00
|
|
|
title: Requirements
|
|
|
|
summary: How to include and require other assets in your templates such as javascript and CSS files.
|
|
|
|
|
|
|
|
# Requirements
|
|
|
|
|
|
|
|
The requirements class takes care of including CSS and JavaScript into your applications. This is preferred to hard
|
|
|
|
coding any references in the `<head>` tag of your template, as it enables a more flexible handling through the
|
2017-07-03 03:22:12 +02:00
|
|
|
[Requirements](api:SilverStripe\View\Requirements) class.
|
2014-10-13 09:49:30 +02:00
|
|
|
|
2016-05-27 03:08:22 +02:00
|
|
|
The examples below are using certain folder naming conventions (CSS files in `css/`, JavaScript files in `javascript/`).
|
|
|
|
SilverStripe core modules like `cms` use a different naming convention (CSS and JavaScript files in `client/src/`).
|
|
|
|
The `Requirements` class can work with arbitrary file paths.
|
|
|
|
|
2014-10-13 09:49:30 +02:00
|
|
|
## Template Requirements API
|
|
|
|
|
2016-05-27 03:08:22 +02:00
|
|
|
**<my-module-dir>/templates/SomeTemplate.ss**
|
2014-10-13 09:49:30 +02:00
|
|
|
|
2017-08-03 02:51:32 +02:00
|
|
|
```ss
|
2017-10-27 04:38:27 +02:00
|
|
|
<% require css("<my-module-dir>/css/some_file.css") %>
|
|
|
|
<% require themedCSS("some_themed_file") %>
|
|
|
|
<% require javascript("<my-module-dir>/javascript/some_file.js") %>
|
2017-08-03 02:51:32 +02:00
|
|
|
```
|
2014-10-13 09:49:30 +02:00
|
|
|
|
|
|
|
<div class="alert" markdown="1">
|
|
|
|
Requiring assets from the template is restricted compared to the PHP API.
|
|
|
|
</div>
|
|
|
|
|
|
|
|
## PHP Requirements API
|
|
|
|
|
2015-02-28 01:09:15 +01:00
|
|
|
It is common practice to include most Requirements either in the *init()*-method of your [controller](../controllers/), or
|
2017-07-03 03:22:12 +02:00
|
|
|
as close to rendering as possible (e.g. in [FormField](api:SilverStripe\Forms\FormField)).
|
2014-10-13 09:49:30 +02:00
|
|
|
|
2017-07-10 01:00:25 +02:00
|
|
|
```php
|
|
|
|
use SilverStripe\Control\Director;
|
|
|
|
use SilverStripe\View\Requirements;
|
2014-10-13 09:49:30 +02:00
|
|
|
|
2017-07-10 01:00:25 +02:00
|
|
|
class MyCustomController extends Controller
|
|
|
|
{
|
|
|
|
protected function init()
|
|
|
|
{
|
|
|
|
parent::init();
|
2014-10-13 09:49:30 +02:00
|
|
|
|
2017-07-10 01:00:25 +02:00
|
|
|
Requirements::javascript("<my-module-dir>/javascript/some_file.js");
|
|
|
|
Requirements::css("<my-module-dir>/css/some_file.css");
|
2016-01-06 00:28:26 +01:00
|
|
|
}
|
2017-07-10 01:00:25 +02:00
|
|
|
}
|
|
|
|
```
|
2014-10-13 09:49:30 +02:00
|
|
|
|
|
|
|
### CSS Files
|
|
|
|
|
2017-07-10 01:00:25 +02:00
|
|
|
```php
|
2017-10-26 02:22:02 +02:00
|
|
|
use SilverStripe\View\Requirements;
|
|
|
|
|
2017-07-10 01:00:25 +02:00
|
|
|
Requirements::css($path, $media);
|
|
|
|
```
|
2014-10-13 09:49:30 +02:00
|
|
|
|
|
|
|
If you're using the CSS method a second argument can be used. This argument defines the 'media' attribute of the
|
|
|
|
`<link>` element, so you can define 'screen' or 'print' for example.
|
|
|
|
|
2017-07-10 01:00:25 +02:00
|
|
|
```php
|
|
|
|
Requirements::css("<my-module-dir>/css/some_file.css", "screen,projection");
|
|
|
|
```
|
2014-10-13 09:49:30 +02:00
|
|
|
|
|
|
|
### Javascript Files
|
|
|
|
|
2017-07-10 01:00:25 +02:00
|
|
|
```php
|
|
|
|
Requirements::javascript($path, $options);
|
|
|
|
```
|
2014-10-13 09:49:30 +02:00
|
|
|
|
|
|
|
A variant on the inclusion of custom javascript is the inclusion of *templated* javascript. Here, you keep your
|
|
|
|
JavaScript in a separate file and instead load, via search and replace, several PHP-generated variables into that code.
|
|
|
|
|
2017-07-10 01:00:25 +02:00
|
|
|
```php
|
|
|
|
$vars = [
|
2017-08-07 02:09:39 +02:00
|
|
|
"MemberID" => Security::getCurrentUser()->ID,
|
2017-07-10 01:00:25 +02:00
|
|
|
];
|
2014-10-13 09:49:30 +02:00
|
|
|
|
2017-07-10 01:00:25 +02:00
|
|
|
Requirements::javascriptTemplate("<my-module-dir>/javascript/some_file.js", $vars);
|
|
|
|
```
|
2014-10-13 09:49:30 +02:00
|
|
|
|
2016-05-27 03:08:22 +02:00
|
|
|
In this example, `some_file.js` is expected to contain a replaceable variable expressed as `MemberID`.
|
2014-10-30 05:16:38 +01:00
|
|
|
|
2016-01-14 05:23:21 +01:00
|
|
|
If you are using front-end script combination mechanisms, you can optionally declare
|
|
|
|
that your included files provide these scripts. This will ensure that subsequent
|
|
|
|
Requirement calls that rely on those included scripts will not double include those
|
|
|
|
files.
|
|
|
|
|
2017-07-10 01:00:25 +02:00
|
|
|
```php
|
|
|
|
Requirements::javascript('<my-module-dir>/javascript/dist/bundle.js', ['provides' => [
|
|
|
|
'<my-module-dir>/javascript/jquery.js'
|
|
|
|
'<my-module-dir>/javascript/src/main.js',
|
|
|
|
'<my-module-dir>/javascript/src/functions.js'
|
|
|
|
]]);
|
|
|
|
Requirements::javascript('<my-module-dir>/javascript/jquery.js'); // Will will skip this file
|
|
|
|
```
|
|
|
|
|
|
|
|
You can also use the second argument to add the 'async' and/or 'defer attributes to the script tag generated:
|
|
|
|
|
|
|
|
```php
|
|
|
|
Requirements::javascript(
|
2017-08-07 05:11:17 +02:00
|
|
|
"<my-module-dir>/javascript/some_file.js",
|
|
|
|
[
|
|
|
|
"async" => true,
|
|
|
|
"defer" => true,
|
|
|
|
]
|
2017-07-10 01:00:25 +02:00
|
|
|
);
|
|
|
|
```
|
2016-01-14 05:23:21 +01:00
|
|
|
|
2014-10-13 09:49:30 +02:00
|
|
|
### Custom Inline CSS or Javascript
|
|
|
|
|
|
|
|
You can also quote custom script directly. This may seem a bit ugly, but is useful when you need to transfer some kind
|
|
|
|
of 'configuration' from the database in a raw format. You'll need to use the `heredoc` syntax to quote JS and CSS,
|
|
|
|
this is generally speaking the best way to do these things - it clearly marks the copy as belonging to a different
|
|
|
|
language.
|
|
|
|
|
2017-07-10 01:00:25 +02:00
|
|
|
```php
|
|
|
|
Requirements::customScript(<<<JS
|
|
|
|
alert("hi there");
|
|
|
|
JS
|
|
|
|
);
|
2014-10-13 09:49:30 +02:00
|
|
|
|
2017-07-10 01:00:25 +02:00
|
|
|
Requirements::customCSS(<<<CSS
|
|
|
|
.tree li.$className {
|
|
|
|
background-image: url($icon);
|
|
|
|
}
|
|
|
|
CSS
|
|
|
|
);
|
|
|
|
```
|
2014-10-13 09:49:30 +02:00
|
|
|
|
|
|
|
## Combining Files
|
|
|
|
|
|
|
|
You can concatenate several CSS or javascript files into a single dynamically generated file. This increases performance
|
|
|
|
by reducing HTTP requests.
|
|
|
|
|
2017-07-10 01:00:25 +02:00
|
|
|
```php
|
|
|
|
Requirements::combine_files(
|
|
|
|
'foobar.js',
|
|
|
|
[
|
|
|
|
'<my-module-dir>/javascript/foo.js',
|
|
|
|
'<my-module-dir>/javascript/bar.js',
|
|
|
|
]
|
|
|
|
);
|
|
|
|
```
|
2014-10-13 09:49:30 +02:00
|
|
|
|
|
|
|
<div class="alert" markdown='1'>
|
|
|
|
To make debugging easier in your local environment, combined files is disabled when running your application in `dev`
|
2015-11-30 03:03:46 +01:00
|
|
|
mode. You can re-enable dev combination by setting `Requirements_Backend.combine_in_dev` to true.
|
2014-10-13 09:49:30 +02:00
|
|
|
</div>
|
|
|
|
|
2015-11-30 03:03:46 +01:00
|
|
|
### Configuring combined file storage
|
|
|
|
|
|
|
|
In some situations or server configurations, it may be necessary to customise the behaviour of generated javascript
|
|
|
|
files in order to ensure that current files are served in requests.
|
|
|
|
|
|
|
|
By default, files will be generated on demand in the format `assets/_combinedfiles/name-<hash>.js`,
|
|
|
|
where `<hash>` represents the hash of the source files used to generate that content. The default flysystem backend,
|
2017-07-03 03:22:12 +02:00
|
|
|
as used by the `[AssetStore](api:SilverStripe\Assets\Storage\AssetStore)` backend, is used for this storage, but it can be substituted for any
|
2015-11-30 03:03:46 +01:00
|
|
|
other backend.
|
|
|
|
|
|
|
|
You can also use any of the below options in order to tweak this behaviour:
|
|
|
|
|
|
|
|
* `Requirements.disable_flush_combined` - By default all combined files are deleted on flush.
|
|
|
|
If combined files are stored in source control, and thus updated manually, you might want to
|
|
|
|
turn this on to disable this behaviour.
|
|
|
|
* `Requirements_Backend.combine_hash_querystring` - By default the `<hash>` of the source files is appended to
|
|
|
|
the end of the combined file (prior to the file extension). If combined files are versioned in source control,
|
|
|
|
or running in a distributed environment (such as one where the newest version of a file may not always be
|
|
|
|
immediately available) then it may sometimes be necessary to disable this. When this is set to true, the hash
|
|
|
|
will instead be appended via a querystring parameter to enable cache busting, but not in the
|
|
|
|
filename itself. I.e. `assets/_combinedfiles/name.js?m=<hash>`
|
|
|
|
* `Requirements_Backend.default_combined_files_folder` - This defaults to `_combinedfiles`, and is the folder
|
|
|
|
within the configured asset backend that combined files will be stored in. If using a backend shared with
|
|
|
|
other systems, it is usually necessary to distinguish combined files from other assets.
|
|
|
|
* `Requirements_Backend.combine_in_dev` - By default combined files will not be combined except in test
|
|
|
|
or live environments. Turning this on will allow for pre-combining of files in development mode.
|
|
|
|
|
|
|
|
In some cases it may be necessary to create a new storage backend for combined files, if the default location
|
|
|
|
is not appropriate. Normally a single backend is used for all site assets, so a number of objects must be
|
|
|
|
replaced. For instance, the below will set a new set of dependencies to write to `mysite/javascript/combined`
|
|
|
|
|
|
|
|
|
2017-07-10 01:00:25 +02:00
|
|
|
```yaml
|
|
|
|
---
|
|
|
|
Name: myrequirements
|
|
|
|
---
|
|
|
|
SilverStripe\View\Requirements:
|
|
|
|
disable_flush_combined: true
|
|
|
|
SilverStripe\View\Requirements_Backend:
|
|
|
|
combine_in_dev: true
|
|
|
|
combine_hash_querystring: true
|
|
|
|
default_combined_files_folder: 'combined'
|
|
|
|
SilverStripe\Core\Injector\Injector:
|
|
|
|
# Create adapter that points to the custom directory root
|
|
|
|
SilverStripe\Assets\Flysystem\PublicAdapter.custom-adapter:
|
|
|
|
class: SilverStripe\Assets\Flysystem\PublicAssetAdapter
|
|
|
|
constructor:
|
|
|
|
Root: ./mysite/javascript
|
|
|
|
# Set flysystem filesystem that uses this adapter
|
|
|
|
League\Flysystem\Filesystem.custom-filesystem:
|
|
|
|
class: 'League\Flysystem\Filesystem'
|
|
|
|
constructor:
|
|
|
|
Adapter: '%$SilverStripe\Assets\Flysystem\PublicAdapter.custom-adapter'
|
|
|
|
# Create handler to generate assets using this filesystem
|
|
|
|
SilverStripe\Assets\Storage\GeneratedAssetHandler.custom-generated-assets:
|
|
|
|
class: SilverStripe\Assets\Flysystem\GeneratedAssets
|
|
|
|
properties:
|
|
|
|
Filesystem: %$League\Flysystem\Filesystem.custom-filesystem
|
|
|
|
# Assign this generator to the requirements builder
|
|
|
|
SilverStripe\View\Requirements_Backend:
|
|
|
|
properties:
|
|
|
|
AssetHandler: '%$SilverStripe\Assets\Storage\GeneratedAssetHandler.custom-generated-assets'
|
|
|
|
```
|
2015-11-30 03:03:46 +01:00
|
|
|
|
|
|
|
In the above configuration, automatic expiry of generated files has been disabled, and it is necessary for
|
|
|
|
the developer to maintain these files manually. This may be useful in environments where assets must
|
|
|
|
be pre-cached, where scripts must be served alongside static files, or where no framework php request is
|
|
|
|
guaranteed. Alternatively, files may be served from instances other than the one which generated the
|
|
|
|
page response, and file synchronisation might not occur fast enough to propagate combined files to
|
|
|
|
mirrored filesystems.
|
|
|
|
|
|
|
|
In any case, care should be taken to determine the mechanism appropriate for your development
|
|
|
|
and production environments.
|
|
|
|
|
|
|
|
### Combined CSS Files
|
2014-10-13 09:49:30 +02:00
|
|
|
|
|
|
|
You can also combine CSS files into a media-specific stylesheets as you would with the `Requirements::css` call - use
|
|
|
|
the third paramter of the `combine_files` function:
|
|
|
|
|
2017-01-31 05:00:13 +01:00
|
|
|
```php
|
2017-07-03 03:22:12 +02:00
|
|
|
$loader = SilverStripe\View\ThemeResourceLoader::inst();
|
|
|
|
$themes = SilverStripe\View\SSViewer::get_themes();
|
2017-01-31 05:00:13 +01:00
|
|
|
|
2017-07-10 01:00:25 +02:00
|
|
|
$printStylesheets = [
|
|
|
|
$loader->findThemedCSS('print_HomePage.css', $themes),
|
|
|
|
$loader->findThemedCSS('print_Page.css', $themes)
|
|
|
|
];
|
2014-10-13 09:49:30 +02:00
|
|
|
|
2017-07-03 03:22:12 +02:00
|
|
|
SilverStripe\View\Requirements::combine_files('print.css', $printStylesheets, 'print');
|
2017-01-31 05:00:13 +01:00
|
|
|
```
|
2014-10-13 09:49:30 +02:00
|
|
|
|
2016-05-12 02:46:30 +02:00
|
|
|
By default, all requirements files are flushed (deleted) when ?flush querystring parameter is set.
|
|
|
|
This can be disabled by setting the `Requirements.disable_flush_combined` config to `true`.
|
|
|
|
|
2015-11-30 03:03:46 +01:00
|
|
|
<div class="alert" markdown='1'>
|
|
|
|
When combining CSS files, take care of relative urls, as these will not be re-written to match
|
|
|
|
the destination location of the resulting combined CSS.
|
|
|
|
</div>
|
|
|
|
|
2016-07-04 08:55:35 +02:00
|
|
|
### Combined JS Files
|
|
|
|
|
|
|
|
You can also add the 'async' and/or 'defer' attributes to combined Javascript files as you would with the
|
|
|
|
`Requirements::javascript` call - use the third paramter of the `combine_files` function:
|
|
|
|
|
2017-01-31 05:00:13 +01:00
|
|
|
```php
|
2017-07-03 03:22:12 +02:00
|
|
|
$loader = SilverStripe\View\ThemeResourceLoader::inst();
|
|
|
|
$themes = SilverStripe\View\SSViewer::get_themes();
|
2017-01-31 05:00:13 +01:00
|
|
|
|
2017-07-10 01:00:25 +02:00
|
|
|
$scripts = [
|
2017-01-31 05:00:13 +01:00
|
|
|
$loader->findThemedJavascript('some_script.js', $themes),
|
|
|
|
$loader->findThemedJavascript('some_other_script.js', $themes)
|
2017-07-10 01:00:25 +02:00
|
|
|
];
|
2016-07-04 08:55:35 +02:00
|
|
|
|
2017-07-10 01:00:25 +02:00
|
|
|
SilverStripe\View\Requirements::combine_files('scripts.js', $scripts, ['async' => true, 'defer' => true]);
|
2017-01-31 05:00:13 +01:00
|
|
|
```
|
2016-07-04 08:55:35 +02:00
|
|
|
|
2017-05-10 06:24:30 +02:00
|
|
|
### Minification of CSS and JS files
|
|
|
|
|
|
|
|
You can minify combined Javascript and CSS files at runtime using an implementation of the
|
|
|
|
`SilverStripe\View\Requirements_Minifier` interface.
|
|
|
|
|
|
|
|
```php
|
|
|
|
namespace MyProject;
|
|
|
|
|
|
|
|
use SilverStripe\View\Requirements_Minifier;
|
|
|
|
|
|
|
|
class MyMinifier implements Requirements_Minifier
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* Minify the given content
|
|
|
|
*
|
|
|
|
* @param string $content
|
|
|
|
* @param string $type Either js or css
|
|
|
|
* @param string $filename Name of file to display in case of error
|
|
|
|
* @return string minified content
|
2017-08-07 05:11:17 +02:00
|
|
|
*/
|
|
|
|
public function minify ($content, $type, $fileName)
|
|
|
|
{
|
|
|
|
// Minify $content;
|
2017-05-10 06:24:30 +02:00
|
|
|
|
2017-08-07 05:11:17 +02:00
|
|
|
return $minifiedContent;
|
|
|
|
}
|
2017-05-10 06:24:30 +02:00
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Then, inject this service in `Requirements_Backend`.
|
|
|
|
|
|
|
|
```yaml
|
|
|
|
SilverStripe\Core\Injector\Injector:
|
|
|
|
SilverStripe\View\Requirements_Backend:
|
|
|
|
properties:
|
2017-10-08 23:46:31 +02:00
|
|
|
MinifyCombinedFiles: true
|
2017-05-10 06:24:30 +02:00
|
|
|
Minifier: %$MyProject\MyMinifier
|
|
|
|
```
|
|
|
|
|
|
|
|
<div class="alert" markdown='1'>
|
|
|
|
While the framework does afford you the option of minification at runtime, we recommend using one of many frontend build
|
|
|
|
tools to do this for you, e.g. [Webpack](https://webpack.github.io/), [Gulp](http://gulpjs.com/), or [Grunt](https://gruntjs.com/).
|
|
|
|
</div>
|
|
|
|
|
2016-07-04 08:55:35 +02:00
|
|
|
|
2014-10-13 09:49:30 +02:00
|
|
|
## Clearing assets
|
|
|
|
|
2017-07-10 01:00:25 +02:00
|
|
|
```php
|
|
|
|
Requirements::clear();
|
|
|
|
```
|
2014-10-13 09:49:30 +02:00
|
|
|
|
|
|
|
Clears all defined requirements. You can also clear specific requirements.
|
|
|
|
|
2017-07-10 01:00:25 +02:00
|
|
|
```php
|
|
|
|
Requirements::clear('modulename/javascript/some-lib.js');
|
|
|
|
```
|
2014-10-13 09:49:30 +02:00
|
|
|
|
|
|
|
<div class="alert" markdown="1">
|
|
|
|
Depending on where you call this command, a Requirement might be *re-included* afterwards.
|
|
|
|
</div>
|
|
|
|
|
|
|
|
## Blocking
|
|
|
|
|
|
|
|
Requirements can also be explicitly blocked from inclusion, which is useful to avoid conflicting JavaScript logic or
|
|
|
|
CSS rules. These blocking rules are independent of where the `block()` call is made. It applies both for already
|
|
|
|
included requirements, and ones included after the `block()` call.
|
|
|
|
|
|
|
|
One common example is to block the core `jquery.js` added by various form fields and core controllers, and use a newer
|
|
|
|
version in a custom location. This assumes you have tested your application with the newer version.
|
|
|
|
|
2017-07-10 01:00:25 +02:00
|
|
|
```php
|
|
|
|
Requirements::block('silverstripe-admin/thirdparty/jquery/jquery.js');
|
|
|
|
```
|
2014-10-13 09:49:30 +02:00
|
|
|
|
|
|
|
<div class="alert" markdown="1">
|
|
|
|
The CMS also uses the `Requirements` system, and its operation can be affected by `block()` calls. Avoid this by
|
|
|
|
limiting the scope of your blocking operations, e.g. in `init()` of your controller.
|
|
|
|
</div>
|
|
|
|
|
|
|
|
## Inclusion Order
|
|
|
|
|
|
|
|
Requirements acts like a stack, where everything is rendered sequentially in the order it was included. There is no way
|
|
|
|
to change inclusion-order, other than using *Requirements::clear* and rebuilding the whole set of requirements.
|
|
|
|
|
|
|
|
<div class="alert" markdown="1">
|
|
|
|
Inclusion order is both relevant for CSS and Javascript files in terms of dependencies, inheritance and overlays - be
|
|
|
|
careful when messing with the order of requirements.
|
|
|
|
</div>
|
|
|
|
|
|
|
|
## Javascript placement
|
|
|
|
|
|
|
|
By default, SilverStripe includes all Javascript files at the bottom of the page body, unless there's another script
|
|
|
|
already loaded, then, it's inserted before the first `<script>` tag. If this causes problems, it can be configured.
|
|
|
|
|
2017-07-10 01:00:25 +02:00
|
|
|
```php
|
|
|
|
Requirements::set_force_js_to_bottom(true);
|
|
|
|
```
|
2014-10-13 09:49:30 +02:00
|
|
|
|
|
|
|
`Requirements.force_js_to_bottom`, will force SilverStripe to write the Javascript to the bottom of the page body, even
|
|
|
|
if there is an earlier script tag.
|
|
|
|
|
2015-02-23 17:58:31 +01:00
|
|
|
If the Javascript files are preferred to be placed in the `<head>` tag rather than in the `<body>` tag,
|
|
|
|
`Requirements.write_js_to_body` should be set to false.
|
|
|
|
|
2017-07-10 01:00:25 +02:00
|
|
|
```php
|
|
|
|
Requirements::set_write_js_to_body(false);
|
|
|
|
```
|
2015-02-23 17:58:31 +01:00
|
|
|
|
2017-10-03 01:20:49 +02:00
|
|
|
## Direct resource urls
|
|
|
|
|
|
|
|
In templates you can use the `$resourcePath()` or `$resourceURL()` helper methods to inject links to
|
|
|
|
resources directly. If you want to link to resources within a specific module you can use
|
|
|
|
the `vendor/module:some/path/to/file.jpg` syntax.
|
|
|
|
|
|
|
|
E.g.
|
|
|
|
|
|
|
|
```ss
|
|
|
|
<div class="loading">
|
|
|
|
<img src="$resourceURL('silverstripe/admin:client/dist/images/spinner.gif')" />
|
|
|
|
</div>
|
|
|
|
```
|
|
|
|
|
|
|
|
In PHP you can directly resolve these urls using the `ModuleResourceLoader` helper.
|
|
|
|
|
|
|
|
```php
|
|
|
|
$file = ModuleResourceLoader::singleton()
|
|
|
|
->resolveURL('silverstripe/admin:client/dist/images/spinner.gif');
|
|
|
|
```
|
2014-10-13 09:49:30 +02:00
|
|
|
|
|
|
|
## API Documentation
|
|
|
|
|
2017-07-03 03:22:12 +02:00
|
|
|
* [Requirements](api:SilverStripe\View\Requirements)
|
2016-09-15 04:48:38 +02:00
|
|
|
* [CMS Architecture and Build Tooling](/developer_guides/customising_the_admin_interface/cms_architecture)
|